diff options
| author | Tian Gao <gaogaotiantian@hotmail.com> | 2024-01-31 13:03:05 (GMT) |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-01-31 13:03:05 (GMT) |
| commit | 765b9ce9fb357bdb79a50ce51207c827fbd13dd8 (patch) | |
| tree | f9f7ddf1b764707b285e234d25e08dc849398c6d | |
| parent | 1c2ea8b33c6b1f995db0aca0b223a9cc22426708 (diff) | |
| download | cpython-765b9ce9fb357bdb79a50ce51207c827fbd13dd8.zip cpython-765b9ce9fb357bdb79a50ce51207c827fbd13dd8.tar.gz cpython-765b9ce9fb357bdb79a50ce51207c827fbd13dd8.tar.bz2 | |
gh-59013: Set breakpoint on the first executable line of function when using `break func` in pdb (#112470)
| -rwxr-xr-x | Lib/pdb.py | 51 | ||||
| -rw-r--r-- | Lib/test/test_pdb.py | 31 | ||||
| -rw-r--r-- | Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst | 1 |
3 files changed, 61 insertions, 22 deletions
@@ -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): @@ -975,7 +1005,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): #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 @@ -1078,23 +1108,6 @@ class Pdb(bdb.Bdb, cmd.Cmd): 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 ...] diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index c64df62..b2283cf 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -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): @@ -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): @@ -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: diff --git a/Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst b/Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst new file mode 100644 index 0000000..a2be2fb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst @@ -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` |
