From 24857e4a5c05460ab3ee0e8ace5b23302c5cb876 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Mon, 30 Jun 2025 21:45:25 -0700 Subject: [PATCH 1/7] Allow step and next to step over for loops --- Lib/bdb.py | 19 +++++++++++++++---- Lib/test/test_pdb.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 4290ef22302a42..7c2b508582e6f9 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -306,7 +306,12 @@ def dispatch_line(self, frame): self.user_line(). 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): + # GH-136057 + # For line events, besides whether we should stop at the frame, we + # also need to check if it's the same line as we issue the command. + if (self.stop_here(frame) or self.break_here(frame)) and not ( + self.startframe == frame and self.startlineno == frame.f_lineno + ): self.user_line(frame) self.restart_events() if self.quitting: raise BdbQuit @@ -535,7 +540,8 @@ def _set_trace_opcodes(self, trace_opcodes): if self.monitoring_tracer: 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, + startframe=None, startlineno=None): """Set the attributes for stopping. If stoplineno is greater than or equal to 0, then stop at line @@ -548,6 +554,10 @@ def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False): # stoplineno >= 0 means: stop at line >= the stoplineno # stoplineno -1 means: don't stop at all self.stoplineno = stoplineno + # startframe/startlineno is the frame/line number when the user does + # step or next. We don't want to stop at the same line for those commands. + self.startframe = startframe + self.startlineno = startlineno self._set_trace_opcodes(opcode) def _set_caller_tracefunc(self, current_frame): @@ -573,7 +583,8 @@ def set_until(self, frame, lineno=None): def set_step(self): """Stop after one line of code.""" - self._set_stopinfo(None, None) + self._set_stopinfo(None, None, startframe=self.enterframe, + startlineno=self.enterframe.f_lineno) def set_stepinstr(self): """Stop before the next instruction.""" @@ -581,7 +592,7 @@ def set_stepinstr(self): def set_next(self, frame): """Stop on the next line in or below the given frame.""" - self._set_stopinfo(frame, None) + self._set_stopinfo(frame, None, startframe=frame, startlineno=frame.f_lineno) def set_return(self, frame): """Stop when returning from the given frame.""" diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 6b74e21ad73d1a..e34b3e1351092a 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -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() + > (2)test_function() + -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + (Pdb) next + > (3)test_function() + -> lst = [i for i in range(10)] + (Pdb) next + > (4)test_function() + -> for i in lst: pass + (Pdb) step + --Return-- + > (4)test_function()->None + -> for i in lst: pass + (Pdb) continue + """ + + def test_pdb_issue_gh_80731(): """See GH-80731 From 83cd3b3d9626dc27c164a0463c0e6567da189fba Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 04:58:02 +0000 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-07-01-04-57-57.gh-issue-136057.4-t596.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-07-01-04-57-57.gh-issue-136057.4-t596.rst diff --git a/Misc/NEWS.d/next/Library/2025-07-01-04-57-57.gh-issue-136057.4-t596.rst b/Misc/NEWS.d/next/Library/2025-07-01-04-57-57.gh-issue-136057.4-t596.rst new file mode 100644 index 00000000000000..5fcbf8cc313cdd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-01-04-57-57.gh-issue-136057.4-t596.rst @@ -0,0 +1 @@ +Fixed the bug in :mod:`pdb` and :`bdb` where ``next`` and ``step`` can't go over the line if a loop exists in the line. From e47731e09844a2ce79bb873f71c3037c4389166a Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Mon, 30 Jun 2025 22:08:26 -0700 Subject: [PATCH 3/7] Update 2025-07-01-04-57-57.gh-issue-136057.4-t596.rst --- .../next/Library/2025-07-01-04-57-57.gh-issue-136057.4-t596.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-07-01-04-57-57.gh-issue-136057.4-t596.rst b/Misc/NEWS.d/next/Library/2025-07-01-04-57-57.gh-issue-136057.4-t596.rst index 5fcbf8cc313cdd..e237a0e98cc486 100644 --- a/Misc/NEWS.d/next/Library/2025-07-01-04-57-57.gh-issue-136057.4-t596.rst +++ b/Misc/NEWS.d/next/Library/2025-07-01-04-57-57.gh-issue-136057.4-t596.rst @@ -1 +1 @@ -Fixed the bug in :mod:`pdb` and :`bdb` where ``next`` and ``step`` can't go over the line if a loop exists in the line. +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. From 99837fdb0d8a5413ed926275c6085a7213bb6854 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Mon, 30 Jun 2025 22:34:37 -0700 Subject: [PATCH 4/7] Deal with enterframe == None in set_step --- Lib/bdb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 7c2b508582e6f9..e5e3682f7872ca 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -583,8 +583,9 @@ def set_until(self, frame, lineno=None): def set_step(self): """Stop after one line of code.""" + # set_step() could be called from signal handler so enterframe might be None self._set_stopinfo(None, None, startframe=self.enterframe, - startlineno=self.enterframe.f_lineno) + startlineno=getattr(self.enterframe, 'f_lineno', None)) def set_stepinstr(self): """Stop before the next instruction.""" From 6ad85980d8bc605701579e3af75ee9a5ce21628c Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sat, 5 Jul 2025 11:16:02 -0700 Subject: [PATCH 5/7] Rename the variable and update the comments --- Lib/bdb.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index e5e3682f7872ca..239ce8a71ab560 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -307,10 +307,10 @@ def dispatch_line(self, frame): Return self.trace_dispatch to continue tracing in this scope. """ # GH-136057 - # For line events, besides whether we should stop at the frame, we - # also need to check if it's the same line as we issue the command. + # For line events, we don't want to stop at the same line where + # we issue the previous next/step command. if (self.stop_here(frame) or self.break_here(frame)) and not ( - self.startframe == frame and self.startlineno == frame.f_lineno + self.cmdframe == frame and self.cmdlineno == frame.f_lineno ): self.user_line(frame) self.restart_events() @@ -541,7 +541,7 @@ def _set_trace_opcodes(self, trace_opcodes): self.monitoring_tracer.update_local_events() def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False, - startframe=None, startlineno=None): + cmdframe=None, cmdlineno=None): """Set the attributes for stopping. If stoplineno is greater than or equal to 0, then stop at line @@ -554,10 +554,11 @@ def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False, # stoplineno >= 0 means: stop at line >= the stoplineno # stoplineno -1 means: don't stop at all self.stoplineno = stoplineno - # startframe/startlineno is the frame/line number when the user does - # step or next. We don't want to stop at the same line for those commands. - self.startframe = startframe - self.startlineno = startlineno + # cmdframe/cmdlineno is the frame/line number when the user issue + # step/next commands. We don't want to stop at the same line for + # those commands. + self.cmdframe = cmdframe + self.cmdlineno = cmdlineno self._set_trace_opcodes(opcode) def _set_caller_tracefunc(self, current_frame): @@ -584,8 +585,8 @@ def set_until(self, frame, lineno=None): def set_step(self): """Stop after one line of code.""" # set_step() could be called from signal handler so enterframe might be None - self._set_stopinfo(None, None, startframe=self.enterframe, - startlineno=getattr(self.enterframe, 'f_lineno', None)) + self._set_stopinfo(None, None, cmdframe=self.enterframe, + cmdlineno=getattr(self.enterframe, 'f_lineno', None)) def set_stepinstr(self): """Stop before the next instruction.""" @@ -593,7 +594,7 @@ def set_stepinstr(self): def set_next(self, frame): """Stop on the next line in or below the given frame.""" - self._set_stopinfo(frame, None, startframe=frame, startlineno=frame.f_lineno) + self._set_stopinfo(frame, None, cmdframe=frame, cmdlineno=frame.f_lineno) def set_return(self, frame): """Stop when returning from the given frame.""" From 21d27833bcdc1f15cf9ef7e2d687c42a5f8c7ba3 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sat, 5 Jul 2025 12:14:42 -0700 Subject: [PATCH 6/7] Fix lint --- Lib/bdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 239ce8a71ab560..0bda011ec941fb 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -554,7 +554,7 @@ def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False, # stoplineno >= 0 means: stop at line >= the stoplineno # stoplineno -1 means: don't stop at all self.stoplineno = stoplineno - # cmdframe/cmdlineno is the frame/line number when the user issue + # cmdframe/cmdlineno is the frame/line number when the user issues # step/next commands. We don't want to stop at the same line for # those commands. self.cmdframe = cmdframe From 4094efc8d35b440b601d3f38ae25e7cc2df72f20 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sat, 5 Jul 2025 13:57:29 -0700 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> --- Lib/bdb.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 0bda011ec941fb..05f9c3a27649da 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -308,7 +308,7 @@ def dispatch_line(self, frame): """ # GH-136057 # For line events, we don't want to stop at the same line where - # we issue the previous next/step command. + # 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 ): @@ -554,9 +554,8 @@ def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False, # stoplineno >= 0 means: stop at line >= the stoplineno # stoplineno -1 means: don't stop at all self.stoplineno = stoplineno - # cmdframe/cmdlineno is the frame/line number when the user issues - # step/next commands. We don't want to stop at the same line for - # those commands. + # 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)