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:
parent
cb7b3f30d6
commit
4a9faa1ea0
45
Lib/bdb.py
45
Lib/bdb.py
|
@ -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:
|
||||||
|
|
51
Lib/pdb.py
51
Lib/pdb.py
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue