Skip to content

Commit 5f3433f

Browse files
authored
gh-106670: Fix Pdb handling of chained Exceptions with no stacks. (#108865)
1 parent 44892b1 commit 5f3433f

File tree

2 files changed

+108
-30
lines changed

2 files changed

+108
-30
lines changed

Lib/pdb.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,8 @@ def interaction(self, frame, tb_or_exc):
494494
Pdb._previous_sigint_handler = None
495495

496496
_chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
497+
if isinstance(tb_or_exc, BaseException):
498+
assert tb is not None, "main exception must have a traceback"
497499
with self._hold_exceptions(_chained_exceptions):
498500
if self.setup(frame, tb):
499501
# no interaction desired at this time (happens if .pdbrc contains
@@ -1169,14 +1171,23 @@ def do_exceptions(self, arg):
11691171
rep = repr(exc)
11701172
if len(rep) > 80:
11711173
rep = rep[:77] + "..."
1172-
self.message(f"{prompt} {ix:>3} {rep}")
1174+
indicator = (
1175+
" -"
1176+
if self._chained_exceptions[ix].__traceback__ is None
1177+
else f"{ix:>3}"
1178+
)
1179+
self.message(f"{prompt} {indicator} {rep}")
11731180
else:
11741181
try:
11751182
number = int(arg)
11761183
except ValueError:
11771184
self.error("Argument must be an integer")
11781185
return
11791186
if 0 <= number < len(self._chained_exceptions):
1187+
if self._chained_exceptions[number].__traceback__ is None:
1188+
self.error("This exception does not have a traceback, cannot jump to it")
1189+
return
1190+
11801191
self._chained_exception_index = number
11811192
self.setup(None, self._chained_exceptions[number].__traceback__)
11821193
self.print_stack_entry(self.stack[self.curindex])
@@ -2010,19 +2021,27 @@ def post_mortem(t=None):
20102021
If `t` is an exception object, the `exceptions` command makes it possible to
20112022
list and inspect its chained exceptions (if any).
20122023
"""
2024+
return _post_mortem(t, Pdb())
2025+
2026+
2027+
def _post_mortem(t, pdb_instance):
2028+
"""
2029+
Private version of post_mortem, which allow to pass a pdb instance
2030+
for testing purposes.
2031+
"""
20132032
# handling the default
20142033
if t is None:
20152034
exc = sys.exception()
20162035
if exc is not None:
20172036
t = exc.__traceback__
20182037

2019-
if t is None:
2038+
if t is None or (isinstance(t, BaseException) and t.__traceback__ is None):
20202039
raise ValueError("A valid traceback must be passed if no "
20212040
"exception is being handled")
20222041

2023-
p = Pdb()
2024-
p.reset()
2025-
p.interaction(None, t)
2042+
pdb_instance.reset()
2043+
pdb_instance.interaction(None, t)
2044+
20262045

20272046
def pm():
20282047
"""Enter post-mortem debugging of the traceback found in sys.last_exc."""

Lib/test/test_pdb.py

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -848,9 +848,7 @@ def test_post_mortem_chained():
848848
... try:
849849
... test_function_reraise()
850850
... except Exception as e:
851-
... # same as pdb.post_mortem(e), but with custom pdb instance.
852-
... instance.reset()
853-
... instance.interaction(None, e)
851+
... pdb._post_mortem(e, instance)
854852
855853
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
856854
... 'exceptions',
@@ -907,24 +905,30 @@ def test_post_mortem_chained():
907905
def test_post_mortem_cause_no_context():
908906
"""Test post mortem traceback debugging of chained exception
909907
908+
>>> def make_exc_with_stack(type_, *content, from_=None):
909+
... try:
910+
... raise type_(*content) from from_
911+
... except Exception as out:
912+
... return out
913+
...
914+
910915
>>> def main():
911916
... try:
912917
... raise ValueError('Context Not Shown')
913918
... except Exception as e1:
914-
... raise ValueError("With Cause") from TypeError('The Cause')
919+
... raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause')
915920
916921
>>> def test_function():
917922
... import pdb;
918923
... instance = pdb.Pdb(nosigint=True, readrc=False)
919924
... try:
920925
... main()
921926
... except Exception as e:
922-
... # same as pdb.post_mortem(e), but with custom pdb instance.
923-
... instance.reset()
924-
... instance.interaction(None, e)
927+
... pdb._post_mortem(e, instance)
925928
926929
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
927930
... 'exceptions',
931+
... 'exceptions 0',
928932
... 'exceptions 1',
929933
... 'up',
930934
... 'down',
@@ -934,20 +938,23 @@ def test_post_mortem_cause_no_context():
934938
... test_function()
935939
... except ValueError:
936940
... print('Ok.')
937-
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(5)main()
938-
-> raise ValueError("With Cause") from TypeError('The Cause')
941+
> <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)main()
942+
-> raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause')
939943
(Pdb) exceptions
940-
0 TypeError('The Cause')
941-
> 1 ValueError('With Cause')
944+
0 TypeError('The Cause')
945+
> 1 ValueError('With Cause')
946+
(Pdb) exceptions 0
947+
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(3)make_exc_with_stack()
948+
-> raise type_(*content) from from_
942949
(Pdb) exceptions 1
943-
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(5)main()
944-
-> raise ValueError("With Cause") from TypeError('The Cause')
950+
> <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)main()
951+
-> raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause')
945952
(Pdb) up
946-
> <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)test_function()
953+
> <doctest test.test_pdb.test_post_mortem_cause_no_context[2]>(5)test_function()
947954
-> main()
948955
(Pdb) down
949-
> <doctest test.test_pdb.test_post_mortem_cause_no_context[0]>(5)main()
950-
-> raise ValueError("With Cause") from TypeError('The Cause')
956+
> <doctest test.test_pdb.test_post_mortem_cause_no_context[1]>(5)main()
957+
-> raise ValueError("With Cause") from make_exc_with_stack(TypeError,'The Cause')
951958
(Pdb) exit"""
952959

953960

@@ -971,9 +978,7 @@ def test_post_mortem_context_of_the_cause():
971978
... try:
972979
... main()
973980
... except Exception as e:
974-
... # same as pdb.post_mortem(e), but with custom pdb instance.
975-
... instance.reset()
976-
... instance.interaction(None, e)
981+
... pdb._post_mortem(e, instance)
977982
978983
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
979984
... 'exceptions',
@@ -1046,9 +1051,7 @@ def test_post_mortem_from_none():
10461051
... try:
10471052
... main()
10481053
... except Exception as e:
1049-
... # same as pdb.post_mortem(e), but with custom pdb instance.
1050-
... instance.reset()
1051-
... instance.interaction(None, e)
1054+
... pdb._post_mortem(e, instance)
10521055
10531056
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
10541057
... 'exceptions',
@@ -1066,6 +1069,64 @@ def test_post_mortem_from_none():
10661069
"""
10671070

