33import fnmatch
44import sys
55import os
6+ from contextlib import contextmanager
67from inspect import CO_GENERATOR , CO_COROUTINE , CO_ASYNC_GENERATOR
78
89__all__ = ["BdbQuit" , "Bdb" , "Breakpoint" ]
@@ -32,7 +33,12 @@ def __init__(self, skip=None):
3233 self .skip = set (skip ) if skip else None
3334 self .breaks = {}
3435 self .fncache = {}
36+ self .frame_trace_lines_opcodes = {}
3537 self .frame_returning = None
38+ self .trace_opcodes = False
39+ self .enterframe = None
40+ self .cmdframe = None
41+ self .cmdlineno = None
3642
3743 self ._load_breaks ()
3844
@@ -60,6 +66,12 @@ def reset(self):
6066 self .botframe = None
6167 self ._set_stopinfo (None , None )
6268
69+ @contextmanager
70+ def set_enterframe (self , frame ):
71+ self .enterframe = frame
72+ yield
73+ self .enterframe = None
74+
6375 def trace_dispatch (self , frame , event , arg ):
6476 """Dispatch a trace function for debugged frames based on the event.
6577
@@ -84,24 +96,28 @@ def trace_dispatch(self, frame, event, arg):
8496
8597 The arg parameter depends on the previous event.
8698 """
87- if self .quitting :
88- return # None
89- if event == 'line' :
90- return self .dispatch_line (frame )
91- if event == 'call' :
92- return self .dispatch_call (frame , arg )
93- if event == 'return' :
94- return self .dispatch_return (frame , arg )
95- if event == 'exception' :
96- return self .dispatch_exception (frame , arg )
97- if event == 'c_call' :
98- return self .trace_dispatch
99- if event == 'c_exception' :
100- return self .trace_dispatch
101- if event == 'c_return' :
99+
100+ with self .set_enterframe (frame ):
101+ if self .quitting :
102+ return # None
103+ if event == 'line' :
104+ return self .dispatch_line (frame )
105+ if event == 'call' :
106+ return self .dispatch_call (frame , arg )
107+ if event == 'return' :
108+ return self .dispatch_return (frame , arg )
109+ if event == 'exception' :
110+ return self .dispatch_exception (frame , arg )
111+ if event == 'c_call' :
112+ return self .trace_dispatch
113+ if event == 'c_exception' :
114+ return self .trace_dispatch
115+ if event == 'c_return' :
116+ return self .trace_dispatch
117+ if event == 'opcode' :
118+ return self .dispatch_opcode (frame , arg )
119+ print ('bdb.Bdb.dispatch: unknown debugging event:' , repr (event ))
102120 return self .trace_dispatch
103- print ('bdb.Bdb.dispatch: unknown debugging event:' , repr (event ))
104- return self .trace_dispatch
105121
106122 def dispatch_line (self , frame ):
107123 """Invoke user function and return trace function for line event.
@@ -110,7 +126,12 @@ def dispatch_line(self, frame):
110126 self.user_line(). Raise BdbQuit if self.quitting is set.
111127 Return self.trace_dispatch to continue tracing in this scope.
112128 """
113- if self .stop_here (frame ) or self .break_here (frame ):
129+ # GH-136057
130+ # For line events, we don't want to stop at the same line where
131+ # the latest next/step command was issued.
132+ if (self .stop_here (frame ) or self .break_here (frame )) and not (
133+ self .cmdframe == frame and self .cmdlineno == frame .f_lineno
134+ ):
114135 self .user_line (frame )
115136 if self .quitting : raise BdbQuit
116137 return self .trace_dispatch
@@ -157,6 +178,11 @@ def dispatch_return(self, frame, arg):
157178 # The user issued a 'next' or 'until' command.
158179 if self .stopframe is frame and self .stoplineno != - 1 :
159180 self ._set_stopinfo (None , None )
181+ # The previous frame might not have f_trace set, unless we are
182+ # issuing a command that does not expect to stop, we should set
183+ # f_trace
184+ if self .stoplineno != - 1 :
185+ self ._set_caller_tracefunc (frame )
160186 return self .trace_dispatch
161187
162188 def dispatch_exception (self , frame , arg ):
@@ -186,6 +212,17 @@ def dispatch_exception(self, frame, arg):
186212
187213 return self .trace_dispatch
188214
215+ def dispatch_opcode (self , frame , arg ):
216+ """Invoke user function and return trace function for opcode event.
217+ If the debugger stops on the current opcode, invoke
218+ self.user_opcode(). Raise BdbQuit if self.quitting is set.
219+ Return self.trace_dispatch to continue tracing in this scope.
220+ """
221+ if self .stop_here (frame ) or self .break_here (frame ):
222+ self .user_opcode (frame )
223+ if self .quitting : raise BdbQuit
224+ return self .trace_dispatch
225+
189226 # Normally derived classes don't override the following
190227 # methods, but they may if they want to redefine the
191228 # definition of stopping and breakpoints.
@@ -272,7 +309,22 @@ def user_exception(self, frame, exc_info):
272309 """Called when we stop on an exception."""
273310 pass
274311
275- def _set_stopinfo (self , stopframe , returnframe , stoplineno = 0 ):
312+ def user_opcode (self , frame ):
313+ """Called when we are about to execute an opcode."""
314+ pass
315+
316+ def _set_trace_opcodes (self , trace_opcodes ):
317+ if trace_opcodes != self .trace_opcodes :
318+ self .trace_opcodes = trace_opcodes
319+ frame = self .enterframe
320+ while frame is not None :
321+ frame .f_trace_opcodes = trace_opcodes
322+ if frame is self .botframe :
323+ break
324+ frame = frame .f_back
325+
326+ def _set_stopinfo (self , stopframe , returnframe , stoplineno = 0 , opcode = False ,
327+ cmdframe = None , cmdlineno = None ):
276328 """Set the attributes for stopping.
277329
278330 If stoplineno is greater than or equal to 0, then stop at line
@@ -285,6 +337,21 @@ def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
285337 # stoplineno >= 0 means: stop at line >= the stoplineno
286338 # stoplineno -1 means: don't stop at all
287339 self .stoplineno = stoplineno
340+ # cmdframe/cmdlineno is the frame/line number when the user issued
341+ # step/next commands.
342+ self .cmdframe = cmdframe
343+ self .cmdlineno = cmdlineno
344+ self ._set_trace_opcodes (opcode )
345+
346+ def _set_caller_tracefunc (self , current_frame ):
347+ # Issue #13183: pdb skips frames after hitting a breakpoint and running
348+ # step commands.
349+ # Restore the trace function in the caller (that may not have been set
350+ # for performance reasons) when returning from the current frame, unless
351+ # the caller is the botframe.
352+ caller_frame = current_frame .f_back
353+ if caller_frame and not caller_frame .f_trace and caller_frame is not self .botframe :
354+ caller_frame .f_trace = self .trace_dispatch
288355
289356 # Derived classes and clients can call the following methods
290357 # to affect the stepping state.
@@ -299,19 +366,17 @@ def set_until(self, frame, lineno=None):
299366
300367 def set_step (self ):
301368 """Stop after one line of code."""
302- # Issue #13183: pdb skips frames after hitting a breakpoint and running
303- # step commands.
304- # Restore the trace function in the caller (that may not have been set
305- # for performance reasons) when returning from the current frame.
306- if self .frame_returning :
307- caller_frame = self .frame_returning .f_back
308- if caller_frame and not caller_frame .f_trace :
309- caller_frame .f_trace = self .trace_dispatch
310- self ._set_stopinfo (None , None )
369+ # set_step() could be called from signal handler so enterframe might be None
370+ self ._set_stopinfo (None , None , cmdframe = self .enterframe ,
371+ cmdlineno = getattr (self .enterframe , 'f_lineno' , None ))
372+
373+ def set_stepinstr (self ):
374+ """Stop before the next instruction."""
375+ self ._set_stopinfo (None , None , opcode = True )
311376
312377 def set_next (self , frame ):
313378 """Stop on the next line in or below the given frame."""
314- self ._set_stopinfo (frame , None )
379+ self ._set_stopinfo (frame , None , cmdframe = frame , cmdlineno = frame . f_lineno )
315380
316381 def set_return (self , frame ):
317382 """Stop when returning from the given frame."""
@@ -328,11 +393,15 @@ def set_trace(self, frame=None):
328393 if frame is None :
329394 frame = sys ._getframe ().f_back
330395 self .reset ()
331- while frame :
332- frame .f_trace = self .trace_dispatch
333- self .botframe = frame
334- frame = frame .f_back
335- self .set_step ()
396+ with self .set_enterframe (frame ):
397+ while frame :
398+ frame .f_trace = self .trace_dispatch
399+ self .botframe = frame
400+ self .frame_trace_lines_opcodes [frame ] = (frame .f_trace_lines , frame .f_trace_opcodes )
401+ # We need f_trace_lines == True for the debugger to work
402+ frame .f_trace_lines = True
403+ frame = frame .f_back
404+ self .set_stepinstr ()
336405 sys .settrace (self .trace_dispatch )
337406
338407 def set_continue (self ):
@@ -349,6 +418,9 @@ def set_continue(self):
349418 while frame and frame is not self .botframe :
350419 del frame .f_trace
351420 frame = frame .f_back
421+ for frame , (trace_lines , trace_opcodes ) in self .frame_trace_lines_opcodes .items ():
422+ frame .f_trace_lines , frame .f_trace_opcodes = trace_lines , trace_opcodes
423+ self .frame_trace_lines_opcodes = {}
352424
353425 def set_quit (self ):
354426 """Set quitting attribute to True.
@@ -387,6 +459,14 @@ def set_break(self, filename, lineno, temporary=False, cond=None,
387459 return 'Line %s:%d does not exist' % (filename , lineno )
388460 self ._add_to_breaks (filename , lineno )
389461 bp = Breakpoint (filename , lineno , temporary , cond , funcname )
462+ # After we set a new breakpoint, we need to search through all frames
463+ # and set f_trace to trace_dispatch if there could be a breakpoint in
464+ # that frame.
465+ frame = self .enterframe
466+ while frame :
467+ if self .break_anywhere (frame ):
468+ frame .f_trace = self .trace_dispatch
469+ frame = frame .f_back
390470 return None
391471
392472 def _load_breaks (self ):
0 commit comments