Skip to content

Commit beb6a48

Browse files
committed
Fix SystemError during REPL input compilation corrupting display
When compile() raises SystemError while checking if user input is complete (e.g. due to a compiler bug as in pythongh-150700), two problems occurred: 1. The SystemError propagated out of _more_lines() mid-readline, corrupting the terminal display (traceback appeared before the prompt line was submitted). 2. The SystemError carried _IncompleteInputError as __context__, leaking an internal codeop detail to the user. Fix both by: - Catching SystemError in _more_lines() and returning False (treat input as complete), so readline finishes normally before the error is shown. - Catching SystemError from the second compile in _maybe_compile() and falling through to the final compile, so the resulting exception has no _IncompleteInputError context.
1 parent 1d4e965 commit beb6a48

6 files changed

Lines changed: 23 additions & 7 deletions

File tree

‎Lib/_pyrepl/simple_interact.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ def _more_lines(console: code.InteractiveConsole, unicodetext: str) -> bool:
9393
not_empty = last_line.strip() != ""
9494
incomplete = not last_line.endswith("\n")
9595
return (was_indented or not_empty) and incomplete
96+
except SystemError:
97+
return False
9698
else:
9799
return code is None
98100

‎Lib/code.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def runsource(self, source, filename="<input>", symbol="single"):
6262
"""
6363
try:
6464
code = self.compile(source, filename, symbol)
65-
except (OverflowError, SyntaxError, ValueError):
65+
except (OverflowError, SyntaxError, ValueError, SystemError):
6666
# Case 1
6767
self.showsyntaxerror(filename, source=source)
6868
return False

‎Lib/codeop.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def _maybe_compile(compiler, source, filename, symbol, flags):
7171
except SyntaxError:
7272
pass
7373
# fallthrough
74-
74+
``
7575
return compiler(source, filename, symbol, incomplete_input=False)
7676

7777
def _compile(source, filename, symbol, incomplete_input=True, *, flags=0):
@@ -147,8 +147,8 @@ def __call__(self, source, filename="<input>", symbol="single"):
147147
148148
- Return a code object if the command is complete and valid
149149
- Return None if the command is incomplete
150-
- Raise SyntaxError, ValueError or OverflowError if the command is a
151-
syntax error (OverflowError and ValueError can be produced by
152-
malformed literals).
150+
- Raise SyntaxError, ValueError, OverflowError or SystemError.
151+
OverflowError and ValueError can be produced by malformed
152+
literals, SystemError if source cannot be compiled.
153153
"""
154154
return _maybe_compile(self.compiler, source, filename, symbol, flags=self.compiler.flags)

‎Lib/test/test_codeop.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
import warnings
77
from test.support import warnings_helper
88
from textwrap import dedent
9+
from unittest.mock import Mock
910

10-
from codeop import compile_command, PyCF_DONT_IMPLY_DEDENT
11+
from codeop import compile_command, _maybe_compile, PyCF_DONT_IMPLY_DEDENT
1112

1213
class CodeopTests(unittest.TestCase):
1314

@@ -312,6 +313,11 @@ def test_invalid_warning(self):
312313
self.assertRegex(str(w[0].message), 'invalid escape sequence')
313314
self.assertEqual(w[0].filename, '<input>')
314315

316+
def test_systemeror_propagates(self):
317+
mock_compiler = Mock(side_effect=[SystemError("compiler bug")])
318+
with self.assertRaises(SystemError):
319+
_maybe_compile(mock_compiler, "x + 1", "<input>", "single", 0)
320+
315321
def assertSyntaxErrorMatches(self, code, message):
316322
with self.subTest(code):
317323
with self.assertRaisesRegex(SyntaxError, message):
@@ -325,6 +331,5 @@ def foo(x,x):
325331
"""), "duplicate parameter 'x' in function definition")
326332

327333

328-
329334
if __name__ == "__main__":
330335
unittest.main()

‎Lib/test/test_pyrepl/test_interact.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,13 @@ def test_incomplete_statement(self):
275275
console = InteractiveColoredConsole(namespace, filename="<stdin>")
276276
self.assertTrue(_more_lines(console, code))
277277

278+
def test_system_error_during_compilation(self):
279+
namespace = {}
280+
console = InteractiveColoredConsole(namespace, filename="<stdin>")
281+
with patch.object(console, "compile", side_effect=SystemError("compiler bug")):
282+
result = _more_lines(console, "some code")
283+
self.assertFalse(result)
284+
278285

279286
class TestWarnings(unittest.TestCase):
280287
def test_pep_765_warning(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :exc:`SystemError` raised during input compilation in the REPL corrupting
2+
the display and leaking :exc:`!_IncompleteInputError` in the exception chain.

0 commit comments

Comments
 (0)