From ba6e6fd4b06a38b4ede9eea3129b17b891f0d482 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 25 Sep 2024 15:01:07 -0700 Subject: [PATCH 1/6] Improve bdb breakpoint check --- Lib/bdb.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index d7543017940d4f..92711e6d4eb794 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -3,6 +3,7 @@ import fnmatch import sys import os +import weakref from inspect import CO_GENERATOR, CO_COROUTINE, CO_ASYNC_GENERATOR __all__ = ["BdbQuit", "Bdb", "Breakpoint"] @@ -36,6 +37,7 @@ def __init__(self, skip=None): self.frame_returning = None self.trace_opcodes = False self.enterframe = None + self.code_lineno = weakref.WeakKeyDictionary() self._load_breaks() @@ -155,6 +157,9 @@ def dispatch_return(self, frame, arg): if self.stop_here(frame) or frame == self.returnframe: # Ignore return events in generator except when stepping. if self.stopframe and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS: + # It's possible to trigger a StopIteration exception in + # the caller so we must set the trace function in the caller + self._set_caller_tracefunc(frame) return self.trace_dispatch try: self.frame_returning = frame @@ -275,7 +280,25 @@ def do_clear(self, arg): def break_anywhere(self, frame): """Return True if there is any breakpoint for frame's filename. """ - return self.canonic(frame.f_code.co_filename) in self.breaks + filename = self.canonic(frame.f_code.co_filename) + if filename not in self.breaks: + return False + for lineno in self.breaks[filename]: + if self.lineno_in_frame(lineno, frame): + return True + return False + + def lineno_in_frame(self, lineno, frame): + """Return True if the line number is in the frame's code object. + """ + code = frame.f_code + if lineno < code.co_firstlineno: + return False + if code not in self.code_lineno: + self.code_lineno[code] = set() + for _, _, lineno in code.co_lines(): + self.code_lineno[code].add(lineno) + return lineno in self.code_lineno[frame.f_code] # Derived classes should override the user_* methods # to gain control. @@ -360,7 +383,7 @@ def set_next(self, frame): def set_return(self, frame): """Stop when returning from the given frame.""" if frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS: - self._set_stopinfo(frame, None, -1) + self._set_stopinfo(frame, frame, -1) else: self._set_stopinfo(frame.f_back, frame) From 6766505834c21b3fdb18ac6730a27851b2de9ce8 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 22:06:55 +0000 Subject: [PATCH 2/6] =?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/2024-09-25-22-06-52.gh-issue-124552.1nQKNM.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2024-09-25-22-06-52.gh-issue-124552.1nQKNM.rst diff --git a/Misc/NEWS.d/next/Library/2024-09-25-22-06-52.gh-issue-124552.1nQKNM.rst b/Misc/NEWS.d/next/Library/2024-09-25-22-06-52.gh-issue-124552.1nQKNM.rst new file mode 100644 index 00000000000000..1742aef1e5ade3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-09-25-22-06-52.gh-issue-124552.1nQKNM.rst @@ -0,0 +1 @@ +Improve the accuracy of possible breakpoint check in bdb so we can disable unnecessary events in functions. From f2e6926eccd83a820796ecdddda84dddb4f154f7 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 27 Sep 2024 13:35:06 -0700 Subject: [PATCH 3/6] Avoid calling set.add() and add test for break_anywhere() --- Lib/bdb.py | 4 +--- Lib/test/test_pdb.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 92711e6d4eb794..5316c6e4f2e9fb 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -295,9 +295,7 @@ def lineno_in_frame(self, lineno, frame): if lineno < code.co_firstlineno: return False if code not in self.code_lineno: - self.code_lineno[code] = set() - for _, _, lineno in code.co_lines(): - self.code_lineno[code].add(lineno) + self.code_lineno[code] = set(lineno for _, _, lineno in code.co_lines()) return lineno in self.code_lineno[frame.f_code] # Derived classes should override the user_* methods diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 84c0e1073a1054..c88f89bc05b46c 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -470,6 +470,42 @@ def test_pdb_breakpoints_preserved_across_interactive_sessions(): (Pdb) continue """ +def test_pdb_break_anywhere(): + """Test break_anywhere() method of Pdb. + + >>> def outer(): + ... def inner(): + ... import pdb + ... import sys + ... p = pdb.Pdb(nosigint=True, readrc=False) + ... p.set_trace() + ... frame = sys._getframe() + ... print(p.break_anywhere(frame)) # inner + ... print(p.break_anywhere(frame.f_back)) # outer + ... print(p.break_anywhere(frame.f_back.f_back)) # caller + ... inner() + + >>> def caller(): + ... outer() + + >>> def test_function(): + ... caller() + + >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE + ... 'b 3', + ... 'c', + ... ]): + ... test_function() + > (6)inner() + -> p.set_trace() + (Pdb) b 3 + Breakpoint 1 at :3 + (Pdb) c + True + False + False + """ + def test_pdb_pp_repr_exc(): """Test that do_p/do_pp do not swallow exceptions. From 53a6c5faceff1da6d6009d19e275beea5c266382 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 27 Sep 2024 13:49:31 -0700 Subject: [PATCH 4/6] Need to reset breakpoints --- Lib/test/test_pdb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index c88f89bc05b46c..b5ffe16e1c1c1c 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -491,6 +491,7 @@ def test_pdb_break_anywhere(): >>> def test_function(): ... caller() + >>> reset_Breakpoint() >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE ... 'b 3', ... 'c', From 4fef93ed2b84e24abab49d123d68489f30a5d47c Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 4 Oct 2024 19:04:47 -0400 Subject: [PATCH 5/6] Update Misc/NEWS.d/next/Library/2024-09-25-22-06-52.gh-issue-124552.1nQKNM.rst Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> --- .../next/Library/2024-09-25-22-06-52.gh-issue-124552.1nQKNM.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2024-09-25-22-06-52.gh-issue-124552.1nQKNM.rst b/Misc/NEWS.d/next/Library/2024-09-25-22-06-52.gh-issue-124552.1nQKNM.rst index 1742aef1e5ade3..39dde4c774ba5d 100644 --- a/Misc/NEWS.d/next/Library/2024-09-25-22-06-52.gh-issue-124552.1nQKNM.rst +++ b/Misc/NEWS.d/next/Library/2024-09-25-22-06-52.gh-issue-124552.1nQKNM.rst @@ -1 +1 @@ -Improve the accuracy of possible breakpoint check in bdb so we can disable unnecessary events in functions. +Improve the accuracy of :mod:`bdb`'s check for the possibility of breakpoint in a frame. This makes it possible to disable unnecessary events in functions. From 92408c94c4cc1a2ae2970ad18c672c04579e013e Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 4 Oct 2024 19:08:38 -0400 Subject: [PATCH 6/6] Fix comment, private a method and plural a member --- Lib/bdb.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 5316c6e4f2e9fb..666f9714eb9b7a 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -37,7 +37,7 @@ def __init__(self, skip=None): self.frame_returning = None self.trace_opcodes = False self.enterframe = None - self.code_lineno = weakref.WeakKeyDictionary() + self.code_linenos = weakref.WeakKeyDictionary() self._load_breaks() @@ -278,25 +278,25 @@ def do_clear(self, arg): raise NotImplementedError("subclass of bdb must implement do_clear()") def break_anywhere(self, frame): - """Return True if there is any breakpoint for frame's filename. + """Return True if there is any breakpoint in that frame """ filename = self.canonic(frame.f_code.co_filename) if filename not in self.breaks: return False for lineno in self.breaks[filename]: - if self.lineno_in_frame(lineno, frame): + if self._lineno_in_frame(lineno, frame): return True return False - def lineno_in_frame(self, lineno, frame): + def _lineno_in_frame(self, lineno, frame): """Return True if the line number is in the frame's code object. """ code = frame.f_code if lineno < code.co_firstlineno: return False - if code not in self.code_lineno: - self.code_lineno[code] = set(lineno for _, _, lineno in code.co_lines()) - return lineno in self.code_lineno[frame.f_code] + if code not in self.code_linenos: + self.code_linenos[code] = set(lineno for _, _, lineno in code.co_lines()) + return lineno in self.code_linenos[code] # Derived classes should override the user_* methods # to gain control.