Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Doc/c-api/veryhigh.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
62 changes: 62 additions & 0 deletions Lib/test/test_repl.py
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix the interactive interpreter looping endlessly when no memory.
60 changes: 42 additions & 18 deletions Python/pythonrun.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,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 */

Expand All @@ -86,6 +87,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) {
Expand All @@ -107,19 +109,29 @@ 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);
_PY_DEBUG_PRINT_TOTAL_REFS();
if (ret == E_EOF) {
err = 0;
break;
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;
}
/*
if (ret == E_NOMEM)
break;
*/
}
_PY_DEBUG_PRINT_TOTAL_REFS();
} while (ret != E_EOF);
Py_DECREF(filename);
return err;
}
Expand Down Expand Up @@ -148,8 +160,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;
Expand All @@ -161,7 +176,6 @@ PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags)

mod_name = _PyUnicode_FromId(&PyId___main__); /* borrowed */
if (mod_name == NULL) {
PyErr_Print();
return -1;
}

Expand Down Expand Up @@ -221,7 +235,6 @@ PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags)
PyErr_Clear();
return E_EOF;
}
PyErr_Print();
return -1;
}
m = PyImport_AddModuleObject(mod_name);
Expand All @@ -233,15 +246,26 @@ 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);
flush_io();
return 0;
}

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)
{
Expand Down