eric7/DebugClients/Python/DebugBase.py

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

eric ide

mercurial