Skip to content

Commit 6bd1d90

Browse files
authored
Updated bdb.py + test_bdb.py (#6593)
* Updated bdb.py + test_bdb.py * Double checked test_bdb.py with the script
1 parent 6ed0b4f commit 6bd1d90

File tree

2 files changed

+177
-66
lines changed

2 files changed

+177
-66
lines changed

Lib/bdb.py

Lines changed: 114 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import fnmatch
44
import sys
55
import os
6+
from contextlib import contextmanager
67
from 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

Comments
 (0)