python / python/cpython

bdb.clear_all_file_breaks skips breakpoints when multiple breakpoints share a line

Đang mở
#149,015 2 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

stdlib type-bug
Ngôn ngữ chính
Python
Star
77.2k
Fork
35.9k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

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

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu trong Lib/bdb.py tại Bdb.clear_all_file_breaks() và so sánh cách lặp qua breakpoint của nó với clear_break(). Xem lại các test liên quan trong Lib/test/test_bdb.py, sau đó chạy test suite bdb tập trung. Hoàn thành khi việc xóa một file loại bỏ mọi breakpoint, bao gồm nhiều breakpoint dùng chung một dòng, mà không để lại các entry mồ côi.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
devtools
Loại issue
Lỗi
Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
35/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.