src/eric7/DebugClients/Python/DebugClientBase.py

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

eric ide

mercurial