eric6/DebugClients/Python/DebugBase.py

branch
maintenance
changeset 8043
0acf98cd089a
parent 7924
8a96736d465e
parent 7986
2971d5d19951
child 8273
698ae46f40a4
--- a/eric6/DebugClients/Python/DebugBase.py	Sun Jan 17 13:53:08 2021 +0100
+++ b/eric6/DebugClients/Python/DebugBase.py	Mon Feb 01 10:38:16 2021 +0100
@@ -14,7 +14,6 @@
 import inspect
 import ctypes
 import time
-from inspect import CO_GENERATOR
 import dis
 
 from BreakpointWatch import Breakpoint, Watch
@@ -24,6 +23,15 @@
 
 gRecursionLimit = 64
 
+try:
+    GENERATOR_AND_COROUTINE_FLAGS = (
+        inspect.CO_GENERATOR | inspect.CO_COROUTINE |
+        inspect.CO_ASYNC_GENERATOR
+    )
+except AttributeError:
+    # Python < 3.7
+    GENERATOR_AND_COROUTINE_FLAGS = inspect.CO_GENERATOR
+
 
 def printerr(s):
     """
@@ -84,6 +92,7 @@
         self.skipFrames = 0
         
         self.isBroken = False
+        self.isException = False
         self.cFrame = None
         
         # current frame we are at
@@ -105,7 +114,7 @@
         # Use it like this:
         # if hasattr(sys, 'breakpoint): sys.breakpoint()
         sys.breakpoint = self.set_trace
-        if sys.version_info[:2] >= (3, 7):
+        if sys.version_info >= (3, 7):
             sys.breakpointhook = self.set_trace
 
     def __eventPollTimer(self):
@@ -136,7 +145,7 @@
         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
+        @param frmnr distance of frame to get locals dictionary of. 0 is
             the current frame (int)
         @return locals dictionary of the frame
         """
@@ -151,7 +160,7 @@
         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
+        @param frmnr distance of frame to store locals dictionary to. 0 is
             the current frame (int)
         """
         cf = self.currentFrame
@@ -309,8 +318,11 @@
         
         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"):
+                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:
@@ -318,19 +330,33 @@
             return self.trace_dispatch
         
         if event == 'call':
-            if (self.stop_here(frame) or
-                    self.__checkBreakInFrame(frame) or
-                    Watch.watches != []):
+            if (
+                self.stop_here(frame) or
+                self.__checkBreakInFrame(frame) or
+                Watch.watches != []
+            ):
+                return self.trace_dispatch
+            elif (
+                self.stopframe and
+                frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
+            ):
                 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
+            if self.stop_here(frame) or frame == self.returnframe:
+                # Ignore return events in generator except when stepping.
+                if (
+                    self.stopframe and
+                    frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
+                ):
+                    return self.trace_dispatch
+                # Only true if we didn't stop in this frame, because it's
                 # belonging to the eric debugger.
-                self._set_stopinfo(None, frame.f_back)
+                if self.stopframe is frame and self.stoplineno != -1:
+                    self._set_stopinfo(None, frame.f_back)
             return None
         
         if event == 'exception':
@@ -339,16 +365,23 @@
                 # 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):
+                if not (
+                    frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS 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)):
+            elif (
+                self.stopframe and
+                frame is not self.stopframe and
+                (self.stopframe.f_code.co_flags &
+                 GENERATOR_AND_COROUTINE_FLAGS) and
+                arg[0] in (StopIteration, GeneratorExit)
+            ):
                 self.user_exception(arg)
             return None
 
@@ -362,6 +395,7 @@
         print('DebugBase.trace_dispatch:'       # __IGNORE_WARNING_M801__
               ' unknown debugging event: ',
               repr(event))
+        
         return self.trace_dispatch
 
     def set_trace(self, frame=None):
@@ -372,7 +406,7 @@
         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
+        @param frame frame to start debugging from
         @type frame object
         """
         if frame is None:
@@ -383,7 +417,7 @@
         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):
+            if frame.f_back.f_code == stopOnHandleCommand:
                 frame.f_trace = self.trace_dispatch
                 break
             
@@ -420,17 +454,23 @@
             sys.settrace(None)
             sys.setprofile(None)
     
