bdb.clear_all_file_breaks skips breakpoints when multiple breakpoints share a line
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 77.2k
- Forks
- 35.9k
- PR merge metrics
- PR metrics pending
Description
Bug report
Bug description:
bdb.Bdb.clear_all_file_breaks(filename) iterates over a list while mutating
it via Breakpoint.deleteMe(), which removes elements from the same list.
When two or more breakpoints exist at the same (filename, lineno), this
classic iterate-and-delete pattern skips alternate elements, leaving orphan
Breakpoint instances in Breakpoint.bplist and Breakpoint.bpbynumber.
The same module already uses the defensive slice-copy pattern in
clear_break() (the sibling function), so this is a clear local
inconsistency, not a contractual choice.
Internal asymmetry (smoking gun):
In Lib/bdb.py:
clear_break() — defensive copy:
for bp in Breakpoint.bplist[filename, lineno][:]:
bp.deleteMe()
clear_all_file_breaks() — no copy:
for line in self.breaks[filename]:
blist = Breakpoint.bplist[filename, line]
for bp in blist: # iterating raw list
bp.deleteMe() # mutates blist
Both functions delete breakpoints via the same deleteMe() mechanism, which
removes from Breakpoint.bplist[(filename, lineno)]. The asymmetric defense
between sibling functions is the strongest evidence this is an oversight
rather than a deliberate design choice.
Reproducer:
import bdb
class MyDbg(bdb.Bdb): pass
dbg = MyDbg()
# Three breakpoints at the same (file, line)
dbg.set_break(__file__, 10)
dbg.set_break(__file__, 10)
dbg.set_break(__file__, 10)
# Snapshot before clearing
key = (bdb.canonic(__file__), 10)
before = list(bdb.Breakpoint.bplist.get(key, []))
print(f"Before: {len(before)} breakpoints in Breakpoint.bplist[{key}]")
# Clear all in this file
dbg.clear_all_file_breaks(bdb.canonic(__file__))
# Snapshot after clearing
after = list(bdb.Breakpoint.bplist.get(key, []))
print(f"After: {len(after)} breakpoints in Breakpoint.bplist[{key}]")
print(f"Orphans in Breakpoint.bpbynumber: "
f"{[n for n, b in enumerate(bdb.Breakpoint.bpbynumber) if b is not None]}")
Expected behavior:
Before: 3 breakpoints in Breakpoint.bplist[...]
After: 0 breakpoints in Breakpoint.bplist[...]
Orphans in Breakpoint.bpbynumber: []
Actual behavior (Python 3.14.3):
Before: 3 breakpoints in Breakpoint.bplist[...]
After: 1 breakpoints in Breakpoint.bplist[...] ← orphan remains
Orphans in Breakpoint.bpbynumber: [<some index>] ← orphan remains
For N breakpoints at the same (file, line), floor(N/2) orphans remain.
Suggested fix (one-character diff):
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -755,7 +755,7 @@
if filename not in self.breaks:
return 'There are no breakpoints in %s' % filename
for line in self.breaks[filename]:
blist = Breakpoint.bplist[filename, line]
- for bp in blist:
+ for bp in blist[:]:
bp.deleteMe()
del self.breaks[filename]
This matches the existing pattern at clear_break() in the same file.
Suggested test (Lib/test/test_bdb.py):
def test_clear_all_file_breaks_with_multiple_bps_same_line(self):
"""Regression test: clear_all_file_breaks must remove all breakpoints,
even when multiple breakpoints share the same (file, line)."""
dbg = bdb.Bdb()
src = bdb.canonic(__file__)
dbg.set_break(src, 10)
dbg.set_break(src, 10)
dbg.set_break(src, 10)
self.assertEqual(len(bdb.Breakpoint.bplist[(src, 10)]), 3)
dbg.clear_all_file_breaks(src)
self.assertNotIn((src, 10), bdb.Breakpoint.bplist)
Versions:
- Reproduced on Python 3.14.3 (Windows, MINGW64).
- Reading source confirms identical pattern in
mainand 3.13 branches. - Behavior identical regardless of whether the breakpoints are set with
conditions, ignore counts, or via differentBdbsubclasses.
Related (not duplicate):
Issue #54770 (bpo-10561, fixed 2010) covers a different scenario: the
clear bpnumber pdb command potentially deleting more than one breakpoint.
That issue addresses clear_bpbynumber / breakpoint identity. The bug
reported here is in clear_all_file_breaks and concerns iterate-and-delete
on the shared Breakpoint.bplist list. No prior issue or PR specifically
targets this asymmetry between clear_break (defensive) and
clear_all_file_breaks (vulnerable).
Linked PRs
- gh-149039
- gh-149843
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in Lib/bdb.py at Bdb.clear_all_file_breaks() and compare its breakpoint iteration with clear_break(). Review the related tests in Lib/test/test_bdb.py, then run the focused bdb test suite. Done means clearing a file removes every breakpoint, including multiple breakpoints sharing one line, without orphaned entries.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100