From: Vladimir Feinberg Date: Mon, 12 Jul 2021 15:58:47 +0000 (-0700) Subject: [3.9] bpo-43048: RecursionError traceback RecursionError bugfix for cpy3.9 (GH-24460... X-Git-Tag: v3.9.7~164 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=489c27358376e772a61753c3f67daa836ca1eab7;p=thirdparty%2FPython%2Fcpython.git [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> --- diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 5bb3a58b2a10..987fcb955087 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 d7fbdae680be..d65a6098cc62 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 000000000000..99f6b2bbe9e3 --- /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.