eric6/DebugClients/Python/DebugBase.py

changeset 6942
2602857055c5
parent 6891
93f82da09f22
child 7360
9190402e4505
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/DebugClients/Python/DebugBase.py	Sun Apr 14 15:09:21 2019 +0200
@@ -0,0 +1,1049 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2002 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the debug base class which based originally on bdb.
+"""
+
+import sys
+import os
+import types
+import atexit
+import inspect
+import ctypes
+import time
+from inspect import CO_GENERATOR
+
+from BreakpointWatch import Breakpoint, Watch
+
+if sys.version_info[0] == 2:
+    import thread as _thread
+    from inspect import getargvalues, formatargvalues
+else:
+    import _thread
+    from DebugUtilities import getargvalues, formatargvalues
+    unicode = str
+    basestring = str
+
+gRecursionLimit = 64
+
+
+def printerr(s):
+    """
+    Module function used for debugging the debug client.
+    
+    @param s data to be printed
+    """
+    sys.__stderr__.write('{0!s}\n'.format(s))
+    sys.__stderr__.flush()
+
+
+def setRecursionLimit(limit):
+    """
+    Module function to set the recursion limit.
+    
+    @param limit recursion limit (integer)
+    """
+    global gRecursionLimit
+    gRecursionLimit = limit
+
+
+class DebugBase(object):
+    """
+    Class implementing base class of the debugger.
+
+    Provides methods for the 'owning' client to call to step etc.
+    """
+    # Don't thrust distutils.sysconfig.get_python_lib: possible case mismatch
+    #  on Windows
+    lib = os.path.dirname(inspect.__file__)
+    # tuple required because it's accessed a lot of times by startswith method
+    pathsToSkip = ('<', os.path.dirname(__file__), inspect.__file__[:-1])
+    filesToSkip = {}
+
+    # cache for fixed file names
+    _fnCache = {}
+    
+    # Stop all timers, when greenlets are used
+    pollTimerEnabled = True
+
+    def __init__(self, dbgClient):
+        """
+        Constructor
+        
+        @param dbgClient the owning client
+        """
+        self._dbgClient = dbgClient
+        
+        # Some informations about the thread
+        self.isMainThread = False
+        self.quitting = False
+        self.id = -1
+        self.name = ''
+        
+        self.tracePythonLibs(0)
+        
+        # Special handling of a recursion error
+        self.skipFrames = 0
+        
+        self.isBroken = False
+        self.cFrame = None
+        
+        # current frame we are at
+        self.currentFrame = None
+        
+        # frames, where we want to stop or release debugger
+        self.stopframe = None
+        self.returnframe = None
+        self.stop_everywhere = False
+        
+        self.__recursionDepth = -1
+        self.setRecursionDepth(inspect.currentframe())
+        
+        # background task to periodicaly check for client interactions
+        self.eventPollFlag = False
+        self.timer = _thread.start_new_thread(self.__eventPollTimer, ())
+        
+        # provide a hook to perform a hard breakpoint
+        # Use it like this:
+        # if hasattr(sys, 'breakpoint): sys.breakpoint()
+        sys.breakpoint = self.set_trace
+        if sys.version_info[:2] >= (3, 7):
+            sys.breakpointhook = self.set_trace
+
+    def __eventPollTimer(self):
+        """
+        Private method to set a flag every 0.5 s to check for new messages.
+        """
+        while DebugBase.pollTimerEnabled:
+            time.sleep(0.5)
+            self.eventPollFlag = True
+        
+        self.eventPollFlag = False
+    
+    def getCurrentFrame(self):
+        """
+        Public method to return the current frame.
+        
+        @return the current frame
+        @rtype frame object
+        """
+        # Don't show any local frames after the program was stopped
+        if self.quitting:
+            return None
+        
+        return self.currentFrame
+    
+    def getFrameLocals(self, frmnr=0):
+        """
+        Public method to return the locals dictionary of the current frame
+        or a frame below.
+        
+        @keyparam frmnr distance of frame to get locals dictionary of. 0 is
+            the current frame (int)
+        @return locals dictionary of the frame
+        """
+        f = self.currentFrame
+        while f is not None and frmnr > 0:
+            f = f.f_back
+            frmnr -= 1
+        return f.f_locals
+    
+    def storeFrameLocals(self, frmnr=0):
+        """
+        Public method to store the locals into the frame, so an access to
+        frame.f_locals returns the last data.
+        
+        @keyparam frmnr distance of frame to store locals dictionary to. 0 is
+            the current frame (int)
+        """
+        cf = self.currentFrame
+        while cf is not None and frmnr > 0:
+            cf = cf.f_back
+            frmnr -= 1
+        
+        try:
+            if "__pypy__" in sys.builtin_module_names:
+                import __pypy__
+                __pypy__.locals_to_fast(cf)
+                return
+        except Exception:
+            pass
+        
+        ctypes.pythonapi.PyFrame_LocalsToFast(
+            ctypes.py_object(cf),
+            ctypes.c_int(0))
+    
+    def step(self, traceMode):
+        """
+        Public method to perform a step operation in this thread.
+        
+        @param traceMode If it is True, then the step is a step into,
+              otherwise it is a step over.
+        """
+        if traceMode:
+            self.set_step()
+        else:
+            self.set_next(self.currentFrame)
+    
+    def stepOut(self):
+        """
+        Public method to perform a step out of the current call.
+        """
+        self.set_return(self.currentFrame)
+    
+    def go(self, special):
+        """
+        Public method to resume the thread.
+
+        It resumes the thread stopping only at breakpoints or exceptions.
+        
+        @param special flag indicating a special continue operation
+        """
+        self.set_continue(special)
+    
+    def setRecursionDepth(self, frame):
+        """
+        Public method to determine the current recursion depth.
+        
+        @param frame The current stack frame.
+        """
+        self.__recursionDepth = 0
+        while frame is not None:
+            self.__recursionDepth += 1
+            frame = frame.f_back
+    
+    def profileWithRecursion(self, frame, event, arg):
+        """
+        Public method used to trace some stuff independent of the debugger
+        trace function.
+        
+        @param frame current stack frame
+        @type frame object
+        @param event trace event
+        @type str
+        @param arg arguments
+        @type depends on the previous event parameter
+        @exception RuntimeError raised to indicate too many recursions
+        """
+        if event == 'return':
+            self.cFrame = frame.f_back
+            self.__recursionDepth -= 1
+            if self._dbgClient.callTraceEnabled:
+                self.__sendCallTrace(event, frame, self.cFrame)
+        elif event == 'call':
+            if self._dbgClient.callTraceEnabled:
+                self.__sendCallTrace(event, self.cFrame, frame)
+            self.cFrame = frame
+            self.__recursionDepth += 1
+            if self.__recursionDepth > gRecursionLimit:
+                raise RuntimeError(
+                    'maximum recursion depth exceeded\n'
+                    '(offending frame is two down the stack)')
+    
+    def profile(self, frame, event, arg):
+        """
+        Public method used to trace some stuff independent of the debugger
+        trace function.
+        
+        @param frame current stack frame
+        @type frame object
+        @param event trace event
+        @type str
+        @param arg arguments
+        @type depends on the previous event parameter
+        """
+        if event == 'return':
+            self.__sendCallTrace(event, frame, frame.f_back)
+        elif event == 'call':
+            self.__sendCallTrace(event, frame.f_back, frame)
+    
+    def __sendCallTrace(self, event, fromFrame, toFrame):
+        """
+        Private method to send a call/return trace.
+        
+        @param event trace event
+        @type str
+        @param fromFrame originating frame
+        @type frame object
+        @param toFrame destination frame
+        @type frame object
+        """
+        if not self.__skipFrame(fromFrame) and not self.__skipFrame(toFrame):
+            fromInfo = {
+                "filename": self._dbgClient.absPath(
+                    self.fix_frame_filename(fromFrame)),
+                "linenumber": fromFrame.f_lineno,
+                "codename": fromFrame.f_code.co_name,
+            }
+            toInfo = {
+                "filename": self._dbgClient.absPath(
+                    self.fix_frame_filename(toFrame)),
+                "linenumber": toFrame.f_lineno,
+                "codename": toFrame.f_code.co_name,
+            }
+            self._dbgClient.sendCallTrace(event, fromInfo, toInfo)
+    
+    def trace_dispatch(self, frame, event, arg):
+        """
+        Public method reimplemented from bdb.py to do some special things.
+        
+        This specialty is to check the connection to the debug server
+        for new events (i.e. new breakpoints) while we are going through
+        the code.
+        
+        @param frame The current stack frame
+        @type frame object
+        @param event The trace event
+        @type str
+        @param arg The arguments
+        @type depends on the previous event parameter
+        @return local trace function
+        @rtype trace function or None
+        @exception SystemExit
+        """
+        # give the client a chance to push through new break points.
+        if self.eventPollFlag:
+            self._dbgClient.eventPoll()
+            self.eventPollFlag = False
+            
+            if self.quitting:
+                raise SystemExit
+        
+        if event == 'line':
+            if self.stop_here(frame) or self.break_here(frame):
+                if (self.stop_everywhere and frame.f_back and
+                        frame.f_back.f_code.co_name == "prepareJsonCommand"):
+                    # Just stepped into print statement, so skip these frames
+                    self._set_stopinfo(None, frame.f_back)
+                else:
+                    self.user_line(frame)
+            return self.trace_dispatch
+        
+        if event == 'call':
+            if (self.stop_here(frame) or
+                    self.__checkBreakInFrame(frame) or
+                    Watch.watches != []):
+                return self.trace_dispatch
+            else:
+                # No need to trace this function
+                return None
+        
+        if event == 'return':
+            if frame == self.returnframe:
+                # Only true if we didn't stopped in this frame, because it's
+                # belonging to the eric debugger.
+                self._set_stopinfo(None, frame.f_back)
+            return None
+        
+        if event == 'exception':
+            if not self.__skipFrame(frame):
+                # When stepping with next/until/return in a generator frame,
+                # skip the internal StopIteration exception (with no traceback)
+                # triggered by a subiterator run with the 'yield from'
+                # statement.
+                if not (frame.f_code.co_flags & CO_GENERATOR and
+                        arg[0] is StopIteration and arg[2] is None):
+                    self.user_exception(arg)
+            # Stop at the StopIteration or GeneratorExit exception when the
+            # user has set stopframe in a generator by issuing a return
+            # command, or a next/until command at the last statement in the
+            # generator before the exception.
+            elif (self.stopframe and frame is not self.stopframe and
+                    self.stopframe.f_code.co_flags & CO_GENERATOR and
+                    arg[0] in (StopIteration, GeneratorExit)):
+                self.user_exception(arg)
+            return None
+
+        if event == 'c_call':
+            return None
+        if event == 'c_exception':
+            return None
+        if event == 'c_return':
+            return None
+        
+        print('DebugBase.trace_dispatch:'       # __IGNORE_WARNING_M801__
+              ' unknown debugging event: ',
+              repr(event))
+        return self.trace_dispatch
+
+    def set_trace(self, frame=None):
+        """
+        Public method to start debugging from 'frame'.
+
+        If frame is not specified, debugging starts from caller's frame.
+        Because of jump optimizations it's not possible to use sys.breakpoint()
+        as last instruction in a function or method.
+        
+        @keyparam frame frame to start debugging from
+        @type frame object
+        """
+        if frame is None:
+            frame = sys._getframe().f_back  # Skip set_trace method
+        
+        if sys.version_info[0] == 2:
+            stopOnHandleCommand = self._dbgClient.handleJsonCommand.func_code
+        else:
+            stopOnHandleCommand = self._dbgClient.handleJsonCommand.__code__
+        
+        frame.f_trace = self.trace_dispatch
+        while frame.f_back is not None:
+            # stop at eric's debugger frame or a threading bootstrap
+            if (frame.f_back.f_code == stopOnHandleCommand):
+                frame.f_trace = self.trace_dispatch
+                break
+            
+            frame = frame.f_back
+        
+        self.stop_everywhere = True
+        sys.settrace(self.trace_dispatch)
+        sys.setprofile(self._dbgClient.callTraceEnabled)
+    
+    def bootstrap(self, target, args, kwargs):
+        """
+        Public method to bootstrap a thread.
+        
+        It wraps the call to the user function to enable tracing
+        before hand.
+        
+        @param target function which is called in the new created thread
+        @type function pointer
+        @param args arguments to pass to target
+        @type tuple
+        @param kwargs keyword arguments to pass to target
+        @type dict
+        """
+        try:
+            # Because in the initial run method the "base debug" function is
+            # set up, it's also valid for the threads afterwards.
+            sys.settrace(self.trace_dispatch)
+            
+            target(*args, **kwargs)
+        except Exception:
+            excinfo = sys.exc_info()
+            self.user_exception(excinfo, True)
+        finally:
+            sys.settrace(None)
+            sys.setprofile(None)
+    
+    def run(self, cmd, globalsDict=None, localsDict=None, debug=True):
+        """
+        Public method to start a given command under debugger control.
+        
+        @param cmd command / code to execute under debugger control
+        @type str or CodeType
+        @keyparam globalsDict dictionary of global variables for cmd
+        @type dict
+        @keyparam localsDict dictionary of local variables for cmd
+        @type dict
+        @keyparam debug flag if command should run under debugger control
+        @type bool
+        """
+        if globalsDict is None:
+            import __main__
+            globalsDict = __main__.__dict__
+        
+        if localsDict is None:
+            localsDict = globalsDict
+        
+        if not isinstance(cmd, types.CodeType):
+            cmd = compile(cmd, "<string>", "exec")
+        
+        if debug:
+            # First time the trace_dispatch function is called, a "base debug"
+            # function has to be returned, which is called at every user code
+            # function call. This is ensured by setting stop_everywhere.
+            self.stop_everywhere = True
+            sys.settrace(self.trace_dispatch)
+        
+        try:
+            exec(cmd, globalsDict, localsDict)
+            atexit._run_exitfuncs()
+            self._dbgClient.progTerminated(0)
+        except SystemExit:
+            atexit._run_exitfuncs()
+            excinfo = sys.exc_info()
+            exitcode, message = self.__extractSystemExitMessage(excinfo)
+            self._dbgClient.progTerminated(exitcode, message)
+        except Exception:
+            excinfo = sys.exc_info()
+            self.user_exception(excinfo, True)
+        finally:
+            self.quitting = True
+            sys.settrace(None)
+
+    def _set_stopinfo(self, stopframe, returnframe):
+        """
+        Protected method to update the frame pointers.
+        
+        @param stopframe the frame object where to stop
+        @type frame object
+        @param returnframe the frame object where to stop on a function return
+        @type frame object
+        """
+        self.stopframe = stopframe
+        self.returnframe = returnframe
+        if returnframe is not None:
+            # Ensure to be able to stop on the return frame
+            returnframe.f_trace = self.trace_dispatch
+        self.stop_everywhere = False
+
+    def set_continue(self, special):
+        """
+        Public method to stop only on next breakpoint.
+        
+        @param special flag indicating a special continue operation
+        @type bool
+        """
+        # Here we only set a new stop frame if it is a normal continue.
+        if not special:
+            self._set_stopinfo(None, None)
+        
+        # Disable tracing if not started in debug mode
+        if not self._dbgClient.debugging:
+            sys.settrace(None)
+            sys.setprofile(None)
+
+    def set_step(self):
+        """
+        Public method to stop after one line of code.
+        """
+        self._set_stopinfo(None, None)
+        self.stop_everywhere = True
+
+    def set_next(self, frame):
+        """
+        Public method to stop on the next line in or below the given frame.
+        
+        @param frame the frame object
+        @type frame object
+        """
+        self._set_stopinfo(frame, frame.f_back)
+        frame.f_trace = self.trace_dispatch
+
+    def set_return(self, frame):
+        """
+        Public method to stop when returning from the given frame.
+        
+        @param frame the frame object
+        @type frame object
+        """
+        self._set_stopinfo(None, frame.f_back)
+    
+    def move_instruction_pointer(self, lineno):
+        """
+        Public methode to move the instruction pointer to another line.
+        
+        @param lineno new line number
+        @type int
+        """
+        try:
+            self.currentFrame.f_lineno = lineno
+            stack = self.getStack(self.currentFrame)
+            self._dbgClient.sendResponseLine(stack)
+        except Exception as e:
+            printerr(e)
+
+    def set_quit(self):
+        """
+        Public method to quit.
+        
+        Disables the trace functions and resets all frame pointer.
+        """
+        sys.setprofile(None)
+        self.stopframe = None
+        self.returnframe = None
+        for debugThread in self._dbgClient.threads.values():
+            debugThread.quitting = True
+    
+    def fix_frame_filename(self, frame):
+        """
+        Public method used to fixup the filename for a given frame.
+        
+        The logic employed here is that if a module was loaded
+        from a .pyc file, then the correct .py to operate with
+        should be in the same path as the .pyc. The reason this
+        logic is needed is that when a .pyc file is generated, the
+        filename embedded and thus what is readable in the code object
+        of the frame object is the fully qualified filepath when the
+        pyc is generated. If files are moved from machine to machine
+        this can break debugging as the .pyc will refer to the .py
+        on the original machine. Another case might be sharing
+        code over a network... This logic deals with that.
+        
+        @param frame the frame object
+        @type frame object
+        @return fixed up file name
+        @rtype str
+        """
+        # get module name from __file__
+        fn = frame.f_globals.get('__file__')
+        try:
+            return self._fnCache[fn]
+        except KeyError:
+            if fn is None:
+                return frame.f_code.co_filename
+            
+            absFilename = os.path.abspath(fn)
+            if absFilename.endswith(('.pyc', '.pyo', '.pyd')):
+                fixedName = absFilename[:-1]
+                if not os.path.exists(fixedName):
+                    fixedName = absFilename
+            else:
+                fixedName = absFilename
+            # update cache
+            self._fnCache[fn] = fixedName
+            return fixedName
+
+    def __checkBreakInFrame(self, frame):
+        """
+        Private method to check if the function / method has a line number
+        which is a breakpoint.
+        
+        @param frame the frame object
+        @type frame object
+        @return Flag indicating a function / method with breakpoint
+        @rtype bool
+        """
+        try:
+            return Breakpoint.breakInFrameCache[
+                frame.f_globals.get('__file__'),
+                frame.f_code.co_firstlineno]
+        except KeyError:
+            filename = self.fix_frame_filename(frame)
+            if filename not in Breakpoint.breakInFile:
+                Breakpoint.breakInFrameCache[
+                    frame.f_globals.get('__file__'),
+                    frame.f_code.co_firstlineno] = False
+                return False
+            lineNo = frame.f_code.co_firstlineno
+            lineNumbers = [lineNo]
+            
+            if sys.version_info[0] == 2:
+                co_lnotab = map(ord, frame.f_code.co_lnotab[1::2])
+            else:
+                co_lnotab = frame.f_code.co_lnotab[1::2]
+            
+            # No need to handle special case if a lot of lines between
+            # (e.g. closure), because the additional lines won't cause a bp
+            for co_lno in co_lnotab:
+                lineNo += co_lno
+                lineNumbers.append(lineNo)
+            
+            for bp in Breakpoint.breakInFile[filename]:
+                if bp in lineNumbers:
+                    Breakpoint.breakInFrameCache[
+                        frame.f_globals.get('__file__'),
+                        frame.f_code.co_firstlineno] = True
+                    return True
+            Breakpoint.breakInFrameCache[
+                frame.f_globals.get('__file__'),
+                frame.f_code.co_firstlineno] = False
+            return False
+    
+    def break_here(self, frame):
+        """
+        Public method reimplemented from bdb.py to fix the filename from the
+        frame.
+        
+        See fix_frame_filename for more info.
+        
+        @param frame the frame object
+        @type frame object
+        @return flag indicating the break status
+        @rtype bool
+        """
+        filename = self.fix_frame_filename(frame)
+        if (filename, frame.f_lineno) in Breakpoint.breaks:
+            bp, flag = Breakpoint.effectiveBreak(
+                filename, frame.f_lineno, frame)
+            if bp:
+                # flag says ok to delete temp. bp
+                if flag and bp.temporary:
+                    self.__do_clearBreak(filename, frame.f_lineno)
+                return True
+        
+        if Watch.watches != []:
+            bp, flag = Watch.effectiveWatch(frame)
+            if bp:
+                # flag says ok to delete temp. watch
+                if flag and bp.temporary:
+                    self.__do_clearWatch(bp.cond)
+                return True
+        
+        return False
+
+    def __do_clearBreak(self, filename, lineno):
+        """
+        Private method called to clear a temporary breakpoint.
+        
+        @param filename name of the file the bp belongs to
+        @type str
+        @param lineno linenumber of the bp
+        @type int
+        """
+        Breakpoint.clear_break(filename, lineno)
+        self._dbgClient.sendClearTemporaryBreakpoint(filename, lineno)
+
+    def __do_clearWatch(self, cond):
+        """
+        Private method called to clear a temporary watch expression.
+        
+        @param cond expression of the watch expression to be cleared
+        @type str
+        """
+        Watch.clear_watch(cond)
+        self._dbgClient.sendClearTemporaryWatch(cond)
+
+    def getStack(self, frame=None, applyTrace=False):
+        """
+        Public method to get the stack.
+        
+        @keyparam frame frame object to inspect
+        @type frame object or list
+        @keyparam applyTrace flag to assign trace function to fr.f_trace
+        @type bool
+        @return list of lists with file name (string), line number (integer)
+            and function name (string)
+        """
+        tb_lineno = None
+        if frame is None:
+            fr = self.getCurrentFrame()
+        elif type(frame) == list:
+            fr, tb_lineno = frame.pop(0)
+        else:
+            fr = frame
+        
+        stack = []
+        while fr is not None:
+            if applyTrace:
+                # Reset the trace function so we can be sure
+                # to trace all functions up the stack... This gets around
+                # problems where an exception/breakpoint has occurred
+                # but we had disabled tracing along the way via a None
+                # return from dispatch_call
+                fr.f_trace = self.trace_dispatch
+            
+            fname = self._dbgClient.absPath(self.fix_frame_filename(fr))
+            # Always show at least one stack frame, even if it's from eric.
+            if stack and os.path.basename(fname).startswith(
+                    ("DebugBase.py", "DebugClientBase.py",
+                     "ThreadExtension.py", "threading.py")):
+                break
+            
+            fline = tb_lineno or fr.f_lineno
+            ffunc = fr.f_code.co_name
+            
+            if ffunc == '?':
+                ffunc = ''
+            
+            if ffunc and not ffunc.startswith("<"):
+                argInfo = getargvalues(fr)
+                try:
+                    fargs = formatargvalues(
+                        argInfo.args, argInfo.varargs,
+                        argInfo.keywords, argInfo.locals)
+                except Exception:
+                    fargs = ""
+            else:
+                fargs = ""
+            
+            stack.append([fname, fline, ffunc, fargs])
+            
+            # is it a stack frame or exception list?
+            if type(frame) == list:
+                if frame != []:
+                    fr, tb_lineno = frame.pop(0)
+                else:
+                    fr = None
+            else:
+                fr = fr.f_back
+        
+        return stack
+    
+    def user_line(self, frame):
+        """
+        Public method reimplemented to handle the program about to execute a
+        particular line.
+        
+        @param frame the frame object
+        """
+        # We never stop on line 0.
+        if frame.f_lineno == 0:
+            return
+        
+        self.isBroken = True
+        self.currentFrame = frame
+        stack = self.getStack(frame, applyTrace=True)
+        
+        self._dbgClient.lockClient()
+        self._dbgClient.currentThread = self
+        self._dbgClient.currentThreadExec = self
+        
+        self._dbgClient.sendResponseLine(stack)
+        self._dbgClient.eventLoop()
+        
+        self.isBroken = False
+        self._dbgClient.unlockClient()
+        
+    def user_exception(self, excinfo, unhandled=False):
+        """
+        Public method reimplemented to report an exception to the debug server.
+        
+        @param excinfo details about the exception
+        @type tuple(Exception, excval object, traceback frame object)
+        @keyparam unhandled flag indicating an uncaught exception
+        @type bool
+        """
+        exctype, excval, exctb = excinfo
+        
+        if ((exctype in [GeneratorExit, StopIteration] and
+             unhandled is False) or
+                exctype == SystemExit):
+            # ignore these
+            return
+        
+        if exctype in [SyntaxError, IndentationError]:
+            try:
+                # tuple could only occure on Python 2, but not always!
+                if type(excval) == tuple:
+                    message, details = excval
+                    filename, lineno, charno, text = details
+                else:
+                    message = excval.msg
+                    filename = excval.filename
+                    lineno = excval.lineno
+                    charno = excval.offset
+                
+                if filename is None:
+                    realSyntaxError = False
+                else:
+                    if charno is None:
+                        charno = 0
+                    
+                    filename = os.path.abspath(filename)
+                    realSyntaxError = os.path.exists(filename)
+                
+            except (AttributeError, ValueError):
+                message = ""
+                filename = ""
+                lineno = 0
+                charno = 0
+                realSyntaxError = True
+            
+            if realSyntaxError:
+                self._dbgClient.sendSyntaxError(
+                    message, filename, lineno, charno)
+                self._dbgClient.eventLoop()
+                return
+        
+        self.skipFrames = 0
+        if (exctype == RuntimeError and
+                str(excval).startswith('maximum recursion depth exceeded') or
+                sys.version_info >= (3, 5) and
+                exctype == RecursionError):  # __IGNORE_WARNING__
+            excval = 'maximum recursion depth exceeded'
+            depth = 0
+            tb = exctb
+            while tb:
+                tb = tb.tb_next
+                
+                if (tb and tb.tb_frame.f_code.co_name == 'trace_dispatch' and
+                        __file__.startswith(tb.tb_frame.f_code.co_filename)):
+                    depth = 1
+                self.skipFrames += depth
+            
+            # always 1 if running without debugger
+            self.skipFrames = max(1, self.skipFrames)
+        
+        exctype = self.__extractExceptionName(exctype)
+        
+        if excval is None:
+            excval = ''
+        
+        if unhandled:
+            exctypetxt = "unhandled {0!s}".format(str(exctype))
+        else:
+            exctypetxt = str(exctype)
+        
+        if sys.version_info[0] == 2:
+            try:
+                excvaltxt = unicode(excval).encode(self._dbgClient.getCoding())
+            except UnicodeError:
+                excvaltxt = str(excval)
+        else:
+            excvaltxt = str(excval)
+        
+        # Don't step into libraries, which are used by our debugger methods
+        if exctb is not None:
+            self.stop_everywhere = False
+        
+        self.isBroken = True
+        
+        stack = []
+        if exctb:
+            frlist = self.__extract_stack(exctb)
+            frlist.reverse()
+            
+            self.currentFrame = frlist[0][0]
+            stack = self.getStack(frlist[self.skipFrames:])
+        
+        self._dbgClient.lockClient()
+        self._dbgClient.currentThread = self
+        self._dbgClient.currentThreadExec = self
+        self._dbgClient.sendException(exctypetxt, excvaltxt, stack)
+        self._dbgClient.dumpThreadList()
+        
+        if exctb is not None:
+            # When polling kept enabled, it isn't possible to resume after an
+            # unhandled exception without further user interaction.
+            self._dbgClient.eventLoop(True)
+        
+        self.skipFrames = 0
+        
+        self.isBroken = False
+        stop_everywhere = self.stop_everywhere
+        self.stop_everywhere = False
+        self.eventPollFlag = False
+        self._dbgClient.unlockClient()
+        self.stop_everywhere = stop_everywhere
+    
+    def __extractExceptionName(self, exctype):
+        """
+        Private method to extract the exception name given the exception
+        type object.
+        
+        @param exctype type of the exception
+        @return exception name (string)
+        """
+        if sys.version_info[0] == 2:
+            if type(exctype) in [types.ClassType,   # Python up to 2.4
+                                 types.TypeType]:   # Python 2.5+
+                return exctype.__name__
+            else:
+                return exctype
+        else:
+            return str(exctype).replace("<class '", "").replace("'>", "")
+    
+    def __extract_stack(self, exctb):
+        """
+        Private member to return a list of stack frames.
+        
+        @param exctb exception traceback
+        @return list of stack frames
+        """
+        tb = exctb
+        stack = []
+        while tb is not None:
+            stack.append((tb.tb_frame, tb.tb_lineno))
+            tb = tb.tb_next
+        tb = None
+        return stack
+
+    def __extractSystemExitMessage(self, excinfo):
+        """
+        Private method to get the SystemExit code and message.
+        
+        @param excinfo details about the SystemExit exception
+        @type tuple(Exception, excval object, traceback frame object)
+        @return SystemExit code and message
+        @rtype int, str
+        """
+        exctype, excval, exctb = excinfo
+        if excval is None:
+            exitcode = 0
+            message = ""
+        elif isinstance(excval, basestring):
+            exitcode = 1
+            message = excval
+        elif isinstance(excval, bytes):
+            exitcode = 1
+            message = excval.decode()
+        elif isinstance(excval, int):
+            exitcode = excval
+            message = ""
+        elif isinstance(excval, SystemExit):
+            code = excval.code
+            if isinstance(code, basestring):
+                exitcode = 1
+                message = code
+            elif isinstance(code, bytes):
+                exitcode = 1
+                message = code.decode()
+            elif isinstance(code, int):
+                exitcode = code
+                message = ""
+            else:
+                exitcode = 1
+                message = str(code)
+        else:
+            exitcode = 1
+            message = str(excval)
+        
+        return exitcode, message
+
+    def stop_here(self, frame):
+        """
+        Public method reimplemented to filter out debugger files.
+        
+        Tracing is turned off for files that are part of the
+        debugger that are called from the application being debugged.
+        
+        @param frame the frame object
+        @type frame object
+        @return flag indicating whether the debugger should stop here
+        @rtype bool
+        """
+        if self.__skipFrame(frame):
+            return False
+        
+        return (self.stop_everywhere or
+                frame is self.stopframe or
+                frame is self.returnframe)
+
+    def tracePythonLibs(self, enable):
+        """
+        Public method to update the settings to trace into Python libraries.
+        
+        @param enable flag to debug into Python libraries
+        @type bool
+        """
+        pathsToSkip = list(self.pathsToSkip)
+        # don't trace into Python library?
+        if enable:
+            pathsToSkip = [x for x in pathsToSkip if not x.endswith(
+                ("site-packages", "dist-packages", self.lib))]
+        else:
+            pathsToSkip.append(self.lib)
+            localLib = [x for x in sys.path if x.endswith(("site-packages",
+                        "dist-packages")) and not x.startswith(self.lib)]
+            pathsToSkip.extend(localLib)
+        
+        self.pathsToSkip = tuple(set(pathsToSkip))
+
+    def __skipFrame(self, frame):
+        """
+        Private method to filter out debugger files.
+        
+        Tracing is turned off for files that are part of the
+        debugger that are called from the application being debugged.
+        
+        @param frame the frame object
+        @type frame object
+        @return flag indicating whether the debugger should skip this frame
+        @rtype bool
+        """
+        try:
+            return self.filesToSkip[frame.f_code.co_filename]
+        except KeyError:
+            ret = frame.f_code.co_filename.startswith(self.pathsToSkip)
+            self.filesToSkip[frame.f_code.co_filename] = ret
+            return ret
+        except AttributeError:
+            # if frame is None
+            return True
+    
+#
+# eflag: noqa = M702

eric ide

mercurial