src/eric7/DebugClients/Python/DebugBase.py

branch
eric7-maintenance
changeset 9264
18a7312cfdb3
parent 9221
bf71ee032bb4
child 9462
e65379fdbd97
equal deleted inserted replaced
9241:d23e9854aea4 9264:18a7312cfdb3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the debug base class which based originally on bdb.
8 """
9
10 import sys
11 import os
12 import types
13 import atexit
14 import inspect
15 import ctypes
16 import time
17 import dis
18 import contextlib
19
20 from BreakpointWatch import Breakpoint, Watch
21
22 import _thread
23 from DebugUtilities import getargvalues, formatargvalues
24
25 gRecursionLimit = 64
26
27 try:
28 GENERATOR_AND_COROUTINE_FLAGS = (
29 inspect.CO_GENERATOR | inspect.CO_COROUTINE | inspect.CO_ASYNC_GENERATOR
30 )
31 except AttributeError:
32 # Python < 3.7
33 GENERATOR_AND_COROUTINE_FLAGS = inspect.CO_GENERATOR
34
35
36 def printerr(s):
37 """
38 Module function used for debugging the debug client.
39
40 @param s data to be printed
41 """
42 sys.__stderr__.write("{0!s}\n".format(s))
43 sys.__stderr__.flush()
44
45
46 def setRecursionLimit(limit):
47 """
48 Module function to set the recursion limit.
49
50 @param limit recursion limit (integer)
51 """
52 global gRecursionLimit
53 gRecursionLimit = limit
54
55
56 class DebugBase:
57 """
58 Class implementing base class of the debugger.
59
60 Provides methods for the 'owning' client to call to step etc.
61 """
62
63 lib = os.path.dirname(inspect.__file__)
64 # tuple required because it's accessed a lot of times by startswith method
65 pathsToSkip = ("<", os.path.dirname(__file__), inspect.__file__[:-1])
66 filesToSkip = {}
67
68 # cache for fixed file names
69 _fnCache = {}
70
71 # Stop all timers, when greenlets are used
72 pollTimerEnabled = True
73
74 def __init__(self, dbgClient):
75 """
76 Constructor
77
78 @param dbgClient the owning client
79 """
80 self._dbgClient = dbgClient
81
82 # Some informations about the thread
83 self.isMainThread = False
84 self.quitting = False
85 self.id = -1
86 self.name = ""
87
88 self.tracePythonLibs(False)
89
90 # Special handling of a recursion error
91 self.skipFrames = 0
92
93 self.isBroken = False
94 self.isException = False
95 self.cFrame = None
96
97 # current frame we are at
98 self.currentFrame = None
99
100 # frames, where we want to stop or release debugger
101 self.stopframe = None
102 self.returnframe = None
103 self.stop_everywhere = False
104
105 self.__recursionDepth = -1
106 self.setRecursionDepth(inspect.currentframe())
107
108 # background task to periodicaly check for client interactions
109 self.eventPollFlag = False
110 self.timer = _thread.start_new_thread(self.__eventPollTimer, ())
111
112 # provide a hook to perform a hard breakpoint
113 # Use it like this:
114 # if hasattr(sys, 'breakpoint): sys.breakpoint()
115 sys.breakpoint = self.set_trace
116 if sys.version_info >= (3, 7):
117 sys.breakpointhook = self.set_trace
118
119 def __eventPollTimer(self):
120 """
121 Private method to set a flag every 0.5 s to check for new messages.
122 """
123 while DebugBase.pollTimerEnabled:
124 time.sleep(0.5)
125 self.eventPollFlag = True
126
127 self.eventPollFlag = False
128
129 def getCurrentFrame(self):
130 """
131 Public method to return the current frame.
132
133 @return the current frame
134 @rtype frame object
135 """
136 # Don't show any local frames after the program was stopped
137 if self.quitting:
138 return None
139
140 return self.currentFrame
141
142 def getFrameLocals(self, frmnr=0):
143 """
144 Public method to return the locals dictionary of the current frame
145 or a frame below.
146
147 @param frmnr distance of frame to get locals dictionary of. 0 is
148 the current frame (int)
149 @return locals dictionary of the frame
150 """
151 f = self.currentFrame
152 while f is not None and frmnr > 0:
153 f = f.f_back
154 frmnr -= 1
155 return f.f_locals
156
157 def storeFrameLocals(self, frmnr=0):
158 """
159 Public method to store the locals into the frame, so an access to
160 frame.f_locals returns the last data.
161
162 @param frmnr distance of frame to store locals dictionary to. 0 is
163 the current frame (int)
164 """
165 cf = self.currentFrame
166 while cf is not None and frmnr > 0:
167 cf = cf.f_back
168 frmnr -= 1
169
170 with contextlib.suppress(Exception):
171 if "__pypy__" in sys.builtin_module_names:
172 import __pypy__
173
174 __pypy__.locals_to_fast(cf)
175 return
176
177 ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(cf), ctypes.c_int(0))
178
179 def step(self, traceMode):
180 """
181 Public method to perform a step operation in this thread.
182
183 @param traceMode If it is True, then the step is a step into,
184 otherwise it is a step over.
185 """
186 if traceMode:
187 self.set_step()
188 else:
189 self.set_next(self.currentFrame)
190
191 def stepOut(self):
192 """
193 Public method to perform a step out of the current call.
194 """
195 self.set_return(self.currentFrame)
196
197 def go(self, special):
198 """
199 Public method to resume the thread.
200
201 It resumes the thread stopping only at breakpoints or exceptions.
202
203 @param special flag indicating a special continue operation
204 """
205 self.set_continue(special)
206
207 def setRecursionDepth(self, frame):
208 """
209 Public method to determine the current recursion depth.
210
211 @param frame The current stack frame.
212 """
213 self.__recursionDepth = 0
214 while frame is not None:
215 self.__recursionDepth += 1
216 frame = frame.f_back
217
218 def profileWithRecursion(self, frame, event, arg):
219 """
220 Public method used to trace some stuff independent of the debugger
221 trace function.
222
223 @param frame current stack frame
224 @type frame object
225 @param event trace event
226 @type str
227 @param arg arguments
228 @type depends on the previous event parameter
229 @exception RuntimeError raised to indicate too many recursions
230 """
231 if event == "return":
232 self.cFrame = frame.f_back
233 self.__recursionDepth -= 1
234 if self._dbgClient.callTraceEnabled:
235 self.__sendCallTrace(event, frame, self.cFrame)
236 elif event == "call":
237 if self._dbgClient.callTraceEnabled:
238 self.__sendCallTrace(event, self.cFrame, frame)
239 self.cFrame = frame
240 self.__recursionDepth += 1
241 if self.__recursionDepth > gRecursionLimit:
242 raise RuntimeError(
243 "maximum recursion depth exceeded\n"
244 "(offending frame is two down the stack)"
245 )
246
247 def profile(self, frame, event, arg):
248 """
249 Public method used to trace some stuff independent of the debugger
250 trace function.
251
252 @param frame current stack frame
253 @type frame object
254 @param event trace event
255 @type str
256 @param arg arguments
257 @type depends on the previous event parameter
258 """
259 if event == "return":
260 self.__sendCallTrace(event, frame, frame.f_back)
261 elif event == "call":
262 self.__sendCallTrace(event, frame.f_back, frame)
263
264 def __sendCallTrace(self, event, fromFrame, toFrame):
265 """
266 Private method to send a call/return trace.
267
268 @param event trace event
269 @type str
270 @param fromFrame originating frame
271 @type frame object
272 @param toFrame destination frame
273 @type frame object
274 """
275 if not self.__skipFrame(fromFrame) and not self.__skipFrame(toFrame):
276 fromInfo = {
277 "filename": self._dbgClient.absPath(self.fix_frame_filename(fromFrame)),
278 "linenumber": fromFrame.f_lineno,
279 "codename": fromFrame.f_code.co_name,
280 }
281 toInfo = {
282 "filename": self._dbgClient.absPath(self.fix_frame_filename(toFrame)),
283 "linenumber": toFrame.f_lineno,
284 "codename": toFrame.f_code.co_name,
285 }
286 self._dbgClient.sendCallTrace(event, fromInfo, toInfo)
287
288 def trace_dispatch(self, frame, event, arg):
289 """
290 Public method reimplemented from bdb.py to do some special things.
291
292 This specialty is to check the connection to the debug server
293 for new events (i.e. new breakpoints) while we are going through
294 the code.
295
296 @param frame The current stack frame
297 @type frame object
298 @param event The trace event
299 @type str
300 @param arg The arguments
301 @type depends on the previous event parameter
302 @return local trace function
303 @rtype trace function or None
304 @exception SystemExit
305 """
306 # give the client a chance to push through new break points.
307 if self.eventPollFlag:
308 self._dbgClient.eventPoll()
309 self.eventPollFlag = False
310
311 if self.quitting:
312 raise SystemExit
313
314 if event == "line":
315 if self.stop_here(frame) or self.break_here(frame):
316 if (
317 self.stop_everywhere
318 and frame.f_back
319 and frame.f_back.f_code.co_name == "prepareJsonCommand"
320 ):
321 # Just stepped into print statement, so skip these frames
322 self._set_stopinfo(None, frame.f_back)
323 else:
324 self.user_line(frame)
325 return self.trace_dispatch
326
327 if event == "call":
328 if (
329 self.stop_here(frame)
330 or self.__checkBreakInFrame(frame)
331 or Watch.watches != []
332 ) or (
333 self.stopframe and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
334 ):
335 return self.trace_dispatch
336 else:
337 # No need to trace this function
338 return None
339
340 if event == "return":
341 if self.stop_here(frame) or frame == self.returnframe:
342 # Ignore return events in generator except when stepping.
343 if (
344 self.stopframe
345 and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
346 ):
347 return self.trace_dispatch
348 # Only true if we didn't stop in this frame, because it's
349 # belonging to the eric debugger.
350 if self.stopframe is frame and self.stoplineno != -1:
351 self._set_stopinfo(None, frame.f_back)
352 return None
353
354 if event == "exception":
355 if not self.__skipFrame(frame):
356 # When stepping with next/until/return in a generator frame,
357 # skip the internal StopIteration exception (with no traceback)
358 # triggered by a subiterator run with the 'yield from'
359 # statement.
360 if not (
361 frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
362 and arg[0] is StopIteration
363 and arg[2] is None
364 ):
365 self.user_exception(arg)
366 # Stop at the StopIteration or GeneratorExit exception when the
367 # user has set stopframe in a generator by issuing a return
368 # command, or a next/until command at the last statement in the
369 # generator before the exception.
370 elif (
371 self.stopframe
372 and frame is not self.stopframe
373 and (self.stopframe.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS)
374 and arg[0] in (StopIteration, GeneratorExit)
375 ):
376 self.user_exception(arg)
377 return None
378
379 if event == "c_call":
380 return None
381 if event == "c_exception":
382 return None
383 if event == "c_return":
384 return None
385
386 print( # __IGNORE_WARNING_M801__
387 "DebugBase.trace_dispatch:" " unknown debugging event: ",
388 repr(event),
389 )
390
391 return self.trace_dispatch
392
393 def set_trace(self, frame=None):
394 """
395 Public method to start debugging from 'frame'.
396
397 If frame is not specified, debugging starts from caller's frame.
398 Because of jump optimizations it's not possible to use sys.breakpoint()
399 as last instruction in a function or method.
400
401 @param frame frame to start debugging from
402 @type frame object
403 """
404 if frame is None:
405 frame = sys._getframe().f_back # Skip set_trace method
406
407 stopOnHandleCommand = self._dbgClient.handleJsonCommand.__code__
408
409 frame.f_trace = self.trace_dispatch
410 while frame.f_back is not None:
411 # stop at eric's debugger frame or a threading bootstrap
412 if frame.f_back.f_code == stopOnHandleCommand:
413 frame.f_trace = self.trace_dispatch
414 break
415
416 frame = frame.f_back
417
418 self.stop_everywhere = True
419 sys.settrace(self.trace_dispatch)
420 sys.setprofile(self._dbgClient.callTraceEnabled)
421
422 def bootstrap(self, target, args, kwargs):
423 """
424 Public method to bootstrap a thread.
425
426 It wraps the call to the user function to enable tracing
427 before hand.
428
429 @param target function which is called in the new created thread
430 @type function pointer
431 @param args arguments to pass to target
432 @type tuple
433 @param kwargs keyword arguments to pass to target
434 @type dict
435 """
436 try:
437 # Because in the initial run method the "base debug" function is
438 # set up, it's also valid for the threads afterwards.
439 sys.settrace(self.trace_dispatch)
440
441 target(*args, **kwargs)
442 except Exception:
443 excinfo = sys.exc_info()
444 self.user_exception(excinfo, True)
445 finally:
446 sys.settrace(None)
447 sys.setprofile(None)
448
449 def run(
450 self, cmd, globalsDict=None, localsDict=None, debug=True, closeSession=True
451 ):
452 """
453 Public method to start a given command under debugger control.
454
455 @param cmd command / code to execute under debugger control
456 @type str or CodeType
457 @param globalsDict dictionary of global variables for cmd
458 @type dict
459 @param localsDict dictionary of local variables for cmd
460 @type dict
461 @param debug flag if command should run under debugger control
462 @type bool
463 @return exit code of the program
464 @rtype int
465 @param closeSession flag indicating to close the debugger session
466 at exit
467 @type bool
468 """
469 if globalsDict is None:
470 import __main__
471
472 globalsDict = __main__.__dict__
473
474 if localsDict is None:
475 localsDict = globalsDict
476
477 if not isinstance(cmd, types.CodeType):
478 cmd = compile(cmd, "<string>", "exec")
479
480 if debug:
481 # First time the trace_dispatch function is called, a "base debug"
482 # function has to be returned, which is called at every user code
483 # function call. This is ensured by setting stop_everywhere.
484 self.stop_everywhere = True
485 sys.settrace(self.trace_dispatch)
486
487 try:
488 exec(cmd, globalsDict, localsDict) # secok
489 atexit._run_exitfuncs()
490 self._dbgClient.progTerminated(0, closeSession=closeSession)
491 exitcode = 0
492 except SystemExit:
493 atexit._run_exitfuncs()
494 excinfo = sys.exc_info()
495 exitcode, message = self.__extractSystemExitMessage(excinfo)
496 self._dbgClient.progTerminated(
497 exitcode, message=message, closeSession=closeSession
498 )
499 except Exception:
500 excinfo = sys.exc_info()
501 self.user_exception(excinfo, True)
502 exitcode = 242
503 finally:
504 self.quitting = True
505 sys.settrace(None)
506 return exitcode
507
508 def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
509 """
510 Protected method to update the frame pointers.
511
512 @param stopframe the frame object where to stop
513 @type frame object
514 @param returnframe the frame object where to stop on a function return
515 @type frame object
516 @param stoplineno line number to stop at. If stoplineno is greater than
517 or equal to 0, then stop at line greater than or equal to the
518 stopline. If stoplineno is -1, then don't stop at all.
519 @type int
520 """
521 self.stopframe = stopframe
522 self.returnframe = returnframe
523 # stoplineno >= 0 means: stop at line >= the stoplineno
524 # stoplineno -1 means: don't stop at all
525 self.stoplineno = stoplineno
526
527 if returnframe is not None:
528 # Ensure to be able to stop on the return frame
529 returnframe.f_trace = self.trace_dispatch
530 self.stop_everywhere = False
531
532 def set_continue(self, special):
533 """
534 Public method to stop only on next breakpoint.
535
536 @param special flag indicating a special continue operation
537 @type bool
538 """
539 # Here we only set a new stop frame if it is a normal continue.
540 if not special:
541 self._set_stopinfo(None, None, -1)
542
543 # Disable tracing if not started in debug mode
544 if not self._dbgClient.debugging:
545 sys.settrace(None)
546 sys.setprofile(None)
547
548 def set_until(self, frame=None, lineno=None):
549 """
550 Public method to stop when the line with the lineno greater than the
551 current one is reached or when returning from current frame.
552
553 @param frame reference to the frame object
554 @type frame object
555 @param lineno line number to continue to
556 @type int
557 """
558 # the name "until" is borrowed from gdb
559 if frame is None:
560 frame = self.currentFrame
561 if lineno is None:
562 lineno = frame.f_lineno + 1
563 self._set_stopinfo(frame, frame, lineno)
564
565 def set_step(self):
566 """
567 Public method to stop after one line of code.
568 """
569 self._set_stopinfo(None, None)
570 self.stop_everywhere = True
571
572 def set_next(self, frame):
573 """
574 Public method to stop on the next line in or below the given frame.
575
576 @param frame the frame object
577 @type frame object
578 """
579 self._set_stopinfo(frame, frame.f_back)
580 frame.f_trace = self.trace_dispatch
581
582 def set_return(self, frame):
583 """
584 Public method to stop when returning from the given frame.
585
586 @param frame the frame object
587 @type frame object
588 """
589 self._set_stopinfo(None, frame.f_back)
590
591 def move_instruction_pointer(self, lineno):
592 """
593 Public method to move the instruction pointer to another line.
594
595 @param lineno new line number
596 @type int
597 """
598 try:
599 self.currentFrame.f_lineno = lineno
600 stack = self.getStack(self.currentFrame)
601 self._dbgClient.sendResponseLine(stack, self.name)
602 except Exception as e:
603 printerr(e)
604
605 def set_quit(self):
606 """
607 Public method to quit.
608
609 Disables the trace functions and resets all frame pointer.
610 """
611 sys.setprofile(None)
612 self.stopframe = None
613 self.returnframe = None
614 for debugThread in self._dbgClient.threads.values():
615 debugThread.quitting = True
616
617 def fix_frame_filename(self, frame):
618 """
619 Public method used to fixup the filename for a given frame.
620
621 The logic employed here is that if a module was loaded
622 from a .pyc file, then the correct .py to operate with
623 should be in the same path as the .pyc. The reason this
624 logic is needed is that when a .pyc file is generated, the
625 filename embedded and thus what is readable in the code object
626 of the frame object is the fully qualified filepath when the
627 pyc is generated. If files are moved from machine to machine
628 this can break debugging as the .pyc will refer to the .py
629 on the original machine. Another case might be sharing
630 code over a network... This logic deals with that.
631
632 @param frame the frame object
633 @type frame object
634 @return fixed up file name
635 @rtype str
636 """
637 # get module name from __file__
638 fn = frame.f_globals.get("__file__")
639 try:
640 return self._fnCache[fn]
641 except KeyError:
642 if fn is None:
643 return frame.f_code.co_filename
644
645 absFilename = os.path.abspath(fn)
646 if absFilename.endswith((".pyc", ".pyo", ".pyd")):
647 fixedName = absFilename[:-1]
648 if not os.path.exists(fixedName):
649 fixedName = absFilename
650 else:
651 fixedName = absFilename
652 # update cache
653 self._fnCache[fn] = fixedName
654 return fixedName
655
656 def __checkBreakInFrame(self, frame):
657 """
658 Private method to check if the function / method has a line number
659 which is a breakpoint.
660
661 @param frame the frame object
662 @type frame object
663 @return Flag indicating a function / method with breakpoint
664 @rtype bool
665 """
666 try:
667 return Breakpoint.breakInFrameCache[
668 frame.f_globals.get("__file__"), frame.f_code.co_firstlineno
669 ]
670 except KeyError:
671 filename = self.fix_frame_filename(frame)
672 if filename not in Breakpoint.breakInFile:
673 Breakpoint.breakInFrameCache[
674 frame.f_globals.get("__file__"), frame.f_code.co_firstlineno
675 ] = False
676 return False
677 lineNo = frame.f_code.co_firstlineno
678 lineNumbers = [lineNo]
679
680 co_lnotab = frame.f_code.co_lnotab[1::2]
681
682 # No need to handle special case if a lot of lines between
683 # (e.g. closure), because the additional lines won't cause a bp
684 for co_lno in co_lnotab:
685 if co_lno >= 0x80:
686 lineNo -= 0x100
687 lineNo += co_lno
688 lineNumbers.append(lineNo)
689
690 for bp in Breakpoint.breakInFile[filename]:
691 if bp in lineNumbers:
692 Breakpoint.breakInFrameCache[
693 frame.f_globals.get("__file__"), frame.f_code.co_firstlineno
694 ] = True
695 return True
696 Breakpoint.breakInFrameCache[
697 frame.f_globals.get("__file__"), frame.f_code.co_firstlineno
698 ] = False
699 return False
700
701 def break_here(self, frame):
702 """
703 Public method reimplemented from bdb.py to fix the filename from the
704 frame.
705
706 See fix_frame_filename for more info.
707
708 @param frame the frame object
709 @type frame object
710 @return flag indicating the break status
711 @rtype bool
712 """
713 filename = self.fix_frame_filename(frame)
714 if (filename, frame.f_lineno) in Breakpoint.breaks:
715 bp, flag = Breakpoint.effectiveBreak(filename, frame.f_lineno, frame)
716 if bp:
717 # flag says ok to delete temp. bp
718 if flag and bp.temporary:
719 self.__do_clearBreak(filename, frame.f_lineno)
720 return True
721
722 if Watch.watches != []:
723 bp, flag = Watch.effectiveWatch(frame)
724 if bp:
725 # flag says ok to delete temp. watch
726 if flag and bp.temporary:
727 self.__do_clearWatch(bp.cond)
728 return True
729
730 return False
731
732 def __do_clearBreak(self, filename, lineno):
733 """
734 Private method called to clear a temporary breakpoint.
735
736 @param filename name of the file the bp belongs to
737 @type str
738 @param lineno linenumber of the bp
739 @type int
740 """
741 Breakpoint.clear_break(filename, lineno)
742 self._dbgClient.sendClearTemporaryBreakpoint(filename, lineno)
743
744 def __do_clearWatch(self, cond):
745 """
746 Private method called to clear a temporary watch expression.
747
748 @param cond expression of the watch expression to be cleared
749 @type str
750 """
751 Watch.clear_watch(cond)
752 self._dbgClient.sendClearTemporaryWatch(cond)
753
754 def getStack(self, frame=None, applyTrace=False):
755 """
756 Public method to get the stack.
757
758 @param frame frame object to inspect
759 @type frame object or list
760 @param applyTrace flag to assign trace function to fr.f_trace
761 @type bool
762 @return list of lists with file name (string), line number (integer)
763 and function name (string)
764 """
765 tb_lineno = None
766 if frame is None:
767 fr = self.getCurrentFrame()
768 elif type(frame) == list:
769 fr, tb_lineno = frame.pop(0)
770 else:
771 fr = frame
772
773 stack = []
774 while fr is not None:
775 if applyTrace:
776 # Reset the trace function so we can be sure
777 # to trace all functions up the stack... This gets around
778 # problems where an exception/breakpoint has occurred
779 # but we had disabled tracing along the way via a None
780 # return from dispatch_call
781 fr.f_trace = self.trace_dispatch
782
783 fname = self._dbgClient.absPath(self.fix_frame_filename(fr))
784 # Always show at least one stack frame, even if it's from eric.
785 if stack and os.path.basename(fname).startswith(
786 (
787 "DebugBase.py",
788 "DebugClientBase.py",
789 "ThreadExtension.py",
790 "threading.py",
791 )
792 ):
793 break
794
795 fline = tb_lineno or fr.f_lineno
796 ffunc = fr.f_code.co_name
797
798 if ffunc == "?":
799 ffunc = ""
800
801 if ffunc and not ffunc.startswith("<"):
802 argInfo = getargvalues(fr)
803 try:
804 fargs = formatargvalues(
805 argInfo.args, argInfo.varargs, argInfo.keywords, argInfo.locals
806 )
807 except Exception:
808 fargs = ""
809 else:
810 fargs = ""
811
812 stack.append([fname, fline, ffunc, fargs])
813
814 # is it a stack frame or exception list?
815 if type(frame) == list:
816 if frame != []:
817 fr, tb_lineno = frame.pop(0)
818 else:
819 fr = None
820 else:
821 fr = fr.f_back
822
823 return stack
824
825 def user_line(self, frame):
826 """
827 Public method reimplemented to handle the program about to execute a
828 particular line.
829
830 @param frame the frame object
831 """
832 # We never stop on line 0.
833 if frame.f_lineno == 0:
834 return
835
836 self.isBroken = True
837 self.currentFrame = frame
838 stack = self.getStack(frame, applyTrace=True)
839
840 self._dbgClient.lockClient()
841 self._dbgClient.currentThread = self
842 self._dbgClient.currentThreadExec = self
843
844 self._dbgClient.sendResponseLine(stack, self.name)
845 self._dbgClient.eventLoop()
846
847 self.isBroken = False
848 self._dbgClient.unlockClient()
849
850 self._dbgClient.dumpThreadList()
851
852 def user_exception(self, excinfo, unhandled=False):
853 """
854 Public method reimplemented to report an exception to the debug server.
855
856 @param excinfo details about the exception
857 @type tuple(Exception, excval object, traceback frame object)
858 @param unhandled flag indicating an uncaught exception
859 @type bool
860 """
861 exctype, excval, exctb = excinfo
862
863 if (
864 exctype in [GeneratorExit, StopIteration] and unhandled is False
865 ) or exctype == SystemExit:
866 # ignore these
867 return
868
869 if exctype in [SyntaxError, IndentationError]:
870 try:
871 if type(excval) == tuple:
872 message, details = excval
873 filename, lineno, charno, text = details
874 else:
875 message = excval.msg
876 filename = excval.filename
877 lineno = excval.lineno
878 charno = excval.offset
879
880 if filename is None:
881 realSyntaxError = False
882 else:
883 if charno is None:
884 charno = 0
885
886 filename = os.path.abspath(filename)
887 realSyntaxError = os.path.exists(filename)
888
889 except (AttributeError, ValueError):
890 message = ""
891 filename = ""
892 lineno = 0
893 charno = 0
894 realSyntaxError = True
895
896 if realSyntaxError:
897 self._dbgClient.sendSyntaxError(
898 message, filename, lineno, charno, self.name
899 )
900 self._dbgClient.eventLoop()
901 return
902
903 self.skipFrames = 0
904 if (
905 exctype == RuntimeError
906 and str(excval).startswith("maximum recursion depth exceeded")
907 or exctype == RecursionError
908 ): # __IGNORE_WARNING__
909 excval = "maximum recursion depth exceeded"
910 depth = 0
911 tb = exctb
912 while tb:
913 tb = tb.tb_next
914
915 if (
916 tb
917 and tb.tb_frame.f_code.co_name == "trace_dispatch"
918 and __file__.startswith(tb.tb_frame.f_code.co_filename)
919 ):
920 depth = 1
921 self.skipFrames += depth
922
923 # always 1 if running without debugger
924 self.skipFrames = max(1, self.skipFrames)
925
926 exctype = self.__extractExceptionName(exctype)
927
928 if excval is None:
929 excval = ""
930
931 exctypetxt = (
932 "unhandled {0!s}".format(str(exctype)) if unhandled else str(exctype)
933 )
934 excvaltxt = str(excval)
935
936 # Don't step into libraries, which are used by our debugger methods
937 if exctb is not None:
938 self.stop_everywhere = False
939
940 self.isBroken = True
941 self.isException = True
942
943 disassembly = None
944 stack = []
945 if exctb:
946 frlist = self.__extract_stack(exctb)
947 frlist.reverse()
948 disassembly = self.__disassemble(frlist[0][0])
949
950 self.currentFrame = frlist[0][0]
951 stack = self.getStack(frlist[self.skipFrames :])
952
953 self._dbgClient.lockClient()
954 self._dbgClient.currentThread = self
955 self._dbgClient.currentThreadExec = self
956 self._dbgClient.sendException(exctypetxt, excvaltxt, stack, self.name)
957 self._dbgClient.setDisassembly(disassembly)
958 self._dbgClient.dumpThreadList()
959
960 if exctb is not None:
961 # When polling kept enabled, it isn't possible to resume after an
962 # unhandled exception without further user interaction.
963 self._dbgClient.eventLoop(True)
964
965 self.skipFrames = 0
966
967 self.isBroken = False
968 self.isException = False
969 stop_everywhere = self.stop_everywhere
970 self.stop_everywhere = False
971 self.eventPollFlag = False
972 self._dbgClient.unlockClient()
973 self.stop_everywhere = stop_everywhere
974
975 self._dbgClient.dumpThreadList()
976
977 def __extractExceptionName(self, exctype):
978 """
979 Private method to extract the exception name given the exception
980 type object.
981
982 @param exctype type of the exception
983 @return exception name (string)
984 """
985 return str(exctype).replace("<class '", "").replace("'>", "")
986
987 def __extract_stack(self, exctb):
988 """
989 Private member to return a list of stack frames.
990
991 @param exctb exception traceback
992 @return list of stack frames
993 """
994 tb = exctb
995 stack = []
996 while tb is not None:
997 stack.append((tb.tb_frame, tb.tb_lineno))
998 tb = tb.tb_next
999 tb = None
1000 return stack
1001
1002 def __disassemble(self, frame):
1003 """
1004 Private method to generate a disassembly of the given code object.
1005
1006 @param frame frame object to be disassembled
1007 @type code
1008 @return dictionary containing the disassembly information
1009 @rtype dict
1010 """
1011 co = frame.f_code
1012 disDict = {
1013 "lasti": frame.f_lasti,
1014 "firstlineno": co.co_firstlineno,
1015 "instructions": [],
1016 }
1017
1018 # 1. disassembly info
1019 for instr in dis.get_instructions(co):
1020 instrDict = {
1021 "lineno": 0 if instr.starts_line is None else instr.starts_line,
1022 "isJumpTarget": instr.is_jump_target,
1023 "offset": instr.offset,
1024 "opname": instr.opname,
1025 "arg": instr.arg,
1026 "argrepr": instr.argrepr,
1027 }
1028 disDict["instructions"].append(instrDict)
1029
1030 # 2. code info
1031 # Note: keep in sync with PythonDisViewer.__createCodeInfo()
1032 disDict["codeinfo"] = {
1033 "name": co.co_name,
1034 "filename": co.co_filename,
1035 "firstlineno": co.co_firstlineno,
1036 "argcount": co.co_argcount,
1037 "kwonlyargcount": co.co_kwonlyargcount,
1038 "nlocals": co.co_nlocals,
1039 "stacksize": co.co_stacksize,
1040 "flags": dis.pretty_flags(co.co_flags),
1041 "consts": [str(const) for const in co.co_consts],
1042 "names": [str(name) for name in co.co_names],
1043 "varnames": [str(name) for name in co.co_varnames],
1044 "freevars": [str(var) for var in co.co_freevars],
1045 "cellvars": [str(var) for var in co.co_cellvars],
1046 }
1047 try:
1048 disDict["codeinfo"]["posonlyargcount"] = co.co_posonlyargcount
1049 except AttributeError:
1050 # does not exist prior to 3.8.0
1051 disDict["codeinfo"]["posonlyargcount"] = 0
1052
1053 return disDict
1054
1055 def __extractSystemExitMessage(self, excinfo):
1056 """
1057 Private method to get the SystemExit code and message.
1058
1059 @param excinfo details about the SystemExit exception
1060 @type tuple(Exception, excval object, traceback frame object)
1061 @return SystemExit code and message
1062 @rtype int, str
1063 """
1064 exctype, excval, exctb = excinfo
1065 if excval is None:
1066 exitcode = 0
1067 message = ""
1068 elif isinstance(excval, str):
1069 exitcode = 1
1070 message = excval
1071 elif isinstance(excval, bytes):
1072 exitcode = 1
1073 message = excval.decode()
1074 elif isinstance(excval, int):
1075 exitcode = excval
1076 message = ""
1077 elif isinstance(excval, SystemExit):
1078 code = excval.code
1079 if isinstance(code, str):
1080 exitcode = 1
1081 message = code
1082 elif isinstance(code, bytes):
1083 exitcode = 1
1084 message = code.decode()
1085 elif isinstance(code, int):
1086 exitcode = code
1087 message = ""
1088 elif code is None:
1089 exitcode = 0
1090 message = ""
1091 else:
1092 exitcode = 1
1093 message = str(code)
1094 else:
1095 exitcode = 1
1096 message = str(excval)
1097
1098 return exitcode, message
1099
1100 def stop_here(self, frame):
1101 """
1102 Public method reimplemented to filter out debugger files.
1103
1104 Tracing is turned off for files that are part of the
1105 debugger that are called from the application being debugged.
1106
1107 @param frame the frame object
1108 @type frame object
1109 @return flag indicating whether the debugger should stop here
1110 @rtype bool
1111 """
1112 if self.__skipFrame(frame):
1113 return False
1114
1115 if frame is self.stopframe:
1116 if self.stoplineno == -1:
1117 return False
1118 return frame.f_lineno >= self.stoplineno
1119 return self.stop_everywhere or frame is self.returnframe
1120
1121 def tracePythonLibs(self, enable):
1122 """
1123 Public method to update the settings to trace into Python libraries.
1124
1125 @param enable flag to debug into Python libraries
1126 @type bool
1127 """
1128 pathsToSkip = list(self.pathsToSkip)
1129 # don't trace into Python library?
1130 if enable:
1131 pathsToSkip = [
1132 x
1133 for x in pathsToSkip
1134 if not x.endswith(("site-packages", "dist-packages", self.lib))
1135 ]
1136 else:
1137 pathsToSkip.append(self.lib)
1138 localLib = [
1139 x
1140 for x in sys.path
1141 if x.endswith(("site-packages", "dist-packages"))
1142 and not x.startswith(self.lib)
1143 ]
1144 pathsToSkip.extend(localLib)
1145
1146 self.pathsToSkip = tuple(set(pathsToSkip))
1147
1148 def __skipFrame(self, frame):
1149 """
1150 Private method to filter out debugger files.
1151
1152 Tracing is turned off for files that are part of the
1153 debugger that are called from the application being debugged.
1154
1155 @param frame the frame object
1156 @type frame object
1157 @return flag indicating whether the debugger should skip this frame
1158 @rtype bool
1159 """
1160 try:
1161 return self.filesToSkip[frame.f_code.co_filename]
1162 except KeyError:
1163 ret = frame.f_code.co_filename.startswith(self.pathsToSkip)
1164 self.filesToSkip[frame.f_code.co_filename] = ret
1165 return ret
1166 except AttributeError:
1167 # if frame is None
1168 return True

eric ide

mercurial