]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-124552 : Improve the accuracy of possible breakpoint check in bdb (#124553)
authorTian Gao <gaogaotiantian@hotmail.com>
Sat, 5 Oct 2024 01:32:57 +0000 (18:32 -0700)
committerGitHub <noreply@github.com>
Sat, 5 Oct 2024 01:32:57 +0000 (21:32 -0400)
Lib/bdb.py
Lib/test/test_pdb.py
Misc/NEWS.d/next/Library/2024-09-25-22-06-52.gh-issue-124552.1nQKNM.rst [new file with mode: 0644]

index d7543017940d4fb378dc0e6e9b5bbe9b475bbe83..666f9714eb9b7a2e2f4e4bc00c75f293181c3959 100644 (file)
@@ -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 @@ class Bdb:
         self.frame_returning = None
         self.trace_opcodes = False
         self.enterframe = None
+        self.code_linenos = weakref.WeakKeyDictionary()
 
         self._load_breaks()
 
@@ -155,6 +157,9 @@ class Bdb:
         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
@@ -273,9 +278,25 @@ class Bdb:
         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
         """
-        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_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.
@@ -360,7 +381,7 @@ class Bdb:
     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)
 
index 4c64a800cb32d25520b7a15185eb67e4ce6e1852..46eb00261042bc5b32b407ccbd35d77edce78550 100644 (file)
@@ -518,6 +518,43 @@ 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()
+
+    >>> reset_Breakpoint()
+    >>> with PdbTestInput([  # doctest: +NORMALIZE_WHITESPACE
+    ...     'b 3',
+    ...     'c',
+    ... ]):
+    ...     test_function()
+    > <doctest test.test_pdb.test_pdb_break_anywhere[0]>(6)inner()
+    -> p.set_trace()
+    (Pdb) b 3
+    Breakpoint 1 at <doctest test.test_pdb.test_pdb_break_anywhere[0]>:3
+    (Pdb) c
+    True
+    False
+    False
+    """
+
 def test_pdb_pp_repr_exc():
     """Test that do_p/do_pp do not swallow exceptions.
 
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 (file)
index 0000000..39dde4c
--- /dev/null
@@ -0,0 +1 @@
+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.