Skip to content

gh-106670: Fix Pdb handling of chained Exceptions with no stacks. #108865

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 12 commits into from
Sep 6, 2023
29 changes: 24 additions & 5 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
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):
assert tb is not None, "main exception must have a traceback"
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 does not have a 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 @@ -2007,19 +2018,27 @@ def post_mortem(t=None):
If `t` is an exception object, the `exceptions` command makes it possible to
list and inspect its chained exceptions (if any).
"""
return _post_mortem(t, Pdb())


def _post_mortem(t, pdb_instance):
"""
Private version of post_mortem, which allow to pass a pdb instance
for testing purposes.
"""
# handling the default
if t is None:
exc = sys.exception()
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")

p = Pdb()
p.reset()
p.interaction(None, t)
pdb_instance.reset()
pdb_instance.interaction(None, t)


def pm():
"""Enter post-mortem debugging of the traceback found in sys.last_exc."""
Expand Down
109 changes: 84 additions & 25 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -848,9 +848,7 @@ def test_post_mortem_chained():
... try:
... test_function_reraise()
... except Exception as e:
... # same as pdb.post_mortem(e), but with custom pdb instance.
... instance.reset()
... instance.interaction(None, e)
... pdb._post_mortem(e, instance)

>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
... 'exceptions',
Expand Down Expand Up @@ -907,24 +905,30 @@ 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;
... 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)
... pdb._post_mortem(e, instance)

>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
... 'exceptions',
... 'exceptions 0',
... 'exceptions 1',
... 'up',
... 'down',
Expand All @@ -934,20 +938,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 All @@ -971,9 +978,7 @@ def test_post_mortem_context_of_the_cause():
... try:
... main()
... except Exception as e:
... # same as pdb.post_mortem(e), but with custom pdb instance.
... instance.reset()
... instance.interaction(None, e)
... pdb._post_mortem(e, instance)

>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
... 'exceptions',
Expand Down Expand Up @@ -1046,9 +1051,7 @@ def test_post_mortem_from_none():
... try:
... main()
... except Exception as e:
... # same as pdb.post_mortem(e), but with custom pdb instance.
... instance.reset()
... instance.interaction(None, e)
... pdb._post_mortem(e, instance)

>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
... 'exceptions',
Expand All @@ -1066,6 +1069,64 @@ 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 no 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:
... pdb._post_mortem(e, instance)

>>> 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 does not have a traceback, cannot jump to it
(Pdb) exit
"""


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


>>> def test_function():
... import pdb;
... instance = pdb.Pdb(nosigint=True, readrc=False)
... import sys
... sys.last_exc = Exception()
... pdb._post_mortem(sys.last_exc, instance)

>>> with PdbTestInput( # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
... []
... ):
... try:
... test_function()
... except ValueError as e:
... print(e)
A valid traceback must be passed if no exception is being handled
"""

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

Expand Down Expand Up @@ -1130,9 +1191,7 @@ def test_post_mortem_complex():
... try:
... main()
... except Exception as e:
... # same as pdb.post_mortem(e), but with custom pdb instance.
... instance.reset()
... instance.interaction(None, e)
... pdb._post_mortem(e, instance)

>>> with PdbTestInput( # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
... ["exceptions",
Expand Down