Commit 0a9c3e91 authored by Georg Brandl's avatar Georg Brandl

Show the traceback line numbers as well as the current line numbers if an...

Show the traceback line numbers as well as the current line numbers if an exception is being debugged.  Courtesy of pdb++ by Antonio Cuni.  Also document -> and >> markers for "list".
parent cdf66a9a
...@@ -368,9 +368,18 @@ by the local file. ...@@ -368,9 +368,18 @@ by the local file.
list 11 lines around at that line. With two arguments, list the given range; 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. 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 .. 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 .. versionadded:: 3.2
......
...@@ -70,8 +70,10 @@ import sys ...@@ -70,8 +70,10 @@ import sys
import linecache import linecache
import cmd import cmd
import bdb import bdb
import dis
import os import os
import re import re
import code
import pprint import pprint
import traceback import traceback
import inspect import inspect
...@@ -107,14 +109,22 @@ def find_function(funcname, filename): ...@@ -107,14 +109,22 @@ def find_function(funcname, filename):
def getsourcelines(obj): def getsourcelines(obj):
lines, lineno = inspect.findsource(obj) lines, lineno = inspect.findsource(obj)
if inspect.isframe(obj) and lineno == 0 and \ if inspect.isframe(obj) and obj.f_globals is obj.f_locals:
obj.f_globals is obj.f_locals:
# must be a module frame: do not try to cut a block out of it # 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): elif inspect.ismodule(obj):
return lines, 0 return lines, 1
return inspect.getblock(lines[lineno:]), lineno+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 # Interaction prompt line will separate file and call info from code
# text using value of line_prefix string. A newline and arrow may # 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 # be to your liking. You can set it once pdb is imported using the
...@@ -133,6 +143,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): ...@@ -133,6 +143,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.aliases = {} self.aliases = {}
self.mainpyfile = '' self.mainpyfile = ''
self._wait_for_mainpyfile = 0 self._wait_for_mainpyfile = 0
self.tb_lineno = {}
# Try to load readline if it exists # Try to load readline if it exists
try: try:
import readline import readline
...@@ -179,10 +190,18 @@ class Pdb(bdb.Bdb, cmd.Cmd): ...@@ -179,10 +190,18 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.stack = [] self.stack = []
self.curindex = 0 self.curindex = 0
self.curframe = None self.curframe = None
self.tb_lineno.clear()
def setup(self, f, t): def setup(self, f, tb):
self.forget() 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] self.curframe = self.stack[self.curindex][0]
# The f_locals dictionary is updated from the actual frame # The f_locals dictionary is updated from the actual frame
# locals whenever the .f_locals accessor is called, so we # locals whenever the .f_locals accessor is called, so we
...@@ -1005,13 +1024,18 @@ class Pdb(bdb.Bdb, cmd.Cmd): ...@@ -1005,13 +1024,18 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def do_list(self, arg): def do_list(self, arg):
"""l(ist) [first [,last] | .] """l(ist) [first [,last] | .]
List source code for the current file.
Without arguments, list 11 lines around the current line List source code for the current file. Without arguments,
or continue the previous listing. list 11 lines around the current line or continue the previous
With . as argument, list 11 lines around the current line. listing. With . as argument, list 11 lines around the current
With one argument, list 11 lines starting at that line. line. With one argument, list 11 lines starting at that line.
With two arguments, list the given range; With two arguments, list the given range; if the second
if the second argument is less than the first, it is a count. 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' self.lastcmd = 'list'
last = None last = None
...@@ -1039,10 +1063,9 @@ class Pdb(bdb.Bdb, cmd.Cmd): ...@@ -1039,10 +1063,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
filename = self.curframe.f_code.co_filename filename = self.curframe.f_code.co_filename
breaklist = self.get_file_breaks(filename) breaklist = self.get_file_breaks(filename)
try: try:
# XXX add tb_lineno feature
lines = linecache.getlines(filename, self.curframe.f_globals) lines = linecache.getlines(filename, self.curframe.f_globals)
self._print_lines(lines[first-1:last], first, breaklist, self._print_lines(lines[first-1:last], first, breaklist,
self.curframe.f_lineno, -1) self.curframe)
self.lineno = min(last, len(lines)) self.lineno = min(last, len(lines))
if len(lines) < last: if len(lines) < last:
self.message('[EOF]') self.message('[EOF]')
...@@ -1061,7 +1084,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): ...@@ -1061,7 +1084,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
except IOError as err: except IOError as err:
self.error(err) self.error(err)
return return
self._print_lines(lines, lineno, breaklist, self.curframe.f_lineno, -1) self._print_lines(lines, lineno, breaklist, self.curframe)
do_ll = do_longlist do_ll = do_longlist
def do_source(self, arg): def do_source(self, arg):
...@@ -1077,10 +1100,15 @@ class Pdb(bdb.Bdb, cmd.Cmd): ...@@ -1077,10 +1100,15 @@ class Pdb(bdb.Bdb, cmd.Cmd):
except (IOError, TypeError) as err: except (IOError, TypeError) as err:
self.error(err) self.error(err)
return 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.""" """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): for lineno, line in enumerate(lines, start):
s = str(lineno).rjust(3) s = str(lineno).rjust(3)
if len(s) < 4: if len(s) < 4:
...@@ -1089,9 +1117,9 @@ class Pdb(bdb.Bdb, cmd.Cmd): ...@@ -1089,9 +1117,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
s += 'B' s += 'B'
else: else:
s += ' ' s += ' '
if lineno == current: if lineno == current_lineno:
s += '->' s += '->'
elif lineno == special: elif lineno == exc_lineno:
s += '>>' s += '>>'
self.message(s + '\t' + line.rstrip()) self.message(s + '\t' + line.rstrip())
......
...@@ -359,6 +359,68 @@ def test_list_commands(): ...@@ -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(): def test_pdb_skip_modules():
"""This illustrates the simple case of module skipping. """This illustrates the simple case of module skipping.
......
...@@ -475,6 +475,10 @@ C-API ...@@ -475,6 +475,10 @@ C-API
Library 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 - The pdb command "source" has been added. It displays the source
code for a given object, if possible. code for a given object, if possible.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment