]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-111744: Support opcode events in bdb (GH-111834)
authorTian Gao <gaogaotiantian@hotmail.com>
Sat, 4 May 2024 14:44:49 +0000 (07:44 -0700)
committerGitHub <noreply@github.com>
Sat, 4 May 2024 14:44:49 +0000 (07:44 -0700)
Lib/bdb.py
Lib/test/test_bdb.py
Lib/test/test_pdb.py
Misc/NEWS.d/next/Library/2023-11-07-22-41-42.gh-issue-111744.TbLxF0.rst [new file with mode: 0644]

index 1acf7957f0d66931f32cc9fefb9f4e4e0b45c7f3..675c8ae51df4c3d238405a8dd29e1eef1a7a8194 100644 (file)
@@ -32,8 +32,10 @@ class Bdb:
         self.skip = set(skip) if skip else None
         self.breaks = {}
         self.fncache = {}
-        self.frame_trace_lines = {}
+        self.frame_trace_lines_opcodes = {}
         self.frame_returning = None
+        self.trace_opcodes = False
+        self.enterframe = None
 
         self._load_breaks()
 
@@ -85,6 +87,9 @@ class Bdb:
 
         The arg parameter depends on the previous event.
         """
+
+        self.enterframe = frame
+
         if self.quitting:
             return # None
         if event == 'line':
@@ -101,6 +106,8 @@ class Bdb:
             return self.trace_dispatch
         if event == 'c_return':
             return self.trace_dispatch
+        if event == 'opcode':
+            return self.dispatch_opcode(frame, arg)
         print('bdb.Bdb.dispatch: unknown debugging event:', repr(event))
         return self.trace_dispatch
 
@@ -187,6 +194,17 @@ class Bdb:
 
         return self.trace_dispatch
 
+    def dispatch_opcode(self, frame, arg):
+        """Invoke user function and return trace function for opcode event.
+        If the debugger stops on the current opcode, invoke
+        self.user_opcode(). Raise BdbQuit if self.quitting is set.
+        Return self.trace_dispatch to continue tracing in this scope.
+        """
+        if self.stop_here(frame) or self.break_here(frame):
+            self.user_opcode(frame)
+            if self.quitting: raise BdbQuit
+        return self.trace_dispatch
+
     # Normally derived classes don't override the following
     # methods, but they may if they want to redefine the
     # definition of stopping and breakpoints.
@@ -273,7 +291,21 @@ class Bdb:
         """Called when we stop on an exception."""
         pass
 
-    def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
+    def user_opcode(self, frame):
+        """Called when we are about to execute an opcode."""
+        pass
+
+    def _set_trace_opcodes(self, trace_opcodes):
+        if trace_opcodes != self.trace_opcodes:
+            self.trace_opcodes = trace_opcodes
+            frame = self.enterframe
+            while frame is not None:
+                frame.f_trace_opcodes = trace_opcodes
+                if frame is self.botframe:
+                    break
+                frame = frame.f_back
+
+    def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False):
         """Set the attributes for stopping.
 
         If stoplineno is greater than or equal to 0, then stop at line
@@ -286,6 +318,17 @@ class Bdb:
         # stoplineno >= 0 means: stop at line >= the stoplineno
         # stoplineno -1 means: don't stop at all
         self.stoplineno = stoplineno
+        self._set_trace_opcodes(opcode)
+
+    def _set_caller_tracefunc(self):
+        # Issue #13183: pdb skips frames after hitting a breakpoint and running
+        # step commands.
+        # Restore the trace function in the caller (that may not have been set
+        # for performance reasons) when returning from the current frame.
+        if self.frame_returning:
+            caller_frame = self.frame_returning.f_back
+            if caller_frame and not caller_frame.f_trace:
+                caller_frame.f_trace = self.trace_dispatch
 
     # Derived classes and clients can call the following methods
     # to affect the stepping state.
@@ -300,16 +343,14 @@ class Bdb:
 
     def set_step(self):
         """Stop after one line of code."""
-        # Issue #13183: pdb skips frames after hitting a breakpoint and running
-        # step commands.
-        # Restore the trace function in the caller (that may not have been set
-        # for performance reasons) when returning from the current frame.
-        if self.frame_returning:
-            caller_frame = self.frame_returning.f_back
-            if caller_frame and not caller_frame.f_trace:
-                caller_frame.f_trace = self.trace_dispatch
+        self._set_caller_tracefunc()
         self._set_stopinfo(None, None)
 
