]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] gh-87320: In the code module, handle exceptions raised in sys.excepthook ...
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 31 Jul 2024 11:54:06 +0000 (14:54 +0300)
committerGitHub <noreply@github.com>
Wed, 31 Jul 2024 11:54:06 +0000 (11:54 +0000)
Before, the exception caused by calling non-default sys.excepthook
in code.InteractiveInterpreter bubbled up to the caller, ending the REPL.
(cherry picked from commit bd3d31f380cd451a4ab6da5fbfde463fed95b5b5)

Co-authored-by: CF Bolz-Tereick <cfbolz@gmx.de>
Lib/code.py
Lib/test/test_code_module.py
Misc/NEWS.d/next/Library/2024-07-30-14-46-16.gh-issue-87320.-Yk1wb.rst [new file with mode: 0644]

index 2bd5fa3e795a61975de291264da7d066da5d6da0..b4b1ef3b8b96fb28ebc8ba33ed04760e07e126d3 100644 (file)
@@ -127,7 +127,7 @@ class InteractiveInterpreter:
         else:
             # If someone has set sys.excepthook, we let that take precedence
             # over self.write
-            sys.excepthook(type, value, tb)
+            self._call_excepthook(type, value, tb)
 
     def showtraceback(self):
         """Display the exception that just occurred.
@@ -141,16 +141,29 @@ class InteractiveInterpreter:
         sys.last_traceback = last_tb
         sys.last_exc = ei[1]
         try:
-            lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
             if sys.excepthook is sys.__excepthook__:
+                lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
                 self.write(''.join(lines))
             else:
                 # If someone has set sys.excepthook, we let that take precedence
                 # over self.write
-                sys.excepthook(ei[0], ei[1], last_tb)
+                self._call_excepthook(ei[0], ei[1], last_tb)
         finally:
             last_tb = ei = None
 
+    def _call_excepthook(self, typ, value, tb):
+        try:
+            sys.excepthook(typ, value, tb)
+        except SystemExit:
+            raise
+        except BaseException as e:
+            e.__context__ = None
+            print('Error in sys.excepthook:', file=sys.stderr)
+            sys.__excepthook__(type(e), e, e.__traceback__.tb_next)
+            print(file=sys.stderr)
+            print('Original exception was:', file=sys.stderr)
+            sys.__excepthook__(typ, value, tb)
+
     def write(self, data):
         """Write a string.
 
index 226bc3a853b7b94a8016c60387a08cc7b9904733..6082abe898633e098054ad068748c4d1f8afdb1b 100644 (file)
@@ -74,6 +74,39 @@ class TestInteractiveConsole(unittest.TestCase):
         self.console.interact()
         self.assertTrue(hook.called)
 
+    def test_sysexcepthook_crashing_doesnt_close_repl(self):
+        self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')]
+        self.sysmod.excepthook = 1
+        self.console.interact()
+        self.assertEqual(['write', ('123', ), {}], self.stdout.method_calls[0])
+        error = "".join(call.args[0] for call in self.stderr.method_calls if call[0] == 'write')
+        self.assertIn("Error in sys.excepthook:", error)
+        self.assertEqual(error.count("'int' object is not callable"), 1)
+        self.assertIn("Original exception was:", error)
+        self.assertIn("division by zero", error)
+
+    def test_sysexcepthook_raising_BaseException(self):
+        self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')]
+        s = "not so fast"
+        def raise_base(*args, **kwargs):
+            raise BaseException(s)
+        self.sysmod.excepthook = raise_base
+        self.console.interact()
+        self.assertEqual(['write', ('123', ), {}], self.stdout.method_calls[0])
+        error = "".join(call.args[0] for call in self.stderr.method_calls if call[0] == 'write')
+        self.assertIn("Error in sys.excepthook:", error)
+        self.assertEqual(error.count("not so fast"), 1)
+        self.assertIn("Original exception was:", error)
+        self.assertIn("division by zero", error)
+
+    def test_sysexcepthook_raising_SystemExit_gets_through(self):
+        self.infunc.side_effect = ["1/0"]
+        def raise_base(*args, **kwargs):
+            raise SystemExit
+        self.sysmod.excepthook = raise_base
+        with self.assertRaises(SystemExit):
+            self.console.interact()
+
     def test_banner(self):
         # with banner
         self.infunc.side_effect = EOFError('Finished')
diff --git a/Misc/NEWS.d/next/Library/2024-07-30-14-46-16.gh-issue-87320.-Yk1wb.rst b/Misc/NEWS.d/next/Library/2024-07-30-14-46-16.gh-issue-87320.-Yk1wb.rst
new file mode 100644 (file)
index 0000000..adce11e
--- /dev/null
@@ -0,0 +1,3 @@
+In :class:`code.InteractiveInterpreter`, handle exceptions caused by calling a
+non-default :func:`sys.excepthook`. Before, the exception bubbled up to the
+caller, ending the REPL.