]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-132308: prevent `TracebackException` swallowing attributes of a falsey `Exception...
authorDuprat <yduprat@gmail.com>
Sat, 19 Apr 2025 09:49:18 +0000 (11:49 +0200)
committerGitHub <noreply@github.com>
Sat, 19 Apr 2025 09:49:18 +0000 (09:49 +0000)
Lib/test/test_traceback.py
Lib/traceback.py
Misc/NEWS.d/next/Library/2025-04-10-13-06-42.gh-issue-132308.1js5SI.rst [new file with mode: 0644]

index 5c390fd056b3e3b82cec2882d809d95e8d8e5460..a806dbf158226888a0520f546d190521b86ccb7a 100644 (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 @@ class TestTracebackException(unittest.TestCase):
              '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 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
         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
 
index 647c23ed782c41a67d4f611cf828ff33cf61f098..78c35136ea9e8c658c2bcbfa0174aef698b3c948 100644 (file)
@@ -1120,7 +1120,7 @@ class TracebackException:
             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 @@ class TracebackException:
                                     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 @@ class TracebackException:
                 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 (file)
index 0000000..8e8b99c
--- /dev/null
@@ -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`.