summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVladimir Feinberg <vlad17@users.noreply.github.com>2021-07-12 08:58:47 -0700
committerGitHub <noreply@github.com>2021-07-12 17:58:47 +0200
commit489c27358376e772a61753c3f67daa836ca1eab7 (patch)
tree1375d3ee17e23f5615c86c489f0b76e260c177a9
parent47abf240365ddd54a91c6ac167900d4bf6806c4f (diff)
downloadcpython-git-489c27358376e772a61753c3f67daa836ca1eab7.tar.gz
[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>
-rw-r--r--Lib/test/test_traceback.py37
-rw-r--r--Lib/traceback.py57
-rw-r--r--Misc/NEWS.d/next/Library/2021-02-06-05-34-01.bpo-43048.yCPUmo.rst1
3 files changed, 73 insertions, 22 deletions
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 5bb3a58b2a..987fcb9550 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -92,6 +92,43 @@ class TracebackCases(unittest.TestCase):
lst = traceback.format_exception_only(e.__class__, e)
self.assertEqual(lst, ['KeyboardInterrupt\n'])
+ def test_traceback_context_recursionerror(self):
+ # Test that for long traceback chains traceback does not itself
+ # raise a recursion error while printing (Issue43048)
+
+ # Calling f() creates a stack-overflowing __context__ chain.
+ def f():
+ try:
+ raise ValueError('hello')
+ except ValueError:
+ f()
+
+ try:
+ f()
+ except RecursionError:
+ exc_info = sys.exc_info()
+
+ traceback.format_exception(exc_info[0], exc_info[1], exc_info[2])
+
+ def test_traceback_cause_recursionerror(self):
+ # Same as test_traceback_context_recursionerror, but with
+ # a __cause__ chain.
+
+ def f():
+ e = None
+ try:
+ f()
+ except Exception as exc:
+ e = exc
+ raise Exception from e
+
+ try:
+ f()
+ except Exception:
+ exc_info = sys.exc_info()
+
+ traceback.format_exception(exc_info[0], exc_info[1], exc_info[2])
+
def test_format_exception_only_bad__str__(self):
class X(Exception):
def __str__(self):
diff --git a/Lib/traceback.py b/Lib/traceback.py
index d7fbdae680..d65a6098cc 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -476,29 +476,38 @@ class TracebackException:
_seen.add(id(exc_value))
# Gracefully handle (the way Python 2.4 and earlier did) the case of
# being called with no type or value (None, None, None).
- if (exc_value and exc_value.__cause__ is not None
- and id(exc_value.__cause__) not in _seen):
- cause = TracebackException(
- type(exc_value.__cause__),
- exc_value.__cause__,
- exc_value.__cause__.__traceback__,
- limit=limit,
- lookup_lines=False,
- capture_locals=capture_locals,
- _seen=_seen)
- else:
+ self._truncated = False
+ try:
+ if (exc_value and exc_value.__cause__ is not None
+ and id(exc_value.__cause__) not in _seen):
+ cause = TracebackException(
+ type(exc_value.__cause__),
+ exc_value.__cause__,
+ exc_value.__cause__.__traceback__,
+ limit=limit,
+ lookup_lines=False,
+ capture_locals=capture_locals,
+ _seen=_seen)
+ else:
+ cause = None
+ if (exc_value and exc_value.__context__ is not None
+ and id(exc_value.__context__) not in _seen):
+ context = TracebackException(
+ type(exc_value.__context__),
+ exc_value.__context__,
+ exc_value.__context__.__traceback__,
+ limit=limit,
+ lookup_lines=False,
+ capture_locals=capture_locals,
+ _seen=_seen)
+ else:
+ context = None
+ except RecursionError:
+ # The recursive call to the constructors above
+ # may result in a stack overflow for long exception chains,
+ # so we must truncate.
+ self._truncated = True
cause = None
- if (exc_value and exc_value.__context__ is not None
- and id(exc_value.__context__) not in _seen):
- context = TracebackException(
- type(exc_value.__context__),
- exc_value.__context__,
- exc_value.__context__.__traceback__,
- limit=limit,
- lookup_lines=False,
- capture_locals=capture_locals,
- _seen=_seen)
- else:
context = None
self.__cause__ = cause
self.__context__ = context
@@ -620,6 +629,10 @@ class TracebackException:
not self.__suppress_context__):
yield from self.__context__.format(chain=chain)
yield _context_message
+ if self._truncated:
+ yield (
+ 'Chained exceptions have been truncated to avoid '
+ 'stack overflow in traceback formatting:\n')
if self.stack:
yield 'Traceback (most recent call last):\n'
yield from self.stack.format()
diff --git a/Misc/NEWS.d/next/Library/2021-02-06-05-34-01.bpo-43048.yCPUmo.rst b/Misc/NEWS.d/next/Library/2021-02-06-05-34-01.bpo-43048.yCPUmo.rst
new file mode 100644
index 0000000000..99f6b2bbe9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-02-06-05-34-01.bpo-43048.yCPUmo.rst
@@ -0,0 +1 @@
+Handle `RecursionError` in :class:`~traceback.TracebackException`'s constructor, so that long exceptions chains are truncated instead of causing traceback formatting to fail.