Skip to content

Commit

Permalink
pythongh-59013: Set breakpoint on the first executable line of functi…
Browse files Browse the repository at this point in the history
…on when using `break func` in pdb (python#112470)
  • Loading branch information
gaogaotiantian committed Jan 31, 2024
1 parent 1c2ea8b commit 765b9ce
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 22 deletions.
51 changes: 32 additions & 19 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,47 @@ class Restart(Exception):
__all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace",
"post_mortem", "help"]


def find_first_executable_line(code):
""" Try to find the first executable line of the code object.
Equivalently, find the line number of the instruction that's
after RESUME
Return code.co_firstlineno if no executable line is found.
"""
prev = None
for instr in dis.get_instructions(code):
if prev is not None and prev.opname == 'RESUME':
if instr.positions.lineno is not None:
return instr.positions.lineno
return code.co_firstlineno
prev = instr
return code.co_firstlineno

def find_function(funcname, filename):
cre = re.compile(r'def\s+%s\s*[(]' % re.escape(funcname))
try:
fp = tokenize.open(filename)
except OSError:
return None
funcdef = ""
funcstart = None
# consumer of this info expects the first line to be 1
with fp:
for lineno, line in enumerate(fp, start=1):
if cre.match(line):
return funcname, filename, lineno
funcstart, funcdef = lineno, line
elif funcdef:
funcdef += line

if funcdef:
try:
funccode = compile(funcdef, filename, 'exec').co_consts[0]
except SyntaxError:
continue
lineno_offset = find_first_executable_line(funccode)
return funcname, filename, funcstart + lineno_offset - 1
return None

def lasti2lineno(code, lasti):
Expand Down Expand Up @@ -975,7 +1005,7 @@ def do_break(self, arg, temporary = 0):
#use co_name to identify the bkpt (function names
#could be aliased, but co_name is invariant)
funcname = code.co_name
lineno = self._find_first_executable_line(code)
lineno = find_first_executable_line(code)
filename = code.co_filename
except:
# last thing to try
Expand Down Expand Up @@ -1078,23 +1108,6 @@ def checkline(self, filename, lineno):
return 0
return lineno

def _find_first_executable_line(self, code):
""" Try to find the first executable line of the code object.
Equivalently, find the line number of the instruction that's
after RESUME
Return code.co_firstlineno if no executable line is found.
"""
prev = None
for instr in dis.get_instructions(code):
if prev is not None and prev.opname == 'RESUME':
if instr.positions.lineno is not None:
return instr.positions.lineno
return code.co_firstlineno
prev = instr
return code.co_firstlineno

def do_enable(self, arg):
"""enable bpnumber [bpnumber ...]
Expand Down
31 changes: 28 additions & 3 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -2661,7 +2661,7 @@ def quux():
pass
""".encode(),
'bœr',
('bœr', 4),
('bœr', 5),
)

def test_find_function_found_with_encoding_cookie(self):
Expand All @@ -2678,7 +2678,7 @@ def quux():
pass
""".encode('iso-8859-15'),
'bœr',
('bœr', 5),
('bœr', 6),
)

def test_find_function_found_with_bom(self):
Expand All @@ -2688,9 +2688,34 @@ def bœr():
pass
""".encode(),
'bœr',
('bœr', 1),
('bœr', 2),
)

def test_find_function_first_executable_line(self):
code = textwrap.dedent("""\
def foo(): pass
def bar():
pass # line 4
def baz():
# comment
pass # line 8
def mul():
# code on multiple lines
code = compile( # line 12
'def f()',
'<string>',
'exec',
)
""").encode()

self._assert_find_function(code, 'foo', ('foo', 1))
self._assert_find_function(code, 'bar', ('bar', 4))
self._assert_find_function(code, 'baz', ('baz', 8))
self._assert_find_function(code, 'mul', ('mul', 12))

def test_issue7964(self):
# open the file as binary so we can force \r\n newline
with open(os_helper.TESTFN, 'wb') as f:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Set breakpoint on the first executable line of the function, instead of the line of function definition when the user do ``break func`` using :mod:`pdb`

0 comments on commit 765b9ce

Please sign in to comment.