https://github.com/python/cpython/commit/f34e965e52b9bdf157b829371870edfde45b80bf
commit: f34e965e52b9bdf157b829371870edfde45b80bf
branch: main
author: Tian Gao <[email protected]>
committer: brandtbucher <[email protected]>
date: 2024-05-04T07:44:49-07:00
summary:

GH-111744: Support opcode events in bdb (GH-111834)

files:
A Misc/NEWS.d/next/Library/2023-11-07-22-41-42.gh-issue-111744.TbLxF0.rst
M Lib/bdb.py
M Lib/test/test_bdb.py
M Lib/test/test_pdb.py

diff --git a/Lib/bdb.py b/Lib/bdb.py
index 1acf7957f0d669..675c8ae51df4c3 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -32,8 +32,10 @@ def __init__(self, skip=None):
         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 @@ def trace_dispatch(self, frame, event, arg):
 
         The arg parameter depends on the previous event.
         """
+
+        self.enterframe = frame
+
         if self.quitting:
             return # None
         if event == 'line':
@@ -101,6 +106,8 @@ def trace_dispatch(self, frame, event, arg):
             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 @@ def dispatch_exception(self, frame, arg):
 
         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 @@ def user_exception(self, frame, exc_info):
         """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 @@ def _set_stopinfo(self, stopframe, returnframe, 
stoplineno=0):
         # 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 @@ def set_until(self, frame, lineno=None):
 
     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 @@ def set_trace(self, frame=None):
         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 @@ def set_continue(self):
             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.
diff --git a/Lib/test/test_bdb.py b/Lib/test/test_bdb.py
index 568c88e326c087..ed1a63daea1186 100644
--- a/Lib/test/test_bdb.py
+++ b/Lib/test/test_bdb.py
@@ -228,6 +228,10 @@ def user_exception(self, frame, exc_info):
         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 @@ def next_set_method(self):
         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 @@ def test_step_next_on_last_statement(self):
                 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', ),
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index c9ea52717490b1..001562acae7458 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -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
index 00000000000000..ed856e7667a372
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-11-07-22-41-42.gh-issue-111744.TbLxF0.rst
@@ -0,0 +1 @@
+Support opcode events in :mod:`bdb`

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to