[3.14] gh-136057: Allow step and next to step over for loops (GH-136160) (#141640)

gh-136057: Allow step and next to step over for loops (GH-136160)
(cherry picked from commit 8be3b2f479)

Co-authored-by: Tian Gao <gaogaotiantian@hotmail.com>
This commit is contained in:
Miss Islington (bot) 2025-11-16 23:22:11 +01:00 committed by GitHub
parent 0d8fb0b852
commit eead7b43bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 50 additions and 4 deletions

View file

@ -199,6 +199,8 @@ def __init__(self, skip=None, backend='settrace'):
self.frame_returning = None self.frame_returning = None
self.trace_opcodes = False self.trace_opcodes = False
self.enterframe = None self.enterframe = None
self.cmdframe = None
self.cmdlineno = None
self.code_linenos = weakref.WeakKeyDictionary() self.code_linenos = weakref.WeakKeyDictionary()
self.backend = backend self.backend = backend
if backend == 'monitoring': if backend == 'monitoring':
@ -306,7 +308,12 @@ def dispatch_line(self, frame):
self.user_line(). Raise BdbQuit if self.quitting is set. self.user_line(). Raise BdbQuit if self.quitting is set.
Return self.trace_dispatch to continue tracing in this scope. Return self.trace_dispatch to continue tracing in this scope.
""" """
if self.stop_here(frame) or self.break_here(frame): # GH-136057
# For line events, we don't want to stop at the same line where
# the latest next/step command was issued.
if (self.stop_here(frame) or self.break_here(frame)) and not (
self.cmdframe == frame and self.cmdlineno == frame.f_lineno
):
self.user_line(frame) self.user_line(frame)
self.restart_events() self.restart_events()
if self.quitting: raise BdbQuit if self.quitting: raise BdbQuit
@ -535,7 +542,8 @@ def _set_trace_opcodes(self, trace_opcodes):
if self.monitoring_tracer: if self.monitoring_tracer:
self.monitoring_tracer.update_local_events() self.monitoring_tracer.update_local_events()
def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False): def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False,
cmdframe=None, cmdlineno=None):
"""Set the attributes for stopping. """Set the attributes for stopping.
If stoplineno is greater than or equal to 0, then stop at line If stoplineno is greater than or equal to 0, then stop at line
@ -548,6 +556,10 @@ def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False):
# stoplineno >= 0 means: stop at line >= the stoplineno # stoplineno >= 0 means: stop at line >= the stoplineno
# stoplineno -1 means: don't stop at all # stoplineno -1 means: don't stop at all
self.stoplineno = stoplineno self.stoplineno = stoplineno
# cmdframe/cmdlineno is the frame/line number when the user issued
# step/next commands.
self.cmdframe = cmdframe
self.cmdlineno = cmdlineno
self._set_trace_opcodes(opcode) self._set_trace_opcodes(opcode)
def _set_caller_tracefunc(self, current_frame): def _set_caller_tracefunc(self, current_frame):
@ -573,7 +585,9 @@ def set_until(self, frame, lineno=None):
def set_step(self): def set_step(self):
"""Stop after one line of code.""" """Stop after one line of code."""
self._set_stopinfo(None, None) # set_step() could be called from signal handler so enterframe might be None
self._set_stopinfo(None, None, cmdframe=self.enterframe,
cmdlineno=getattr(self.enterframe, 'f_lineno', None))
def set_stepinstr(self): def set_stepinstr(self):
"""Stop before the next instruction.""" """Stop before the next instruction."""
@ -581,7 +595,7 @@ def set_stepinstr(self):
def set_next(self, frame): def set_next(self, frame):
"""Stop on the next line in or below the given frame.""" """Stop on the next line in or below the given frame."""
self._set_stopinfo(frame, None) self._set_stopinfo(frame, None, cmdframe=frame, cmdlineno=frame.f_lineno)
def set_return(self, frame): def set_return(self, frame):
"""Stop when returning from the given frame.""" """Stop when returning from the given frame."""

View file

@ -3232,6 +3232,37 @@ def test_pdb_issue_gh_127321():
""" """
def test_pdb_issue_gh_136057():
"""See GH-136057
"step" and "next" commands should be able to get over list comprehensions
>>> def test_function():
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
... lst = [i for i in range(10)]
... for i in lst: pass
>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
... 'next',
... 'next',
... 'step',
... 'continue',
... ]):
... test_function()
> <doctest test.test_pdb.test_pdb_issue_gh_136057[0]>(2)test_function()
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
(Pdb) next
> <doctest test.test_pdb.test_pdb_issue_gh_136057[0]>(3)test_function()
-> lst = [i for i in range(10)]
(Pdb) next
> <doctest test.test_pdb.test_pdb_issue_gh_136057[0]>(4)test_function()
-> for i in lst: pass
(Pdb) step
--Return--
> <doctest test.test_pdb.test_pdb_issue_gh_136057[0]>(4)test_function()->None
-> for i in lst: pass
(Pdb) continue
"""
def test_pdb_issue_gh_80731(): def test_pdb_issue_gh_80731():
"""See GH-80731 """See GH-80731

View file

@ -0,0 +1 @@
Fixed the bug in :mod:`pdb` and :mod:`bdb` where ``next`` and ``step`` can't go over the line if a loop exists in the line.