Skip to content

Commit 69cda31

Browse files
authored
gh-132308: prevent TracebackException swallowing attributes of a falsey Exception or ExceptionGroup (#132363)
1 parent 427e7fc commit 69cda31

File tree

3 files changed

+57
-3
lines changed

3 files changed

+57
-3
lines changed

Lib/test/test_traceback.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3413,6 +3413,19 @@ class Unrepresentable:
34133413
def __repr__(self) -> str:
34143414
raise Exception("Unrepresentable")
34153415

3416+
3417+
# Used in test_dont_swallow_cause_or_context_of_falsey_exception and
3418+
# test_dont_swallow_subexceptions_of_falsey_exceptiongroup.
3419+
class FalseyException(Exception):
3420+
def __bool__(self):
3421+
return False
3422+
3423+
3424+
class FalseyExceptionGroup(ExceptionGroup):
3425+
def __bool__(self):
3426+
return False
3427+
3428+
34163429
class TestTracebackException(unittest.TestCase):
34173430
def do_test_smoke(self, exc, expected_type_str):
34183431
try:
@@ -3759,6 +3772,24 @@ def f():
37593772
'ZeroDivisionError: division by zero',
37603773
''])
37613774

3775+
def test_dont_swallow_cause_or_context_of_falsey_exception(self):
3776+
# see gh-132308: Ensure that __cause__ or __context__ attributes of exceptions
3777+
# that evaluate as falsey are included in the output. For falsey term,
3778+
# see https://docs.python.org/3/library/stdtypes.html#truth-value-testing.
3779+
3780+
try:
3781+
raise FalseyException from KeyError
3782+
except FalseyException as e:
3783+
self.assertIn(cause_message, traceback.format_exception(e))
3784+
3785+
try:
3786+
try:
3787+
1/0
3788+
except ZeroDivisionError:
3789+
raise FalseyException
3790+
except FalseyException as e:
3791+
self.assertIn(context_message, traceback.format_exception(e))
3792+
37623793

37633794
class TestTracebackException_ExceptionGroups(unittest.TestCase):
37643795
def setUp(self):
@@ -3960,6 +3991,26 @@ def test_comparison(self):
39603991
self.assertNotEqual(exc, object())
39613992
self.assertEqual(exc, ALWAYS_EQ)
39623993

3994+
def test_dont_swallow_subexceptions_of_falsey_exceptiongroup(self):
3995+
# see gh-132308: Ensure that subexceptions of exception groups
3996+
# that evaluate as falsey are displayed in the output. For falsey term,
3997+
# see https://docs.python.org/3/library/stdtypes.html#truth-value-testing.
3998+
3999+
try:
4000+
raise FalseyExceptionGroup("Gih", (KeyError(), NameError()))
4001+
except Exception as ee:
4002+
str_exc = ''.join(traceback.format_exception(ee))
4003+
self.assertIn('+---------------- 1 ----------------', str_exc)
4004+
self.assertIn('+---------------- 2 ----------------', str_exc)
4005+
4006+
# Test with a falsey exception, in last position, as sub-exceptions.
4007+
msg = 'bool'
4008+
try:
4009+
raise FalseyExceptionGroup("Gah", (KeyError(), FalseyException(msg)))
4010+
except Exception as ee:
4011+
str_exc = traceback.format_exception(ee)
4012+
self.assertIn(f'{FalseyException.__name__}: {msg}', str_exc[-2])
4013+
39634014

39644015
global_for_suggestions = None
39654016

Lib/traceback.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,7 +1120,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
11201120
queue = [(self, exc_value)]
11211121
while queue:
11221122
te, e = queue.pop()
1123-
if (e and e.__cause__ is not None
1123+
if (e is not None and e.__cause__ is not None
11241124
and id(e.__cause__) not in _seen):
11251125
cause = TracebackException(
11261126
type(e.__cause__),
@@ -1141,7 +1141,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
11411141
not e.__suppress_context__)
11421142
else:
11431143
need_context = True
1144-
if (e and e.__context__ is not None
1144+
if (e is not None and e.__context__ is not None
11451145
and need_context and id(e.__context__) not in _seen):
11461146
context = TracebackException(
11471147
type(e.__context__),
@@ -1156,7 +1156,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
11561156
else:
11571157
context = None
11581158

1159-
if e and isinstance(e, BaseExceptionGroup):
1159+
if e is not None and isinstance(e, BaseExceptionGroup):
11601160
exceptions = []
11611161
for exc in e.exceptions:
11621162
texc = TracebackException(
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
A :class:`traceback.TracebackException` now correctly renders the ``__context__``
2+
and ``__cause__`` attributes from :ref:`falsey <truth>` :class:`Exception`,
3+
and the ``exceptions`` attribute from falsey :class:`ExceptionGroup`.

0 commit comments

Comments
 (0)