+    def set_stepinstr(self):
+        """Stop before the next instruction."""
+        self._set_caller_tracefunc()
+        self._set_stopinfo(None, None, opcode=True)
+
     def set_next(self, frame):
         """Stop on the next line in or below the given frame."""
         self._set_stopinfo(frame, None)
@@ -329,11 +370,12 @@ class Bdb:
         if frame is None:
             frame = sys._getframe().f_back
         self.reset()
+        self.enterframe = frame
         while frame:
             frame.f_trace = self.trace_dispatch
             self.botframe = frame
-            # We need f_trace_liens == True for the debugger to work
-            self.frame_trace_lines[frame] = frame.f_trace_lines
+            self.frame_trace_lines_opcodes[frame] = (frame.f_trace_lines, frame.f_trace_opcodes)
+            # We need f_trace_lines == True for the debugger to work
             frame.f_trace_lines = True
             frame = frame.f_back
         self.set_step()
@@ -353,9 +395,9 @@ class Bdb:
             while frame and frame is not self.botframe:
                 del frame.f_trace
                 frame = frame.f_back
-            for frame, prev_trace_lines in self.frame_trace_lines.items():
-                frame.f_trace_lines = prev_trace_lines
-            self.frame_trace_lines = {}
+            for frame, (trace_lines, trace_opcodes) in self.frame_trace_lines_opcodes.items():
+                frame.f_trace_lines, frame.f_trace_opcodes = trace_lines, trace_opcodes
+            self.frame_trace_lines_opcodes = {}
 
     def set_quit(self):
         """Set quitting attribute to True.
index 568c88e326c0873b59447532fd25bb97a2a1bf69..ed1a63daea11869c8fe3043187e7db1cf6f7c122 100644 (file)
@@ -228,6 +228,10 @@ class Tracer(Bdb):
         self.process_event('exception', frame)
         self.next_set_method()
 
+    def user_opcode(self, frame):
+        self.process_event('opcode', frame)
+        self.next_set_method()
+
     def do_clear(self, arg):
         # The temporary breakpoints are deleted in user_line().
         bp_list = [self.currentbp]
@@ -366,7 +370,7 @@ class Tracer(Bdb):
         set_method = getattr(self, 'set_' + set_type)
 
         # The following set methods give back control to the tracer.
-        if set_type in ('step', 'continue', 'quit'):
+        if set_type in ('step', 'stepinstr', 'continue', 'quit'):
             set_method()
             return
         elif set_type in ('next', 'return'):
@@ -610,6 +614,15 @@ class StateTestCase(BaseTestCase):
                 with TracerRun(self) as tracer:
                     tracer.runcall(tfunc_main)
 
+    def test_stepinstr(self):
+        self.expect_set = [
+            ('line',   2, 'tfunc_main'),  ('stepinstr', ),
+            ('opcode', 2, 'tfunc_main'),  ('next', ),
+            ('line',   3, 'tfunc_main'),  ('quit', ),
+        ]
+        with TracerRun(self) as tracer:
+            tracer.runcall(tfunc_main)
+
     def test_next(self):
         self.expect_set = [
             ('line', 2, 'tfunc_main'),   ('step', ),
index c9ea52717490b1d86ac9375f2cf19816fdd7b962..001562acae74586e202a9af00e719227a088cef8 100644 (file)
@@ -2456,7 +2456,6 @@ def test_pdb_issue_gh_108976():
     ...     'continue'
     ... ]):
     ...    test_function()
-    bdb.Bdb.dispatch: unknown debugging event: 'opcode'
     > <doctest test.test_pdb.test_pdb_issue_gh_108976[0]>(5)test_function()
     -> a = 1
     (Pdb) continue
diff --git a/Misc/NEWS.d/next/Library/2023-11-07-22-41-42.gh-issue-111744.TbLxF0.rst b/Misc/NEWS.d/next/Library/2023-11-07-22-41-42.gh-issue-111744.TbLxF0.rst
new file mode 100644 (file)
index 0000000..ed856e7
--- /dev/null
@@ -0,0 +1 @@
+Support opcode events in :mod:`bdb`