https://github.com/python/cpython/commit/dfaa384991022bb1b577a8e030c5f80ca6d912fc commit: dfaa384991022bb1b577a8e030c5f80ca6d912fc branch: 3.13 author: Miss Islington (bot) <31488909+miss-isling...@users.noreply.github.com> committer: picnixz <10796600+picn...@users.noreply.github.com> date: 2025-04-30T09:19:53+02:00 summary:
[3.13] gh-132308: prevent `TracebackException` swallowing attributes of a falsey `Exception` or `ExceptionGroup` (GH-132363) (#132725) gh-132308: prevent `TracebackException` swallowing attributes of a falsey `Exception` or `ExceptionGroup` (GH-132363) (cherry picked from commit 69cda31261dd98b0462dc5ca63bdbcd0954dfa77) Co-authored-by: Duprat <ydup...@gmail.com> files: A Misc/NEWS.d/next/Library/2025-04-10-13-06-42.gh-issue-132308.1js5SI.rst M Lib/test/test_traceback.py M Lib/traceback.py diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 2dfee81681d42d..e906fcc17c6c5d 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -3408,6 +3408,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: @@ -3753,6 +3766,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): @@ -3954,6 +3985,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 diff --git a/Lib/traceback.py b/Lib/traceback.py index 15f59bba54d0db..12235a8d93ea5c 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -1116,7 +1116,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__), @@ -1137,7 +1137,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__), @@ -1152,7 +1152,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( diff --git a/Misc/NEWS.d/next/Library/2025-04-10-13-06-42.gh-issue-132308.1js5SI.rst b/Misc/NEWS.d/next/Library/2025-04-10-13-06-42.gh-issue-132308.1js5SI.rst new file mode 100644 index 00000000000000..8e8b99c2be31ec --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-10-13-06-42.gh-issue-132308.1js5SI.rst @@ -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`. _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com