src/eric7/DebugClients/Python/DebugBase.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8964
29344a31ee2a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing 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 |
30 inspect.CO_ASYNC_GENERATOR
31 )
32 except AttributeError:
33 # Python < 3.7
34 GENERATOR_AND_COROUTINE_FLAGS = inspect.CO_GENERATOR
35
36
37 def printerr(s):
38 """
39 Module function used for debugging the debug client.
40
41 @param s data to be printed
42 """
43 sys.__stderr__.write('{0!s}\n'.format(s))
44 sys.__stderr__.flush()
45
46
47 def setRecursionLimit(limit):
48 """
49 Module function to set the recursion limit.
50
51 @param limit recursion limit (integer)
52 """
53 global gRecursionLimit
54 gRecursionLimit = limit
55
56
57 class DebugBase:
58 """
59 Class implementing base class of the debugger.
60
61 Provides methods for the 'owning' client to call to step etc.
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 __pypy__.locals_to_fast(cf)
174 return
175
176 ctypes.pythonapi.PyFrame_LocalsToFast(
177 ctypes.py_object(cf),
178 ctypes.c_int(0))
179
180 def step(self, traceMode):
181 """
182 Public method to perform a step operation in this thread.
183
184 @param traceMode If it is True, then the step is a step into,
185 otherwise it is a step over.
186 """
187 if traceMode:
188 self.set_step()
189 else:
190 self.set_next(self.currentFrame)
191
192 def stepOut(self):
193 """
194 Public method to perform a step out of the current call.
195 """
196 self.set_return(self.currentFrame)
197
198 def go(self, special):
199 """
200 Public method to resume the thread.
201
202 It resumes the thread stopping only at breakpoints or exceptions.
203
204 @param special flag indicating a special continue operation
205 """
206 self.set_continue(special)
207
208 def setRecursionDepth(self, frame):
209 """
210 Public method to determine the current recursion depth.
211
212 @param frame The current stack frame.
213 """
214 self.__recursionDepth = 0
215 while frame is not None:
216 self.__recursionDepth += 1
217 frame = frame.f_back
218
219 def profileWithRecursion(self, frame, event, arg):
220 """
221 Public method used to trace some stuff independent of the debugger
222 trace function.
223
224 @param frame current stack frame
225 @type frame object
226 @param event trace event
227 @type str
228 @param arg arguments
229 @type depends on the previous event parameter
230 @exception RuntimeError raised to indicate too many recursions
231 """
232 if event == 'return':
233 self.cFrame = frame.f_back
234 self.__recursionDepth -= 1
235 if self._dbgClient.callTraceEnabled:
236 self.__sendCallTrace(event, frame, self.cFrame)
237 elif event == 'call':
238 if self._dbgClient.callTraceEnabled:
239 self.__sendCallTrace(event, self.cFrame, frame)
240 self.cFrame = frame
241 self.__recursionDepth += 1
242 if self.__recursionDepth > gRecursionLimit:
243 raise RuntimeError(
244 'maximum recursion depth exceeded\n'
245 '(offending frame is two down the stack)')
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(
278 self.fix_frame_filename(fromFrame)),
279 "linenumber": fromFrame.f_lineno,
280 "codename": fromFrame.f_code.co_name,
281 }
282 toInfo = {
283 "filename": self._dbgClient.absPath(
284 self.fix_frame_filename(toFrame)),
285 "linenumber": toFrame.f_lineno,
286 "codename": toFrame.f_code.co_name,
287 }
288 self._dbgClient.sendCallTrace(event, fromInfo, toInfo)
289
290 def trace_dispatch(self, frame, event, arg):
291 """
292 Public method reimplemented from bdb.py to do some special things.
293
294 This specialty is to check the connection to the debug server
295 for new events (i.e. new breakpoints) while we are going through
296 the code.
297
298 @param frame The current stack frame
299 @type frame object
300 @param event The trace event
301 @type str
302 @param arg The arguments
303 @type depends on the previous event parameter
304 @return local trace function
305 @rtype trace function or None
306 @exception SystemExit
307 """
308 # give the client a chance to push through new break points.
309 if self.eventPollFlag:
310 self._dbgClient.eventPoll()
311 self.eventPollFlag = False
312
313 if self.quitting:
314 raise SystemExit
315
316 if event == 'line':
317 if self.stop_here(frame) or self.break_here(frame):
318 if (
319 self.stop_everywhere and
320 frame.f_back and
321 frame.f_back.f_code.co_name == "prepareJsonCommand"
322 ):
323 # Just stepped into print statement, so skip these frames
324 self._set_stopinfo(None, frame.f_back)
325 else:
326 self.user_line(frame)
327 return self.trace_dispatch
328
329 if event == 'call':
330 if (
331 self.stop_here(frame) or
332 self.__checkBreakInFrame(frame) or
333 Watch.watches != []
334 ) or (
335 self.stopframe and
336 frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
337 ):
338 return self.trace_dispatch
339 else:
340 # No need to trace this function
341 return None
342
343 if event == 'return':
344 if self.stop_here(frame) or frame == self.returnframe:
345 # Ignore return events in generator except when stepping.
346 if (
347 self.stopframe and
348 frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
349 ):
350 return self.trace_dispatch
351 # Only true if we didn't stop in this frame, because it's
352 # belonging to the eric debugger.
353 if self.stopframe is frame and self.stoplineno != -1:
354 self._set_stopinfo(None, frame.f_back)
355 return None
356
357 if event == 'exception':
358 if not self.__skipFrame(frame):
359 # When stepping with next/until/return in a generator frame,
360 # skip the internal StopIteration exception (with no traceback)
361 # triggered by a subiterator run with the 'yield from'
362 # statement.
363 if not (
364 frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS and
365 arg[0] is StopIteration and
366 arg[2] is None
367 ):
368 self.user_exception(arg)
369 # Stop at the StopIteration or GeneratorExit exception when the
370 # user has set stopframe in a generator by issuing a return
371 # command, or a next/until command at the last statement in the
372 # generator before the exception.
373 elif (
374 self.stopframe and
375 frame is not self.stopframe and
376 (self.stopframe.f_code.co_flags &
377 GENERATOR_AND_COROUTINE_FLAGS) and
378 arg[0] in (StopIteration, GeneratorExit)
379 ):
380 self.user_exception(arg)
381 return None
382
383 if event == 'c_call':
384 return None
385 if event == 'c_exception':
386 return None
387 if event == 'c_return':
388 return None
389
390 print('DebugBase.trace_dispatch:' # __IGNORE_WARNING_M801__
391 ' unknown debugging event: ',
392 repr(event))
393
394 return self.trace_dispatch
395
396 def set_trace(self, frame=None):
397 """
398 Public method to start debugging from 'frame'.
399
400 If frame is not specified, debugging starts from caller's frame.
401 Because of jump optimizations it's not possible to use sys.breakpoint()
402 as last instruction in a function or method.
403
404 @param frame frame to start debugging from
405 @type frame object
406 """
407 if frame is None:
408 frame = sys._getframe().f_back # Skip set_trace method
409
410 stopOnHandleCommand = self._dbgClient.handleJsonCommand.__code__
411
412 frame.f_trace = self.trace_dispatch
413 while frame.f_back is not None:
414 # stop at eric's debugger frame or a threading bootstrap
415 if frame.f_back.f_code == stopOnHandleCommand:
416 frame.f_trace = self.trace_dispatch
417 break
418
419 frame = frame.f_back
420
421 self.stop_everywhere = True
422 sys.settrace(self.trace_dispatch)
423 sys.setprofile(self._dbgClient.callTraceEnabled)
424
425 def bootstrap(self, target, args, kwargs):
426 """
427 Public method to bootstrap a thread.
428
429 It wraps the call to the user function to enable tracing
430 before hand.
431
432 @param target function which is called in the new created thread
433 @type function pointer
434 @param args arguments to pass to target
435 @type tuple
436 @param kwargs keyword arguments to pass to target
437 @type dict
438 """
439 try:
440 # Because in the initial run method the "base debug" function is
441 # set up, it's also valid for the threads afterwards.
442 sys.settrace(self.trace_dispatch)
443
444 target(*args, **kwargs)
445 except Exception:
446 excinfo = sys.exc_info()
447 self.user_exception(excinfo, True)
448 finally:
449 sys.settrace(None)
450 sys.setprofile(None)
451
452 def run(self, cmd, globalsDict=None, localsDict=None, debug=True,
453 closeSession=True):
454 """
455 Public method to start a given command under debugger control.
456
457 @param cmd command / code to execute under debugger control
458 @type str or CodeType
459 @param globalsDict dictionary of global variables for cmd
460 @type dict
461 @param localsDict dictionary of local variables for cmd
462 @type dict
463 @param debug flag if command should run under debugger control
464 @type bool
465 @return exit code of the program
466 @rtype int
467 @param closeSession flag indicating to close the debugger session
468 at exit
469 @type bool
470 """
471 if globalsDict is None:
472 import __main__
473 globalsDict = __main__.__dict__
474
475 if localsDict is None:
476 localsDict = globalsDict
477
478 if not isinstance(cmd, types.CodeType):
479 cmd = compile(cmd, "<string>", "exec")
480
481 if debug:
482 # First time the trace_dispatch function is called, a "base debug"
483 # function has to be returned, which is called at every user code
484 # function call. This is ensured by setting stop_everywhere.
485 self.stop_everywhere = True
486 sys.settrace(self.trace_dispatch)
487
488 try:
489 exec(cmd, globalsDict, localsDict) # secok
490 atexit._run_exitfuncs()
491 self._dbgClient.progTerminated(0, closeSession=closeSession)
492 exitcode = 0
493 except SystemExit:
494 atexit._run_exitfuncs()
495 excinfo = sys.exc_info()
496 exitcode, message = self.__extractSystemExitMessage(excinfo)
497 self._dbgClient.progTerminated(exitcode, message=message,
498 closeSession=closeSession)
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__'),
669 frame.f_code.co_firstlineno]
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__'),
675 frame.f_code.co_firstlineno] = 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__'),
694 frame.f_code.co_firstlineno] = True
695 return True
696 Breakpoint.breakInFrameCache[
697 frame.f_globals.get('__file__'),
698 frame.f_code.co_firstlineno] = 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(
716 filename, frame.f_lineno, frame)
717 if bp:
718 # flag says ok to delete temp. bp
719 if flag and bp.temporary:
720 self.__do_clearBreak(filename, frame.f_lineno)
721 return True
722
723 if Watch.watches != []:
724 bp, flag = Watch.effectiveWatch(frame)
725 if bp:
726 # flag says ok to delete temp. watch
727 if flag and bp.temporary:
728 self.__do_clearWatch(bp.cond)
729 return True
730
731 return False
732
733 def __do_clearBreak(self, filename, lineno):
734 """
735 Private method called to clear a temporary breakpoint.
736
737 @param filename name of the file the bp belongs to
738 @type str
739 @param lineno linenumber of the bp
740 @type int
741 """
742 Breakpoint.clear_break(filename, lineno)
743 self._dbgClient.sendClearTemporaryBreakpoint(filename, lineno)
744
745 def __do_clearWatch(self, cond):
746 """
747 Private method called to clear a temporary watch expression.
748
749 @param cond expression of the watch expression to be cleared
750 @type str
751 """
752 Watch.clear_watch(cond)
753 self._dbgClient.sendClearTemporaryWatch(cond)
754
755 def getStack(self, frame=None, applyTrace=False):
756 """
757 Public method to get the stack.
758
759 @param frame frame object to inspect
760 @type frame object or list
761 @param applyTrace flag to assign trace function to fr.f_trace
762 @type bool
763 @return list of lists with file name (string), line number (integer)
764 and function name (string)
765 """
766 tb_lineno = None
767 if frame is None:
768 fr = self.getCurrentFrame()
769 elif type(frame) == list:
770 fr, tb_lineno = frame.pop(0)
771 else:
772 fr = frame
773
774 stack = []
775 while fr is not None:
776 if applyTrace:
777 # Reset the trace function so we can be sure
778 # to trace all functions up the stack... This gets around
779 # problems where an exception/breakpoint has occurred
780 # but we had disabled tracing along the way via a None
781 # return from dispatch_call
782 fr.f_trace = self.trace_dispatch
783
784 fname = self._dbgClient.absPath(self.fix_frame_filename(fr))
785 # Always show at least one stack frame, even if it's from eric.
786 if stack and os.path.basename(fname).startswith(
787 ("DebugBase.py", "DebugClientBase.py",
788 "ThreadExtension.py", "threading.py")
789 ):
790 break
791
792 fline = tb_lineno or fr.f_lineno
793 ffunc = fr.f_code.co_name
794
795 if ffunc == '?':
796 ffunc = ''
797
798 if ffunc and not ffunc.startswith("<"):
799 argInfo = getargvalues(fr)
800 try:
801 fargs = formatargvalues(
802 argInfo.args, argInfo.varargs,
803 argInfo.keywords, argInfo.locals)
804 except Exception:
805 fargs = ""
806 else:
807 fargs = ""
808
809 stack.append([fname, fline, ffunc, fargs])
810
811 # is it a stack frame or exception list?
812 if type(frame) == list:
813 if frame != []:
814 fr, tb_lineno = frame.pop(0)
815 else:
816 fr = None
817 else:
818 fr = fr.f_back
819
820 return stack
821
822 def user_line(self, frame):
823 """
824 Public method reimplemented to handle the program about to execute a
825 particular line.
826
827 @param frame the frame object
828 """
829 # We never stop on line 0.
830 if frame.f_lineno == 0:
831 return
832
833 self.isBroken = True
834 self.currentFrame = frame
835 stack = self.getStack(frame, applyTrace=True)
836
837 self._dbgClient.lockClient()
838 self._dbgClient.currentThread = self
839 self._dbgClient.currentThreadExec = self
840
841 self._dbgClient.sendResponseLine(stack, self.name)
842 self._dbgClient.eventLoop()
843
844 self.isBroken = False
845 self._dbgClient.unlockClient()
846
847 self._dbgClient.dumpThreadList()
848
849 def user_exception(self, excinfo, unhandled=False):
850 """
851 Public method reimplemented to report an exception to the debug server.
852
853 @param excinfo details about the exception
854 @type tuple(Exception, excval object, traceback frame object)
855 @param unhandled flag indicating an uncaught exception
856 @type bool
857 """
858 exctype, excval, exctb = excinfo
859
860 if ((exctype in [GeneratorExit, StopIteration] and
861 unhandled is False) or
862 exctype == SystemExit):
863 # ignore these
864 return
865
866 if exctype in [SyntaxError, IndentationError]:
867 try:
868 if type(excval) == tuple:
869 message, details = excval
870 filename, lineno, charno, text = details
871 else:
872 message = excval.msg
873 filename = excval.filename
874 lineno = excval.lineno
875 charno = excval.offset
876
877 if filename is None:
878 realSyntaxError = False
879 else:
880 if charno is None:
881 charno = 0
882
883 filename = os.path.abspath(filename)
884 realSyntaxError = os.path.exists(filename)
885
886 except (AttributeError, ValueError):
887 message = ""
888 filename = ""
889 lineno = 0
890 charno = 0
891 realSyntaxError = True
892
893 if realSyntaxError:
894 self._dbgClient.sendSyntaxError(
895 message, filename, lineno, charno, self.name)
896 self._dbgClient.eventLoop()
897 return
898
899 self.skipFrames = 0
900 if (exctype == RuntimeError and
901 str(excval).startswith('maximum recursion depth exceeded') or
902 exctype == RecursionError): # __IGNORE_WARNING__
903 excval = 'maximum recursion depth exceeded'
904 depth = 0
905 tb = exctb
906 while tb:
907 tb = tb.tb_next
908
909 if (tb and tb.tb_frame.f_code.co_name == 'trace_dispatch' and
910 __file__.startswith(tb.tb_frame.f_code.co_filename)):
911 depth = 1
912 self.skipFrames += depth
913
914 # always 1 if running without debugger
915 self.skipFrames = max(1, self.skipFrames)
916
917 exctype = self.__extractExceptionName(exctype)
918
919 if excval is None:
920 excval = ''
921
922 exctypetxt = (
923 "unhandled {0!s}".format(str(exctype))
924 if unhandled else
925 str(exctype)
926 )
927 excvaltxt = str(excval)
928
929 # Don't step into libraries, which are used by our debugger methods
930 if exctb is not None:
931 self.stop_everywhere = False
932
933 self.isBroken = True
934 self.isException = True
935
936 disassembly = None
937 stack = []
938 if exctb:
939 frlist = self.__extract_stack(exctb)
940 frlist.reverse()
941 disassembly = self.__disassemble(frlist[0][0])
942
943 self.currentFrame = frlist[0][0]
944 stack = self.getStack(frlist[self.skipFrames:])
945
946 self._dbgClient.lockClient()
947 self._dbgClient.currentThread = self
948 self._dbgClient.currentThreadExec = self
949 self._dbgClient.sendException(exctypetxt, excvaltxt, stack, self.name)
950 self._dbgClient.setDisassembly(disassembly)
951 self._dbgClient.dumpThreadList()
952
953 if exctb is not None:
954 # When polling kept enabled, it isn't possible to resume after an
955 # unhandled exception without further user interaction.
956 self._dbgClient.eventLoop(True)
957
958 self.skipFrames = 0
959
960 self.isBroken = False
961 self.isException = False
962 stop_everywhere = self.stop_everywhere
963 self.stop_everywhere = False
964 self.eventPollFlag = False
965 self._dbgClient.unlockClient()
966 self.stop_everywhere = stop_everywhere
967
968 self._dbgClient.dumpThreadList()
969
970 def __extractExceptionName(self, exctype):
971 """
972 Private method to extract the exception name given the exception
973 type object.
974
975 @param exctype type of the exception
976 @return exception name (string)
977 """
978 return str(exctype).replace("<class '", "").replace("'>", "")
979
980 def __extract_stack(self, exctb):
981 """
982 Private member to return a list of stack frames.
983
984 @param exctb exception traceback
985 @return list of stack frames
986 """
987 tb = exctb
988 stack = []
989 while tb is not None:
990 stack.append((tb.tb_frame, tb.tb_lineno))
991 tb = tb.tb_next
992 tb = None
993 return stack
994
995 def __disassemble(self, frame):
996 """
997 Private method to generate a disassembly of the given code object.
998
999 @param frame frame object to be disassembled
1000 @type code
1001 @return dictionary containing the disassembly information
1002 @rtype dict
1003 """
1004 co = frame.f_code
1005 disDict = {
1006 "lasti": frame.f_lasti,
1007 "firstlineno": co.co_firstlineno,
1008 "instructions": [],
1009 }
1010
1011 # 1. disassembly info
1012 for instr in dis.get_instructions(co):
1013 instrDict = {
1014 "lineno":
1015 0 if instr.starts_line is None else instr.starts_line,
1016 "isJumpTarget": instr.is_jump_target,
1017 "offset": instr.offset,
1018 "opname": instr.opname,
1019 "arg": instr.arg,
1020 "argrepr": instr.argrepr,
1021 }
1022 disDict["instructions"].append(instrDict)
1023
1024 # 2. code info
1025 # Note: keep in sync with PythonDisViewer.__createCodeInfo()
1026 disDict["codeinfo"] = {
1027 "name": co.co_name,
1028 "filename": co.co_filename,
1029 "firstlineno": co.co_firstlineno,
1030 "argcount": co.co_argcount,
1031 "kwonlyargcount": co.co_kwonlyargcount,
1032 "nlocals": co.co_nlocals,
1033 "stacksize": co.co_stacksize,
1034 "flags": dis.pretty_flags(co.co_flags),
1035 "consts": [str(const) for const in co.co_consts],
1036 "names": [str(name) for name in co.co_names],
1037 "varnames": [str(name) for name in co.co_varnames],
1038 "freevars": [str(var) for var in co.co_freevars],
1039 "cellvars": [str(var) for var in co.co_cellvars],
1040 }
1041 try:
1042 disDict["codeinfo"]["posonlyargcount"] = co.co_posonlyargcount
1043 except AttributeError:
1044 # does not exist prior to 3.8.0
1045 disDict["codeinfo"]["posonlyargcount"] = 0
1046
1047 return disDict
1048
1049 def __extractSystemExitMessage(self, excinfo):
1050 """
1051 Private method to get the SystemExit code and message.
1052
1053 @param excinfo details about the SystemExit exception
1054 @type tuple(Exception, excval object, traceback frame object)
1055 @return SystemExit code and message
1056 @rtype int, str
1057 """
1058 exctype, excval, exctb = excinfo
1059 if excval is None:
1060 exitcode = 0
1061 message = ""
1062 elif isinstance(excval, str):
1063 exitcode = 1
1064 message = excval
1065 elif isinstance(excval, bytes):
1066 exitcode = 1
1067 message = excval.decode()
1068 elif isinstance(excval, int):
1069 exitcode = excval
1070 message = ""
1071 elif isinstance(excval, SystemExit):
1072 code = excval.code
1073 if isinstance(code, str):
1074 exitcode = 1
1075 message = code
1076 elif isinstance(code, bytes):
1077 exitcode = 1
1078 message = code.decode()
1079 elif isinstance(code, int):
1080 exitcode = code
1081 message = ""
1082 elif code is None:
1083 exitcode = 0
1084 message = ""
1085 else:
1086 exitcode = 1
1087 message = str(code)
1088 else:
1089 exitcode = 1
1090 message = str(excval)
1091
1092 return exitcode, message
1093
1094 def stop_here(self, frame):
1095 """
1096 Public method reimplemented to filter out debugger files.
1097
1098 Tracing is turned off for files that are part of the
1099 debugger that are called from the application being debugged.
1100
1101 @param frame the frame object
1102 @type frame object
1103 @return flag indicating whether the debugger should stop here
1104 @rtype bool
1105 """
1106 if self.__skipFrame(frame):
1107 return False
1108
1109 if frame is self.stopframe:
1110 if self.stoplineno == -1:
1111 return False
1112 return frame.f_lineno >= self.stoplineno
1113 return self.stop_everywhere or frame is self.returnframe
1114
1115 def tracePythonLibs(self, enable):
1116 """
1117 Public method to update the settings to trace into Python libraries.
1118
1119 @param enable flag to debug into Python libraries
1120 @type bool
1121 """
1122 pathsToSkip = list(self.pathsToSkip)
1123 # don't trace into Python library?
1124 if enable:
1125 pathsToSkip = [x for x in pathsToSkip if not x.endswith(
1126 ("site-packages", "dist-packages", self.lib))]
1127 else:
1128 pathsToSkip.append(self.lib)
1129 localLib = [x for x in sys.path if x.endswith(("site-packages",
1130 "dist-packages")) and not x.startswith(self.lib)]
1131 pathsToSkip.extend(localLib)
1132
1133 self.pathsToSkip = tuple(set(pathsToSkip))
1134
1135 def __skipFrame(self, frame):
1136 """
1137 Private method to filter out debugger files.
1138
1139 Tracing is turned off for files that are part of the
1140 debugger that are called from the application being debugged.
1141
1142 @param frame the frame object
1143 @type frame object
1144 @return flag indicating whether the debugger should skip this frame
1145 @rtype bool
1146 """
1147 try:
1148 return self.filesToSkip[frame.f_code.co_filename]
1149 except KeyError:
1150 ret = frame.f_code.co_filename.startswith(self.pathsToSkip)
1151 self.filesToSkip[frame.f_code.co_filename] = ret
1152 return ret
1153 except AttributeError:
1154 # if frame is None
1155 return True

eric ide

mercurial