Skip to content

Commit 245b7f4

Browse files
committed
pythongh-87320: dont crash in code module with bad sys.excepthook
1 parent d27a53f commit 245b7f4

File tree

4 files changed

+50
-2
lines changed

4 files changed

+50
-2
lines changed

Lib/code.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def showsyntaxerror(self, filename=None, **kwargs):
129129
else:
130130
# If someone has set sys.excepthook, we let that take precedence
131131
# over self.write
132-
sys.excepthook(type, value, tb)
132+
self._call_excepthook(type, value, tb)
133133

134134
def showtraceback(self, **kwargs):
135135
"""Display the exception that just occurred.
@@ -150,10 +150,21 @@ def showtraceback(self, **kwargs):
150150
else:
151151
# If someone has set sys.excepthook, we let that take precedence
152152
# over self.write
153-
sys.excepthook(ei[0], ei[1], last_tb)
153+
self._call_excepthook(ei[0], ei[1], last_tb)
154154
finally:
155155
last_tb = ei = None
156156

157+
def _call_excepthook(self, typ, value, tb):
158+
try:
159+
sys.excepthook(typ, value, tb)
160+
except Exception as e:
161+
e.__context__ = None
162+
print('Error in sys.excepthook:', file=sys.stderr)
163+
sys.__excepthook__(type(e), e, e.__traceback__.tb_next)
164+
print(file=sys.stderr)
165+
print('Original exception was:', file=sys.stderr)
166+
sys.__excepthook__(typ, value, tb)
167+
157168
def write(self, data):
158169
"""Write a string.
159170

Lib/test/test_code_module.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,17 @@ def test_sysexcepthook(self):
7777
self.console.interact()
7878
self.assertTrue(hook.called)
7979

80+
def test_sysexcepthook_crashing_doesnt_close_repl(self):
81+
self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')]
82+
self.sysmod.excepthook = 1
83+
self.console.interact()
84+
self.assertEqual(['write', ('123', ), {}], self.stdout.method_calls[0])
85+
error = "".join(call.args[0] for call in self.stderr.method_calls if call[0] == 'write')
86+
self.assertIn("Error in sys.excepthook:", error)
87+
self.assertEqual(error.count("'int' object is not callable"), 1)
88+
self.assertIn("Original exception was:", error)
89+
self.assertIn("division by zero", error)
90+
8091
def test_banner(self):
8192
# with banner
8293
self.infunc.side_effect = EOFError('Finished')

Lib/test/test_pyrepl/test_pyrepl.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,30 @@ def test_python_basic_repl(self):
10491049
self.assertNotIn("Exception", output)
10501050
self.assertNotIn("Traceback", output)
10511051

1052+
@force_not_colorized
1053+
def test_bad_sys_excepthook_doesnt_crash_pyrepl(self):
1054+
env = os.environ.copy()
1055+
commands = ("import sys\n"
1056+
"sys.excepthook = 1\n"
1057+
"1/0\n"
1058+
"exit()\n")
1059+
1060+
def check(output, exitcode):
1061+
self.assertIn("Error in sys.excepthook:", output)
1062+
self.assertEqual(output.count("'int' object is not callable"), 1)
1063+
self.assertIn("Original exception was:", output)
1064+
self.assertIn("division by zero", output)
1065+
self.assertEqual(exitcode, 0)
1066+
env.pop("PYTHON_BASIC_REPL", None)
1067+
output, exit_code = self.run_repl(commands, env=env)
1068+
if "can\'t use pyrepl" in output:
1069+
self.skipTest("pyrepl not available")
1070+
check(output, exit_code)
1071+
1072+
env["PYTHON_BASIC_REPL"] = "1"
1073+
output, exit_code = self.run_repl(commands, env=env)
1074+
check(output, exit_code)
1075+
10521076
def test_not_wiping_history_file(self):
10531077
# skip, if readline module is not available
10541078
import_module('readline')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Don't crash :class:`code.InteractiveInterpreter` when calling
2+
:func:`sys.excepthook` fails.

0 commit comments

Comments
 (0)