]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.9] bpo-43048: RecursionError traceback RecursionError bugfix for cpy3.9 (GH-24460...
authorVladimir Feinberg <vlad17@users.noreply.github.com>
Mon, 12 Jul 2021 15:58:47 +0000 (08:58 -0700)
committerGitHub <noreply@github.com>
Mon, 12 Jul 2021 15:58:47 +0000 (17:58 +0200)
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Lib/test/test_traceback.py
Lib/traceback.py
Misc/NEWS.d/next/Library/2021-02-06-05-34-01.bpo-43048.yCPUmo.rst [new file with mode: 0644]

index 5bb3a58b2a103bec529c72440ed69de099ea3fa4..987fcb955087e48e4492ecb1e8ce28c56e6c1a9a 100644 (file)
@@ -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):
index d7fbdae680be6242b7e7b8874d2671bc0f7945c2..d65a6098cc621fdde90f0dffe9fdb59d4aef620f 100644 (file)
@@ -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 (file)
index 0000000..99f6b2b
--- /dev/null
@@ -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.