Skip to content
17 changes: 14 additions & 3 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ def _get_tb_and_exceptions(self, tb_or_exc):
traceback, current = tb_or_exc.__traceback__, tb_or_exc

while current is not None:
if current in _exceptions:
if current in _exceptions or not current:
break
_exceptions.append(current)
if current.__cause__ is not None:
Expand Down Expand Up @@ -491,6 +491,8 @@ def interaction(self, frame, tb_or_exc):
Pdb._previous_sigint_handler = None

_chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
if isinstance(tb_or_exc, BaseException) and tb is None:
raise ValueError("No exception traceback to inspect")
with self._hold_exceptions(_chained_exceptions):
if self.setup(frame, tb):
# no interaction desired at this time (happens if .pdbrc contains
Expand Down Expand Up @@ -1166,14 +1168,23 @@ def do_exceptions(self, arg):
rep = repr(exc)
if len(rep) > 80:
rep = rep[:77] + "..."
self.message(f"{prompt} {ix:>3} {rep}")
indicator = (
" -"
if self._chained_exceptions[ix].__traceback__ is None
else f"{ix:>3}"
)
self.message(f"{prompt} {indicator} {rep}")
else:
try:
number = int(arg)
except ValueError:
self.error("Argument must be an integer")
return
if 0 <= number < len(self._chained_exceptions):
if self._chained_exceptions[number].__traceback__ is None:
self.error("This exception has not traceback, cannot jump to it")
return

self._chained_exception_index = number
self.setup(None, self._chained_exceptions[number].__traceback__)
self.print_stack_entry(self.stack[self.curindex])
Expand Down Expand Up @@ -2013,7 +2024,7 @@ def post_mortem(t=None):
if exc is not None:
t = exc.__traceback__

if t is None:
if t is None or (isinstance(t, BaseException) and t.__traceback__ is None):
raise ValueError("A valid traceback must be passed if no "
"exception is being handled")

Expand Down
94 changes: 84 additions & 10 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -907,11 +907,18 @@ def test_post_mortem_chained():
def test_post_mortem_cause_no_context():
"""Test post mortem traceback debugging of chained exception

>>> def make_exc_with_stack(type_, *content, from_=None):
... try:
... raise type_(*content) from from_
... except Exception as out:
... return out
...

>>> def main():
... try:
... raise ValueError('Context Not Shown')
... except Exception as e1:
... raise ValueError("With Cause") from TypeError('The Cause')
... raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause')

>>> def test_function():
... import pdb;
Expand All @@ -925,6 +932,7 @@ def test_post_mortem_cause_no_context():

>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
... 'exceptions',
... 'exceptions 0',
... 'exceptions 1',
... 'up',
... 'down',
Expand All @@ -934,20 +942,23 @@ def test_post_mortem_cause_no_context():
... test_function()
... except ValueError:
... print('Ok.')
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(5)main()
-> raise ValueError("With Cause") from TypeError('The Cause')
> <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)main()
-> raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause')
(Pdb) exceptions
0 TypeError('The Cause')
> 1 ValueError('With Cause')
0 TypeError('The Cause')
> 1 ValueError('With Cause')
(Pdb) exceptions 0
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(3)make_exc_with_stack()
-> raise type_(*content) from from_
(Pdb) exceptions 1
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(5)main()
-> raise ValueError("With Cause") from TypeError('The Cause')
> <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)main()
-> raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause')
(Pdb) up
> <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)test_function()
> <doctest test.test_pdb.test_post_mortem_cause_no_context[2]>(5)test_function()
-> main()
(Pdb) down
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(5)main()
-> raise ValueError("With Cause") from TypeError('The Cause')
> <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)main()
-> raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause')
(Pdb) exit"""


Expand Down Expand Up @@ -1066,6 +1077,69 @@ def test_post_mortem_from_none():
"""


def test_post_mortem_from_no_stack():
"""Test post mortem traceback debugging of chained exception

especially when one exception has not stack.

>>> def main():
... raise Exception() from Exception()


>>> def test_function():
... import pdb;
... instance = pdb.Pdb(nosigint=True, readrc=False)
... try:
... main()
... except Exception as e:
... # same as pdb.post_mortem(e), but with custom pdb instance.
... instance.reset()
... instance.interaction(None, e)

>>> with PdbTestInput( # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
... ["exceptions",
... "exceptions 0",
... "exit"],
... ):
... try:
... test_function()
... except ValueError:
... print('Correctly reraised.')
> <doctest test.test_pdb.test_post_mortem_from_no_stack[0]>(2)main()
-> raise Exception() from Exception()
(Pdb) exceptions
- Exception()
> 1 Exception()
(Pdb) exceptions 0
*** This exception has not traceback, cannot jump to it
(Pdb) exit
"""


def test_post_mortem_single_no_stack():
"""Test post mortem called when origin exception has not stack


>>> def test_function():
... import pdb;
... instance = pdb.Pdb(nosigint=True, readrc=False)
... import sys
... sys.last_exc = Exception()
... # same as pdb.post_mortem(e), but with custom pdb instance.
... instance.reset()
... instance.interaction(None, sys.last_exc)

>>> with PdbTestInput( # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
... []
... ):
... try:
... test_function()
... except ValueError as e:
... print(e)
No exception traceback to inspect
"""


def test_post_mortem_complex():
"""Test post mortem traceback debugging of chained exception

Expand Down