From acfd3d570ebb7c82797d1b3447865a090d7d041f Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 15 Oct 2024 12:56:33 -0700 Subject: [PATCH 1/3] gh-58956: Set f_trace on frames with breakpoints after setting a new breakpoint (GH-124454) (cherry picked from commit 12eaadc0ad33411bb02945d700b6ed7e758bb188) Co-authored-by: Tian Gao --- Lib/bdb.py | 8 +++++ Lib/test/test_pdb.py | 30 +++++++++++++++++++ ...4-09-24-18-16-59.gh-issue-58956.0wFrBR.rst | 1 + 3 files changed, 39 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-09-24-18-16-59.gh-issue-58956.0wFrBR.rst diff --git a/Lib/bdb.py b/Lib/bdb.py index 3486deacd86a7c..3f6cfc4d4979d9 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -396,6 +396,14 @@ def set_break(self, filename, lineno, temporary=False, cond=None, return 'Line %s:%d does not exist' % (filename, lineno) self._add_to_breaks(filename, lineno) bp = Breakpoint(filename, lineno, temporary, cond, funcname) + # After we set a new breakpoint, we need to search through all frames + # and set f_trace to trace_dispatch if there could be a breakpoint in + # that frame. + frame = self.enterframe + while frame: + if self.break_anywhere(frame): + frame.f_trace = self.trace_dispatch + frame = frame.f_back return None def _load_breaks(self): diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 778aa03a63ab63..7a921a36b997f9 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2371,6 +2371,36 @@ def test_issue26053(self): self.assertRegex(res, "Restarting .* with arguments:\na b c") self.assertRegex(res, "Restarting .* with arguments:\nd e f") + def test_issue58956(self): + # Set a breakpoint in a function that already exists on the call stack + # should enable the trace function for the frame. + script = """ + import bar + def foo(): + ret = bar.bar() + pass + foo() + """ + commands = """ + b bar.bar + c + b main.py:5 + c + p ret + quit + """ + bar = """ + def bar(): + return 42 + """ + with open('bar.py', 'w') as f: + f.write(textwrap.dedent(bar)) + self.addCleanup(os_helper.unlink, 'bar.py') + stdout, stderr = self.run_pdb_script(script, commands) + lines = stdout.splitlines() + self.assertIn('-> pass', lines) + self.assertIn('(Pdb) 42', lines) + def test_step_into_botframe(self): # gh-125422 # pdb should not be able to step into the botframe (bdb.py) diff --git a/Misc/NEWS.d/next/Library/2024-09-24-18-16-59.gh-issue-58956.0wFrBR.rst b/Misc/NEWS.d/next/Library/2024-09-24-18-16-59.gh-issue-58956.0wFrBR.rst new file mode 100644 index 00000000000000..a882a632fddf1b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-09-24-18-16-59.gh-issue-58956.0wFrBR.rst @@ -0,0 +1 @@ +Fixed a bug in :mod:`pdb` where sometimes the breakpoint won't trigger if it was set on a function which is already in the call stack. From ea13885617710e2233de88e6c23308757c2d46f7 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 15 Oct 2024 18:05:29 -0400 Subject: [PATCH 2/3] Add enterframe to bdb --- Lib/bdb.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/bdb.py b/Lib/bdb.py index 3f6cfc4d4979d9..43463acf90801e 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -33,6 +33,7 @@ def __init__(self, skip=None): self.breaks = {} self.fncache = {} self.frame_returning = None + self.enterframe = None self._load_breaks() @@ -84,6 +85,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': @@ -335,6 +339,7 @@ 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 From 4f9d5dd128ed290c36be23e77cb71c210c3cefae Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 17 Jan 2025 15:27:12 -0500 Subject: [PATCH 3/3] Clear trace when enter set_trace --- Lib/bdb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/bdb.py b/Lib/bdb.py index 43463acf90801e..4c6ea316df5551 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -336,6 +336,7 @@ def set_trace(self, frame=None): If frame is not specified, debugging starts from caller's frame. """ + sys.settrace(None) if frame is None: frame = sys._getframe().f_back self.reset()