bdb.clear_all_file_breaks skips breakpoints when multiple breakpoints share a line
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- Python
- Sterne
- 77.2k
- Forks
- 35.9k
- PR-Merge-Kennzahlen
- PR-Kennzahlen ausstehend
Beschreibung
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
Beitragsleitfaden
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Rechercherichtung
Beginne in Lib/bdb.py bei Bdb.clear_all_file_breaks() und vergleiche dessen Breakpoint-Iteration mit clear_break(). Überprüfe die zugehörigen Tests in Lib/test/test_bdb.py und führe anschließend die fokussierte bdb-Testsuite aus. Erledigt ist die Aufgabe, wenn das Löschen einer Datei jeden Breakpoint entfernt, einschließlich mehrerer Breakpoints in derselben Zeile, ohne verwaiste Einträge.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- python
- Bereich
- devtools
- Issue-Typ
- Bug
- Schwierigkeit
- 2/5
- Geschätzter Aufwand
- 1-3 Stunden
- Aktivitätsstatus
- Veraltet
- Klarheit
- Klar beschrieben
- Anfängerfreundlichkeit
- 35/100