From 7196fae0962242b73aa10232faafbf4e76076fe0 Mon Sep 17 00:00:00 2001 From: Beomsoo Kim Date: Fri, 29 Nov 2024 18:23:56 +0900 Subject: [PATCH 1/8] Make effective a method of Bdb --- Lib/bdb.py | 101 ++++++++++++++++++++++++++--------------------------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 81bba8a130f97c..5e7bd798d2e54c 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -243,6 +243,56 @@ def stop_here(self, frame): return True return False + def effective(self, file, line, frame): + """Return (active breakpoint, delete temporary flag) or (None, None) as + breakpoint to act upon. + + The "active breakpoint" is the first entry in bplist[line, file] (which + must exist) that is enabled, for which checkfuncname is True, and that + has neither a False condition nor a positive ignore count. The flag, + meaning that a temporary breakpoint should be deleted, is False only + when the condiion cannot be evaluated (in which case, ignore count is + ignored). + + If no such entry exists, then (None, None) is returned. + """ + possibles = self.breaks[file][line] + for b in possibles: + if not b.enabled: + continue + if not checkfuncname(b, frame): + continue + # Count every hit when bp is enabled + b.hits += 1 + if not b.cond: + # If unconditional, and ignoring go on to next, else break + if b.ignore > 0: + b.ignore -= 1 + continue + else: + # breakpoint and marker that it's ok to delete if temporary + return (b, True) + else: + # Conditional bp. + # Ignore count applies only to those bpt hits where the + # condition evaluates to true. + try: + val = eval(b.cond, frame.f_globals, frame.f_locals) + if val: + if b.ignore > 0: + b.ignore -= 1 + # continue + else: + return (b, True) + # else: + # continue + except: + # if eval fails, most conservative thing is to stop on + # breakpoint regardless of ignore count. Don't delete + # temporary, as another hint to user. + return (b, False) + return (None, None) + def break_here(self, frame): """Return True if there is an effective breakpoint for this line. @@ -890,57 +940,6 @@ def checkfuncname(b, frame): return True -def effective(file, line, frame): - """Return (active breakpoint, delete temporary flag) or (None, None) as - breakpoint to act upon. - - The "active breakpoint" is the first entry in bplist[line, file] (which - must exist) that is enabled, for which checkfuncname is True, and that - has neither a False condition nor a positive ignore count. The flag, - meaning that a temporary breakpoint should be deleted, is False only - when the condiion cannot be evaluated (in which case, ignore count is - ignored). - - If no such entry exists, then (None, None) is returned. - """ - possibles = Breakpoint.bplist[file, line] - for b in possibles: - if not b.enabled: - continue - if not checkfuncname(b, frame): - continue - # Count every hit when bp is enabled - b.hits += 1 - if not b.cond: - # If unconditional, and ignoring go on to next, else break - if b.ignore > 0: - b.ignore -= 1 - continue - else: - # breakpoint and marker that it's ok to delete if temporary - return (b, True) - else: - # Conditional bp. - # Ignore count applies only to those bpt hits where the - # condition evaluates to true. - try: - val = eval(b.cond, frame.f_globals, frame.f_locals) - if val: - if b.ignore > 0: - b.ignore -= 1 - # continue - else: - return (b, True) - # else: - # continue - except: - # if eval fails, most conservative thing is to stop on - # breakpoint regardless of ignore count. Don't delete - # temporary, as another hint to user. - return (b, False) - return (None, None) - - # -------------------- testing -------------------- class Tdb(Bdb): From cf642a46ac4a73251e8094da252c4e54545a6a6f Mon Sep 17 00:00:00 2001 From: Beomsoo Kim Date: Fri, 29 Nov 2024 18:46:50 +0900 Subject: [PATCH 2/8] Remove Breakpoint class states and add corresponding instance states in Bdb. Remove prune method as pruning is not needed as the breakpoints are not maintined in the Breakpoint class and only maintained in Bdb instances --- Lib/bdb.py | 138 +++++++++++++++++++++++------------------------------ 1 file changed, 60 insertions(+), 78 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 5e7bd798d2e54c..962f377c0bfa9f 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -31,7 +31,11 @@ class Bdb: def __init__(self, skip=None): self.skip = set(skip) if skip else None - self.breaks = {} + + self.next_bp_number = 1 + self.breaks = {} # self.breaks[file][line] -> list of breakpoints + self.bpbynumber = [None] # Each entry of bpbynumber is None or an + # instance of Breakpoint. index 0 is unused. self.fncache = {} self.frame_trace_lines_opcodes = {} self.frame_returning = None @@ -39,8 +43,6 @@ def __init__(self, skip=None): self.enterframe = None self.code_linenos = weakref.WeakKeyDictionary() - self._load_breaks() - def canonic(self, filename): """Return canonical form of filename. @@ -310,8 +312,8 @@ def break_here(self, frame): if lineno not in self.breaks[filename]: return False - # flag says ok to delete temp. bp - (bp, flag) = effective(filename, lineno, frame) + # flag says if it's ok to delete temporary bp + (bp, flag) = self.effective(filename, lineno, frame) if bp: self.currentbp = bp.number if (flag and bp.temporary): @@ -484,18 +486,43 @@ def set_quit(self): self.quitting = True sys.settrace(None) + def _add_bp(self, bp, filename, lineno): + """Add breakpoint to breaks and bpbynumber if not already there. + """ + bp_linenos = self.breaks.setdefault(filename, {}) + blist = bp_linenos.setdefault(lineno, []) + + if bp in blist: + return + + blist.append(bp) + self.bpbynumber.append(bp) + self.next_bp_number += 1 + + def _remove_bp(self, bp): + assert ( + bp in self.bpbynumber + and bp.file in self.breaks + and bp.line in self.breaks[bp.file] + and bp in self.breaks[bp.file][bp.line] + ), f"Breakpoint {bp} not in breaks" + + self.bpbynumber[bp.number] = None # No longer in list + self.breaks[bp.file][bp.line].remove(bp) + + if not self.breaks[bp.file][bp.line]: + # No more bp for this f:l combo + del self.breaks[bp.file][bp.line] + + if not self.breaks[bp.file]: + del self.breaks[bp.file] + # Derived classes and clients can call the following methods # to manipulate breakpoints. These methods return an # error message if something went wrong, None if all is well. # Set_break prints out the breakpoint line and file:lineno. # Call self.get_*break*() to see the breakpoints or better - # for bp in Breakpoint.bpbynumber: if bp: bp.bpprint(). - - def _add_to_breaks(self, filename, lineno): - """Add breakpoint to breaks, if not already there.""" - bp_linenos = self.breaks.setdefault(filename, []) - if lineno not in bp_linenos: - bp_linenos.append(lineno) + # for bp in self.bpbynumber: if bp: bp.bpprint(). def set_break(self, filename, lineno, temporary=False, cond=None, funcname=None): @@ -509,8 +536,10 @@ def set_break(self, filename, lineno, temporary=False, cond=None, line = linecache.getline(filename, lineno) if not line: return 'Line %s:%d does not exist' % (filename, lineno) - self._add_to_breaks(filename, lineno) - bp = Breakpoint(filename, lineno, temporary, cond, funcname) + + bp = Breakpoint(filename, lineno, self.next_bp_number, temporary, cond, funcname) + self._add_bp(bp, filename, lineno) + # After we set a new breakpoint, we need to search through all frames # and set f_trace to trace_dispatch if there could be a breakpoint in # that frame. @@ -521,30 +550,6 @@ def set_break(self, filename, lineno, temporary=False, cond=None, frame = frame.f_back return None - def _load_breaks(self): - """Apply all breakpoints (set in other instances) to this one. - - Populates this instance's breaks list from the Breakpoint class's - list, which can have breakpoints set by another Bdb instance. This - is necessary for interactive sessions to keep the breakpoints - active across multiple calls to run(). - """ - for (filename, lineno) in Breakpoint.bplist.keys(): - self._add_to_breaks(filename, lineno) - - def _prune_breaks(self, filename, lineno): - """Prune breakpoints for filename:lineno. - - A list of breakpoints is maintained in the Bdb instance and in - the Breakpoint class. If a breakpoint in the Bdb instance no - longer exists in the Breakpoint class, then it's removed from the - Bdb instance. - """ - if (filename, lineno) not in Breakpoint.bplist: - self.breaks[filename].remove(lineno) - if not self.breaks[filename]: - del self.breaks[filename] - def clear_break(self, filename, lineno): """Delete breakpoints for filename:lineno. @@ -557,13 +562,12 @@ def clear_break(self, filename, lineno): return 'There is no breakpoint at %s:%d' % (filename, lineno) # If there's only one bp in the list for that file,line # pair, then remove the breaks entry - for bp in Breakpoint.bplist[filename, lineno][:]: - bp.deleteMe() - self._prune_breaks(filename, lineno) + for bp in self.breaks[filename][lineno][:]: + self._remove_bp(bp) return None def clear_bpbynumber(self, arg): - """Delete a breakpoint by its index in Breakpoint.bpbynumber. + """Delete a breakpoint by its index in self.bpbynumber. If arg is invalid, return an error message. """ @@ -571,8 +575,7 @@ def clear_bpbynumber(self, arg): bp = self.get_bpbynumber(arg) except ValueError as err: return str(err) - bp.deleteMe() - self._prune_breaks(bp.file, bp.line) + self._remove_bp(bp) return None def clear_all_file_breaks(self, filename): @@ -584,9 +587,9 @@ def clear_all_file_breaks(self, filename): 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] + blist = self.breaks[filename][line] for bp in blist: - bp.deleteMe() + self._remove_bp(bp) del self.breaks[filename] return None @@ -597,14 +600,14 @@ def clear_all_breaks(self): """ if not self.breaks: return 'There are no breakpoints' - for bp in Breakpoint.bpbynumber: + for bp in self.bpbynumber: if bp: - bp.deleteMe() + self._remove_bp(bp) self.breaks = {} return None def get_bpbynumber(self, arg): - """Return a breakpoint by its index in Breakpoint.bybpnumber. + """Return a breakpoint by its index in self.bybynumber. For invalid arg values or if the breakpoint doesn't exist, raise a ValueError. @@ -616,7 +619,7 @@ def get_bpbynumber(self, arg): except ValueError: raise ValueError('Non-numeric breakpoint number %s' % arg) from None try: - bp = Breakpoint.bpbynumber[number] + bp = self.bpbynumber[number] except IndexError: raise ValueError('Breakpoint number %d out of range' % number) from None if bp is None: @@ -627,7 +630,8 @@ def get_break(self, filename, lineno): """Return True if there is a breakpoint for filename:lineno.""" filename = self.canonic(filename) return filename in self.breaks and \ - lineno in self.breaks[filename] + lineno in self.breaks[filename] and \ + bool(self.breaks[filename][lineno]) def get_breaks(self, filename, lineno): """Return all breakpoints for filename:lineno. @@ -637,7 +641,7 @@ def get_breaks(self, filename, lineno): filename = self.canonic(filename) return filename in self.breaks and \ lineno in self.breaks[filename] and \ - Breakpoint.bplist[filename, lineno] or [] + self.breaks[filename][lineno] or [] def get_file_breaks(self, filename): """Return all lines with breakpoints for filename. @@ -646,13 +650,13 @@ def get_file_breaks(self, filename): """ filename = self.canonic(filename) if filename in self.breaks: - return self.breaks[filename] + return list(self.breaks[filename].keys()) else: return [] def get_all_breaks(self): """Return all breakpoints that are set.""" - return self.breaks + return {f: list(self.breaks[f].keys()) for f in self.breaks} # Derived classes and clients can call the following method # to get a data structure representing a stack trace. @@ -804,16 +808,7 @@ class Breakpoint: conditional breakpoint always counts a hit. """ - # XXX Keeping state in the class is a mistake -- this means - # you cannot have more than one active Bdb instance. - - next = 1 # Next bp to be assigned - bplist = {} # indexed by (file, lineno) tuple - bpbynumber = [None] # Each entry is None or an instance of Bpt - # index 0 is unused, except for marking an - # effective break .... see effective() - - def __init__(self, file, line, temporary=False, cond=None, funcname=None): + def __init__(self, file, line, number, temporary=False, cond=None, funcname=None): self.funcname = funcname # Needed if funcname is not None. self.func_first_executable_line = None @@ -824,20 +819,7 @@ def __init__(self, file, line, temporary=False, cond=None, funcname=None): self.enabled = True self.ignore = 0 self.hits = 0 - self.number = Breakpoint.next - Breakpoint.next += 1 - # Build the two lists - self.bpbynumber.append(self) - if (file, line) in self.bplist: - self.bplist[file, line].append(self) - else: - self.bplist[file, line] = [self] - - @staticmethod - def clearBreakpoints(): - Breakpoint.next = 1 - Breakpoint.bplist = {} - Breakpoint.bpbynumber = [None] + self.number = number def deleteMe(self): """Delete the breakpoint from the list associated to a file:line. From f02a237cb6acfed886796542860eeb18f4f2881f Mon Sep 17 00:00:00 2001 From: Beomsoo Kim Date: Fri, 29 Nov 2024 18:49:41 +0900 Subject: [PATCH 3/8] Remove an obsolete comment; Refer to https://github.com/python/cpython/commit/6ea27cc2c6659b040a16c829fa51a66eefc23925 --- Lib/bdb.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 962f377c0bfa9f..e44b3880ab5a71 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -560,8 +560,7 @@ def clear_break(self, filename, lineno): return 'There are no breakpoints in %s' % filename if lineno not in self.breaks[filename]: return 'There is no breakpoint at %s:%d' % (filename, lineno) - # If there's only one bp in the list for that file,line - # pair, then remove the breaks entry + for bp in self.breaks[filename][lineno][:]: self._remove_bp(bp) return None From 3ba0c75ff8582f90aa4d7e2f9453eed30ce8ef0c Mon Sep 17 00:00:00 2001 From: Beomsoo Kim Date: Fri, 29 Nov 2024 18:54:22 +0900 Subject: [PATCH 4/8] Remove unncessary reset_Breakpoint and simplify tests --- Lib/test/test_bdb.py | 29 +++++++++++------------------ Lib/test/test_pdb.py | 41 ++++++----------------------------------- 2 files changed, 17 insertions(+), 53 deletions(-) diff --git a/Lib/test/test_bdb.py b/Lib/test/test_bdb.py index f15dae13eb384e..e26d35dc014424 100644 --- a/Lib/test/test_bdb.py +++ b/Lib/test/test_bdb.py @@ -73,9 +73,6 @@ class BdbNotExpectedError(BdbException): """Unexpected result.""" # after each 'line' event where a breakpoint has been hit. dry_run = 0 -def reset_Breakpoint(): - _bdb.Breakpoint.clearBreakpoints() - def info_breakpoints(): bp_list = [bp for bp in _bdb.Breakpoint.bpbynumber if bp] if not bp_list: @@ -426,12 +423,10 @@ def __init__(self, test_case, skip=None): def __enter__(self): # test_pdb does not reset Breakpoint class attributes on exit :-( - reset_Breakpoint() self._original_tracer = sys.gettrace() return self.tracer def __exit__(self, type_=None, value=None, traceback=None): - reset_Breakpoint() sys.settrace(self._original_tracer) not_empty = '' @@ -973,7 +968,6 @@ def test_clear_at_no_bp(self): self.assertRaises(BdbError, tracer.runcall, tfunc_import) def test_load_bps_from_previous_Bdb_instance(self): - reset_Breakpoint() db1 = Bdb() fname = db1.canonic(__file__) db1.set_break(__file__, 1) @@ -984,35 +978,34 @@ def test_load_bps_from_previous_Bdb_instance(self): db2.set_break(__file__, 3) db2.set_break(__file__, 4) self.assertEqual(db1.get_all_breaks(), {fname: [1]}) - self.assertEqual(db2.get_all_breaks(), {fname: [1, 2, 3, 4]}) - db2.clear_break(__file__, 1) + self.assertEqual(db2.get_all_breaks(), {fname: [2, 3, 4]}) self.assertEqual(db1.get_all_breaks(), {fname: [1]}) self.assertEqual(db2.get_all_breaks(), {fname: [2, 3, 4]}) db3 = Bdb() + db3.set_break(__file__, 5) self.assertEqual(db1.get_all_breaks(), {fname: [1]}) self.assertEqual(db2.get_all_breaks(), {fname: [2, 3, 4]}) - self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]}) + self.assertEqual(db3.get_all_breaks(), {fname: [5]}) db2.clear_break(__file__, 2) self.assertEqual(db1.get_all_breaks(), {fname: [1]}) self.assertEqual(db2.get_all_breaks(), {fname: [3, 4]}) - self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]}) + self.assertEqual(db3.get_all_breaks(), {fname: [5]}) db4 = Bdb() - db4.set_break(__file__, 5) + db4.set_break(__file__, 6) self.assertEqual(db1.get_all_breaks(), {fname: [1]}) self.assertEqual(db2.get_all_breaks(), {fname: [3, 4]}) - self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]}) - self.assertEqual(db4.get_all_breaks(), {fname: [3, 4, 5]}) - reset_Breakpoint() + self.assertEqual(db3.get_all_breaks(), {fname: [5]}) + self.assertEqual(db4.get_all_breaks(), {fname: [6]}) db5 = Bdb() - db5.set_break(__file__, 6) + db5.set_break(__file__, 7) self.assertEqual(db1.get_all_breaks(), {fname: [1]}) self.assertEqual(db2.get_all_breaks(), {fname: [3, 4]}) - self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]}) - self.assertEqual(db4.get_all_breaks(), {fname: [3, 4, 5]}) - self.assertEqual(db5.get_all_breaks(), {fname: [6]}) + self.assertEqual(db3.get_all_breaks(), {fname: [5]}) + self.assertEqual(db4.get_all_breaks(), {fname: [6]}) + self.assertEqual(db5.get_all_breaks(), {fname: [7]}) class RunTestCase(BaseTestCase): diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index e5f9848319021a..81e8b40c0b8bac 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -222,10 +222,6 @@ def test_pdb_basic_commands(): BAZ """ -def reset_Breakpoint(): - import bdb - bdb.Breakpoint.clearBreakpoints() - def test_pdb_breakpoint_commands(): """Test basic commands related to breakpoints. @@ -236,11 +232,6 @@ def test_pdb_breakpoint_commands(): ... print(3) ... print(4) - First, need to clear bdb state that might be left over from previous tests. - Otherwise, the new breakpoints might get assigned different numbers. - - >>> reset_Breakpoint() - Now test the breakpoint commands. NORMALIZE_WHITESPACE is needed because the breakpoint list outputs a tab for the "stop only" and "ignore next" lines, which we don't want to put in here. @@ -375,8 +366,6 @@ def test_pdb_breakpoint_on_annotated_function_def(): >>> def foobar[T]() -> int: ... return 0 - >>> reset_Breakpoint() - >>> def test_function(): ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() ... pass @@ -388,7 +377,7 @@ def test_pdb_breakpoint_on_annotated_function_def(): ... 'continue', ... ]): ... test_function() - > (2)test_function() + > (2)test_function() -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() (Pdb) break foo Breakpoint 1 at :2 @@ -408,8 +397,6 @@ def test_pdb_commands(): ... print(2) ... print(3) - >>> reset_Breakpoint() - >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE ... 'b 3', ... 'commands', @@ -457,12 +444,6 @@ def test_pdb_breakpoint_with_filename(): ... mod2.func88() ... mod2.func114() ... # Be a good citizen and clean up the mess - ... reset_Breakpoint() - - First, need to clear bdb state that might be left over from previous tests. - Otherwise, the new breakpoints might get assigned different numbers. - - >>> reset_Breakpoint() >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS ... 'break test.test_inspect.inspect_fodder2:90', @@ -490,7 +471,7 @@ def test_pdb_breakpoint_with_filename(): def test_pdb_breakpoints_preserved_across_interactive_sessions(): """Breakpoints are remembered between interactive sessions - >>> reset_Breakpoint() + >>> pdb_instance = pdb.Pdb() >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE ... 'import test.test_pdb', ... 'break test.test_pdb.do_something', @@ -498,7 +479,7 @@ def test_pdb_breakpoints_preserved_across_interactive_sessions(): ... 'break', ... 'continue', ... ]): - ... pdb.run('print()') + ... pdb_instance.run('print()') > (1)()... (Pdb) import test.test_pdb (Pdb) break test.test_pdb.do_something @@ -518,7 +499,7 @@ def test_pdb_breakpoints_preserved_across_interactive_sessions(): ... 'clear 1', ... 'continue', ... ]): - ... pdb.run('print()') + ... pdb_instance.run('print()') > (1)()... (Pdb) break Num Type Disp Enb Where @@ -541,7 +522,7 @@ def test_pdb_breakpoints_preserved_across_interactive_sessions(): ... 'clear 3', ... 'continue', ... ]): - ... pdb.run('print()') + ... pdb_instance.run('print()') > (1)()... (Pdb) break Num Type Disp Enb Where @@ -575,7 +556,6 @@ def test_pdb_break_anywhere(): >>> def test_function(): ... caller() - >>> reset_Breakpoint() >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE ... 'b 3', ... 'c', @@ -1689,7 +1669,6 @@ def test_pdb_return_to_different_file(): ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() ... pprint.pprint(A()) - >>> reset_Breakpoint() >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE ... 'b A.__repr__', ... 'continue', @@ -1936,7 +1915,6 @@ def test_next_until_return_at_return_event(): ... test_function_2() ... end = 1 - >>> reset_Breakpoint() >>> with PdbTestInput(['break test_function_2', ... 'continue', ... 'return', @@ -2394,7 +2372,6 @@ def test_pdb_next_command_in_generator_for_loop(): ... print('value', i) ... x = 123 - >>> reset_Breakpoint() >>> with PdbTestInput(['break test_gen', ... 'continue', ... 'next', @@ -2665,7 +2642,6 @@ def test_pdb_issue_20766(): ... print('pdb %d: %s' % (i, sess._previous_sigint_handler)) ... i += 1 - >>> reset_Breakpoint() >>> with PdbTestInput(['continue', ... 'continue']): ... test_function() @@ -2688,7 +2664,6 @@ def test_pdb_issue_43318(): ... print(2) ... print(3) ... print(4) - >>> reset_Breakpoint() >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE ... 'break 3', ... 'clear :3', @@ -2725,7 +2700,6 @@ def test_pdb_issue_gh_91742(): ... about() - >>> reset_Breakpoint() >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE ... 'step', ... 'step', @@ -2772,7 +2746,6 @@ def test_pdb_issue_gh_94215(): ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() ... func() - >>> reset_Breakpoint() >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE ... 'step', ... 'step', @@ -2987,8 +2960,6 @@ def test_pdb_f_trace_lines(): pdb should work even if f_trace_lines is set to False on some frames. - >>> reset_Breakpoint() - >>> def test_function(): ... import sys ... frame = sys._getframe() @@ -3001,7 +2972,7 @@ def test_pdb_f_trace_lines(): ... 'continue' ... ]): ... test_function() - > (5)test_function() + > (5)test_function() -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() (Pdb) continue """ From fd68d879396513d0e35e98960e545d463cdfe605 Mon Sep 17 00:00:00 2001 From: Beomsoo Kim Date: Fri, 29 Nov 2024 21:29:20 +0900 Subject: [PATCH 5/8] Remove deleteMe --- Lib/bdb.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index e44b3880ab5a71..7b0c1407071e40 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -820,20 +820,6 @@ def __init__(self, file, line, number, temporary=False, cond=None, funcname=None self.hits = 0 self.number = number - def deleteMe(self): - """Delete the breakpoint from the list associated to a file:line. - - If it is the last breakpoint in that position, it also deletes - the entry for the file:line. - """ - - index = (self.file, self.line) - self.bpbynumber[self.number] = None # No longer in list - self.bplist[index].remove(self) - if not self.bplist[index]: - # No more bp for this f:l combo - del self.bplist[index] - def enable(self): """Mark the breakpoint as enabled.""" self.enabled = True From e7b5db5fc82dea49b9a7123716bc038832efc581 Mon Sep 17 00:00:00 2001 From: Beomsoo Kim Date: Fri, 29 Nov 2024 21:30:04 +0900 Subject: [PATCH 6/8] Move a part of comment to a more appropriate place --- Lib/bdb.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 7b0c1407071e40..285480076e9394 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -27,6 +27,12 @@ class Bdb: that originate in a module that matches one of these patterns. Whether a frame is considered to originate in a certain module is determined by the __name__ in the frame globals. + + Each Bdb instance holds breakpoints. Breakpoints are indexed by + number through self.bpbynumber and by (file, line) using + self.breaks. The former maps to a single instance of the Breakpoint + class, while the latter maps to a list of such instances, as + there may be more than one breakpoint per line. """ def __init__(self, skip=None): @@ -795,12 +801,6 @@ class Breakpoint: Implements temporary breakpoints, ignore counts, disabling and (re)-enabling, and conditionals. - Breakpoints are indexed by number through bpbynumber and by - the (file, line) tuple using bplist. The former points to a - single instance of class Breakpoint. The latter points to a - list of such instances since there may be more than one - breakpoint per line. - When creating a breakpoint, its associated filename should be in canonical form. If funcname is defined, a breakpoint hit will be counted when the first line of that function is executed. A From 87755e25329dbfa44924427db211ddd8cd1ba548 Mon Sep 17 00:00:00 2001 From: Beomsoo Kim Date: Fri, 29 Nov 2024 21:31:57 +0900 Subject: [PATCH 7/8] Refactor pdb to be compatible with the new bdb logic --- Lib/pdb.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index b7f6fd4323407e..fa9081e361902d 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -946,7 +946,7 @@ def _complete_bpnumber(self, text, line, begidx, endidx): # Complete a breakpoint number. (This would be more helpful if we could # display additional info along with the completions, such as file/line # of the breakpoint.) - return [str(i) for i, bp in enumerate(bdb.Breakpoint.bpbynumber) + return [str(i) for i, bp in enumerate(self.bpbynumber) if bp is not None and str(i).startswith(text)] def _complete_expression(self, text, line, begidx, endidx): @@ -1049,7 +1049,7 @@ def do_commands(self, arg): reached. """ if not arg: - bnum = len(bdb.Breakpoint.bpbynumber) - 1 + bnum = len(self.bpbynumber) - 1 else: try: bnum = int(arg) @@ -1107,7 +1107,7 @@ def do_break(self, arg, temporary=False): if not arg: if self.breaks: # There's at least one self.message("Num Type Disp Enb Where") - for bp in bdb.Breakpoint.bpbynumber: + for bp in self.bpbynumber: if bp: self.message(bp.bpformat()) return @@ -1391,7 +1391,7 @@ def do_clear(self, arg): reply = 'no' reply = reply.strip().lower() if reply in ('y', 'yes'): - bplist = [bp for bp in bdb.Breakpoint.bpbynumber if bp] + bplist = [bp for bp in self.bpbynumber if bp] self.clear_all_breaks() for bp in bplist: self.message('Deleted %s' % bp) From 24e71b76241b2d0615de0cbdca63c78a83f66b61 Mon Sep 17 00:00:00 2001 From: Beomsoo Kim Date: Fri, 29 Nov 2024 22:24:01 +0900 Subject: [PATCH 8/8] Update bdb.rst --- Doc/library/bdb.rst | 63 ++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/Doc/library/bdb.rst b/Doc/library/bdb.rst index 85df7914a9a014..42817ec456af5d 100644 --- a/Doc/library/bdb.rst +++ b/Doc/library/bdb.rst @@ -20,16 +20,11 @@ The following exception is defined: The :mod:`bdb` module also defines two classes: -.. class:: Breakpoint(self, file, line, temporary=False, cond=None, funcname=None) +.. class:: Breakpoint(file, line, number, temporary=False, cond=None, funcname=None) This class implements temporary breakpoints, ignore counts, disabling and (re-)enabling, and conditionals. - Breakpoints are indexed by number through a list called :attr:`bpbynumber` - and by ``(file, line)`` pairs through :attr:`bplist`. The former points to - a single instance of class :class:`Breakpoint`. The latter points to a list - of such instances since there may be more than one breakpoint per line. - When creating a breakpoint, its associated :attr:`file name ` should be in canonical form. If a :attr:`funcname` is defined, a breakpoint :attr:`hit ` will be counted when the first line of that function is @@ -38,12 +33,6 @@ The :mod:`bdb` module also defines two classes: :class:`Breakpoint` instances have the following methods: - .. method:: deleteMe() - - Delete the breakpoint from the list associated to a file/line. If it is - the last breakpoint in that position, it also deletes the entry for the - file/line. - .. method:: enable() @@ -101,15 +90,6 @@ The :mod:`bdb` module also defines two classes: ``True`` if :class:`Breakpoint` is enabled. - .. attribute:: bpbynumber - - Numeric index for a single instance of a :class:`Breakpoint`. - - .. attribute:: bplist - - Dictionary of :class:`Breakpoint` instances indexed by - (:attr:`file`, :attr:`line`) tuples. - .. attribute:: ignore Number of times to ignore a :class:`Breakpoint`. @@ -221,6 +201,25 @@ The :mod:`bdb` module also defines two classes: Return ``True`` if *frame* is below the starting frame in the stack. + .. method:: effective(file, line, frame) + + Return ``(active breakpoint, delete temporary flag)`` or ``(None, None)`` as the + breakpoint to act upon. + + The *active breakpoint* is the first entry in `self.breaks[file][line]` + (which must exist) that is :attr:`enabled `, for + which :func:`checkfuncname` is true, and that has neither a false + :attr:`condition ` nor positive + :attr:`ignore ` count. The *flag*, meaning that a + temporary breakpoint should be deleted, is ``False`` only when the + :attr:`cond ` cannot be evaluated (in which case, + :attr:`ignore ` count is ignored). + + If no such entry exists, then ``(None, None)`` is returned. + + .. versionchanged:: 3.14 + ``effective`` was originally a function, but has been converted to a method. + .. method:: break_here(frame) Return ``True`` if there is an effective breakpoint for this line. @@ -323,8 +322,8 @@ The :mod:`bdb` module also defines two classes: .. method:: clear_bpbynumber(arg) - Delete the breakpoint which has the index *arg* in the - :attr:`Breakpoint.bpbynumber`. If *arg* is not numeric or out of range, + Delete the breakpoint which has the index *arg*. + If *arg* is not numeric or out of range, return an error message. .. method:: clear_all_file_breaks(filename) @@ -422,24 +421,6 @@ Finally, the module defines the following functions: the right *frame* (the right function) and if we are on its first executable line. -.. function:: effective(file, line, frame) - - Return ``(active breakpoint, delete temporary flag)`` or ``(None, None)`` as the - breakpoint to act upon. - - The *active breakpoint* is the first entry in - :attr:`bplist ` for the - (:attr:`file `, :attr:`line `) - (which must exist) that is :attr:`enabled `, for - which :func:`checkfuncname` is true, and that has neither a false - :attr:`condition ` nor positive - :attr:`ignore ` count. The *flag*, meaning that a - temporary breakpoint should be deleted, is ``False`` only when the - :attr:`cond ` cannot be evaluated (in which case, - :attr:`ignore ` count is ignored). - - If no such entry exists, then ``(None, None)`` is returned. - .. function:: set_trace()