Skip to content

Commit

Permalink
gh-124552 : Improve the accuracy of possible breakpoint check in bdb (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
gaogaotiantian authored Oct 5, 2024
1 parent 2d8b6a4 commit adfe765
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 3 deletions.
27 changes: 24 additions & 3 deletions Lib/bdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -36,6 +37,7 @@ def __init__(self, skip=None):
self.frame_returning = None
self.trace_opcodes = False
self.enterframe = None
self.code_linenos = weakref.WeakKeyDictionary()

self._load_breaks()

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -273,9 +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
"""
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.
Expand Down Expand Up @@ -360,7 +381,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)

Expand Down
37 changes: 37 additions & 0 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.

0 comments on commit adfe765

Please sign in to comment.