From 4a9faa1ea077c0cf054d0d2ee1dc889b2972a63b Mon Sep 17 00:00:00 2001 From: Johannes Gijsbers Date: Mon, 30 Aug 2004 13:29:44 +0000 Subject: [PATCH] Patch #1003640: replace checkline() function parsing with new breakpoint logic: 1) When a breakpoint is set via a function name: - the breakpoint gets the lineno of the def statement - a new funcname attribute is attached to the breakpoint 2) bdb.effective() calls new function checkfuncname() to handle: - def statement is executed: don't break. - a first executable line of a function with a breakpoint on the lineno of the def statement is reached: break. This fixes bugs 976878, 926369 and 875404. Thanks Ilya Sandler. --- Lib/bdb.py | 45 +++++++++++++++++++++++++++++++++++++++++---- Lib/pdb.py | 51 ++++++++++----------------------------------------- 2 files changed, 51 insertions(+), 45 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 19a97227231..f5550781b36 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -113,7 +113,12 @@ def break_here(self, frame): return False lineno = frame.f_lineno if not lineno in self.breaks[filename]: - return False + # The line itself has no breakpoint, but maybe the line is the + # first line of a function with breakpoint set by function name. + lineno = frame.f_code.co_firstlineno + if not lineno in self.breaks[filename]: + return False + # flag says ok to delete temp. bp (bp, flag) = effective(filename, lineno, frame) if bp: @@ -210,7 +215,8 @@ def set_quit(self): # Call self.get_*break*() to see the breakpoints or better # for bp in Breakpoint.bpbynumber: if bp: bp.bpprint(). - def set_break(self, filename, lineno, temporary=0, cond = None): + def set_break(self, filename, lineno, temporary=0, cond = None, + funcname=None): filename = self.canonic(filename) import linecache # Import as late as possible line = linecache.getline(filename, lineno) @@ -222,7 +228,7 @@ def set_break(self, filename, lineno, temporary=0, cond = None): list = self.breaks[filename] if not lineno in list: list.append(lineno) - bp = Breakpoint(filename, lineno, temporary, cond) + bp = Breakpoint(filename, lineno, temporary, cond, funcname) def clear_break(self, filename, lineno): filename = self.canonic(filename) @@ -428,7 +434,10 @@ class Breakpoint: # index 0 is unused, except for marking an # effective break .... see effective() - def __init__(self, file, line, temporary=0, cond = None): + def __init__(self, file, line, temporary=0, cond=None, funcname=None): + self.funcname = funcname + # Needed if funcname is not None. + self.func_first_executable_line = None self.file = file # This better be in canonical form! self.line = line self.temporary = temporary @@ -483,6 +492,32 @@ def bpprint(self): # -----------end of Breakpoint class---------- +def checkfuncname(b, frame): + """Check whether we should break here because of `b.funcname`.""" + if not b.funcname: + # Breakpoint was set via line number. + if b.line != frame.f_lineno: + # Breakpoint was set at a line with a def statement and the function + # defined is called: don't break. + return False + return True + + # Breakpoint set via function name. + + if frame.f_code.co_name != b.funcname: + # It's not a function call, but rather execution of def statement. + return False + + # We are in the right frame. + if not b.func_first_executable_line: + # The function is entered for the 1st time. + b.func_first_executable_line = frame.f_lineno + + if b.func_first_executable_line != frame.f_lineno: + # But we are not at the first line number: don't break. + return False + return True + # Determines if there is an effective (active) breakpoint at this # line of code. Returns breakpoint number or 0 if none def effective(file, line, frame): @@ -498,6 +533,8 @@ def effective(file, line, frame): b = possibles[i] if b.enabled == 0: continue + if not checkfuncname(b, frame): + continue # Count every hit when bp is enabled b.hits = b.hits + 1 if not b.cond: diff --git a/Lib/pdb.py b/Lib/pdb.py index 58de097cfff..c7ec9c4e535 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -215,6 +215,7 @@ def do_break(self, arg, temporary = 0): arg = arg[:comma].rstrip() # parse stuff before comma: [filename:]lineno | function colon = arg.rfind(':') + funcname = None if colon >= 0: filename = arg[:colon].rstrip() f = self.lookupmodule(filename) @@ -245,6 +246,9 @@ def do_break(self, arg, temporary = 0): if hasattr(func, 'im_func'): func = func.im_func code = func.func_code + #use co_name to identify the bkpt (function names + #could be aliased, but co_name is invariant) + funcname = code.co_name lineno = code.co_firstlineno filename = code.co_filename except: @@ -257,6 +261,7 @@ def do_break(self, arg, temporary = 0): print ('or was not found ' 'along sys.path.') return + funcname = ok # ok contains a function name lineno = int(ln) if not filename: filename = self.defaultFile() @@ -264,7 +269,7 @@ def do_break(self, arg, temporary = 0): line = self.checkline(filename, lineno) if line: # now set the break point - err = self.set_break(filename, line, temporary, cond) + err = self.set_break(filename, line, temporary, cond, funcname) if err: print '***', err else: bp = self.get_breaks(filename, line)[-1] @@ -319,13 +324,11 @@ def lineinfo(self, identifier): return answer or failed def checkline(self, filename, lineno): - """Return line number of first line at or after input - argument such that if the input points to a 'def', the - returned line number is the first - non-blank/non-comment line to follow. If the input - points to a blank or comment line, return 0. At end - of file, also return 0.""" + """Check whether specified line seems to be executable. + Return `lineno` if it is, 0 if not (e.g. a docstring, comment, blank + line or EOF). Warning: testing is not comprehensive. + """ line = linecache.getline(filename, lineno) if not line: print 'End of file' @@ -336,40 +339,6 @@ def checkline(self, filename, lineno): (line[:3] == '"""') or line[:3] == "'''"): print '*** Blank or comment' return 0 - # When a file is read in and a breakpoint is at - # the 'def' statement, the system stops there at - # code parse time. We don't want that, so all breakpoints - # set at 'def' statements are moved one line onward - if line[:3] == 'def': - instr = '' - brackets = 0 - while 1: - skipone = 0 - for c in line: - if instr: - if skipone: - skipone = 0 - elif c == '\\': - skipone = 1 - elif c == instr: - instr = '' - elif c == '#': - break - elif c in ('"',"'"): - instr = c - elif c in ('(','{','['): - brackets = brackets + 1 - elif c in (')','}',']'): - brackets = brackets - 1 - lineno = lineno+1 - line = linecache.getline(filename, lineno) - if not line: - print 'end of file' - return 0 - line = line.strip() - if not line: continue # Blank line - if brackets <= 0 and line[0] not in ('#','"',"'"): - break return lineno def do_enable(self, arg):