diff options
-rw-r--r-- | Doc/library/pdb.rst | 11 | ||||
-rwxr-xr-x | Lib/pdb.py | 68 | ||||
-rw-r--r-- | Lib/test/test_pdb.py | 62 | ||||
-rw-r--r-- | Misc/NEWS | 4 |
4 files changed, 124 insertions, 21 deletions
diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index e8a457e..d613e0b 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -368,9 +368,18 @@ by the local file. list 11 lines around at that line. With two arguments, list the given range; if the second argument is less than the first, it is interpreted as a count. + The current line in the current frame is indicated by ``->``. If an + exception is being debugged, the line where the exception was originally + raised or propagated is indicated by ``>>``, if it differs from the current + line. + + .. versionadded:: 3.2 + The ``>>`` marker. + .. pdbcommand:: ll | longlist - List all source code for the current function or frame. + List all source code for the current function or frame. Interesting lines + are marked as for :pdbcmd:`list`. .. versionadded:: 3.2 @@ -70,8 +70,10 @@ import sys import linecache import cmd import bdb +import dis import os import re +import code import pprint import traceback import inspect @@ -107,14 +109,22 @@ def find_function(funcname, filename): def getsourcelines(obj): lines, lineno = inspect.findsource(obj) - if inspect.isframe(obj) and lineno == 0 and \ - obj.f_globals is obj.f_locals: + if inspect.isframe(obj) and obj.f_globals is obj.f_locals: # must be a module frame: do not try to cut a block out of it - return lines, 0 + return lines, 1 elif inspect.ismodule(obj): - return lines, 0 + return lines, 1 return inspect.getblock(lines[lineno:]), lineno+1 +def lasti2lineno(code, lasti): + linestarts = list(dis.findlinestarts(code)) + linestarts.reverse() + for i, lineno in linestarts: + if lasti >= i: + return lineno + return 0 + + # Interaction prompt line will separate file and call info from code # text using value of line_prefix string. A newline and arrow may # be to your liking. You can set it once pdb is imported using the @@ -133,6 +143,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): self.aliases = {} self.mainpyfile = '' self._wait_for_mainpyfile = 0 + self.tb_lineno = {} # Try to load readline if it exists try: import readline @@ -179,10 +190,18 @@ class Pdb(bdb.Bdb, cmd.Cmd): self.stack = [] self.curindex = 0 self.curframe = None + self.tb_lineno.clear() - def setup(self, f, t): + def setup(self, f, tb): self.forget() - self.stack, self.curindex = self.get_stack(f, t) + self.stack, self.curindex = self.get_stack(f, tb) + while tb: + # when setting up post-mortem debugging with a traceback, save all + # the original line numbers to be displayed along the current line + # numbers (which can be different, e.g. due to finally clauses) + lineno = lasti2lineno(tb.tb_frame.f_code, tb.tb_lasti) + self.tb_lineno[tb.tb_frame] = lineno + tb = tb.tb_next self.curframe = self.stack[self.curindex][0] # The f_locals dictionary is updated from the actual frame # locals whenever the .f_locals accessor is called, so we @@ -1005,13 +1024,18 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_list(self, arg): """l(ist) [first [,last] | .] - List source code for the current file. - Without arguments, list 11 lines around the current line - or continue the previous listing. - With . as argument, list 11 lines around the current line. - With one argument, list 11 lines starting at that line. - With two arguments, list the given range; - if the second argument is less than the first, it is a count. + + List source code for the current file. Without arguments, + list 11 lines around the current line or continue the previous + listing. With . as argument, list 11 lines around the current + line. With one argument, list 11 lines starting at that line. + With two arguments, list the given range; if the second + argument is less than the first, it is a count. + + The current line in the current frame is indicated by "->". + If an exception is being debugged, the line where the + exception was originally raised or propagated is indicated by + ">>", if it differs from the current line. """ self.lastcmd = 'list' last = None @@ -1039,10 +1063,9 @@ class Pdb(bdb.Bdb, cmd.Cmd): filename = self.curframe.f_code.co_filename breaklist = self.get_file_breaks(filename) try: - # XXX add tb_lineno feature lines = linecache.getlines(filename, self.curframe.f_globals) self._print_lines(lines[first-1:last], first, breaklist, - self.curframe.f_lineno, -1) + self.curframe) self.lineno = min(last, len(lines)) if len(lines) < last: self.message('[EOF]') @@ -1061,7 +1084,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): except IOError as err: self.error(err) return - self._print_lines(lines, lineno, breaklist, self.curframe.f_lineno, -1) + self._print_lines(lines, lineno, breaklist, self.curframe) do_ll = do_longlist def do_source(self, arg): @@ -1077,10 +1100,15 @@ class Pdb(bdb.Bdb, cmd.Cmd): except (IOError, TypeError) as err: self.error(err) return - self._print_lines(lines, lineno, [], -1, -1) + self._print_lines(lines, lineno) - def _print_lines(self, lines, start, breaks, current, special): + def _print_lines(self, lines, start, breaks=(), frame=None): """Print a range of lines.""" + if frame: + current_lineno = frame.f_lineno + exc_lineno = self.tb_lineno.get(frame, -1) + else: + current_lineno = exc_lineno = -1 for lineno, line in enumerate(lines, start): s = str(lineno).rjust(3) if len(s) < 4: @@ -1089,9 +1117,9 @@ class Pdb(bdb.Bdb, cmd.Cmd): s += 'B' else: s += ' ' - if lineno == current: + if lineno == current_lineno: s += '->' - elif lineno == special: + elif lineno == exc_lineno: s += '>>' self.message(s + '\t' + line.rstrip()) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 0861e1e..6010bf3 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -359,6 +359,68 @@ def test_list_commands(): """ +def test_post_mortem(): + """Test post mortem traceback debugging. + + >>> def test_function_2(): + ... try: + ... 1/0 + ... finally: + ... print('Exception!') + + >>> def test_function(): + ... import pdb; pdb.Pdb().set_trace() + ... test_function_2() + ... print('Not reached.') + + >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + ... 'next', # step over exception-raising call + ... 'bt', # get a backtrace + ... 'list', # list code of test_function() + ... 'down', # step into test_function_2() + ... 'list', # list code of test_function_2() + ... 'continue', + ... ]): + ... try: + ... test_function() + ... except ZeroDivisionError: + ... print('Correctly reraised.') + > <doctest test.test_pdb.test_post_mortem[1]>(3)test_function() + -> test_function_2() + (Pdb) next + Exception! + ZeroDivisionError: division by zero + > <doctest test.test_pdb.test_post_mortem[1]>(3)test_function() + -> test_function_2() + (Pdb) bt + ... + <doctest test.test_pdb.test_post_mortem[2]>(10)<module>() + -> test_function() + > <doctest test.test_pdb.test_post_mortem[1]>(3)test_function() + -> test_function_2() + <doctest test.test_pdb.test_post_mortem[0]>(3)test_function_2() + -> 1/0 + (Pdb) list + 1 def test_function(): + 2 import pdb; pdb.Pdb().set_trace() + 3 -> test_function_2() + 4 print('Not reached.') + [EOF] + (Pdb) down + > <doctest test.test_pdb.test_post_mortem[0]>(3)test_function_2() + -> 1/0 + (Pdb) list + 1 def test_function_2(): + 2 try: + 3 >> 1/0 + 4 finally: + 5 -> print('Exception!') + [EOF] + (Pdb) continue + Correctly reraised. + """ + + def test_pdb_skip_modules(): """This illustrates the simple case of module skipping. @@ -475,6 +475,10 @@ C-API Library ------- +- For traceback debugging, the pdb listing now also shows the locations + where the exception was originally (re)raised, if it differs from the + last line executed (e.g. in case of finally clauses). + - The pdb command "source" has been added. It displays the source code for a given object, if possible. |