python / python/cpython

bdb.clear_all_file_breaks skips breakpoints when multiple breakpoints share a line

未關閉
#149,015 2 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

stdlib type-bug
主要語言
Python
星號
77.2k
分支
35.9k
PR 合併指標
PR 指標待擷取

描述

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 main and 3.13 branches.
  • Behavior identical regardless of whether the breakpoints are set with
    conditions, ignore counts, or via different Bdb subclasses.
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

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

從 Lib/bdb.py 中的 Bdb.clear_all_file_breaks() 開始,將其中斷點迭代與 clear_break() 進行比較。檢查 Lib/test/test_bdb.py 中相關的測試,然後執行針對 bdb 的測試套件。完成的標準是清除檔案會移除所有中斷點,包括共用同一行的多個中斷點,且不會留下孤立項目。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
python
領域
devtools
Issue 類型
缺陷
難度
2/5
預估耗時
1-3 小時
活躍度
停滯
描述清晰度
描述清楚
新手友好度
35/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。