gh-132308: prevent TracebackException swallowing attributes of a falsey Exception or ExceptionGroup (#132363)

This commit is contained in:
Duprat 2025-04-19 11:49:18 +02:00 committed by GitHub
parent 427e7fc099
commit 69cda31261
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 57 additions and 3 deletions

View file

@ -3413,6 +3413,19 @@ class Unrepresentable:
def __repr__(self) -> str:
raise Exception("Unrepresentable")
# Used in test_dont_swallow_cause_or_context_of_falsey_exception and
# test_dont_swallow_subexceptions_of_falsey_exceptiongroup.
class FalseyException(Exception):
def __bool__(self):
return False
class FalseyExceptionGroup(ExceptionGroup):
def __bool__(self):
return False
class TestTracebackException(unittest.TestCase):
def do_test_smoke(self, exc, expected_type_str):
try:
@ -3759,6 +3772,24 @@ def f():
'ZeroDivisionError: division by zero',
''])
def test_dont_swallow_cause_or_context_of_falsey_exception(self):
# see gh-132308: Ensure that __cause__ or __context__ attributes of exceptions
# that evaluate as falsey are included in the output. For falsey term,
# see https://docs.python.org/3/library/stdtypes.html#truth-value-testing.
try:
raise FalseyException from KeyError
except FalseyException as e:
self.assertIn(cause_message, traceback.format_exception(e))
try:
try:
1/0
except ZeroDivisionError:
raise FalseyException
except FalseyException as e:
self.assertIn(context_message, traceback.format_exception(e))
class TestTracebackException_ExceptionGroups(unittest.TestCase):
def setUp(self):
@ -3960,6 +3991,26 @@ def test_comparison(self):
self.assertNotEqual(exc, object())
self.assertEqual(exc, ALWAYS_EQ)
def test_dont_swallow_subexceptions_of_falsey_exceptiongroup(self):
# see gh-132308: Ensure that subexceptions of exception groups
# that evaluate as falsey are displayed in the output. For falsey term,
# see https://docs.python.org/3/library/stdtypes.html#truth-value-testing.
try:
raise FalseyExceptionGroup("Gih", (KeyError(), NameError()))
except Exception as ee:
str_exc = ''.join(traceback.format_exception(ee))
self.assertIn('+---------------- 1 ----------------', str_exc)
self.assertIn('+---------------- 2 ----------------', str_exc)
# Test with a falsey exception, in last position, as sub-exceptions.
msg = 'bool'
try:
raise FalseyExceptionGroup("Gah", (KeyError(), FalseyException(msg)))
except Exception as ee:
str_exc = traceback.format_exception(ee)
self.assertIn(f'{FalseyException.__name__}: {msg}', str_exc[-2])
global_for_suggestions = None

View file

@ -1120,7 +1120,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
queue = [(self, exc_value)]
while queue:
te, e = queue.pop()
if (e and e.__cause__ is not None
if (e is not None and e.__cause__ is not None
and id(e.__cause__) not in _seen):
cause = TracebackException(
type(e.__cause__),
@ -1141,7 +1141,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
not e.__suppress_context__)
else:
need_context = True
if (e and e.__context__ is not None
if (e is not None and e.__context__ is not None
and need_context and id(e.__context__) not in _seen):
context = TracebackException(
type(e.__context__),
@ -1156,7 +1156,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
else:
context = None
if e and isinstance(e, BaseExceptionGroup):
if e is not None and isinstance(e, BaseExceptionGroup):
exceptions = []
for exc in e.exceptions:
texc = TracebackException(

View file

@ -0,0 +1,3 @@
A :class:`traceback.TracebackException` now correctly renders the ``__context__``
and ``__cause__`` attributes from :ref:`falsey <truth>` :class:`Exception`,
and the ``exceptions`` attribute from falsey :class:`ExceptionGroup`.