From 46626321d3d2009c5e1d0573a2c19a208edc2269 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 9 Jul 2026 20:28:15 +0200 Subject: [PATCH] gh-152132: Fix Py_RunMain() to return an exit code (#153446) * Fix Py_RunMain() to return an exit code, rather than calling Py_Exit(), when running a script, a command, or the REPL. * _PyRun_SimpleFile() now logs errors to stderr, for example if setting __main__.__file__ fails. * Add tests on Py_RunMain() exitcode. * Rename functions: * _PyRun_SimpleStringFlagsWithName() => _PyRun_SimpleString() * _PyRun_SimpleFileObject() => _PyRun_SimpleFile() * _PyRun_AnyFileObject() => _PyRun_AnyFile() * _PyRun_InteractiveLoopObject() => _PyRun_InteractiveLoop() * Change _PyRun_SimpleString(), _PyRun_SimpleFile(), _PyRun_AnyFile() and _PyRun_InteractiveLoop() return type to PyObject*. * pymain_repl() now displays the error if PySys_Audit() or import _pyrepl failed. * PyRun_SimpleFileExFlags() and PyRun_AnyFileExFlags() now log PyUnicode_DecodeFSDefault() error. So these functions can no longer return -1 with an exception set. (cherry picked from commit fac72f1ee91fb380640a4c4be6df07a8f02d3110) --- Include/internal/pycore_pylifecycle.h | 3 - Include/internal/pycore_pythonrun.h | 15 +- Lib/test/test_embed.py | 68 +++++- ...-07-09-17-33-29.gh-issue-152132.Ma8MJZ.rst | 3 + Modules/_testcapi/run.c | 27 --- Modules/main.c | 167 +++++++++------ Programs/_testembed.c | 161 +++++++++++--- Python/pythonrun.c | 198 +++++++++--------- 8 files changed, 406 insertions(+), 236 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2026-07-09-17-33-29.gh-issue-152132.Ma8MJZ.rst diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h index 8faf7a4d403f84..386ce2e145fb75 100644 --- a/Include/internal/pycore_pylifecycle.h +++ b/Include/internal/pycore_pylifecycle.h @@ -111,9 +111,6 @@ extern int _Py_LegacyLocaleDetected(int warn); // Export for 'readline' shared extension PyAPI_FUNC(char*) _Py_SetLocaleFromEnv(int category); -// Export for special main.c string compiling with source tracebacks -int _PyRun_SimpleStringFlagsWithName(const char *command, const char* name, PyCompilerFlags *flags); - /* interpreter config */ diff --git a/Include/internal/pycore_pythonrun.h b/Include/internal/pycore_pythonrun.h index b25abb4f64c688..5dd93bd61b6e6b 100644 --- a/Include/internal/pycore_pythonrun.h +++ b/Include/internal/pycore_pythonrun.h @@ -8,23 +8,18 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -extern int _PyRun_SimpleFileObject( +extern PyObject* _PyRun_SimpleFile( FILE *fp, PyObject *filename, int closeit, PyCompilerFlags *flags); -extern int _PyRun_AnyFileObject( +extern PyObject* _PyRun_AnyFile( FILE *fp, PyObject *filename, int closeit, PyCompilerFlags *flags); -extern int _PyRun_InteractiveLoopObject( - FILE *fp, - PyObject *filename, - PyCompilerFlags *flags); - extern int _PyObject_SupportedAsScript(PyObject *); extern const char* _Py_SourceAsString( PyObject *cmd, @@ -33,6 +28,12 @@ extern const char* _Py_SourceAsString( PyCompilerFlags *cf, PyObject **cmd_copy); +// Export for special main.c string compiling with source tracebacks +extern PyObject* _PyRun_SimpleString( + const char *command, + PyObject* name, + PyCompilerFlags *flags); + /* Stack size, in "pointers". This must be large enough, so * no two calls to check recursion depth are more than this far diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 7e5ab19d4134fc..ec5767cd918a00 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -68,6 +68,9 @@ if not os.path.isfile(os.path.join(STDLIB_INSTALL, 'os.py')): STDLIB_INSTALL = None +CODE_EXITCODE_123 = 'raise SystemExit(123)' + + def debug_build(program): program = os.path.basename(program) name = os.path.splitext(program)[0] @@ -117,12 +120,16 @@ def run_embedded_interpreter(self, *args, env=None, env = env.copy() env['SYSTEMROOT'] = os.environ['SYSTEMROOT'] - p = subprocess.Popen(cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True, - env=env, - cwd=cwd) + kwargs = dict( + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + env=env, + cwd=cwd, + ) + if input is not None: + kwargs['stdin'] = subprocess.PIPE + p = subprocess.Popen(cmd, **kwargs) try: (out, err) = p.communicate(input=input, timeout=timeout) except: @@ -554,6 +561,55 @@ def _nogil_filtered_err(err: str, mod_name: str) -> str: ] return "\n".join(filtered_err_lines) + def check_program_exitcode(self, *args, check_stderr=True, **kwargs): + out, err = self.run_embedded_interpreter(*args, **kwargs) + self.assertEqual(out.rstrip(), 'ok! Py_RunMain() returned 123') + if check_stderr: + self.assertEqual(err, '') + + def test_init_run_main_code_exitcode(self): + code = CODE_EXITCODE_123 + self.check_program_exitcode("test_init_run_main_code_exitcode", code) + + def test_init_run_main_script_exitcode(self): + with tempfile.TemporaryDirectory() as tmpdir: + filename = os.path.join(tmpdir, 'script.py') + with open(filename, 'w') as fp: + fp.write(CODE_EXITCODE_123) + + self.check_program_exitcode("test_init_run_main_script_exitcode", + filename) + + def test_init_run_main_interactive_exitcode(self): + code = CODE_EXITCODE_123 + self.check_program_exitcode("test_init_run_main_interactive_exitcode", + input=code, + check_stderr=False) + + def test_init_run_main_startup_exitcode(self): + with tempfile.TemporaryDirectory() as tmpdir: + filename = os.path.join(tmpdir, 'startup.py') + with open(filename, 'x') as fp: + fp.write(CODE_EXITCODE_123) + + env = dict(os.environ) + env['PYTHONSTARTUP'] = filename + self.check_program_exitcode("test_init_run_main_interactive_exitcode", + env=env, + check_stderr=False) + + def test_init_run_main_module_exitcode(self): + with tempfile.TemporaryDirectory() as tmpdir: + modname = '_testembed_testmodule' + filename = os.path.join(tmpdir, modname + '.py') + with open(filename, 'x', encoding='utf8') as fp: + fp.write(CODE_EXITCODE_123) + + env = dict(os.environ) + env['PYTHONPATH'] = tmpdir + self.check_program_exitcode("test_init_run_main_module_exitcode", + modname, env=env) + def config_dev_mode(preconfig, config): preconfig['allocator'] = PYMEM_ALLOCATOR_DEBUG diff --git a/Misc/NEWS.d/next/C_API/2026-07-09-17-33-29.gh-issue-152132.Ma8MJZ.rst b/Misc/NEWS.d/next/C_API/2026-07-09-17-33-29.gh-issue-152132.Ma8MJZ.rst new file mode 100644 index 00000000000000..633c41e40bf269 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-07-09-17-33-29.gh-issue-152132.Ma8MJZ.rst @@ -0,0 +1,3 @@ +Fix :c:func:`Py_RunMain` to return an exit code, rather than calling +:c:func:`Py_Exit`, when running a script, a command, or the REPL. Patch by +Victor Stinner. diff --git a/Modules/_testcapi/run.c b/Modules/_testcapi/run.c index 4a63c0f98a74d4..7fc180e136559b 100644 --- a/Modules/_testcapi/run.c +++ b/Modules/_testcapi/run.c @@ -129,9 +129,6 @@ run_simplefile(PyObject *mod, PyObject *args) assert(_Py_IsValidFD(fd)); fclose(fp); - if (res == -1 && PyErr_Occurred()) { - return NULL; - } assert(!PyErr_Occurred()); return PyLong_FromLong(res); } @@ -203,9 +200,6 @@ run_simplefileex(PyObject *mod, PyObject *args) return NULL; } - if (res == -1 && PyErr_Occurred()) { - return NULL; - } assert(!PyErr_Occurred()); return PyLong_FromLong(res); } @@ -243,9 +237,6 @@ run_simplefileexflags(PyObject *mod, PyObject *args) return NULL; } - if (res == -1 && PyErr_Occurred()) { - return NULL; - } assert(!PyErr_Occurred()); return PyLong_FromLong(res); } @@ -272,9 +263,6 @@ run_anyfile(PyObject *mod, PyObject *args) assert(_Py_IsValidFD(fd)); fclose(fp); - if (res == -1 && PyErr_Occurred()) { - return NULL; - } assert(!PyErr_Occurred()); return PyLong_FromLong(res); } @@ -310,9 +298,6 @@ run_anyfileflags(PyObject *mod, PyObject *args) assert(_Py_IsValidFD(fd)); fclose(fp); - if (res == -1 && PyErr_Occurred()) { - return NULL; - } assert(!PyErr_Occurred()); return PyLong_FromLong(res); } @@ -342,9 +327,6 @@ run_anyfileex(PyObject *mod, PyObject *args) return NULL; } - if (res == -1 && PyErr_Occurred()) { - return NULL; - } assert(!PyErr_Occurred()); return PyLong_FromLong(res); } @@ -382,9 +364,6 @@ run_anyfileexflags(PyObject *mod, PyObject *args) return NULL; } - if (res == -1 && PyErr_Occurred()) { - return NULL; - } assert(!PyErr_Occurred()); return PyLong_FromLong(res); } @@ -664,9 +643,6 @@ run_simplestring(PyObject *mod, PyObject *args) } int res = PyRun_SimpleString(str); - if (res == -1 && PyErr_Occurred()) { - return NULL; - } assert(!PyErr_Occurred()); return PyLong_FromLong(res); } @@ -689,9 +665,6 @@ run_simplestringflags(PyObject *mod, PyObject *args) } int res = PyRun_SimpleStringFlags(str, pflags); - if (res == -1 && PyErr_Occurred()) { - return NULL; - } assert(!PyErr_Occurred()); return PyLong_FromLong(res); } diff --git a/Modules/main.c b/Modules/main.c index dbefbda72bede0..f57700b71fe73c 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -10,7 +10,7 @@ #include "pycore_pathconfig.h" // _PyPathConfig_ComputeSysPath0() #include "pycore_pylifecycle.h" // _Py_PreInitializeFromPyArgv() #include "pycore_pystate.h" // _PyInterpreterState_GET() -#include "pycore_pythonrun.h" // _PyRun_AnyFileObject() +#include "pycore_pythonrun.h" // _PyRun_AnyFile() #include "pycore_unicodeobject.h" // _PyUnicode_Dedent() /* Includes for exit_sigint() */ @@ -234,7 +234,6 @@ static int pymain_run_command(wchar_t *command) { PyObject *unicode, *bytes; - int ret; unicode = PyUnicode_FromWideChar(command, -1); if (unicode == NULL) { @@ -258,9 +257,18 @@ pymain_run_command(wchar_t *command) PyCompilerFlags cf = _PyCompilerFlags_INIT; cf.cf_flags |= PyCF_IGNORE_COOKIE; - ret = _PyRun_SimpleStringFlagsWithName(PyBytes_AsString(bytes), "", &cf); + PyObject *result = NULL; + PyObject *string_obj = PyUnicode_FromString(""); + if (string_obj != NULL) { + result = _PyRun_SimpleString(PyBytes_AsString(bytes), + string_obj, &cf); + Py_DECREF(string_obj); + } Py_DECREF(bytes); - return (ret != 0); + if (result == NULL) { + return pymain_exit_err_print(); + } + return 0; error: PySys_WriteStderr("Unable to decode the command from the command line:\n"); @@ -269,9 +277,9 @@ pymain_run_command(wchar_t *command) static int -pymain_start_pyrepl(int pythonstartup) +pymain_run_pyrepl(int pythonstartup) { - int res = 0; + int exitcode = 0; PyObject *console = NULL; PyObject *empty_tuple = NULL; PyObject *kwargs = NULL; @@ -281,37 +289,41 @@ pymain_start_pyrepl(int pythonstartup) PyObject *pyrepl = PyImport_ImportModule("_pyrepl.main"); if (pyrepl == NULL) { fprintf(stderr, "Could not import _pyrepl.main\n"); - res = pymain_exit_err_print(); - goto done; + goto error; } + console = PyObject_GetAttrString(pyrepl, "interactive_console"); if (console == NULL) { fprintf(stderr, "Could not access _pyrepl.main.interactive_console\n"); - res = pymain_exit_err_print(); - goto done; + goto error; } + empty_tuple = PyTuple_New(0); if (empty_tuple == NULL) { - res = pymain_exit_err_print(); - goto done; + goto error; } kwargs = PyDict_New(); if (kwargs == NULL) { - res = pymain_exit_err_print(); - goto done; + goto error; } + main_module = PyImport_AddModuleRef("__main__"); if (main_module == NULL) { - res = pymain_exit_err_print(); - goto done; + goto error; } - if (!PyDict_SetItemString(kwargs, "mainmodule", main_module) - && !PyDict_SetItemString(kwargs, "pythonstartup", pythonstartup ? Py_True : Py_False)) { - console_result = PyObject_Call(console, empty_tuple, kwargs); - if (console_result == NULL) { - res = pymain_exit_err_print(); - } + + PyObject *value = pythonstartup ? Py_True : Py_False; + if (PyDict_SetItemString(kwargs, "mainmodule", main_module) + || PyDict_SetItemString(kwargs, "pythonstartup", value)) + { + goto error; } + + console_result = PyObject_Call(console, empty_tuple, kwargs); + if (console_result == NULL) { + goto error; + } + done: Py_XDECREF(console_result); Py_XDECREF(kwargs); @@ -319,7 +331,11 @@ pymain_start_pyrepl(int pythonstartup) Py_XDECREF(console); Py_XDECREF(pyrepl); Py_XDECREF(main_module); - return res; + return exitcode; + +error: + exitcode = pymain_exit_err_print(); + goto done; } @@ -405,10 +421,14 @@ pymain_run_file_obj(PyObject *program_name, PyObject *filename, return pymain_exit_err_print(); } - /* PyRun_AnyFileExFlags(closeit=1) calls fclose(fp) before running code */ + /* _PyRun_AnyFile(closeit=1) calls fclose(fp) before running code */ PyCompilerFlags cf = _PyCompilerFlags_INIT; - int run = _PyRun_AnyFileObject(fp, filename, 1, &cf); - return (run != 0); + PyObject *result = _PyRun_AnyFile(fp, filename, 1, &cf); + if (result == NULL) { + return pymain_exit_err_print(); + } + Py_DECREF(result); + return 0; } static int @@ -416,28 +436,26 @@ pymain_run_file(const PyConfig *config) { PyObject *filename = PyUnicode_FromWideChar(config->run_filename, -1); if (filename == NULL) { - PyErr_Print(); - return -1; + return pymain_exit_err_print(); } PyObject *program_name = PyUnicode_FromWideChar(config->program_name, -1); if (program_name == NULL) { Py_DECREF(filename); - PyErr_Print(); - return -1; + return pymain_exit_err_print(); } - int res = pymain_run_file_obj(program_name, filename, - config->skip_source_first_line); + int exitcode = pymain_run_file_obj(program_name, filename, + config->skip_source_first_line); Py_DECREF(filename); Py_DECREF(program_name); - return res; + return exitcode; } static int pymain_run_startup(PyConfig *config, int *exitcode) { - int ret; + int ret = 0; if (!config->use_environment) { return 0; } @@ -477,10 +495,12 @@ pymain_run_startup(PyConfig *config, int *exitcode) } PyCompilerFlags cf = _PyCompilerFlags_INIT; - (void) _PyRun_SimpleFileObject(fp, startup, 0, &cf); - PyErr_Clear(); + PyObject *result = _PyRun_SimpleFile(fp, startup, 0, &cf); fclose(fp); - ret = 0; + if (result == NULL) { + goto error; + } + Py_DECREF(result); done: Py_XDECREF(startup); @@ -543,6 +563,47 @@ pymain_set_inspect(PyConfig *config, int inspect) } +static int +_pymain_run_repl(PyConfig *config, int startup) +{ + /* call pending calls like signal handlers (SIGINT) */ + if (Py_MakePendingCalls() == -1) { + return pymain_exit_err_print(); + } + + if (PySys_Audit("cpython.run_stdin", NULL) < 0) { + return pymain_exit_err_print(); + } + + if (isatty(fileno(stdin)) + && !_Py_GetEnv(config->use_environment, "PYTHON_BASIC_REPL")) { + PyObject *pyrepl = PyImport_ImportModule("_pyrepl"); + if (pyrepl != NULL) { + int exitcode = pymain_run_pyrepl(startup); + Py_DECREF(pyrepl); + return exitcode; + } + if (!PyErr_ExceptionMatches(PyExc_ModuleNotFoundError)) { + fprintf(stderr, "Could not import _pyrepl.main\n"); + return pymain_exit_err_print(); + } + PyErr_Clear(); + } + + PyCompilerFlags cf = _PyCompilerFlags_INIT; + PyObject *result = NULL; + PyObject *stdin_obj = PyUnicode_FromString(""); + if (stdin_obj != NULL) { + result = _PyRun_AnyFile(stdin, stdin_obj, 0, &cf); + Py_DECREF(stdin_obj); + } + if (result == NULL) { + return pymain_exit_err_print(); + } + Py_DECREF(result); + return 0; +} + static int pymain_run_stdin(PyConfig *config) { @@ -560,22 +621,7 @@ pymain_run_stdin(PyConfig *config) } } - /* call pending calls like signal handlers (SIGINT) */ - if (Py_MakePendingCalls() == -1) { - return pymain_exit_err_print(); - } - - if (PySys_Audit("cpython.run_stdin", NULL) < 0) { - return pymain_exit_err_print(); - } - - if (!isatty(fileno(stdin)) - || _Py_GetEnv(config->use_environment, "PYTHON_BASIC_REPL")) { - PyCompilerFlags cf = _PyCompilerFlags_INIT; - int run = PyRun_AnyFileExFlags(stdin, "", 0, &cf); - return (run != 0); - } - return pymain_start_pyrepl(0); + return _pymain_run_repl(config, 0); } @@ -597,20 +643,7 @@ pymain_repl(PyConfig *config, int *exitcode) return; } - if (PySys_Audit("cpython.run_stdin", NULL) < 0) { - return; - } - - if (!isatty(fileno(stdin)) - || _Py_GetEnv(config->use_environment, "PYTHON_BASIC_REPL")) { - PyCompilerFlags cf = _PyCompilerFlags_INIT; - int run = PyRun_AnyFileExFlags(stdin, "", 0, &cf); - *exitcode = (run != 0); - return; - } - int run = pymain_start_pyrepl(1); - *exitcode = (run != 0); - return; + *exitcode = _pymain_run_repl(config, 1); } diff --git a/Programs/_testembed.c b/Programs/_testembed.c index fe3e656a22ae31..6845cc7130a505 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -53,6 +53,53 @@ static void error(const char *msg) } +static void error_fmt(const char *format, ...) +{ + va_list vargs; + va_start(vargs, format); + fprintf(stderr, "ERROR: "); + vfprintf(stderr, format, vargs); + fprintf(stderr, "\n"); + va_end(vargs); + fflush(stderr); +} + + +static wchar_t* py_getenv(const char *name) +{ + const char *env = getenv(name); + if (env == NULL) { + error_fmt("need %s env var", name); + return NULL; + } + + wchar_t *result = Py_DecodeLocale(env, NULL); + if (result == NULL) { + error("Py_DecodeLocale() failed"); + return NULL; + } + return result; +} + + +static wchar_t* get_cmdline_arg(const char *arg_name) +{ + if (main_argc < 3) { + const char *test = main_argv[1]; + fprintf(stderr, "usage: %s %s %s\n", PROGRAM, test, arg_name); + return NULL; + } + const char *arg = main_argv[2]; + + wchar_t *result = Py_DecodeLocale(arg, NULL); + if (result == NULL) { + error_fmt("failed to decode %s command line argument", arg_name); + return NULL; + } + return result; +} + + static void config_set_string(PyConfig *config, wchar_t **config_str, const wchar_t *str) { PyStatus status = PyConfig_SetString(config, config_str, str); @@ -1617,14 +1664,8 @@ static int test_init_sys_add(void) static int test_init_setpath(void) { - char *env = getenv("TESTPATH"); - if (!env) { - error("missing TESTPATH env var"); - return 1; - } - wchar_t *path = Py_DecodeLocale(env, NULL); + wchar_t *path = py_getenv("TESTPATH"); if (path == NULL) { - error("failed to decode TESTPATH"); return 1; } Py_SetPath(path); @@ -1650,14 +1691,8 @@ static int test_init_setpath_config(void) Py_ExitStatusException(status); } - char *env = getenv("TESTPATH"); - if (!env) { - error("missing TESTPATH env var"); - return 1; - } - wchar_t *path = Py_DecodeLocale(env, NULL); + wchar_t *path = py_getenv("TESTPATH"); if (path == NULL) { - error("failed to decode TESTPATH"); return 1; } Py_SetPath(path); @@ -1679,14 +1714,8 @@ static int test_init_setpath_config(void) static int test_init_setpythonhome(void) { - char *env = getenv("TESTHOME"); - if (!env) { - error("missing TESTHOME env var"); - return 1; - } - wchar_t *home = Py_DecodeLocale(env, NULL); + wchar_t *home = py_getenv("TESTHOME"); if (home == NULL) { - error("failed to decode TESTHOME"); return 1; } Py_SetPythonHome(home); @@ -1704,14 +1733,8 @@ static int test_init_is_python_build(void) { // gh-91985: in-tree builds fail to check for build directory landmarks // under the effect of 'home' or PYTHONHOME environment variable. - char *env = getenv("TESTHOME"); - if (!env) { - error("missing TESTHOME env var"); - return 1; - } - wchar_t *home = Py_DecodeLocale(env, NULL); + wchar_t *home = py_getenv("TESTHOME"); if (home == NULL) { - error("failed to decode TESTHOME"); return 1; } @@ -1725,7 +1748,7 @@ static int test_init_is_python_build(void) // Use an impossible value so we can detect whether it isn't updated // during initialization. config._is_python_build = INT_MAX; - env = getenv("NEGATIVE_ISPYTHONBUILD"); + char *env = getenv("NEGATIVE_ISPYTHONBUILD"); if (env && strcmp(env, "0") != 0) { config._is_python_build = INT_MIN; } @@ -2042,6 +2065,82 @@ static int test_init_run_main(void) } +static int test_init_run_main_exitcode(Py_ssize_t argc, wchar_t * const *argv) +{ + PyConfig config; + PyConfig_InitPythonConfig(&config); + + config.parse_argv = 1; + config_set_argv(&config, argc, argv); + config_set_string(&config, &config.program_name, L"./python3"); + + init_from_config_clear(&config); + + int exitcode = Py_RunMain(); + if (exitcode != 123) { + error_fmt("Py_RunMain() returned %i, expected 123", exitcode); + return 1; + } + + // If Py_RunMain() calls Py_Exit(), this message is not written to stdout + printf("ok! Py_RunMain() returned 123\n"); + + return 0; +} + + +static int test_init_run_main_script_exitcode(void) +{ + wchar_t *filename = get_cmdline_arg("FILENAME"); + if (filename == NULL) { + return 1; + } + + wchar_t* argv[] = {L"python3", filename}; + int res = test_init_run_main_exitcode(Py_ARRAY_LENGTH(argv), argv); + PyMem_RawFree(filename); + + return res; +} + + +static int test_init_run_main_module_exitcode(void) +{ + wchar_t *module = get_cmdline_arg("MODULE"); + if (module == NULL) { + return 1; + } + + wchar_t* argv[] = {L"python3", L"-m", module}; + int res = test_init_run_main_exitcode(Py_ARRAY_LENGTH(argv), argv); + PyMem_RawFree(module); + + return res; +} + + +static int test_init_run_main_interactive_exitcode(void) +{ + wchar_t* argv[] = {L"python3", L"-i"}; + return test_init_run_main_exitcode(Py_ARRAY_LENGTH(argv), argv); +} + + +static int test_init_run_main_code_exitcode(void) +{ + wchar_t *code = get_cmdline_arg("CODE"); + if (code == NULL) { + return 1; + } + + wchar_t* argv[] = {L"python3", L"-c", code}; + int res = test_init_run_main_exitcode(Py_ARRAY_LENGTH(argv), argv); + PyMem_RawFree(code); + + return res; +} + + static int test_run_main(void) { PyConfig config; @@ -2575,6 +2674,10 @@ static struct TestCase TestCases[] = { {"test_preinit_dont_parse_argv", test_preinit_dont_parse_argv}, {"test_init_read_set", test_init_read_set}, {"test_init_run_main", test_init_run_main}, + {"test_init_run_main_code_exitcode", test_init_run_main_code_exitcode}, + {"test_init_run_main_script_exitcode", test_init_run_main_script_exitcode}, + {"test_init_run_main_module_exitcode", test_init_run_main_module_exitcode}, + {"test_init_run_main_interactive_exitcode", test_init_run_main_interactive_exitcode}, {"test_init_sys_add", test_init_sys_add}, {"test_init_setpath", test_init_setpath}, {"test_init_setpath_config", test_init_setpath_config}, diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 6e69fe0cbac1e3..74cec29f6f0ccb 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -22,7 +22,7 @@ #include "pycore_pyerrors.h" // _PyErr_GetRaisedException() #include "pycore_pylifecycle.h" // _Py_FdIsInteractive() #include "pycore_pystate.h" // _PyInterpreterState_GET() -#include "pycore_pythonrun.h" // export _PyRun_InteractiveLoopObject() +#include "pycore_pythonrun.h" // export _PyRun_AnyFile() #include "pycore_sysmodule.h" // _PySys_SetAttr() #include "pycore_traceback.h" // _PyTraceBack_Print() #include "pycore_unicodeobject.h" // _PyUnicode_Equal() @@ -41,50 +41,53 @@ # include "windows.h" #endif -/* Forward */ +/* Forward declarations */ static void flush_io(void); static PyObject *run_mod(mod_ty, PyObject *, PyObject *, PyObject *, PyCompilerFlags *, PyArena *, PyObject*, int); static PyObject *run_pyc_file(FILE *, PyObject *, PyObject *, PyCompilerFlags *); -static int PyRun_InteractiveOneObjectEx(FILE *, PyObject *, PyCompilerFlags *); -static PyObject* pyrun_file(FILE *fp, PyObject *filename, int start, - PyObject *globals, PyObject *locals, int closeit, - PyCompilerFlags *flags); static PyObject * -_PyRun_StringFlagsWithName(const char *str, PyObject* name, int start, - PyObject *globals, PyObject *locals, PyCompilerFlags *flags, - int generate_new_source); +_PyRun_String(const char *str, PyObject* name, int start, + PyObject *globals, PyObject *locals, PyCompilerFlags *flags, + int generate_new_source); +static PyObject* +_PyRun_InteractiveLoop(FILE *fp, PyObject *filename, PyCompilerFlags *flags); +static int +_PyRun_InteractiveOne(FILE *fp, PyObject *filename, PyCompilerFlags *flags); +static PyObject * +_PyRun_File(FILE *fp, PyObject *filename, int start, PyObject *globals, + PyObject *locals, int closeit, PyCompilerFlags *flags); -int -_PyRun_AnyFileObject(FILE *fp, PyObject *filename, int closeit, - PyCompilerFlags *flags) + +PyObject* +_PyRun_AnyFile(FILE *fp, PyObject *filename, int closeit, + PyCompilerFlags *flags) { int decref_filename = 0; if (filename == NULL) { filename = PyUnicode_FromString("???"); if (filename == NULL) { - PyErr_Print(); - return -1; + return NULL; } decref_filename = 1; } - int res; + PyObject *result; if (_Py_FdIsInteractive(fp, filename)) { - res = _PyRun_InteractiveLoopObject(fp, filename, flags); + result = _PyRun_InteractiveLoop(fp, filename, flags); if (closeit) { fclose(fp); } } else { - res = _PyRun_SimpleFileObject(fp, filename, closeit, flags); + result = _PyRun_SimpleFile(fp, filename, closeit, flags); } if (decref_filename) { Py_DECREF(filename); } - return res; + return result; } int @@ -99,14 +102,20 @@ PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit, return -1; } } - int res = _PyRun_AnyFileObject(fp, filename_obj, closeit, flags); + + PyObject *result = _PyRun_AnyFile(fp, filename_obj, closeit, flags); Py_XDECREF(filename_obj); - return res; + if (result == NULL) { + PyErr_Print(); + return -1; + } + Py_DECREF(result); + return 0; } -int -_PyRun_InteractiveLoopObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags) +static PyObject* +_PyRun_InteractiveLoop(FILE *fp, PyObject *filename, PyCompilerFlags *flags) { PyCompilerFlags local_flags = _PyCompilerFlags_INIT; if (flags == NULL) { @@ -115,8 +124,7 @@ _PyRun_InteractiveLoopObject(FILE *fp, PyObject *filename, PyCompilerFlags *flag PyObject *v; if (_PySys_GetOptionalAttr(&_Py_ID(ps1), &v) < 0) { - PyErr_Print(); - return -1; + return NULL; } if (v == NULL) { v = PyUnicode_FromString(">>> "); @@ -129,8 +137,7 @@ _PyRun_InteractiveLoopObject(FILE *fp, PyObject *filename, PyCompilerFlags *flag } Py_XDECREF(v); if (_PySys_GetOptionalAttr(&_Py_ID(ps2), &v) < 0) { - PyErr_Print(); - return -1; + return NULL; } if (v == NULL) { v = PyUnicode_FromString("... "); @@ -146,36 +153,41 @@ _PyRun_InteractiveLoopObject(FILE *fp, PyObject *filename, PyCompilerFlags *flag #ifdef Py_REF_DEBUG int show_ref_count = _Py_GetConfig()->show_ref_count; #endif - int err = 0; - int ret; int nomem_count = 0; + int ret; do { - ret = PyRun_InteractiveOneObjectEx(fp, filename, flags); + ret = _PyRun_InteractiveOne(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; + return NULL; } } else { nomem_count = 0; } + + int inspect = _Py_GetConfig()->inspect; + if (!inspect && PyErr_ExceptionMatches(PyExc_SystemExit)) { + return NULL; + } + PyErr_Print(); - flush_io(); - } else { + } + else { nomem_count = 0; } + #ifdef Py_REF_DEBUG if (show_ref_count) { _PyDebug_PrintTotalRefs(); } #endif } while (ret != E_EOF); - return err; + + Py_RETURN_NONE; } @@ -188,10 +200,14 @@ PyRun_InteractiveLoopFlags(FILE *fp, const char *filename, PyCompilerFlags *flag return -1; } - int err = _PyRun_InteractiveLoopObject(fp, filename_obj, flags); + PyObject *result = _PyRun_InteractiveLoop(fp, filename_obj, flags); Py_DECREF(filename_obj); - return err; - + if (result == NULL) { + PyErr_Print(); + return -1; + } + Py_DECREF(result); + return 0; } @@ -287,8 +303,7 @@ pyrun_one_parse_ast(FILE *fp, PyObject *filename, /* A PyRun_InteractiveOneObject() auxiliary function that does not print the * error on failure. */ static int -PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename, - PyCompilerFlags *flags) +_PyRun_InteractiveOne(FILE *fp, PyObject *filename, PyCompilerFlags *flags) { if (!PyUnicode_Check(filename)) { PyErr_Format(PyExc_TypeError, "expect str for filename, got %T", @@ -362,9 +377,7 @@ PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename, int PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags) { - int res; - - res = PyRun_InteractiveOneObjectEx(fp, filename, flags); + int res = _PyRun_InteractiveOne(fp, filename, flags); if (res == -1) { PyErr_Print(); flush_io(); @@ -464,24 +477,26 @@ set_main_loader(PyObject *d, PyObject *filename, const char *loader_name) } -int -_PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit, - PyCompilerFlags *flags) +PyObject* +_PyRun_SimpleFile(FILE *fp, PyObject *filename, int closeit, + PyCompilerFlags *flags) { - int ret = -1; + PyObject *res = NULL; + int set_file_name = 0; PyObject *main_module = PyImport_AddModuleRef("__main__"); - if (main_module == NULL) - return -1; + if (main_module == NULL) { + goto done; + } PyObject *dict = PyModule_GetDict(main_module); // borrowed ref - int set_file_name = 0; int has_file = PyDict_ContainsString(dict, "__file__"); if (has_file < 0) { goto done; } if (!has_file) { if (PyDict_SetItemString(dict, "__file__", filename) < 0) { + fprintf(stderr, "python: failed to set __main__.__file__\n"); goto done; } if (PyDict_SetItemString(dict, "__cached__", Py_None) < 0) { @@ -495,7 +510,6 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit, goto done; } - PyObject *v; if (pyc) { FILE *pyc_fp; /* Try to run a pyc file. First, re-open in binary */ @@ -511,42 +525,34 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit, if (set_main_loader(dict, filename, "SourcelessFileLoader") < 0) { fprintf(stderr, "python: failed to set __main__.__loader__\n"); - ret = -1; fclose(pyc_fp); goto done; } - v = run_pyc_file(pyc_fp, dict, dict, flags); + res = run_pyc_file(pyc_fp, dict, dict, flags); } else { /* When running from stdin, leave __main__.__loader__ alone */ if ((!PyUnicode_Check(filename) || !PyUnicode_EqualToUTF8(filename, "")) && set_main_loader(dict, filename, "SourceFileLoader") < 0) { fprintf(stderr, "python: failed to set __main__.__loader__\n"); - ret = -1; goto done; } - v = pyrun_file(fp, filename, Py_file_input, dict, dict, - closeit, flags); + res = _PyRun_File(fp, filename, Py_file_input, dict, dict, + closeit, flags); } flush_io(); - if (v == NULL) { - Py_CLEAR(main_module); - PyErr_Print(); - goto done; - } - Py_DECREF(v); - ret = 0; done: if (set_file_name) { if (PyDict_PopString(dict, "__file__", NULL) < 0) { - PyErr_Print(); + fprintf(stderr, "python: failed to delete __main__.__file__\n"); + Py_CLEAR(res); } if (PyDict_PopString(dict, "__cached__", NULL) < 0) { PyErr_Print(); } } Py_XDECREF(main_module); - return ret; + return res; } @@ -556,50 +562,47 @@ PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit, { PyObject *filename_obj = PyUnicode_DecodeFSDefault(filename); if (filename_obj == NULL) { + PyErr_Print(); return -1; } - int res = _PyRun_SimpleFileObject(fp, filename_obj, closeit, flags); + PyObject *result = _PyRun_SimpleFile(fp, filename_obj, closeit, flags); Py_DECREF(filename_obj); - return res; + if (result == NULL) { + PyErr_Print(); + return -1; + } + return 0; } -int -_PyRun_SimpleStringFlagsWithName(const char *command, const char* name, PyCompilerFlags *flags) { +PyObject* +_PyRun_SimpleString(const char *command, PyObject* name, + PyCompilerFlags *flags) +{ PyObject *main_module = PyImport_AddModuleRef("__main__"); if (main_module == NULL) { - return -1; + return NULL; } PyObject *dict = PyModule_GetDict(main_module); // borrowed ref - PyObject *res = NULL; - if (name == NULL) { - res = PyRun_StringFlags(command, Py_file_input, dict, dict, flags); - } else { - PyObject* the_name = PyUnicode_FromString(name); - if (!the_name) { - PyErr_Print(); - return -1; - } - res = _PyRun_StringFlagsWithName(command, the_name, Py_file_input, dict, dict, flags, 0); - Py_DECREF(the_name); - } + PyObject *res = _PyRun_String(command, name, Py_file_input, + dict, dict, flags, 0); Py_DECREF(main_module); + return res; +} + +int +PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags) +{ + PyObject *res = _PyRun_SimpleString(command, NULL, flags); if (res == NULL) { PyErr_Print(); return -1; } - Py_DECREF(res); return 0; } -int -PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags) -{ - return _PyRun_SimpleStringFlagsWithName(command, NULL, flags); -} - static int parse_exit_code(PyObject *code, int *exitcode_p) { @@ -1236,9 +1239,9 @@ void PyErr_DisplayException(PyObject *exc) } static PyObject * -_PyRun_StringFlagsWithName(const char *str, PyObject* name, int start, - PyObject *globals, PyObject *locals, PyCompilerFlags *flags, - int generate_new_source) +_PyRun_String(const char *str, PyObject* name, int start, + PyObject *globals, PyObject *locals, PyCompilerFlags *flags, + int generate_new_source) { PyObject *ret = NULL; mod_ty mod; @@ -1275,12 +1278,12 @@ PyObject * PyRun_StringFlags(const char *str, int start, PyObject *globals, PyObject *locals, PyCompilerFlags *flags) { - return _PyRun_StringFlagsWithName(str, NULL, start, globals, locals, flags, 0); + return _PyRun_String(str, NULL, start, globals, locals, flags, 0); } static PyObject * -pyrun_file(FILE *fp, PyObject *filename, int start, PyObject *globals, - PyObject *locals, int closeit, PyCompilerFlags *flags) +_PyRun_File(FILE *fp, PyObject *filename, int start, PyObject *globals, + PyObject *locals, int closeit, PyCompilerFlags *flags) { PyArena *arena = _PyArena_New(); if (arena == NULL) { @@ -1314,11 +1317,12 @@ PyRun_FileExFlags(FILE *fp, const char *filename, int start, PyObject *globals, { PyObject *filename_obj = PyUnicode_DecodeFSDefault(filename); if (filename_obj == NULL) { + PyErr_Print(); return NULL; } - PyObject *res = pyrun_file(fp, filename_obj, start, globals, - locals, closeit, flags); + PyObject *res = _PyRun_File(fp, filename_obj, start, globals, + locals, closeit, flags); Py_DECREF(filename_obj); return res;