src/eric7/DebugClients/Python/DebugClientBase.py

branch
eric7-maintenance
changeset 9264
18a7312cfdb3
parent 9221
bf71ee032bb4
child 9383
1d9a71952123
equal deleted inserted replaced
9241:d23e9854aea4 9264:18a7312cfdb3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a debug client base class.
8 """
9
10 import atexit
11 import codecs
12 import codeop
13 import contextlib
14 import fnmatch
15 import json
16 import os
17 import re
18 import select
19 import signal
20 import socket
21 import sys
22 import traceback
23 import types
24
25 import DebugClientCapabilities
26 import DebugVariables
27 from DebugBase import setRecursionLimit, printerr # __IGNORE_WARNING__
28 from AsyncFile import AsyncFile, AsyncPendingWrite
29 from DebugConfig import SpecialAttributes, NonExpandableTypes
30 from FlexCompleter import Completer
31 from DebugUtilities import prepareJsonCommand
32 from BreakpointWatch import Breakpoint, Watch
33 from MultiProcessDebugExtension import patchNewProcessFunctions
34
35 from DebugUtilities import getargvalues, formatargvalues
36
37 DebugClientInstance = None
38
39 ###############################################################################
40
41
42 def DebugClientInput(prompt="", echo=True):
43 """
44 Replacement for the standard input() builtin.
45
46 This function works with the split debugger.
47
48 @param prompt prompt to be shown
49 @type str
50 @param echo flag indicating echoing of the input
51 @type bool
52 @return result of the input() call
53 @rtype str
54 """
55 if DebugClientInstance is None or not DebugClientInstance.redirect:
56 return DebugClientOrigInput(prompt)
57 else:
58 return DebugClientInstance.input(prompt, echo=echo)
59
60
61 # Use our own input().
62 try:
63 DebugClientOrigInput = __builtins__.__dict__["input"]
64 __builtins__.__dict__["input"] = DebugClientInput
65 except (AttributeError, KeyError):
66 import __main__
67
68 DebugClientOrigInput = __main__.__builtins__.__dict__["input"]
69 __main__.__builtins__.__dict__["input"] = DebugClientInput
70
71 ###############################################################################
72
73
74 def DebugClientClose(fd):
75 """
76 Replacement for the standard os.close(fd).
77
78 @param fd open file descriptor to be closed (integer)
79 """
80 if DebugClientInstance is None:
81 DebugClientOrigClose(fd)
82 else:
83 DebugClientInstance.close(fd)
84
85
86 # use our own close().
87 if "close" in dir(os):
88 DebugClientOrigClose = os.close
89 os.close = DebugClientClose
90
91 ###############################################################################
92
93
94 def DebugClientSetRecursionLimit(limit):
95 """
96 Replacement for the standard sys.setrecursionlimit(limit).
97
98 @param limit recursion limit (integer)
99 """
100 rl = max(limit, 64)
101 setRecursionLimit(rl)
102 DebugClientOrigSetRecursionLimit(rl + 64)
103
104
105 # use our own setrecursionlimit().
106 if "setrecursionlimit" in dir(sys):
107 DebugClientOrigSetRecursionLimit = sys.setrecursionlimit
108 sys.setrecursionlimit = DebugClientSetRecursionLimit
109 DebugClientSetRecursionLimit(sys.getrecursionlimit())
110
111 ###############################################################################
112
113
114 class DebugClientBase:
115 """
116 Class implementing the client side of the debugger.
117
118 It provides access to the Python interpeter from a debugger running in
119 another process.
120
121 The protocol between the debugger and the client is based on JSONRPC 2.0
122 PDUs. Each one is sent on a single line, i.e. commands or responses are
123 separated by a linefeed character.
124
125 If the debugger closes the session there is no response from the client.
126 The client may close the session at any time as a result of the script
127 being debugged closing or crashing.
128
129 <b>Note</b>: This class is meant to be subclassed by individual
130 DebugClient classes. Do not instantiate it directly.
131 """
132
133 clientCapabilities = DebugClientCapabilities.HasAll
134
135 Type2Indicators = {
136 # Python types
137 "list": "[]",
138 "tuple": "()",
139 "dict": "{:}", # __IGNORE_WARNING_M613__
140 "set": "{}", # __IGNORE_WARNING_M613__
141 "frozenset": "{}", # __IGNORE_WARNING_M613__
142 "numpy.ndarray": "[ndarray]", # __IGNORE_WARNING_M613__
143 "collections.abc.ItemsView": "[]",
144 "collections.abc.KeysView": "[]",
145 "collections.abc.ValuesView": "[]",
146 }
147
148 def __init__(self):
149 """
150 Constructor
151 """
152 self.breakpoints = {}
153 self.redirect = True
154
155 # special objects representing the main scripts thread and frame
156 self.mainThread = self
157 self.framenr = 0
158
159 # The context to run the debugged program in.
160 self.debugMod = types.ModuleType("__main__")
161 self.debugMod.__dict__["__builtins__"] = __builtins__
162
163 # The list of complete lines to execute.
164 self.buffer = ""
165
166 # The precompiled regexp to filter variables against
167 self.globalsFilterObjects = None
168 self.localsFilterObjects = None
169
170 self._fncache = {}
171 self.dircache = []
172 self.passive = False # used to indicate the passive mode
173 self.running = None
174 self.test = None
175 self.debugging = False
176 self.multiprocessSupport = False
177 self.noDebugList = []
178
179 self.readstream = None
180 self.writestream = None
181 self.errorstream = None
182 self.pollingDisabled = False
183
184 self.__debuggerId = ""
185
186 self.callTraceEnabled = None
187
188 self.compile_command = codeop.CommandCompiler()
189
190 self.coding_re = re.compile(r"coding[:=]\s*([-\w_.]+)")
191 self.defaultCoding = "utf-8"
192 self.__coding = self.defaultCoding
193 self.noencoding = False
194
195 self.startOptions = None
196
197 def getCoding(self):
198 """
199 Public method to return the current coding.
200
201 @return codec name (string)
202 """
203 return self.__coding
204
205 def __setCoding(self, filename):
206 """
207 Private method to set the coding used by a python file.
208
209 @param filename name of the file to inspect (string)
210 """
211 if self.noencoding:
212 self.__coding = sys.getdefaultencoding()
213 else:
214 default = "utf-8"
215 try:
216 with open(filename, "rb") as f:
217 # read the first and second line
218 text = f.readline()
219 text = "{0}{1}".format(text, f.readline())
220 except OSError:
221 self.__coding = default
222 return
223
224 for line in text.splitlines():
225 m = self.coding_re.search(line)
226 if m:
227 self.__coding = m.group(1)
228 return
229 self.__coding = default
230
231 def input(self, prompt, echo=True):
232 """
233 Public method to implement input() using the event loop.
234
235 @param prompt prompt to be shown
236 @type str
237 @param echo flag indicating echoing of the input
238 @type bool
239 @return the entered string
240 @rtype str
241 """
242 self.sendJsonCommand(
243 "RequestRaw",
244 {
245 "prompt": prompt,
246 "echo": echo,
247 },
248 )
249 self.eventLoop(True)
250 return self.rawLine
251
252 def sessionClose(self, terminate=True):
253 """
254 Public method to close the session with the debugger and optionally
255 terminate.
256
257 @param terminate flag indicating to terminate (boolean)
258 """
259 with contextlib.suppress(Exception):
260 self.set_quit()
261
262 self.debugging = False
263 self.multiprocessSupport = False
264 self.noDebugList = []
265
266 # make sure we close down our end of the socket
267 # might be overkill as normally stdin, stdout and stderr
268 # SHOULD be closed on exit, but it does not hurt to do it here
269 self.readstream.close(True)
270 self.writestream.close(True)
271 self.errorstream.close(True)
272
273 if terminate:
274 # Ok, go away.
275 sys.exit()
276
277 def __compileFileSource(self, filename, mode="exec"):
278 """
279 Private method to compile source code read from a file.
280
281 @param filename name of the source file
282 @type str
283 @param mode kind of code to be generated (exec or eval)
284 @type str
285 @return compiled code object (None in case of errors)
286 """
287 with codecs.open(filename, encoding=self.__coding) as fp:
288 statement = fp.read()
289
290 return self.__compileCommand(statement, filename=filename, mode=mode)
291
292 def __compileCommand(self, statement, filename="<string>", mode="exec"):
293 """
294 Private method to compile source code.
295
296 @param statement source code string to be compiled
297 @type str
298 @param filename name of the source file
299 @type str
300 @param mode kind of code to be generated (exec or eval)
301 @type str
302 @return compiled code object (None in case of errors)
303 """
304 try:
305 code = compile(statement + "\n", filename, mode)
306 except SyntaxError:
307 exctype, excval, exctb = sys.exc_info()
308 try:
309 message = str(excval)
310 filename = excval.filename
311 lineno = excval.lineno
312 charno = excval.offset
313 if charno is None:
314 charno = 0
315
316 except (AttributeError, ValueError):
317 message = ""
318 filename = ""
319 lineno = 0
320 charno = 0
321
322 self.sendSyntaxError(message, filename, lineno, charno, self.name)
323 return None
324
325 return code
326
327 def handleJsonCommand(self, jsonStr):
328 """
329 Public method to handle a command serialized as a JSON string.
330
331 @param jsonStr string containing the command received from the IDE
332 @type str
333 """
334 ## printerr(jsonStr) ## debug # __IGNORE_WARNING_M891__
335
336 try:
337 commandDict = json.loads(jsonStr.strip())
338 except (TypeError, ValueError) as err:
339 printerr("Error handling command: " + jsonStr)
340 printerr(str(err))
341 return
342
343 method = commandDict["method"]
344 params = commandDict["params"]
345
346 if method == "RequestVariables":
347 self.__dumpVariables(
348 params["frameNumber"], params["scope"], params["filters"]
349 )
350
351 elif method == "RequestVariable":
352 self.__dumpVariable(
353 params["variable"],
354 params["frameNumber"],
355 params["scope"],
356 params["filters"],
357 )
358
359 elif method == "RequestStack":
360 stack = self.mainThread.getStack()
361 self.sendResponseLine(stack, self.mainThread.name)
362
363 elif method == "RequestThreadList":
364 self.dumpThreadList()
365
366 elif method == "RequestThreadSet":
367 if params["threadID"] == -1:
368 # -1 is indication for the main thread
369 threadId = -1
370 for thread in self.threads.values():
371 if thread.name == "MainThread":
372 threadId = thread.id
373 else:
374 threadId = params["threadID"]
375 if threadId in self.threads:
376 self.setCurrentThread(threadId)
377 self.sendJsonCommand("ResponseThreadSet", {})
378 stack = self.currentThread.getStack()
379 self.sendJsonCommand(
380 "ResponseStack",
381 {
382 "stack": stack,
383 "threadName": self.currentThread.name,
384 },
385 )
386
387 elif method == "RequestDisassembly":
388 if self.disassembly is not None:
389 self.sendJsonCommand(
390 "ResponseDisassembly", {"disassembly": self.disassembly}
391 )
392 else:
393 self.sendJsonCommand("ResponseDisassembly", {"disassembly": {}})
394
395 elif method == "RequestCapabilities":
396 clientType = "Python3"
397 self.sendJsonCommand(
398 "ResponseCapabilities",
399 {"capabilities": self.__clientCapabilities(), "clientType": clientType},
400 )
401
402 elif method == "RequestBanner":
403 self.sendJsonCommand(
404 "ResponseBanner",
405 {
406 "version": "Python {0}".format(sys.version),
407 "platform": socket.gethostname(),
408 },
409 )
410
411 elif method == "RequestSetFilter":
412 self.__generateFilterObjects(params["scope"], params["filter"])
413
414 elif method == "RequestCallTrace":
415 if params["enable"]:
416 callTraceEnabled = self.profile
417 else:
418 callTraceEnabled = None
419
420 if self.debugging:
421 sys.setprofile(callTraceEnabled)
422 else:
423 # remember for later
424 self.callTraceEnabled = callTraceEnabled
425
426 elif method == "RequestEnvironment":
427 for key, value in params["environment"].items():
428 if key.endswith("+"):
429 # append to the key
430 key = key[:-1]
431 if key in os.environ:
432 os.environ[key] += value
433 else:
434 os.environ[key] = value
435 elif key.endswith("-"):
436 # delete the key if it exists
437 key = key[:-1]
438 if key in os.environ:
439 del os.environ[key]
440 else:
441 os.environ[key] = value
442
443 elif method == "RequestLoad":
444 self._fncache = {}
445 self.dircache = []
446 self.disassembly = None
447 sys.argv = []
448 self.__setCoding(params["filename"])
449 sys.argv.append(params["filename"])
450 sys.argv.extend(params["argv"])
451 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
452 if params["workdir"] == "":
453 os.chdir(sys.path[1])
454 else:
455 os.chdir(params["workdir"])
456
457 self.running = sys.argv[0]
458 self.debugging = True
459 self.multiprocessSupport = params["multiprocess"]
460
461 self.threads.clear()
462 self.attachThread(mainThread=True)
463
464 # set the system exception handling function to ensure, that
465 # we report on all unhandled exceptions
466 sys.excepthook = self.__unhandled_exception
467 self.__interceptSignals()
468
469 # clear all old breakpoints, they'll get set after we have
470 # started
471 Breakpoint.clear_all_breaks()
472 Watch.clear_all_watches()
473
474 self.mainThread.tracePythonLibs(params["traceInterpreter"])
475
476 # This will eventually enter a local event loop.
477 self.debugMod.__dict__["__file__"] = self.running
478 sys.modules["__main__"] = self.debugMod
479 code = self.__compileFileSource(self.running)
480 if code:
481 sys.setprofile(self.callTraceEnabled)
482 self.mainThread.run(
483 code, self.debugMod.__dict__, debug=True, closeSession=False
484 )
485
486 elif method == "RequestRun":
487 self.disassembly = None
488 sys.argv = []
489 self.__setCoding(params["filename"])
490 sys.argv.append(params["filename"])
491 sys.argv.extend(params["argv"])
492 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
493 if params["workdir"] == "":
494 os.chdir(sys.path[1])
495 else:
496 os.chdir(params["workdir"])
497
498 self.running = sys.argv[0]
499 self.botframe = None
500
501 self.threads.clear()
502 self.attachThread(mainThread=True)
503
504 # set the system exception handling function to ensure, that
505 # we report on all unhandled exceptions
506 sys.excepthook = self.__unhandled_exception
507 self.__interceptSignals()
508
509 self.mainThread.tracePythonLibs(False)
510
511 self.debugMod.__dict__["__file__"] = sys.argv[0]
512 sys.modules["__main__"] = self.debugMod
513 res = 0
514 code = self.__compileFileSource(self.running)
515 if code:
516 self.mainThread.run(
517 code, self.debugMod.__dict__, debug=False, closeSession=False
518 )
519
520 elif method == "RequestCoverage":
521 from coverage import Coverage
522
523 self.disassembly = None
524 sys.argv = []
525 self.__setCoding(params["filename"])
526 sys.argv.append(params["filename"])
527 sys.argv.extend(params["argv"])
528 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
529 if params["workdir"] == "":
530 os.chdir(sys.path[1])
531 else:
532 os.chdir(params["workdir"])
533
534 # set the system exception handling function to ensure, that
535 # we report on all unhandled exceptions
536 sys.excepthook = self.__unhandled_exception
537 self.__interceptSignals()
538
539 # generate a coverage object
540 self.cover = Coverage(
541 auto_data=True,
542 data_file="{0}.coverage".format(os.path.splitext(sys.argv[0])[0]),
543 )
544
545 if params["erase"]:
546 self.cover.erase()
547 sys.modules["__main__"] = self.debugMod
548 self.debugMod.__dict__["__file__"] = sys.argv[0]
549 code = self.__compileFileSource(sys.argv[0])
550 if code:
551 self.running = sys.argv[0]
552 self.cover.start()
553 self.mainThread.run(
554 code, self.debugMod.__dict__, debug=False, closeSession=False
555 )
556 self.cover.stop()
557 self.cover.save()
558
559 elif method == "RequestProfile":
560 sys.setprofile(None)
561 import PyProfile
562
563 self.disassembly = None
564 sys.argv = []
565 self.__setCoding(params["filename"])
566 sys.argv.append(params["filename"])
567 sys.argv.extend(params["argv"])
568 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
569 if params["workdir"] == "":
570 os.chdir(sys.path[1])
571 else:
572 os.chdir(params["workdir"])
573
574 # set the system exception handling function to ensure, that
575 # we report on all unhandled exceptions
576 sys.excepthook = self.__unhandled_exception
577 self.__interceptSignals()
578
579 # generate a profile object
580 self.prof = PyProfile.PyProfile(sys.argv[0])
581
582 if params["erase"]:
583 self.prof.erase()
584 self.debugMod.__dict__["__file__"] = sys.argv[0]
585 sys.modules["__main__"] = self.debugMod
586 script = ""
587 with codecs.open(sys.argv[0], encoding=self.__coding) as fp:
588 script = fp.read()
589 if script and not script.endswith("\n"):
590 script += "\n"
591
592 if script:
593 self.running = sys.argv[0]
594 res = 0
595 try:
596 self.prof.run(script)
597 atexit._run_exitfuncs()
598 except SystemExit as exc:
599 res = exc.code
600 atexit._run_exitfuncs()
601 except Exception:
602 excinfo = sys.exc_info()
603 self.__unhandled_exception(*excinfo)
604
605 self.prof.save()
606 self.progTerminated(res, closeSession=False)
607
608 elif method == "ExecuteStatement":
609 if self.buffer:
610 self.buffer = self.buffer + "\n" + params["statement"]
611 else:
612 self.buffer = params["statement"]
613
614 try:
615 code = self.compile_command(self.buffer, self.readstream.name)
616 except (OverflowError, SyntaxError, ValueError):
617 # Report the exception
618 sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
619 self.sendJsonCommand(
620 "ClientOutput",
621 {
622 "text": "".join(
623 traceback.format_exception_only(
624 sys.last_type, sys.last_value
625 )
626 )
627 },
628 )
629 self.buffer = ""
630 else:
631 if code is None:
632 self.sendJsonCommand("ResponseContinue", {})
633 return
634 else:
635 self.buffer = ""
636
637 try:
638 if self.running is None:
639 exec(code, self.debugMod.__dict__) # secok
640 else:
641 if self.currentThread is None:
642 # program has terminated
643 self.running = None
644 _globals = self.debugMod.__dict__
645 _locals = _globals
646 else:
647 cf = self.currentThread.getCurrentFrame()
648 # program has terminated
649 if cf is None:
650 self.running = None
651 _globals = self.debugMod.__dict__
652 _locals = _globals
653 else:
654 frmnr = self.framenr
655 while cf is not None and frmnr > 0:
656 cf = cf.f_back
657 frmnr -= 1
658 _globals = cf.f_globals
659 _locals = self.currentThread.getFrameLocals(
660 self.framenr
661 )
662 # transfer all locals into a new globals
663 # to emulate Python scoping rules
664 _updatedGlobals = {}
665 _updatedGlobals.update(_globals)
666 _updatedGlobals.update(_locals)
667 # - reset sys.stdout to our redirector
668 # - (unconditionally)
669 if "sys" in _globals:
670 __stdout = _updatedGlobals["sys"].stdout
671 _updatedGlobals["sys"].stdout = self.writestream
672 exec(code, _updatedGlobals, _locals) # secok
673 _updatedGlobals["sys"].stdout = __stdout
674 elif "sys" in _locals:
675 __stdout = _locals["sys"].stdout
676 _locals["sys"].stdout = self.writestream
677 exec(code, _updatedGlobals, _locals) # secok
678 _locals["sys"].stdout = __stdout
679 else:
680 exec(code, _updatedGlobals, _locals) # secok
681
682 self.currentThread.storeFrameLocals(self.framenr)
683 except SystemExit as exc:
684 self.progTerminated(exc.code)
685 except Exception:
686 # Report the exception and the traceback
687 tlist = []
688 try:
689 exc_type, exc_value, exc_tb = sys.exc_info()
690 sys.last_type = exc_type
691 sys.last_value = exc_value
692 sys.last_traceback = exc_tb
693 tblist = traceback.extract_tb(exc_tb)
694 del tblist[:1]
695 tlist = traceback.format_list(tblist)
696 if tlist:
697 tlist.insert(0, "Traceback (innermost last):\n")
698 tlist.extend(
699 traceback.format_exception_only(exc_type, exc_value)
700 )
701 finally:
702 tblist = exc_tb = None
703
704 self.sendJsonCommand("ClientOutput", {"text": "".join(tlist)})
705
706 self.sendJsonCommand("ResponseOK", {})
707
708 elif method == "RequestStep":
709 self.currentThreadExec.step(True)
710 self.eventExit = True
711
712 elif method == "RequestStepOver":
713 self.currentThreadExec.step(False)
714 self.eventExit = True
715
716 elif method == "RequestStepOut":
717 self.currentThreadExec.stepOut()
718 self.eventExit = True
719
720 elif method == "RequestStepQuit":
721 if self.passive:
722 self.progTerminated(42)
723 else:
724 self.set_quit()
725 self.eventExit = True
726
727 elif method == "RequestMoveIP":
728 newLine = params["newLine"]
729 self.currentThreadExec.move_instruction_pointer(newLine)
730
731 elif method == "RequestContinue":
732 self.currentThreadExec.go(params["special"])
733 self.eventExit = True
734
735 elif method == "RequestContinueUntil":
736 newLine = params["newLine"]
737 self.currentThreadExec.set_until(lineno=newLine)
738 self.eventExit = True
739
740 elif method == "RawInput":
741 # If we are handling raw mode input then break out of the current
742 # event loop.
743 self.rawLine = params["input"]
744 self.eventExit = True
745
746 elif method == "RequestBreakpoint":
747 if params["setBreakpoint"]:
748 if params["condition"] in ["None", ""]:
749 cond = None
750 elif params["condition"] is not None:
751 try:
752 cond = compile(params["condition"], "<string>", "eval")
753 except SyntaxError:
754 self.sendJsonCommand(
755 "ResponseBPConditionError",
756 {
757 "filename": params["filename"],
758 "line": params["line"],
759 },
760 )
761 return
762 else:
763 cond = None
764
765 Breakpoint(
766 params["filename"], params["line"], params["temporary"], cond
767 )
768 else:
769 Breakpoint.clear_break(params["filename"], params["line"])
770
771 elif method == "RequestBreakpointEnable":
772 bp = Breakpoint.get_break(params["filename"], params["line"])
773 if bp is not None:
774 if params["enable"]:
775 bp.enable()
776 else:
777 bp.disable()
778
779 elif method == "RequestBreakpointIgnore":
780 bp = Breakpoint.get_break(params["filename"], params["line"])
781 if bp is not None:
782 bp.ignore = params["count"]
783
784 elif method == "RequestWatch":
785 if params["setWatch"]:
786 if params["condition"].endswith(("??created??", "??changed??")):
787 compiledCond, flag = params["condition"].split()
788 else:
789 compiledCond = params["condition"]
790 flag = ""
791
792 try:
793 compiledCond = compile(compiledCond, "<string>", "eval")
794 except SyntaxError:
795 self.sendJsonCommand(
796 "ResponseWatchConditionError",
797 {
798 "condition": params["condition"],
799 },
800 )
801 return
802 Watch(params["condition"], compiledCond, flag, params["temporary"])
803 else:
804 Watch.clear_watch(params["condition"])
805
806 elif method == "RequestWatchEnable":
807 wp = Watch.get_watch(params["condition"])
808 if wp is not None:
809 if params["enable"]:
810 wp.enable()
811 else:
812 wp.disable()
813
814 elif method == "RequestWatchIgnore":
815 wp = Watch.get_watch(params["condition"])
816 if wp is not None:
817 wp.ignore = params["count"]
818
819 elif method == "RequestShutdown":
820 self.sessionClose()
821
822 elif method == "RequestSetNoDebugList":
823 self.noDebugList = params["noDebug"][:]
824
825 elif method == "RequestCompletion":
826 self.__completionList(params["text"])
827
828 def setDisassembly(self, disassembly):
829 """
830 Public method to store a disassembly of the code object raising an
831 exception.
832
833 @param disassembly dictionary containing the disassembly information
834 @type dict
835 """
836 self.disassembly = disassembly
837
838 def sendJsonCommand(self, method, params):
839 """
840 Public method to send a single command or response to the IDE.
841
842 @param method command or response command name to be sent
843 @type str
844 @param params dictionary of named parameters for the command or
845 response
846 @type dict
847 """
848 # send debugger ID with all responses
849 if "debuggerId" not in params:
850 params["debuggerId"] = self.__debuggerId
851
852 cmd = prepareJsonCommand(method, params)
853
854 self.writestream.write_p(cmd)
855 self.writestream.flush()
856
857 def sendClearTemporaryBreakpoint(self, filename, lineno):
858 """
859 Public method to signal the deletion of a temporary breakpoint.
860
861 @param filename name of the file the bp belongs to
862 @type str
863 @param lineno linenumber of the bp
864 @type int
865 """
866 self.sendJsonCommand(
867 "ResponseClearBreakpoint", {"filename": filename, "line": lineno}
868 )
869
870 def sendClearTemporaryWatch(self, condition):
871 """
872 Public method to signal the deletion of a temporary watch expression.
873
874 @param condition condition of the watch expression to be cleared
875 @type str
876 """
877 self.sendJsonCommand(
878 "ResponseClearWatch",
879 {
880 "condition": condition,
881 },
882 )
883
884 def sendResponseLine(self, stack, threadName):
885 """
886 Public method to send the current call stack.
887
888 @param stack call stack
889 @type list
890 @param threadName name of the thread sending the event
891 @type str
892 """
893 self.sendJsonCommand(
894 "ResponseLine",
895 {
896 "stack": stack,
897 "threadName": threadName,
898 },
899 )
900
901 def sendCallTrace(self, event, fromInfo, toInfo):
902 """
903 Public method to send a call trace entry.
904
905 @param event trace event (call or return)
906 @type str
907 @param fromInfo dictionary containing the origin info
908 @type dict with 'filename', 'linenumber' and 'codename'
909 as keys
910 @param toInfo dictionary containing the target info
911 @type dict with 'filename', 'linenumber' and 'codename'
912 as keys
913 """
914 self.sendJsonCommand(
915 "CallTrace",
916 {
917 "event": event[0],
918 "from": fromInfo,
919 "to": toInfo,
920 },
921 )
922
923 def sendException(self, exceptionType, exceptionMessage, stack, threadName):
924 """
925 Public method to send information for an exception.
926
927 @param exceptionType type of exception raised
928 @type str
929 @param exceptionMessage message of the exception
930 @type str
931 @param stack stack trace information
932 @type list
933 @param threadName name of the thread sending the event
934 @type str
935 """
936 self.sendJsonCommand(
937 "ResponseException",
938 {
939 "type": exceptionType,
940 "message": exceptionMessage,
941 "stack": stack,
942 "threadName": threadName,
943 },
944 )
945
946 def sendSyntaxError(self, message, filename, lineno, charno, threadName):
947 """
948 Public method to send information for a syntax error.
949
950 @param message syntax error message
951 @type str
952 @param filename name of the faulty file
953 @type str
954 @param lineno line number info
955 @type int
956 @param charno character number info
957 @type int
958 @param threadName name of the thread sending the event
959 @type str
960 """
961 self.sendJsonCommand(
962 "ResponseSyntax",
963 {
964 "message": message,
965 "filename": filename,
966 "linenumber": lineno,
967 "characternumber": charno,
968 "threadName": threadName,
969 },
970 )
971
972 def sendPassiveStartup(self, filename, exceptions):
973 """
974 Public method to send the passive start information.
975
976 @param filename name of the script
977 @type str
978 @param exceptions flag to enable exception reporting of the IDE
979 @type bool
980 """
981 self.sendJsonCommand(
982 "PassiveStartup",
983 {
984 "filename": filename,
985 "exceptions": exceptions,
986 },
987 )
988
989 def sendDebuggerId(self, debuggerId):
990 """
991 Public method to send the debug client id.
992
993 @param debuggerId id of this debug client instance (made up of
994 hostname and process ID)
995 @type str
996 """
997 # debugger ID is added automatically by sendJsonCommand
998 self.sendJsonCommand("DebuggerId", {})
999
1000 def __clientCapabilities(self):
1001 """
1002 Private method to determine the clients capabilities.
1003
1004 @return client capabilities (integer)
1005 """
1006 try:
1007 import PyProfile # __IGNORE_WARNING__
1008
1009 with contextlib.suppress(KeyError):
1010 del sys.modules["PyProfile"]
1011 return self.clientCapabilities
1012 except ImportError:
1013 return self.clientCapabilities & ~DebugClientCapabilities.HasProfiler
1014
1015 def readReady(self, stream):
1016 """
1017 Public method called when there is data ready to be read.
1018
1019 @param stream file like object that has data to be read
1020 @return flag indicating an error condition
1021 @rtype bool
1022 """
1023 error = False
1024
1025 self.lockClient()
1026 try:
1027 command = stream.readCommand()
1028 except Exception:
1029 error = True
1030 command = ""
1031 self.unlockClient()
1032
1033 if error or len(command) == 0:
1034 self.sessionClose()
1035 else:
1036 self.handleJsonCommand(command)
1037
1038 return error
1039
1040 def writeReady(self, stream):
1041 """
1042 Public method called when we are ready to write data.
1043
1044 @param stream file like object that has data to be written
1045 """
1046 stream.write_p("")
1047 stream.flush()
1048
1049 def __interact(self):
1050 """
1051 Private method to interact with the debugger.
1052 """
1053 global DebugClientInstance
1054
1055 DebugClientInstance = self
1056 self.__receiveBuffer = ""
1057
1058 if not self.passive:
1059 # At this point simulate an event loop.
1060 self.eventLoop()
1061
1062 def eventLoop(self, disablePolling=False):
1063 """
1064 Public method implementing our event loop.
1065
1066 @param disablePolling flag indicating to enter an event loop with
1067 polling disabled (boolean)
1068 """
1069 self.eventExit = False
1070 self.pollingDisabled = disablePolling
1071 selectErrors = 0
1072
1073 while not self.eventExit:
1074 wrdy = []
1075
1076 if self.writestream.nWriteErrors > self.writestream.MAX_TRIES:
1077 break
1078
1079 if AsyncPendingWrite(self.writestream):
1080 wrdy.append(self.writestream)
1081
1082 if AsyncPendingWrite(self.errorstream):
1083 wrdy.append(self.errorstream)
1084
1085 try:
1086 rrdy, wrdy, xrdy = select.select([self.readstream], wrdy, [])
1087 except (KeyboardInterrupt, OSError):
1088 selectErrors += 1
1089 if selectErrors <= 10: # arbitrarily selected
1090 # just carry on
1091 continue
1092 else:
1093 # give up for too many errors
1094 break
1095 except ValueError:
1096 # the client socket might already be closed, i.e. its fd is -1
1097 break
1098
1099 # reset the select error counter
1100 selectErrors = 0
1101
1102 if self.readstream in rrdy:
1103 error = self.readReady(self.readstream)
1104 if error:
1105 break
1106
1107 if self.writestream in wrdy:
1108 self.writeReady(self.writestream)
1109
1110 if self.errorstream in wrdy:
1111 self.writeReady(self.errorstream)
1112
1113 self.eventExit = False
1114 self.pollingDisabled = False
1115
1116 def eventPoll(self):
1117 """
1118 Public method to poll for events like 'set break point'.
1119 """
1120 if self.pollingDisabled:
1121 return
1122
1123 wrdy = []
1124 if AsyncPendingWrite(self.writestream):
1125 wrdy.append(self.writestream)
1126
1127 if AsyncPendingWrite(self.errorstream):
1128 wrdy.append(self.errorstream)
1129
1130 # immediate return if nothing is ready.
1131 try:
1132 rrdy, wrdy, xrdy = select.select([self.readstream], wrdy, [], 0)
1133 except (KeyboardInterrupt, OSError):
1134 return
1135
1136 if self.readstream in rrdy:
1137 self.readReady(self.readstream)
1138
1139 if self.writestream in wrdy:
1140 self.writeReady(self.writestream)
1141
1142 if self.errorstream in wrdy:
1143 self.writeReady(self.errorstream)
1144
1145 def connectDebugger(self, port, remoteAddress=None, redirect=True, name=""):
1146 """
1147 Public method to establish a session with the debugger.
1148
1149 It opens a network connection to the debugger, connects it to stdin,
1150 stdout and stderr and saves these file objects in case the application
1151 being debugged redirects them itself.
1152
1153 @param port the port number to connect to
1154 @type int
1155 @param remoteAddress the network address of the debug server host
1156 @type str
1157 @param redirect flag indicating redirection of stdin, stdout and
1158 stderr
1159 @type bool
1160 @param name name to be attached to the debugger ID
1161 @type str
1162 """
1163 if remoteAddress is None:
1164 remoteAddress = "127.0.0.1"
1165 elif "@@i" in remoteAddress:
1166 remoteAddress = remoteAddress.split("@@i")[0]
1167 sock = socket.create_connection((remoteAddress, port))
1168
1169 stdinName = sys.stdin.name
1170 # Special case if in a multiprocessing.Process
1171 if isinstance(stdinName, int):
1172 stdinName = "<stdin>"
1173
1174 self.readstream = AsyncFile(sock, sys.stdin.mode, stdinName)
1175 self.writestream = AsyncFile(sock, sys.stdout.mode, sys.stdout.name)
1176 self.errorstream = AsyncFile(sock, sys.stderr.mode, sys.stderr.name)
1177
1178 if redirect:
1179 sys.stdin = self.readstream
1180 sys.stdout = self.writestream
1181 sys.stderr = self.errorstream
1182 self.redirect = redirect
1183
1184 # attach to the main thread here
1185 self.attachThread(mainThread=True)
1186
1187 if not name:
1188 name = "main"
1189 self.__debuggerId = "{0}/{1}/{2}".format(
1190 socket.gethostname(), os.getpid(), name
1191 )
1192
1193 self.sendDebuggerId(self.__debuggerId)
1194
1195 def __unhandled_exception(self, exctype, excval, exctb):
1196 """
1197 Private method called to report an uncaught exception.
1198
1199 @param exctype the type of the exception
1200 @param excval data about the exception
1201 @param exctb traceback for the exception
1202 """
1203 self.mainThread.user_exception((exctype, excval, exctb), True)
1204
1205 def __interceptSignals(self):
1206 """
1207 Private method to intercept common signals.
1208 """
1209 for signum in [
1210 signal.SIGABRT, # abnormal termination
1211 signal.SIGFPE, # floating point exception
1212 signal.SIGILL, # illegal instruction
1213 signal.SIGSEGV, # segmentation violation
1214 ]:
1215 signal.signal(signum, self.__signalHandler)
1216
1217 def __signalHandler(self, signalNumber, stackFrame):
1218 """
1219 Private method to handle signals.
1220
1221 @param signalNumber number of the signal to be handled
1222 @type int
1223 @param stackFrame current stack frame
1224 @type frame object
1225 """
1226 if signalNumber == signal.SIGABRT:
1227 message = "Abnormal Termination"
1228 elif signalNumber == signal.SIGFPE:
1229 message = "Floating Point Exception"
1230 elif signalNumber == signal.SIGILL:
1231 message = "Illegal Instruction"
1232 elif signalNumber == signal.SIGSEGV:
1233 message = "Segmentation Violation"
1234 else:
1235 message = "Unknown Signal '{0}'".format(signalNumber)
1236
1237 filename = self.absPath(stackFrame)
1238
1239 linenr = stackFrame.f_lineno
1240 ffunc = stackFrame.f_code.co_name
1241
1242 if ffunc == "?":
1243 ffunc = ""
1244
1245 if ffunc and not ffunc.startswith("<"):
1246 argInfo = getargvalues(stackFrame)
1247 try:
1248 fargs = formatargvalues(
1249 argInfo.args, argInfo.varargs, argInfo.keywords, argInfo.locals
1250 )
1251 except Exception:
1252 fargs = ""
1253 else:
1254 fargs = ""
1255
1256 self.sendJsonCommand(
1257 "ResponseSignal",
1258 {
1259 "message": message,
1260 "filename": filename,
1261 "linenumber": linenr,
1262 "function": ffunc,
1263 "arguments": fargs,
1264 },
1265 )
1266
1267 def absPath(self, fn):
1268 """
1269 Public method to convert a filename to an absolute name.
1270
1271 sys.path is used as a set of possible prefixes. The name stays
1272 relative if a file could not be found.
1273
1274 @param fn filename (string)
1275 @return the converted filename (string)
1276 """
1277 if os.path.isabs(fn):
1278 return fn
1279
1280 # Check the cache.
1281 if fn in self._fncache:
1282 return self._fncache[fn]
1283
1284 # Search sys.path.
1285 for p in sys.path:
1286 afn = os.path.abspath(os.path.join(p, fn))
1287 nafn = os.path.normcase(afn)
1288
1289 if os.path.exists(nafn):
1290 self._fncache[fn] = afn
1291 d = os.path.dirname(afn)
1292 if (d not in sys.path) and (d not in self.dircache):
1293 self.dircache.append(d)
1294 return afn
1295
1296 # Search the additional directory cache
1297 for p in self.dircache:
1298 afn = os.path.abspath(os.path.join(p, fn))
1299 nafn = os.path.normcase(afn)
1300
1301 if os.path.exists(nafn):
1302 self._fncache[fn] = afn
1303 return afn
1304
1305 # Nothing found.
1306 return fn
1307
1308 def getRunning(self):
1309 """
1310 Public method to return the main script we are currently running.
1311
1312 @return flag indicating a running debug session (boolean)
1313 """
1314 return self.running
1315
1316 def progTerminated(self, status, message="", closeSession=True):
1317 """
1318 Public method to tell the debugger that the program has terminated.
1319
1320 @param status return status
1321 @type int
1322 @param message status message
1323 @type str
1324 @param closeSession flag indicating to close the debugger session
1325 @type bool
1326 """
1327 if status is None:
1328 status = 0
1329 elif not isinstance(status, int):
1330 message = str(status)
1331 status = 1
1332 if message is None:
1333 message = ""
1334
1335 if self.running:
1336 self.set_quit()
1337 program = self.running
1338 self.running = None
1339 self.sendJsonCommand(
1340 "ResponseExit",
1341 {
1342 "status": status,
1343 "message": message,
1344 "program": program,
1345 },
1346 )
1347
1348 # reset coding
1349 self.__coding = self.defaultCoding
1350
1351 if closeSession:
1352 self.sessionClose(False)
1353
1354 def __dumpVariables(self, frmnr, scope, filterList):
1355 """
1356 Private method to return the variables of a frame to the debug server.
1357
1358 @param frmnr distance of frame reported on. 0 is the current frame
1359 @type int
1360 @param scope 1 to report global variables, 0 for local variables
1361 @type int
1362 @param filterList list of variable types to be filtered
1363 @type list of str
1364 """
1365 if self.currentThread is None:
1366 return
1367
1368 self.resolverCache = [{}, {}]
1369 frmnr += self.currentThread.skipFrames
1370 if scope == 0:
1371 self.framenr = frmnr
1372
1373 f = self.currentThread.getCurrentFrame()
1374
1375 while f is not None and frmnr > 0:
1376 f = f.f_back
1377 frmnr -= 1
1378
1379 if f is None:
1380 if scope:
1381 varDict = self.debugMod.__dict__
1382 else:
1383 scope = -2
1384 elif scope:
1385 varDict = f.f_globals
1386 elif f.f_globals is f.f_locals:
1387 scope = -1
1388 else:
1389 varDict = f.f_locals
1390
1391 # Update known types list
1392 DebugVariables.updateTypeMap()
1393
1394 varlist = (
1395 []
1396 if scope < 0
1397 else self.__formatVariablesList(varDict.items(), scope, filterList)
1398 )
1399
1400 self.sendJsonCommand(
1401 "ResponseVariables",
1402 {
1403 "scope": scope,
1404 "variables": varlist,
1405 },
1406 )
1407
1408 def __dumpVariable(self, var, frmnr, scope, filterList):
1409 """
1410 Private method to return the variables of a frame to the debug server.
1411
1412 @param var list encoded name of the requested variable
1413 @type list of str
1414 @param frmnr distance of frame reported on. 0 is the current frame
1415 @type int
1416 @param scope 1 to report global variables, 0 for local variables
1417 @type int
1418 @param filterList list of variable types to be filtered
1419 @type list of int
1420 """
1421 if self.currentThread is None:
1422 return
1423
1424 frmnr += self.currentThread.skipFrames
1425 f = self.currentThread.getCurrentFrame()
1426
1427 while f is not None and frmnr > 0:
1428 f = f.f_back
1429 frmnr -= 1
1430
1431 if f is None:
1432 if scope:
1433 varDict = self.debugMod.__dict__
1434 else:
1435 scope = -1
1436 elif scope:
1437 varDict = f.f_globals
1438 elif f.f_globals is f.f_locals:
1439 scope = -1
1440 else:
1441 varDict = f.f_locals
1442
1443 varlist = []
1444
1445 # fast path if variable was looked up before (see elif)
1446 if scope != -1 and str(var) in self.resolverCache[scope]:
1447 varGen = self.resolverCache[scope][str(var)]
1448 idx, varDict = next(varGen)
1449 if idx != -2: # more elements available
1450 var.insert(0, idx)
1451 varlist = self.__formatVariablesList(varDict, scope, filterList)
1452 elif scope != -1:
1453 variable = varDict
1454 # Lookup the wanted attribute
1455 for attribute in var:
1456 resolver = DebugVariables.getResolver(variable)
1457 if resolver:
1458 variable = resolver.resolve(variable, attribute)
1459 if variable is None:
1460 break
1461
1462 else:
1463 break
1464
1465 idx = -3 # Requested variable doesn't exist anymore
1466 # If found, get the details of attribute
1467 if variable is not None:
1468 resolver = DebugVariables.getResolver(variable)
1469 if resolver:
1470 varGen = resolver.getVariableList(variable)
1471 # cache for next lookup
1472 self.resolverCache[scope][str(var)] = varGen
1473
1474 idx, varDict = next(varGen)
1475 if idx != -2: # more elements available
1476 varlist = self.__formatVariablesList(varDict, scope, filterList)
1477
1478 var.insert(0, idx)
1479
1480 self.sendJsonCommand(
1481 "ResponseVariable",
1482 {
1483 "scope": scope,
1484 "variable": var,
1485 "variables": varlist,
1486 },
1487 )
1488
1489 def __formatVariablesList(self, variables, scope, filterList=None):
1490 """
1491 Private method to produce a formated variables list.
1492
1493 The dictionary passed in to it is scanned. Variables are
1494 only added to the list, if their type is not contained
1495 in the filter list and their name doesn't match any of the filter
1496 expressions. The formated variables list (a list of tuples of 3
1497 values) is returned.
1498
1499 @param variables variables list to be processed
1500 @type list of tuple of (str, Any) or (str, str, Any)
1501 @param scope 1 to filter using the globals filter, 0 using the locals
1502 filter.
1503 Variables are only added to the list, if their name do not match
1504 any of the filter expressions.
1505 @type int
1506 @param filterList list of variable types to be filtered.
1507 Variables are only added to the list, if their type is not
1508 contained in the filter list.
1509 @type list of str
1510 @return A tuple consisting of a list of formatted variables. Each
1511 variable entry is a tuple of three elements, the variable name,
1512 its type and value.
1513 @rtype list of tuple of (str, str, str)
1514 """
1515 filterList = set(filterList or [])
1516
1517 varlist = []
1518 patternFilterObjects = (
1519 self.globalsFilterObjects if scope else self.localsFilterObjects
1520 )
1521
1522 for variabel in variables:
1523 valtype = None
1524 rvalue = None
1525 try:
1526 key, value = variabel
1527 except ValueError:
1528 # Special case for some Qt variables, where the real type is
1529 # overwritten
1530 key, valtype, rvalue = variabel
1531
1532 # filter based on the filter pattern
1533 if patternFilterObjects and patternFilterObjects.match(str(key)):
1534 continue
1535
1536 # filter hidden attributes (filter #0)
1537 if "__" in filterList and str(key)[:2] == "__":
1538 continue
1539
1540 hasChildren = False
1541 # special handling for '__builtins__' (it's way too big)
1542 if key == "__builtins__":
1543 rvalue = "<module builtins (built-in)>"
1544 valtype = "module"
1545 if valtype in filterList:
1546 continue
1547 elif (key in SpecialAttributes and "special_attributes" in filterList) or (
1548 key == "__hash__" and "builtin_function_or_method" in filterList
1549 ):
1550 continue
1551 elif valtype is None:
1552 # valtypestr, e.g. class 'PyQt6.QtCore.QPoint'
1553 valtypestr = str(type(value))
1554 _, valtype = valtypestr.split(" ", 1)
1555 # valtype is the real type, e.g. PyQt6.QtCore.QPoint
1556 # valtype_filter is used for filtering, where the base class is
1557 # also important
1558 valtype = valtype_filter = valtype[1:-2]
1559 # Strip 'instance' to be equal with Python 3
1560 if valtype == "instancemethod":
1561 valtype = valtype_filter = "method"
1562 elif isinstance(value, type):
1563 valtype_filter = "class"
1564 if valtype == "type":
1565 valtype = "class"
1566 elif valtype == "method-wrapper":
1567 valtype = valtype_filter = "builtin_function_or_method"
1568
1569 # Don't process variables which types are on filter list
1570 if (
1571 valtype_filter in filterList
1572 or (
1573 valtype_filter in ("sip.enumtype", "sip.wrappertype")
1574 and "class" in filterList
1575 )
1576 or (
1577 valtype_filter in ("sip.methoddescriptor", "method_descriptor")
1578 and "method" in filterList
1579 )
1580 or (
1581 valtype_filter in ("numpy.ndarray", "array.array")
1582 and "list" in filterList
1583 )
1584 or (
1585 valtype_filter == "django.MultiValueDict"
1586 and "dict" in filterList
1587 )
1588 or "instance" in filterList
1589 ):
1590 continue
1591
1592 length = -2
1593 indicator = ""
1594
1595 if valtype == "str":
1596 rvalue = repr(value)
1597 length = len(rvalue)
1598 elif valtype in NonExpandableTypes:
1599 rvalue = repr(value)
1600
1601 if rvalue is not None:
1602 varlist.append((key, indicator, valtype, hasChildren, length, rvalue))
1603 continue
1604
1605 try:
1606 for dtype in DebugVariables._ArrayTypes:
1607 if isinstance(value, dtype):
1608 try:
1609 length = len(value)
1610 except TypeError:
1611 length = -1 # Uninitialized array
1612
1613 dtype = str(dtype)[8:-2]
1614 # Standard array type indicators
1615 indicator = self.Type2Indicators.get(dtype, "")
1616
1617 # Special handling of some array types
1618 if valtype == "array.array":
1619 indicator = "[<{0}>]".format(value.typecode)
1620 elif valtype == "collections.defaultdict":
1621 if value.default_factory is None:
1622 def_factory = "None"
1623 else:
1624 def_factory = value.default_factory.__name__
1625 indicator = "{{:<{0}>}}".format(def_factory)
1626 elif valtype == "numpy.ndarray" and length > -1:
1627 length = "x".join(str(x) for x in value.shape)
1628 elif valtype.endswith(".MultiValueDict"):
1629 indicator = "{:}" # __IGNORE_WARNING__
1630 valtype = "django.MultiValueDict" # shortened type
1631 break
1632 else:
1633 rvalue = repr(value)
1634
1635 hasChildren = True
1636 except Exception:
1637 rvalue = ""
1638
1639 varlist.append((key, indicator, valtype, hasChildren, length, rvalue))
1640
1641 return varlist
1642
1643 def __generateFilterObjects(self, scope, filterString):
1644 """
1645 Private slot to convert a filter string to a list of filter objects.
1646
1647 @param scope 1 to generate filter for global variables, 0 for local
1648 variables (int)
1649 @param filterString string of filter patterns separated by ';'
1650 """
1651 patternFilterObjects = None
1652 if filterString.strip():
1653 pattern = filterString.replace(";", "|")
1654 with contextlib.suppress(re.error):
1655 patternFilterObjects = re.compile(pattern)
1656
1657 if scope:
1658 self.globalsFilterObjects = patternFilterObjects
1659 else:
1660 self.localsFilterObjects = patternFilterObjects
1661
1662 def __completionList(self, text):
1663 """
1664 Private slot to handle the request for a commandline completion list.
1665
1666 @param text the text to be completed (string)
1667 """
1668 completerDelims = " \t\n`~!@#$%^&*()-=+[{]}\\|;:'\",<>/?"
1669
1670 completions = set()
1671 # find position of last delim character
1672 pos = -1
1673 while pos >= -len(text):
1674 if text[pos] in completerDelims:
1675 if pos == -1:
1676 text = ""
1677 else:
1678 text = text[pos + 1 :]
1679 break
1680 pos -= 1
1681
1682 # Get local and global completions
1683 with contextlib.suppress(AttributeError):
1684 localdict = self.currentThread.getFrameLocals(self.framenr)
1685 localCompleter = Completer(localdict).complete
1686 self.__getCompletionList(text, localCompleter, completions)
1687
1688 cf = self.currentThread.getCurrentFrame()
1689 frmnr = self.framenr
1690 while cf is not None and frmnr > 0:
1691 cf = cf.f_back
1692 frmnr -= 1
1693
1694 globaldict = self.debugMod.__dict__ if cf is None else cf.f_globals
1695
1696 globalCompleter = Completer(globaldict).complete
1697 self.__getCompletionList(text, globalCompleter, completions)
1698
1699 self.sendJsonCommand(
1700 "ResponseCompletion",
1701 {
1702 "completions": list(completions),
1703 "text": text,
1704 },
1705 )
1706
1707 def __getCompletionList(self, text, completer, completions):
1708 """
1709 Private method to create a completions list.
1710
1711 @param text text to complete (string)
1712 @param completer completer method
1713 @param completions set where to add new completions strings (set)
1714 """
1715 state = 0
1716 try:
1717 comp = completer(text, state)
1718 except Exception:
1719 comp = None
1720 while comp is not None:
1721 completions.add(comp)
1722 state += 1
1723 try:
1724 comp = completer(text, state)
1725 except Exception:
1726 comp = None
1727
1728 def startDebugger(
1729 self,
1730 filename=None,
1731 host=None,
1732 port=None,
1733 enableTrace=True,
1734 exceptions=True,
1735 tracePython=False,
1736 redirect=True,
1737 passive=True,
1738 multiprocessSupport=False,
1739 ):
1740 """
1741 Public method used to start the remote debugger.
1742
1743 @param filename the program to be debugged
1744 @type str
1745 @param host hostname of the debug server
1746 @type str
1747 @param port portnumber of the debug server
1748 @type int
1749 @param enableTrace flag to enable the tracing function
1750 @type bool
1751 @param exceptions flag to enable exception reporting of the IDE
1752 @type bool
1753 @param tracePython flag to enable tracing into the Python library
1754 @type bool
1755 @param redirect flag indicating redirection of stdin, stdout and
1756 stderr
1757 @type bool
1758 @param passive flag indicating a passive debugging session
1759 @type bool
1760 @param multiprocessSupport flag indicating to enable multiprocess
1761 debugging support
1762 @type bool
1763 """
1764 if host is None:
1765 host = os.getenv("ERICHOST", "localhost")
1766 if port is None:
1767 port = os.getenv("ERICPORT", 42424)
1768
1769 remoteAddress = self.__resolveHost(host)
1770 name = os.path.basename(filename) if filename is not None else ""
1771 self.connectDebugger(port, remoteAddress, redirect, name=name)
1772 if filename is not None:
1773 self.running = os.path.abspath(filename)
1774 else:
1775 try:
1776 self.running = os.path.abspath(sys.argv[0])
1777 except IndexError:
1778 self.running = None
1779 if self.running:
1780 self.__setCoding(self.running)
1781 self.passive = passive
1782 self.__interact()
1783
1784 # setup the debugger variables
1785 self._fncache = {}
1786 self.dircache = []
1787 self.debugging = True
1788
1789 self.attachThread(mainThread=True)
1790 self.mainThread.tracePythonLibs(tracePython)
1791
1792 # set the system exception handling function to ensure, that
1793 # we report on all unhandled exceptions
1794 sys.excepthook = self.__unhandled_exception
1795 self.__interceptSignals()
1796
1797 # now start debugging
1798 if enableTrace:
1799 self.mainThread.set_trace()
1800
1801 def startProgInDebugger(
1802 self,
1803 progargs,
1804 wd="",
1805 host=None,
1806 port=None,
1807 exceptions=True,
1808 tracePython=False,
1809 redirect=True,
1810 passive=True,
1811 multiprocessSupport=False,
1812 codeStr="",
1813 scriptModule="",
1814 ):
1815 """
1816 Public method used to start the remote debugger.
1817
1818 @param progargs commandline for the program to be debugged
1819 (list of strings)
1820 @param wd working directory for the program execution (string)
1821 @param host hostname of the debug server (string)
1822 @param port portnumber of the debug server (int)
1823 @param exceptions flag to enable exception reporting of the IDE
1824 (boolean)
1825 @param tracePython flag to enable tracing into the Python library
1826 (boolean)
1827 @param redirect flag indicating redirection of stdin, stdout and
1828 stderr (boolean)
1829 @param passive flag indicating a passive debugging session
1830 @type bool
1831 @param multiprocessSupport flag indicating to enable multiprocess
1832 debugging support
1833 @type bool
1834 @param codeStr string containing Python code to execute
1835 @type str
1836 @param scriptModule name of a module to be executed as a script
1837 @type str
1838 @return exit code of the debugged program
1839 @rtype int
1840 """
1841 if host is None:
1842 host = os.getenv("ERICHOST", "localhost")
1843 if port is None:
1844 port = os.getenv("ERICPORT", 42424)
1845
1846 remoteAddress = self.__resolveHost(host)
1847 if progargs:
1848 if not progargs[0].startswith("-"):
1849 name = os.path.basename(progargs[0])
1850 elif progargs[0] == "--multiprocessing-fork":
1851 name = "debug_client_mp-fork"
1852 else:
1853 name = "debug_client_code"
1854 else:
1855 name = "debug_client_code"
1856 self.connectDebugger(port, remoteAddress, redirect, name=name)
1857
1858 self._fncache = {}
1859 self.dircache = []
1860 if codeStr:
1861 self.running = "<string>"
1862 sys.argv = ["<string>"] + progargs[:]
1863 else:
1864 sys.argv = progargs[:]
1865 sys.argv[0] = os.path.abspath(sys.argv[0])
1866 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
1867 if wd == "":
1868 os.chdir(sys.path[1])
1869 else:
1870 os.chdir(wd)
1871 self.running = sys.argv[0]
1872 self.__setCoding(self.running)
1873 self.debugging = True
1874 self.multiprocessSupport = multiprocessSupport
1875
1876 self.passive = passive
1877 if passive:
1878 self.sendPassiveStartup(self.running, exceptions)
1879
1880 self.attachThread(mainThread=True)
1881 self.mainThread.tracePythonLibs(tracePython)
1882
1883 # set the system exception handling function to ensure, that
1884 # we report on all unhandled exceptions
1885 sys.excepthook = self.__unhandled_exception
1886 self.__interceptSignals()
1887
1888 # This will eventually enter a local event loop.
1889 self.debugMod.__dict__["__file__"] = self.running
1890 sys.modules["__main__"] = self.debugMod
1891 if codeStr:
1892 code = self.__compileCommand(codeStr)
1893 elif scriptModule:
1894 import runpy
1895
1896 modName, modSpec, code = runpy._get_module_details(scriptModule)
1897 self.running = code.co_filename
1898 self.debugMod.__dict__.clear()
1899 self.debugMod.__dict__.update(
1900 {
1901 "__name__": "__main__",
1902 "__file__": self.running,
1903 "__package__": modSpec.parent,
1904 "__loader__": modSpec.loader,
1905 "__spec__": modSpec,
1906 "__builtins__": __builtins__,
1907 }
1908 )
1909 else:
1910 code = self.__compileFileSource(self.running)
1911 res = (
1912 self.mainThread.run(code, self.debugMod.__dict__, debug=True)
1913 if code
1914 else 42 # should not happen
1915 )
1916 return res
1917
1918 def run_call(self, scriptname, func, *args):
1919 """
1920 Public method used to start the remote debugger and call a function.
1921
1922 @param scriptname name of the script to be debugged (string)
1923 @param func function to be called
1924 @param *args arguments being passed to func
1925 @return result of the function call
1926 """
1927 self.startDebugger(scriptname, enableTrace=False)
1928 res = self.mainThread.runcall(func, *args)
1929 self.progTerminated(res, closeSession=False)
1930 return res
1931
1932 def __resolveHost(self, host):
1933 """
1934 Private method to resolve a hostname to an IP address.
1935
1936 @param host hostname of the debug server (string)
1937 @return IP address (string)
1938 """
1939 try:
1940 host, version = host.split("@@")
1941 except ValueError:
1942 version = "v4"
1943
1944 family = (
1945 0
1946 if version.startswith("i")
1947 else (socket.AF_INET if version == "v4" else socket.AF_INET6)
1948 )
1949
1950 with contextlib.suppress(OSError):
1951 addrinfo = socket.getaddrinfo(host, None, family, socket.SOCK_STREAM)
1952 return addrinfo[0][4][0]
1953
1954 return None
1955
1956 def main(self):
1957 """
1958 Public method implementing the main method.
1959 """
1960 if "--" in sys.argv:
1961 args = sys.argv[1:]
1962 host = None
1963 port = None
1964 wd = ""
1965 tracePython = False
1966 exceptions = True
1967 redirect = True
1968 passive = True
1969 multiprocess = False
1970 codeStr = ""
1971 scriptModule = ""
1972 while args[0]:
1973 if args[0] == "-h":
1974 host = args[1]
1975 del args[0]
1976 del args[0]
1977 elif args[0] == "-p":
1978 port = int(args[1])
1979 del args[0]
1980 del args[0]
1981 elif args[0] == "-w":
1982 wd = args[1]
1983 del args[0]
1984 del args[0]
1985 elif args[0] == "-t":
1986 tracePython = True
1987 del args[0]
1988 elif args[0] == "-e":
1989 exceptions = False
1990 del args[0]
1991 elif args[0] == "-n":
1992 redirect = False
1993 del args[0]
1994 elif args[0] == "--no-encoding":
1995 self.noencoding = True
1996 del args[0]
1997 elif args[0] == "--no-passive":
1998 passive = False
1999 del args[0]
2000 elif args[0] == "--multiprocess":
2001 multiprocess = True
2002 del args[0]
2003 elif args[0] in ("-c", "--code"):
2004 codeStr = args[1]
2005 del args[0]
2006 del args[0]
2007 elif args[0] in ("-m", "--module"):
2008 scriptModule = args[1]
2009 del args[0]
2010 del args[0]
2011 elif args[0] == "--":
2012 del args[0]
2013 break
2014 else: # unknown option
2015 del args[0]
2016 if not args:
2017 print("No program given. Aborting!")
2018 # __IGNORE_WARNING_M801__
2019 elif "-m" in args:
2020 print("Running module as a script is not supported. Aborting!")
2021 # __IGNORE_WARNING_M801__
2022 else:
2023 # Store options in case a new Python process is created
2024 self.startOptions = (
2025 wd,
2026 host,
2027 port,
2028 exceptions,
2029 tracePython,
2030 redirect,
2031 self.noencoding,
2032 )
2033 if not self.noencoding:
2034 self.__coding = self.defaultCoding
2035 patchNewProcessFunctions(multiprocess, self)
2036 res = self.startProgInDebugger(
2037 args,
2038 wd,
2039 host,
2040 port,
2041 exceptions=exceptions,
2042 tracePython=tracePython,
2043 redirect=redirect,
2044 passive=passive,
2045 multiprocessSupport=multiprocess,
2046 codeStr=codeStr,
2047 scriptModule=scriptModule,
2048 )
2049 sys.exit(res)
2050 else:
2051 if sys.argv[1] == "--no-encoding":
2052 self.noencoding = True
2053 del sys.argv[1]
2054
2055 if sys.argv[1] == "--multiprocess":
2056 self.multiprocessSupport = True
2057 del sys.argv[1]
2058
2059 if sys.argv[1] == "":
2060 del sys.argv[1]
2061
2062 try:
2063 port = int(sys.argv[1])
2064 except (ValueError, IndexError):
2065 port = -1
2066
2067 if sys.argv[2] == "True":
2068 redirect = True
2069 elif sys.argv[2] == "False":
2070 redirect = False
2071 else:
2072 try:
2073 redirect = int(sys.argv[2])
2074 except (ValueError, IndexError):
2075 redirect = True
2076
2077 ipOrHost = sys.argv[3]
2078 if ":" in ipOrHost or ipOrHost[0] in "0123456789":
2079 # IPv6 address or IPv4 address
2080 remoteAddress = ipOrHost
2081 else:
2082 remoteAddress = self.__resolveHost(ipOrHost)
2083
2084 sys.argv = [""]
2085 if "" not in sys.path:
2086 sys.path.insert(0, "")
2087
2088 if port >= 0:
2089 # Store options in case a new Python process is created
2090 self.startOptions = (
2091 "",
2092 remoteAddress,
2093 port,
2094 True,
2095 False,
2096 redirect,
2097 self.noencoding,
2098 )
2099 if not self.noencoding:
2100 self.__coding = self.defaultCoding
2101 patchNewProcessFunctions(self.multiprocessSupport, self)
2102 self.connectDebugger(port, remoteAddress, redirect)
2103 self.__interact()
2104 else:
2105 print("No network port given. Aborting...")
2106 # __IGNORE_WARNING_M801__
2107
2108 def close(self, fd):
2109 """
2110 Public method implementing a close method as a replacement for
2111 os.close().
2112
2113 It prevents the debugger connections from being closed.
2114
2115 @param fd file descriptor to be closed (integer)
2116 """
2117 if fd in [
2118 self.readstream.fileno(),
2119 self.writestream.fileno(),
2120 self.errorstream.fileno(),
2121 ]:
2122 return
2123
2124 DebugClientOrigClose(fd)
2125
2126 def __getSysPath(self, firstEntry):
2127 """
2128 Private slot to calculate a path list including the PYTHONPATH
2129 environment variable.
2130
2131 @param firstEntry entry to be put first in sys.path (string)
2132 @return path list for use as sys.path (list of strings)
2133 """
2134 sysPath = [
2135 path
2136 for path in os.environ.get("PYTHONPATH", "").split(os.pathsep)
2137 if path not in sys.path
2138 ] + sys.path[:]
2139 if "" in sysPath:
2140 sysPath.remove("")
2141 sysPath.insert(0, firstEntry)
2142 sysPath.insert(0, "")
2143 return sysPath
2144
2145 def skipMultiProcessDebugging(self, scriptName):
2146 """
2147 Public method to check, if the given script is eligible for debugging.
2148
2149 @param scriptName name of the script to check
2150 @type str
2151 @return flag indicating eligibility
2152 @rtype bool
2153 """
2154 return any(fnmatch.fnmatch(scriptName, pattern) for pattern in self.noDebugList)

eric ide

mercurial