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.
This commit is contained in:
Johannes Gijsbers 2004-08-30 13:29:44 +00:00
parent cb7b3f30d6
commit 4a9faa1ea0
2 changed files with 51 additions and 45 deletions

View File

@ -113,7 +113,12 @@ class Bdb:
return False return False
lineno = frame.f_lineno lineno = frame.f_lineno
if not lineno in self.breaks[filename]: 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 # flag says ok to delete temp. bp
(bp, flag) = effective(filename, lineno, frame) (bp, flag) = effective(filename, lineno, frame)
if bp: if bp:
@ -210,7 +215,8 @@ class Bdb:
# Call self.get_*break*() to see the breakpoints or better # Call self.get_*break*() to see the breakpoints or better
# for bp in Breakpoint.bpbynumber: if bp: bp.bpprint(). # 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) filename = self.canonic(filename)
import linecache # Import as late as possible import linecache # Import as late as possible
line = linecache.getline(filename, lineno) line = linecache.getline(filename, lineno)
@ -222,7 +228,7 @@ class Bdb:
list = self.breaks[filename] list = self.breaks[filename]
if not lineno in list: if not lineno in list:
list.append(lineno) list.append(lineno)
bp = Breakpoint(filename, lineno, temporary, cond) bp = Breakpoint(filename, lineno, temporary, cond, funcname)
def clear_break(self, filename, lineno): def clear_break(self, filename, lineno):
filename = self.canonic(filename) filename = self.canonic(filename)
@ -428,7 +434,10 @@ class Breakpoint:
# index 0 is unused, except for marking an # index 0 is unused, except for marking an
# effective break .... see effective() # 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.file = file # This better be in canonical form!
self.line = line self.line = line
self.temporary = temporary self.temporary = temporary
@ -483,6 +492,32 @@ class Breakpoint:
# -----------end of Breakpoint class---------- # -----------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 # Determines if there is an effective (active) breakpoint at this
# line of code. Returns breakpoint number or 0 if none # line of code. Returns breakpoint number or 0 if none
def effective(file, line, frame): def effective(file, line, frame):
@ -498,6 +533,8 @@ def effective(file, line, frame):
b = possibles[i] b = possibles[i]
if b.enabled == 0: if b.enabled == 0:
continue continue
if not checkfuncname(b, frame):
continue
# Count every hit when bp is enabled # Count every hit when bp is enabled
b.hits = b.hits + 1 b.hits = b.hits + 1
if not b.cond: if not b.cond:

View File

@ -215,6 +215,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
arg = arg[:comma].rstrip() arg = arg[:comma].rstrip()
# parse stuff before comma: [filename:]lineno | function # parse stuff before comma: [filename:]lineno | function
colon = arg.rfind(':') colon = arg.rfind(':')
funcname = None
if colon >= 0: if colon >= 0:
filename = arg[:colon].rstrip() filename = arg[:colon].rstrip()
f = self.lookupmodule(filename) f = self.lookupmodule(filename)
@ -245,6 +246,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
if hasattr(func, 'im_func'): if hasattr(func, 'im_func'):
func = func.im_func func = func.im_func
code = func.func_code 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 lineno = code.co_firstlineno
filename = code.co_filename filename = code.co_filename
except: except:
@ -257,6 +261,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
print ('or was not found ' print ('or was not found '
'along sys.path.') 'along sys.path.')
return return
funcname = ok # ok contains a function name
lineno = int(ln) lineno = int(ln)
if not filename: if not filename:
filename = self.defaultFile() filename = self.defaultFile()
@ -264,7 +269,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
line = self.checkline(filename, lineno) line = self.checkline(filename, lineno)
if line: if line:
# now set the break point # 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 if err: print '***', err
else: else:
bp = self.get_breaks(filename, line)[-1] bp = self.get_breaks(filename, line)[-1]
@ -319,13 +324,11 @@ class Pdb(bdb.Bdb, cmd.Cmd):
return answer or failed return answer or failed
def checkline(self, filename, lineno): def checkline(self, filename, lineno):
"""Return line number of first line at or after input """Check whether specified line seems to be executable.
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."""
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) line = linecache.getline(filename, lineno)
if not line: if not line:
print 'End of file' print 'End of file'
@ -336,40 +339,6 @@ class Pdb(bdb.Bdb, cmd.Cmd):
(line[:3] == '"""') or line[:3] == "'''"): (line[:3] == '"""') or line[:3] == "'''"):
print '*** Blank or comment' print '*** Blank or comment'
return 0 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 return lineno
def do_enable(self, arg): def do_enable(self, arg):