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