-    def run(self, cmd, globalsDict=None, localsDict=None, debug=True):
+    def run(self, cmd, globalsDict=None, localsDict=None, debug=True,
+            closeSession=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
+        @param globalsDict dictionary of global variables for cmd
+        @type dict
+        @param localsDict dictionary of local 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
+        @param debug flag if command should run under debugger control
+        @type bool
+        @return exit code of the program
+        @rtype int
+        @param closeSession flag indicating to close the debugger session
+            at exit
         @type bool
         """
         if globalsDict is None:
@@ -453,20 +493,24 @@
         try:
             exec(cmd, globalsDict, localsDict)      # secok
             atexit._run_exitfuncs()
-            self._dbgClient.progTerminated(0)
+            self._dbgClient.progTerminated(0, closeSession=closeSession)
+            exitcode = 0
         except SystemExit:
             atexit._run_exitfuncs()
             excinfo = sys.exc_info()
             exitcode, message = self.__extractSystemExitMessage(excinfo)
-            self._dbgClient.progTerminated(exitcode, message)
+            self._dbgClient.progTerminated(exitcode, message=message,
+                                           closeSession=closeSession)
         except Exception:
             excinfo = sys.exc_info()
             self.user_exception(excinfo, True)
+            exitcode = 242
         finally:
             self.quitting = True
             sys.settrace(None)
+        return exitcode
 
-    def _set_stopinfo(self, stopframe, returnframe):
+    def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
         """
         Protected method to update the frame pointers.
         
@@ -474,9 +518,17 @@
         @type frame object
         @param returnframe the frame object where to stop on a function return
         @type frame object
+        @param stoplineno line number to stop at. If stoplineno is greater than
+            or equal to 0, then stop at line greater than or equal to the
+            stopline. If stoplineno is -1, then don't stop at all.
+        @type int
         """
         self.stopframe = stopframe
         self.returnframe = returnframe
+        # stoplineno >= 0 means: stop at line >= the stoplineno
+        # stoplineno -1 means: don't stop at all
+        self.stoplineno = stoplineno
+        
         if returnframe is not None:
             # Ensure to be able to stop on the return frame
             returnframe.f_trace = self.trace_dispatch
@@ -491,13 +543,30 @@
         """
         # Here we only set a new stop frame if it is a normal continue.
         if not special:
-            self._set_stopinfo(None, None)
+            self._set_stopinfo(None, None, -1)
         
         # Disable tracing if not started in debug mode
         if not self._dbgClient.debugging:
             sys.settrace(None)
             sys.setprofile(None)
 
+    def set_until(self, frame=None, lineno=None):
+        """
+        Public method to stop when the line with the lineno greater than the
+        current one is reached or when returning from current frame.
+        
+        @param frame reference to the frame object
+        @type frame object
+        @param lineno line number to continue to
+        @type int
+        """
+        # the name "until" is borrowed from gdb
+        if frame is None:
+            frame = self.currentFrame
+        if lineno is None:
+            lineno = frame.f_lineno + 1
+        self._set_stopinfo(frame, frame, lineno)
+
     def set_step(self):
         """
         Public method to stop after one line of code.
@@ -534,7 +603,7 @@
         try:
             self.currentFrame.f_lineno = lineno
             stack = self.getStack(self.currentFrame)
-            self._dbgClient.sendResponseLine(stack)
+            self._dbgClient.sendResponseLine(stack, self.name)
         except Exception as e:
             printerr(e)
 
@@ -692,9 +761,9 @@
         """
         Public method to get the stack.
         
-        @keyparam frame frame object to inspect
+        @param frame frame object to inspect
         @type frame object or list
-        @keyparam applyTrace flag to assign trace function to fr.f_trace
+        @param 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)
@@ -720,8 +789,9 @@
             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")):
+                ("DebugBase.py", "DebugClientBase.py",
+                 "ThreadExtension.py", "threading.py")
+            ):
                 break
             
             fline = tb_lineno or fr.f_lineno
@@ -773,19 +843,21 @@
         self._dbgClient.currentThread = self
         self._dbgClient.currentThreadExec = self
         
-        self._dbgClient.sendResponseLine(stack)
+        self._dbgClient.sendResponseLine(stack, self.name)
         self._dbgClient.eventLoop()
         
         self.isBroken = False
         self._dbgClient.unlockClient()
         
+        self._dbgClient.dumpThreadList()
+    
     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
+        @param unhandled flag indicating an uncaught exception
         @type bool
         """
         exctype, excval, exctb = excinfo
@@ -825,7 +897,7 @@
             
             if realSyntaxError:
                 self._dbgClient.sendSyntaxError(
-                    message, filename, lineno, charno)
+                    message, filename, lineno, charno, self.name)
                 self._dbgClient.eventLoop()
                 return
         
@@ -865,6 +937,7 @@
             self.stop_everywhere = False
         
         self.isBroken = True
+        self.isException = True
         
         disassembly = None
         stack = []
@@ -879,7 +952,7 @@
         self._dbgClient.lockClient()
         self._dbgClient.currentThread = self
         self._dbgClient.currentThreadExec = self
-        self._dbgClient.sendException(exctypetxt, excvaltxt, stack)
+        self._dbgClient.sendException(exctypetxt, excvaltxt, stack, self.name)
         self._dbgClient.setDisassembly(disassembly)
         self._dbgClient.dumpThreadList()
         
@@ -891,11 +964,14 @@
         self.skipFrames = 0
         
         self.isBroken = False
+        self.isException = False
         stop_everywhere = self.stop_everywhere
         self.stop_everywhere = False
         self.eventPollFlag = False
         self._dbgClient.unlockClient()
         self.stop_everywhere = stop_everywhere
+        
+        self._dbgClient.dumpThreadList()
     
     def __extractExceptionName(self, exctype):
         """
@@ -1009,6 +1085,9 @@
             elif isinstance(code, int):
                 exitcode = code
                 message = ""
+            elif code is None:
+                exitcode = 0
+                message = ""
             else:
                 exitcode = 1
                 message = str(code)
@@ -1017,7 +1096,7 @@
             message = str(excval)
         
         return exitcode, message
-
+    
     def stop_here(self, frame):
         """
         Public method reimplemented to filter out debugger files.
@@ -1033,9 +1112,11 @@
         if self.__skipFrame(frame):
             return False
         
-        return (self.stop_everywhere or
-                frame is self.stopframe or
-                frame is self.returnframe)
+        if frame is self.stopframe:
+            if self.stoplineno == -1:
+                return False
+            return frame.f_lineno >= self.stoplineno
+        return self.stop_everywhere or frame is self.returnframe
 
     def tracePythonLibs(self, enable):
         """

eric ide

mercurial