gh-106670: Fix Pdb handling of chained Exceptions with no stacks. (#108865)
This commit is contained in:
parent
44892b1c52
commit
5f3433f210
29
Lib/pdb.py
29
Lib/pdb.py
@ -494,6 +494,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||||||
Pdb._previous_sigint_handler = None
|
Pdb._previous_sigint_handler = None
|
||||||
|
|
||||||
_chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
|
_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):
|
with self._hold_exceptions(_chained_exceptions):
|
||||||
if self.setup(frame, tb):
|
if self.setup(frame, tb):
|
||||||
# no interaction desired at this time (happens if .pdbrc contains
|
# no interaction desired at this time (happens if .pdbrc contains
|
||||||
@ -1169,7 +1171,12 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||||||
rep = repr(exc)
|
rep = repr(exc)
|
||||||
if len(rep) > 80:
|
if len(rep) > 80:
|
||||||
rep = rep[:77] + "..."
|
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:
|
else:
|
||||||
try:
|
try:
|
||||||
number = int(arg)
|
number = int(arg)
|
||||||
@ -1177,6 +1184,10 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||||||
self.error("Argument must be an integer")
|
self.error("Argument must be an integer")
|
||||||
return
|
return
|
||||||
if 0 <= number < len(self._chained_exceptions):
|
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._chained_exception_index = number
|
||||||
self.setup(None, self._chained_exceptions[number].__traceback__)
|
self.setup(None, self._chained_exceptions[number].__traceback__)
|
||||||
self.print_stack_entry(self.stack[self.curindex])
|
self.print_stack_entry(self.stack[self.curindex])
|
||||||
@ -2010,19 +2021,27 @@ def post_mortem(t=None):
|
|||||||
If `t` is an exception object, the `exceptions` command makes it possible to
|
If `t` is an exception object, the `exceptions` command makes it possible to
|
||||||
list and inspect its chained exceptions (if any).
|
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
|
# handling the default
|
||||||
if t is None:
|
if t is None:
|
||||||
exc = sys.exception()
|
exc = sys.exception()
|
||||||
if exc is not None:
|
if exc is not None:
|
||||||
t = exc.__traceback__
|
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 "
|
raise ValueError("A valid traceback must be passed if no "
|
||||||
"exception is being handled")
|
"exception is being handled")
|
||||||
|
|
||||||
p = Pdb()
|
pdb_instance.reset()
|
||||||
p.reset()
|
pdb_instance.interaction(None, t)
|
||||||
p.interaction(None, t)
|
|
||||||
|
|
||||||
def pm():
|
def pm():
|
||||||
"""Enter post-mortem debugging of the traceback found in sys.last_exc."""
|
"""Enter post-mortem debugging of the traceback found in sys.last_exc."""
|
||||||
|
@ -848,9 +848,7 @@ def test_post_mortem_chained():
|
|||||||
... try:
|
... try:
|
||||||
... test_function_reraise()
|
... test_function_reraise()
|
||||||
... except Exception as e:
|
... except Exception as e:
|
||||||
... # same as pdb.post_mortem(e), but with custom pdb instance.
|
... pdb._post_mortem(e, instance)
|
||||||
... instance.reset()
|
|
||||||
... instance.interaction(None, e)
|
|
||||||
|
|
||||||
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||||
... 'exceptions',
|
... 'exceptions',
|
||||||
@ -907,11 +905,18 @@ def test_post_mortem_chained():
|
|||||||
def test_post_mortem_cause_no_context():
|
def test_post_mortem_cause_no_context():
|
||||||
"""Test post mortem traceback debugging of chained exception
|
"""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():
|
>>> def main():
|
||||||
... try:
|
... try:
|
||||||
... raise ValueError('Context Not Shown')
|
... raise ValueError('Context Not Shown')
|
||||||
... except Exception as e1:
|
... 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():
|
>>> def test_function():
|
||||||
... import pdb;
|
... import pdb;
|
||||||
@ -919,12 +924,11 @@ def test_post_mortem_cause_no_context():
|
|||||||
... try:
|
... try:
|
||||||
... main()
|
... main()
|
||||||
... except Exception as e:
|
... except Exception as e:
|
||||||
... # same as pdb.post_mortem(e), but with custom pdb instance.
|
... pdb._post_mortem(e, instance)
|
||||||
... instance.reset()
|
|
||||||
... instance.interaction(None, e)
|
|
||||||
|
|
||||||
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||||
... 'exceptions',
|
... 'exceptions',
|
||||||
|
... 'exceptions 0',
|
||||||
... 'exceptions 1',
|
... 'exceptions 1',
|
||||||
... 'up',
|
... 'up',
|
||||||
... 'down',
|
... 'down',
|
||||||
@ -934,20 +938,23 @@ def test_post_mortem_cause_no_context():
|
|||||||
... test_function()
|
... test_function()
|
||||||
... except ValueError:
|
... except ValueError:
|
||||||
... print('Ok.')
|
... print('Ok.')
|
||||||
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(5)main()
|
> <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)main()
|
||||||
-> raise ValueError("With Cause") from TypeError('The Cause')
|
-> raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause')
|
||||||
(Pdb) exceptions
|
(Pdb) exceptions
|
||||||
0 TypeError('The Cause')
|
0 TypeError('The Cause')
|
||||||
> 1 ValueError('With 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
|
(Pdb) exceptions 1
|
||||||
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(5)main()
|
> <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)main()
|
||||||
-> raise ValueError("With Cause") from TypeError('The Cause')
|
-> raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause')
|
||||||
(Pdb) up
|
(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()
|
-> main()
|
||||||
(Pdb) down
|
(Pdb) down
|
||||||
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(5)main()
|
> <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)main()
|
||||||
-> raise ValueError("With Cause") from TypeError('The Cause')
|
-> raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause')
|
||||||
(Pdb) exit"""
|
(Pdb) exit"""
|
||||||
|
|
||||||
|
|
||||||
@ -971,9 +978,7 @@ def test_post_mortem_context_of_the_cause():
|
|||||||
... try:
|
... try:
|
||||||
... main()
|
... main()
|
||||||
... except Exception as e:
|
... except Exception as e:
|
||||||
... # same as pdb.post_mortem(e), but with custom pdb instance.
|
... pdb._post_mortem(e, instance)
|
||||||
... instance.reset()
|
|
||||||
... instance.interaction(None, e)
|
|
||||||
|
|
||||||
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||||
... 'exceptions',
|
... 'exceptions',
|
||||||
@ -1046,9 +1051,7 @@ def test_post_mortem_from_none():
|
|||||||
... try:
|
... try:
|
||||||
... main()
|
... main()
|
||||||
... except Exception as e:
|
... except Exception as e:
|
||||||
... # same as pdb.post_mortem(e), but with custom pdb instance.
|
... pdb._post_mortem(e, instance)
|
||||||
... instance.reset()
|
|
||||||
... instance.interaction(None, e)
|
|
||||||
|
|
||||||
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||||
... 'exceptions',
|
... 'exceptions',
|
||||||
@ -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():
|
def test_post_mortem_complex():
|
||||||
"""Test post mortem traceback debugging of chained exception
|
"""Test post mortem traceback debugging of chained exception
|
||||||
|
|
||||||
@ -1130,9 +1191,7 @@ def test_post_mortem_complex():
|
|||||||
... try:
|
... try:
|
||||||
... main()
|
... main()
|
||||||
... except Exception as e:
|
... except Exception as e:
|
||||||
... # same as pdb.post_mortem(e), but with custom pdb instance.
|
... pdb._post_mortem(e, instance)
|
||||||
... instance.reset()
|
|
||||||
... instance.interaction(None, e)
|
|
||||||
|
|
||||||
>>> with PdbTestInput( # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
>>> with PdbTestInput( # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||||
... ["exceptions",
|
... ["exceptions",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user