10681071

1072+
def test_post_mortem_from_no_stack():
1073+
"""Test post mortem traceback debugging of chained exception
1074+
1075+
especially when one exception has no stack.
1076+
1077+
>>> def main():
1078+
... raise Exception() from Exception()
1079+
1080+
1081+
>>> def test_function():
1082+
... import pdb;
1083+
... instance = pdb.Pdb(nosigint=True, readrc=False)
1084+
... try:
1085+
... main()
1086+
... except Exception as e:
1087+
... pdb._post_mortem(e, instance)
1088+
1089+
>>> with PdbTestInput( # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
1090+
... ["exceptions",
1091+
... "exceptions 0",
1092+
... "exit"],
1093+
... ):
1094+
... try:
1095+
... test_function()
1096+
... except ValueError:
1097+
... print('Correctly reraised.')
1098+
> <doctest test.test_pdb.test_post_mortem_from_no_stack[0]>(2)main()
1099+
-> raise Exception() from Exception()
1100+
(Pdb) exceptions
1101+
- Exception()
1102+
> 1 Exception()
1103+
(Pdb) exceptions 0
1104+
*** This exception does not have a traceback, cannot jump to it
1105+
(Pdb) exit
1106+
"""
1107+
1108+
1109+
def test_post_mortem_single_no_stack():
1110+
"""Test post mortem called when origin exception has no stack
1111+
1112+
1113+
>>> def test_function():
1114+
... import pdb;
1115+
... instance = pdb.Pdb(nosigint=True, readrc=False)
1116+
... import sys
1117+
... sys.last_exc = Exception()
1118+
... pdb._post_mortem(sys.last_exc, instance)
1119+
1120+
>>> with PdbTestInput( # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
1121+
... []
1122+
... ):
1123+
... try:
1124+
... test_function()
1125+
... except ValueError as e:
1126+
... print(e)
1127+
A valid traceback must be passed if no exception is being handled
1128+
"""
1129+
10691130
def test_post_mortem_complex():
10701131
"""Test post mortem traceback debugging of chained exception
10711132
@@ -1130,9 +1191,7 @@ def test_post_mortem_complex():
11301191
... try:
11311192
... main()
11321193
... except Exception as e:
1133-
... # same as pdb.post_mortem(e), but with custom pdb instance.
1134-
... instance.reset()
1135-
... instance.interaction(None, e)
1194+
... pdb._post_mortem(e, instance)
11361195
11371196
>>> with PdbTestInput( # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
11381197
... ["exceptions",

0 commit comments

Comments
 (0)