From 9d3b7e24862ae2c480df12ef0e7141fb30a81b05 Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Fri, 5 Feb 2021 21:22:58 -0800 Subject: [PATCH 01/16] bpo-43048: Catch RecursionError traceback RecursionError When traceback is formatting a RecursionError with a long __context__ chain it runs the risk of itself raising a RecursionError. This patch catches the corresponding recursive call's RecursionError, truncates the traceback and emits a warning instead. --- Lib/test/test_traceback.py | 22 ++++++++++++++++++++++ Lib/traceback.py | 22 ++++++++++++++-------- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 5bb3a58b2a103b..9d565fcaa4aa72 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -92,6 +92,28 @@ def test_base_exception(self): lst = traceback.format_exception_only(e.__class__, e) self.assertEqual(lst, ['KeyboardInterrupt\n']) + def test_traceback_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 as e: + # Creating this exception should not fail if internally + # the traceback exception is constructed safely with respect + # to recursion context (since TracebackExceptions are used + # for printing and formatting exceptions we should allow their + # creation even for RecursionErrors with long chains). + exc_info = sys.exc_info() + traceback.TracebackException(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 d7fbdae680be62..b659f754879e7c 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -3,6 +3,7 @@ import collections import itertools import linecache +import warnings import sys __all__ = ['extract_stack', 'extract_tb', 'format_exception', @@ -490,14 +491,19 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, 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) + try: + context = TracebackException( + type(exc_value.__context__), + exc_value.__context__, + exc_value.__context__.__traceback__, + limit=limit, + lookup_lines=False, + capture_locals=capture_locals, + _seen=_seen) + except RecursionError: + warnings.warn( + 'A RecursionError occurred while processing an exception; truncating traceback.'); + context = None else: context = None self.__cause__ = cause From b4dcb16d19684f1d40b730bf2056eb59dc19464c Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 6 Feb 2021 05:34:02 +0000 Subject: [PATCH 02/16] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NEWS.d/next/Library/2021-02-06-05-34-01.bpo-43048.yCPUmo.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2021-02-06-05-34-01.bpo-43048.yCPUmo.rst 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 00000000000000..476f0c15a9ed8b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-02-06-05-34-01.bpo-43048.yCPUmo.rst @@ -0,0 +1 @@ +When traceback is formatting a RecursionError with a long __context__ chain it runs the risk of itself raising a RecursionError. This patch catches the corresponding recursive call's RecursionError, truncates the traceback and emits a warning instead. \ No newline at end of file From cdfef13d8e6f553f72917f197b638fe7bbbfc449 Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Sat, 6 Feb 2021 08:53:07 -0800 Subject: [PATCH 03/16] iritkatriel comments --- Lib/test/test_traceback.py | 2 +- Lib/traceback.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 9d565fcaa4aa72..56a9bc3f3e249d 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -92,7 +92,7 @@ def test_base_exception(self): lst = traceback.format_exception_only(e.__class__, e) self.assertEqual(lst, ['KeyboardInterrupt\n']) - def test_traceback_recursionerror(self): + def test_traceback_context_recursionerror(self): # Test that for long traceback chains traceback does not itself # raise a recursion error while printing (Issue43048) diff --git a/Lib/traceback.py b/Lib/traceback.py index b659f754879e7c..ec2bae39d84e55 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -3,7 +3,6 @@ import collections import itertools import linecache -import warnings import sys __all__ = ['extract_stack', 'extract_tb', 'format_exception', @@ -489,6 +488,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, _seen=_seen) else: cause = None + self._truncated_context = False if (exc_value and exc_value.__context__ is not None and id(exc_value.__context__) not in _seen): try: @@ -501,8 +501,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, capture_locals=capture_locals, _seen=_seen) except RecursionError: - warnings.warn( - 'A RecursionError occurred while processing an exception; truncating traceback.'); + self._truncated_context = True context = None else: context = None @@ -626,6 +625,12 @@ def format(self, *, chain=True): not self.__suppress_context__): yield from self.__context__.format(chain=chain) yield _context_message + if self._truncated_context: + yield ( + 'Warning: during handling of the above exception, ' + 'more exceptions occurred, but were truncated to avoid ' + 'stack overflow in the exception handler. ' + 'See https://bugs.python.org/issue43048 for details.\n') if self.stack: yield 'Traceback (most recent call last):\n' yield from self.stack.format() From c87b34bccc1fc2c35db34e1b7da26e7f1947485f Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Sat, 6 Feb 2021 09:12:08 -0800 Subject: [PATCH 04/16] bonus bug: cause recursion --- Lib/test/test_traceback.py | 37 ++++++++++++++++++++++++++++++++++++- Lib/traceback.py | 30 ++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 56a9bc3f3e249d..8bfbc45593a730 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -105,7 +105,7 @@ def f(): try: f() - except RecursionError as e: + except RecursionError: # Creating this exception should not fail if internally # the traceback exception is constructed safely with respect # to recursion context (since TracebackExceptions are used @@ -114,6 +114,41 @@ def f(): exc_info = sys.exc_info() traceback.TracebackException(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.TracebackException(exc_info[0], exc_info[1], exc_info[2]) + + def test_traceback_cause_context_recursionerror(self): + # Same as test_traceback_context_recursionerror, but with + # both a __cause__ and __context__ chain. + + def f(): + e = None + try: + f() + except Exception as exc: + raise RuntimeException() from exc + + try: + f() + except Exception: + exc_info = sys.exc_info() + traceback.TracebackException(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 ec2bae39d84e55..f767d5d85bfc5c 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -476,16 +476,24 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, _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). + self._truncated_cause = False 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) + try: + cause = TracebackException( + type(exc_value.__cause__), + exc_value.__cause__, + exc_value.__cause__.__traceback__, + limit=limit, + lookup_lines=False, + capture_locals=capture_locals, + _seen=_seen) + except RecursionError: + # The recursive call to the constructor in the try above + # may result in a stack overflow for long exception chains, + # so we must truncate. + self._truncated_cause = True + cause = None else: cause = None self._truncated_context = False @@ -625,6 +633,12 @@ def format(self, *, chain=True): not self.__suppress_context__): yield from self.__context__.format(chain=chain) yield _context_message + if self._truncated_cause: + yield ( + 'Warning: the above exception was the cause of other ' + 'exceptions, but those were truncated to avoid ' + 'stack overflow in the exception handler. ' + 'See https://bugs.python.org/issue43048 for details.\n') if self._truncated_context: yield ( 'Warning: during handling of the above exception, ' From e1243dde14b8e25046e479a01eef6abcf550ed23 Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Sat, 6 Feb 2021 09:36:03 -0800 Subject: [PATCH 05/16] simplify exception --- Lib/test/test_traceback.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 8bfbc45593a730..772c667c845fe8 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -137,11 +137,10 @@ def test_traceback_cause_context_recursionerror(self): # both a __cause__ and __context__ chain. def f(): - e = None try: f() except Exception as exc: - raise RuntimeException() from exc + raise Exception() from exc try: f() From e13a98b3c2a33cf3f9db2603fe7b72bb083f02a7 Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Sat, 6 Feb 2021 12:52:23 -0800 Subject: [PATCH 06/16] test format_exception over TracebackException to validate recursion in format() --- Lib/test/test_traceback.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 772c667c845fe8..d25249500193d3 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -106,13 +106,8 @@ def f(): try: f() except RecursionError: - # Creating this exception should not fail if internally - # the traceback exception is constructed safely with respect - # to recursion context (since TracebackExceptions are used - # for printing and formatting exceptions we should allow their - # creation even for RecursionErrors with long chains). exc_info = sys.exc_info() - traceback.TracebackException(exc_info[0], exc_info[1], exc_info[2]) + 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 @@ -130,7 +125,7 @@ def f(): f() except Exception: exc_info = sys.exc_info() - traceback.TracebackException(exc_info[0], exc_info[1], exc_info[2]) + traceback.format_exception(exc_info[0], exc_info[1], exc_info[2]) def test_traceback_cause_context_recursionerror(self): # Same as test_traceback_context_recursionerror, but with @@ -140,6 +135,7 @@ def f(): try: f() except Exception as exc: + # exc is both the cause and context of this new fresh exception raise Exception() from exc try: From 8550dbdd53b87dde1f32288f250507bc43b68130 Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Sat, 6 Feb 2021 14:14:48 -0800 Subject: [PATCH 07/16] add stub --- Lib/test/test_traceback.py | 2 +- Lib/traceback.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index d25249500193d3..1f4da7576107ba 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -142,7 +142,7 @@ def f(): f() except Exception: exc_info = sys.exc_info() - traceback.TracebackException(exc_info[0], exc_info[1], exc_info[2]) + traceback.format_exception(exc_info[0], exc_info[1], exc_info[2]) def test_format_exception_only_bad__str__(self): class X(Exception): diff --git a/Lib/traceback.py b/Lib/traceback.py index f767d5d85bfc5c..2db761f7799c73 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -465,6 +465,9 @@ class TracebackException: - :attr:`msg` For syntax errors - the compiler error message. """ + def hello(self, exc_value): + return id(exc_value) and exc_value + def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, lookup_lines=True, capture_locals=False, _seen=None): # NB: we need to accept exc_traceback, exc_value, exc_traceback to @@ -476,6 +479,8 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, _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). + + self.hello(exc_value) self._truncated_cause = False if (exc_value and exc_value.__cause__ is not None and id(exc_value.__cause__) not in _seen): From 1ac601dfd38a3e55e895936e9f47681ac6b3ee66 Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Sun, 7 Feb 2021 10:48:03 -0800 Subject: [PATCH 08/16] iritkatriel comments: remove no-op, fix indent, concise truncation messages There seems to be another bug that's out-of-scope for this PR, which is handling the tstate->recursion_headroom clearing that's needed for the cause_context test case to pass. I fixed this temporarily by calling a no-op function which I think allows cpython to notice that the stack has been unwound, but it's a convoluted hack and is better addressed head-on by a fix in the ceval code. --- Lib/test/test_traceback.py | 21 +++------------------ Lib/traceback.py | 12 ++++-------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 1f4da7576107ba..987fcb955087e4 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -107,7 +107,8 @@ def f(): f() except RecursionError: exc_info = sys.exc_info() - traceback.format_exception(exc_info[0], exc_info[1], exc_info[2]) + + 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 @@ -125,24 +126,8 @@ def f(): f() except Exception: exc_info = sys.exc_info() - traceback.format_exception(exc_info[0], exc_info[1], exc_info[2]) - - def test_traceback_cause_context_recursionerror(self): - # Same as test_traceback_context_recursionerror, but with - # both a __cause__ and __context__ chain. - - def f(): - try: - f() - except Exception as exc: - # exc is both the cause and context of this new fresh exception - raise Exception() from exc - try: - f() - except Exception: - exc_info = sys.exc_info() - traceback.format_exception(exc_info[0], exc_info[1], exc_info[2]) + traceback.format_exception(exc_info[0], exc_info[1], exc_info[2]) def test_format_exception_only_bad__str__(self): class X(Exception): diff --git a/Lib/traceback.py b/Lib/traceback.py index 2db761f7799c73..fb05306d3589f4 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -640,16 +640,12 @@ def format(self, *, chain=True): yield _context_message if self._truncated_cause: yield ( - 'Warning: the above exception was the cause of other ' - 'exceptions, but those were truncated to avoid ' - 'stack overflow in the exception handler. ' - 'See https://bugs.python.org/issue43048 for details.\n') + 'Explicitly chained exceptions have been truncated to avoid ' + 'stack overflow in traceback formatting:\n') if self._truncated_context: yield ( - 'Warning: during handling of the above exception, ' - 'more exceptions occurred, but were truncated to avoid ' - 'stack overflow in the exception handler. ' - 'See https://bugs.python.org/issue43048 for details.\n') + 'Implicitly 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() From 4d028b3e8f612b9ddcf86abb981d621dec153e20 Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Sun, 7 Feb 2021 10:49:12 -0800 Subject: [PATCH 09/16] goodbye --- Lib/traceback.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Lib/traceback.py b/Lib/traceback.py index fb05306d3589f4..e47d5d76f01a12 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -465,9 +465,6 @@ class TracebackException: - :attr:`msg` For syntax errors - the compiler error message. """ - def hello(self, exc_value): - return id(exc_value) and exc_value - def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, lookup_lines=True, capture_locals=False, _seen=None): # NB: we need to accept exc_traceback, exc_value, exc_traceback to @@ -480,7 +477,6 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, # Gracefully handle (the way Python 2.4 and earlier did) the case of # being called with no type or value (None, None, None). - self.hello(exc_value) self._truncated_cause = False if (exc_value and exc_value.__cause__ is not None and id(exc_value.__cause__) not in _seen): From 567b478e2310c3447479811a7b6530f29b20f836 Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Sun, 7 Feb 2021 12:15:35 -0800 Subject: [PATCH 10/16] retrigger checks: macos test failure is an unrelated connection flake From 31782e56345b053ad8a321899f1c34ad72cc11df Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Sun, 7 Feb 2021 14:20:18 -0800 Subject: [PATCH 11/16] single type of truncation across cause/context --- Lib/traceback.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Lib/traceback.py b/Lib/traceback.py index e47d5d76f01a12..f46314e1661977 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -477,7 +477,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, # Gracefully handle (the way Python 2.4 and earlier did) the case of # being called with no type or value (None, None, None). - self._truncated_cause = False + self._truncated = False if (exc_value and exc_value.__cause__ is not None and id(exc_value.__cause__) not in _seen): try: @@ -493,11 +493,10 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, # The recursive call to the constructor in the try above # may result in a stack overflow for long exception chains, # so we must truncate. - self._truncated_cause = True + self._truncated = True cause = None else: cause = None - self._truncated_context = False if (exc_value and exc_value.__context__ is not None and id(exc_value.__context__) not in _seen): try: @@ -510,7 +509,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, capture_locals=capture_locals, _seen=_seen) except RecursionError: - self._truncated_context = True + self._truncated = True context = None else: context = None @@ -634,13 +633,9 @@ def format(self, *, chain=True): not self.__suppress_context__): yield from self.__context__.format(chain=chain) yield _context_message - if self._truncated_cause: + if self._truncated: yield ( - 'Explicitly chained exceptions have been truncated to avoid ' - 'stack overflow in traceback formatting:\n') - if self._truncated_context: - yield ( - 'Implicitly chained exceptions have been truncated to avoid ' + 'Chained exceptions have been truncated to avoid ' 'stack overflow in traceback formatting:\n') if self.stack: yield 'Traceback (most recent call last):\n' From 041fcfbe3a72bc4f0893f4c6bc2836cbe8b5e9a0 Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Mon, 8 Feb 2021 08:23:25 -0800 Subject: [PATCH 12/16] don't print context if you have (possibly truncated) cause --- Lib/traceback.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/traceback.py b/Lib/traceback.py index f46314e1661977..083bde19e2e6bd 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -476,7 +476,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, _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). - + self.__suppress_context__ = False self._truncated = False if (exc_value and exc_value.__cause__ is not None and id(exc_value.__cause__) not in _seen): @@ -495,6 +495,9 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, # so we must truncate. self._truncated = True cause = None + # Suppress the context during printing, so that even when + # a cause is truncated we don't print the context. + self.__suppress_context__ = True else: cause = None if (exc_value and exc_value.__context__ is not None @@ -515,8 +518,8 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, context = None self.__cause__ = cause self.__context__ = context - self.__suppress_context__ = \ - exc_value.__suppress_context__ if exc_value else False + self.__suppress_context__ = self.__suppress_context__ or ( + exc_value.__suppress_context__ if exc_value else False) # TODO: locals. self.stack = StackSummary.extract( walk_tb(exc_traceback), limit=limit, lookup_lines=lookup_lines, From f93d09f9bdea3f3f46cb978aadf4da863ecc926d Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Mon, 8 Feb 2021 08:57:22 -0800 Subject: [PATCH 13/16] just have one try/except --- Lib/traceback.py | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/Lib/traceback.py b/Lib/traceback.py index 083bde19e2e6bd..ffe1012f2dd264 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -476,11 +476,10 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, _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). - self.__suppress_context__ = False self._truncated = False - if (exc_value and exc_value.__cause__ is not None - and id(exc_value.__cause__) not in _seen): - try: + 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__, @@ -489,20 +488,10 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, lookup_lines=False, capture_locals=capture_locals, _seen=_seen) - except RecursionError: - # The recursive call to the constructor in the try above - # may result in a stack overflow for long exception chains, - # so we must truncate. - self._truncated = True + else: cause = None - # Suppress the context during printing, so that even when - # a cause is truncated we don't print the context. - self.__suppress_context__ = True - else: - cause = None - if (exc_value and exc_value.__context__ is not None - and id(exc_value.__context__) not in _seen): - try: + 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__, @@ -511,14 +500,18 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, lookup_lines=False, capture_locals=capture_locals, _seen=_seen) - except RecursionError: - self._truncated = True + else: context = None - else: + 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 context = None self.__cause__ = cause self.__context__ = context - self.__suppress_context__ = self.__suppress_context__ or ( + self.__suppress_context__ = ( exc_value.__suppress_context__ if exc_value else False) # TODO: locals. self.stack = StackSummary.extract( From c0ceb243073fb2664b4448ebb7ac2ce77906a5d9 Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Mon, 8 Feb 2021 08:59:01 -0800 Subject: [PATCH 14/16] no edit --- Lib/traceback.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/traceback.py b/Lib/traceback.py index ffe1012f2dd264..d65a6098cc621f 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -511,8 +511,8 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, context = None self.__cause__ = cause self.__context__ = context - self.__suppress_context__ = ( - exc_value.__suppress_context__ if exc_value else False) + self.__suppress_context__ = \ + exc_value.__suppress_context__ if exc_value else False # TODO: locals. self.stack = StackSummary.extract( walk_tb(exc_traceback), limit=limit, lookup_lines=lookup_lines, From 0b29f89df97388a690bed49b912e4125ee307d6d Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Mon, 8 Feb 2021 09:16:46 -0800 Subject: [PATCH 15/16] update news --- .../next/Library/2021-02-06-05-34-01.bpo-43048.yCPUmo.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 476f0c15a9ed8b..90958cb344fec8 100644 --- 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 @@ -1 +1 @@ -When traceback is formatting a RecursionError with a long __context__ chain it runs the risk of itself raising a RecursionError. This patch catches the corresponding recursive call's RecursionError, truncates the traceback and emits a warning instead. \ No newline at end of file +Handle `RecursionError`s in :class:`~traceback.TracebackException`'s constructor, so that long exceptions chains are truncated instead of causing traceback formatting to fail. From 1b585aa6c7e0f11e6799941204536f47595fe0d6 Mon Sep 17 00:00:00 2001 From: Vladimir Feinberg Date: Mon, 8 Feb 2021 17:06:31 -0800 Subject: [PATCH 16/16] singular recursionerror --- .../next/Library/2021-02-06-05-34-01.bpo-43048.yCPUmo.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 90958cb344fec8..99f6b2bbe9e30b 100644 --- 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 @@ -1 +1 @@ -Handle `RecursionError`s in :class:`~traceback.TracebackException`'s constructor, so that long exceptions chains are truncated instead of causing traceback formatting to fail. +Handle `RecursionError` in :class:`~traceback.TracebackException`'s constructor, so that long exceptions chains are truncated instead of causing traceback formatting to fail.