diff options
-rw-r--r-- | Doc/c-api/veryhigh.rst | 3 | ||||
-rw-r--r-- | Lib/test/test_repl.py | 62 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Core and Builtins/2017-10-28-22-06-03.bpo-30696.lhC3HE.rst | 1 | ||||
-rw-r--r-- | Python/pythonrun.c | 60 |
4 files changed, 107 insertions, 19 deletions
diff --git a/Doc/c-api/veryhigh.rst b/Doc/c-api/veryhigh.rst index 6ab5942..3897fdd 100644 --- a/Doc/c-api/veryhigh.rst +++ b/Doc/c-api/veryhigh.rst @@ -141,7 +141,8 @@ the same library that the Python runtime is using. Read and execute statements from a file associated with an interactive device until EOF is reached. The user will be prompted using ``sys.ps1`` and ``sys.ps2``. *filename* is decoded from the filesystem encoding - (:func:`sys.getfilesystemencoding`). Returns ``0`` at EOF. + (:func:`sys.getfilesystemencoding`). Returns ``0`` at EOF or a negative + number upon failure. .. c:var:: int (*PyOS_InputHook)(void) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py new file mode 100644 index 0000000..9efd459 --- /dev/null +++ b/Lib/test/test_repl.py @@ -0,0 +1,62 @@ +"""Test the interactive interpreter.""" + +import sys +import os +import unittest +import subprocess +from textwrap import dedent +from test.support import cpython_only, SuppressCrashReport +from test.support.script_helper import kill_python + +def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw): + """Run the Python REPL with the given arguments. + + kw is extra keyword args to pass to subprocess.Popen. Returns a Popen + object. + """ + + # To run the REPL without using a terminal, spawn python with the command + # line option '-i' and the process name set to '<stdin>'. + # The directory of argv[0] must match the directory of the Python + # executable for the Popen() call to python to succeed as the directory + # path may be used by Py_GetPath() to build the default module search + # path. + stdin_fname = os.path.join(os.path.dirname(sys.executable), "<stdin>") + cmd_line = [stdin_fname, '-E', '-i'] + cmd_line.extend(args) + + # Set TERM=vt100, for the rationale see the comments in spawn_python() of + # test.support.script_helper. + env = kw.setdefault('env', dict(os.environ)) + env['TERM'] = 'vt100' + return subprocess.Popen(cmd_line, executable=sys.executable, + stdin=subprocess.PIPE, + stdout=stdout, stderr=stderr, + **kw) + +class TestInteractiveInterpreter(unittest.TestCase): + + @cpython_only + def test_no_memory(self): + # Issue #30696: Fix the interactive interpreter looping endlessly when + # no memory. Check also that the fix does not break the interactive + # loop when an exception is raised. + user_input = """ + import sys, _testcapi + 1/0 + print('After the exception.') + _testcapi.set_nomemory(0) + sys.exit(0) + """ + user_input = dedent(user_input) + user_input = user_input.encode() + p = spawn_repl() + with SuppressCrashReport(): + p.stdin.write(user_input) + output = kill_python(p) + self.assertIn(b'After the exception.', output) + # Exit code 120: Py_FinalizeEx() failed to flush stdout and stderr. + self.assertIn(p.returncode, (1, 120)) + +if __name__ == "__main__": + unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2017-10-28-22-06-03.bpo-30696.lhC3HE.rst b/Misc/NEWS.d/next/Core and Builtins/2017-10-28-22-06-03.bpo-30696.lhC3HE.rst new file mode 100644 index 0000000..76bc683 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2017-10-28-22-06-03.bpo-30696.lhC3HE.rst @@ -0,0 +1 @@ +Fix the interactive interpreter looping endlessly when no memory. diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 3d63186..90b29cb 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -65,6 +65,7 @@ static PyObject *run_pyc_file(FILE *, const char *, PyObject *, PyObject *, PyCompilerFlags *); static void err_input(perrdetail *); static void err_free(perrdetail *); +static int PyRun_InteractiveOneObjectEx(FILE *, PyObject *, PyCompilerFlags *); /* Parse input from a file and execute it */ int @@ -89,6 +90,7 @@ PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags * PyObject *filename, *v; int ret, err; PyCompilerFlags local_flags; + int nomem_count = 0; filename = PyUnicode_DecodeFSDefault(filename_str); if (filename == NULL) { @@ -110,22 +112,32 @@ PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags * _PySys_SetObjectId(&PyId_ps2, v = PyUnicode_FromString("... ")); Py_XDECREF(v); } - err = -1; - for (;;) { - ret = PyRun_InteractiveOneObject(fp, filename, flags); + err = 0; + do { + ret = PyRun_InteractiveOneObjectEx(fp, filename, flags); + if (ret == -1 && PyErr_Occurred()) { + /* Prevent an endless loop after multiple consecutive MemoryErrors + * while still allowing an interactive command to fail with a + * MemoryError. */ + if (PyErr_ExceptionMatches(PyExc_MemoryError)) { + if (++nomem_count > 16) { + PyErr_Clear(); + err = -1; + break; + } + } else { + nomem_count = 0; + } + PyErr_Print(); + flush_io(); + } else { + nomem_count = 0; + } #ifdef Py_REF_DEBUG if (_PyDebug_XOptionShowRefCount() == Py_True) _PyDebug_PrintTotalRefs(); #endif - if (ret == E_EOF) { - err = 0; - break; - } - /* - if (ret == E_NOMEM) - break; - */ - } + } while (ret != E_EOF); Py_DECREF(filename); return err; } @@ -154,8 +166,11 @@ static int PARSER_FLAGS(PyCompilerFlags *flags) PyPARSE_WITH_IS_KEYWORD : 0)) : 0) #endif -int -PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags) +/* A PyRun_InteractiveOneObject() auxiliary function that does not print the + * error on failure. */ +static int +PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename, + PyCompilerFlags *flags) { PyObject *m, *d, *v, *w, *oenc = NULL, *mod_name; mod_ty mod; @@ -167,7 +182,6 @@ PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags) mod_name = _PyUnicode_FromId(&PyId___main__); /* borrowed */ if (mod_name == NULL) { - PyErr_Print(); return -1; } @@ -227,7 +241,6 @@ PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags) PyErr_Clear(); return E_EOF; } - PyErr_Print(); return -1; } m = PyImport_AddModuleObject(mod_name); @@ -239,8 +252,6 @@ PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags) v = run_mod(mod, filename, d, d, flags, arena); PyArena_Free(arena); if (v == NULL) { - PyErr_Print(); - flush_io(); return -1; } Py_DECREF(v); @@ -249,6 +260,19 @@ PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags) } int +PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags) +{ + int res; + + res = PyRun_InteractiveOneObjectEx(fp, filename, flags); + if (res == -1) { + PyErr_Print(); + flush_io(); + } + return res; +} + +int PyRun_InteractiveOneFlags(FILE *fp, const char *filename_str, PyCompilerFlags *flags) { PyObject *filename; |