eric6/DebugClients/Python/DebugBase.py

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

eric ide

mercurial