Skip to content

gh-59013: Set breakpoint on the first executable line in pdb when using break func #112470

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 32 additions & 19 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,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 @@ -913,7 +943,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 @@ -1016,23 +1046,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 @@ -2587,7 +2587,7 @@ def quux():
pass
""".encode(),
'bœr',
('bœr', 4),
('bœr', 5),
)

def test_find_function_found_with_encoding_cookie(self):
Expand All @@ -2604,7 +2604,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 @@ -2614,9 +2614,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`