From 489c27358376e772a61753c3f67daa836ca1eab7 Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Mon, 12 Jul 2021 08:58:47 -0700 Subject: [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> --- Lib/test/test_traceback.py | 37 ++++++++++++++ Lib/traceback.py | 57 +++++++++++++--------- .../2021-02-06-05-34-01.bpo-43048.yCPUmo.rst | 1 + 3 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-02-06-05-34-01.bpo-43048.yCPUmo.rst diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 5bb3a58..987fcb9 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 d7fbdae..d65a609 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 0000000..99f6b2b --- /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. -- cgit v0.12