8000 [3.9] bpo-43048: RecursionError traceback RecursionError bugfix for c… · python/cpython@489c273 · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 489c273

Browse files
[3.9] bpo-43048: RecursionError traceback RecursionError bugfix for cpy3.9 (GH-24460) (#24460)
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
1 parent 47abf24 commit 489c273

File tree

3 files changed

+73
-22
lines changed

3 files changed

+73
-22
lines changed

Lib/test/test_traceback.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,43 @@ def test_base_exception(self):
9292
lst = traceback.format_exception_only(e.__class__, e)
9393
self.assertEqual(lst, ['KeyboardInterrupt\n'])
9494

95+
def test_traceback_context_recursionerror(self):
96+
# Test that for long traceback chains traceback does not itself
97+
# raise a recursion error while printing (Issue43048)
98+
99+
# Calling f() creates a stack-overflowing __context__ chain.
100+
def f():
101+
try:
102+
raise ValueError('hello')
103+
except ValueError:
104+
f()
105+
106+
try:
107+
f()
108+
except RecursionError:
109+
exc_info = sys.exc_info()
110+
111+
traceback.format_exception(exc_info[0], exc_info[1], exc_info[2])
112+
113+
def test_traceback_cause_recursionerror(self):
114+
# Same as test_traceback_context_recursionerror, but with
115+
# a __cause__ chain.
116+
117+
def f():
118+
e = None
119+
try:
120+
f()
121+
except Exception as exc:
122+
e = exc
123+
raise Exception from e
124+
125+
try:
126+
f()
127+
except Exception:
128+
exc_info = sys.exc_info()
129+
130+
traceback.format_exception(exc_info[0], exc_info[1], exc_info[2])
131+
95132
def test_format_exception_only_bad__str__(self):
96133
class X(Exception):
97134
def __str__(self):

Lib/traceback.py

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -476,29 +476,38 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
476476
_seen.add(id(exc_value))
477477
# Gracefully handle (the way Python 2.4 and earlier did) the case of
478478
# being called with no type or value (None, None, None).
479-
if (exc_value and exc_value.__cause__ is not None
480-
and id(exc_value.__cause__) not in _seen):
481-
cause = TracebackException(
482-
type(exc_value.__cause__),
483-
exc_value.__cause__,
484-
exc_value.__cause__.__traceback__,
485-
limit=limit,
486-
lookup_lines=False,
487-
capture_locals=capture_locals,
488-
_seen=_seen)
489-
else:
479+
self._truncated = False
480+
try:
481+
if (exc_value and exc_value.__cause__ is not None
482+
and id(exc_value.__cause__) not in _seen):
483+
cause = TracebackException(
484+
type(exc_value.__cause__),
485+
exc_value.__cause__,
486+
exc_value.__cause__.__traceback__,
487+
limit=limit,
488+
lookup_lines=False,
489+
capture_locals=capture_locals,
490+
_seen=_seen)
491+
else:
492+
cause = None
493+
if (exc_value and exc_value.__context__ is not None
494+
and id(exc_value.__context__) not in _seen):
495+
context = TracebackException(
496+
type(exc_value.__context__),
497+
exc_value.__context__,
498+
exc_value.__context__.__traceback__,
499+
limit=limit,
500+
lookup_lines=False,
501+
capture_locals=capture_locals,
502+
_seen=_seen)
503+
else:
504+
context = None
505+
except RecursionError:
506+
# The recursive call to the constructors above
507+
# may result in a stack overflow for long exception chains,
508+
# so we must truncate.
509+
self._truncated = True
490510
cause = None
491-
if (exc_value and exc_value.__context__ is not None
492-
and id(exc_value.__context__) not in _seen):
493-
context = TracebackException(
494-
type(exc_value.__context__),
495-
exc_value.__context__,
496-
exc_value.__context__.__traceback__,
497-
limit=limit,
498-
lookup_lines=False,
499-
capture_locals=capture_locals,
500-
_seen=_seen)
501-
else:
502511
context = None
503512
self.__cause__ = cause
504513
self.__context__ = context
@@ -620,6 +629,10 @@ def format(self, *, chain=True):
620629
not self.__suppress_context__):
621630
yield from self.__context__.format(chain=chain)
622631
yield _context_message
632+
if self._truncated:
633+
yield (
634+
'Chained exceptions have been truncated to avoid '
635+
'stack overflow in traceback formatting:\n')
623636
if self.stack:
624637
yield 'Traceback (most recent call last):\n'
625638
yield from self.stack.format()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Handle `RecursionError` in :class:`~traceback.TracebackException`'s constructor, so that long exceptions chains are truncated instead of causing traceback formatting to fail.

0 commit comments

Comments
 (0)
0