diff --git a/Include/errcode.h b/Include/errcode.h index dac5cf068c99d6d..ce9daeaa569aeac 100644 --- a/Include/errcode.h +++ b/Include/errcode.h @@ -6,7 +6,7 @@ // the parser only returns E_EOF when it hits EOF immediately, and it // never returns E_OK. // -// The public PyRun_InteractiveOneObjectEx() function can return E_EOF, +// The public PyRun_InteractiveOneObject() function can return E_EOF, // same as its variants: // // * PyRun_InteractiveOneObject() @@ -38,6 +38,7 @@ extern "C" { #define E_BADSINGLE 27 /* Ill-formed single statement input */ #define E_INTERACT_STOP 28 /* Interactive mode stopped tokenization */ #define E_COLUMNOVERFLOW 29 /* Column offset overflow */ +#define E_EXITCODE 30 /* (internal) got SystemExit, use exitcode */ #ifdef __cplusplus } diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h index 12e1e78526db35a..3d7d5bfaaccf462 100644 --- a/Include/internal/pycore_pylifecycle.h +++ b/Include/internal/pycore_pylifecycle.h @@ -109,7 +109,11 @@ extern int _Py_LegacyLocaleDetected(int warn); 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); +extern int _PyRun_SimpleString( + const char *command, + const char* name, + PyCompilerFlags *flags, + int *exitcode); /* interpreter config */ diff --git a/Include/internal/pycore_pythonrun.h b/Include/internal/pycore_pythonrun.h index 66dd7cd843b04fc..c57a90fef98fe73 100644 --- a/Include/internal/pycore_pythonrun.h +++ b/Include/internal/pycore_pythonrun.h @@ -8,22 +8,25 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -extern int _PyRun_SimpleFileObject( +extern int _PyRun_SimpleFile( FILE *fp, PyObject *filename, int closeit, - PyCompilerFlags *flags); + PyCompilerFlags *flags, + int *exitcode); -extern int _PyRun_AnyFileObject( +extern int _PyRun_AnyFile( FILE *fp, PyObject *filename, int closeit, - PyCompilerFlags *flags); + PyCompilerFlags *flags, + int *exitcode); -extern int _PyRun_InteractiveLoopObject( +extern int _PyRun_InteractiveLoop( FILE *fp, PyObject *filename, - PyCompilerFlags *flags); + PyCompilerFlags *flags, + int *exitcode); extern int _PyObject_SupportedAsScript(PyObject *); extern const char* _Py_SourceAsString( diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index d0ac1d1c0cb1a15..a3cb20fcd05d831 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] @@ -140,12 +143,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: @@ -590,6 +597,43 @@ 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_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-07-21-25-09.gh-issue-152132.fXTUSD.rst b/Misc/NEWS.d/next/C_API/2026-07-07-21-25-09.gh-issue-152132.fXTUSD.rst new file mode 100644 index 000000000000000..633c41e40bf2696 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-07-07-21-25-09.gh-issue-152132.fXTUSD.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/main.c b/Modules/main.c index a4dfddd98e257e2..5edb3f2164c380f 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -1,6 +1,8 @@ /* Python interpreter main program */ #include "Python.h" +#include "errcode.h" // E_EXITCODE + #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_fileutils.h" // struct _Py_stat_struct #include "pycore_import.h" // _PyImport_Fini2() @@ -10,7 +12,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_tuple.h" // _PyTuple_FromPair #include "pycore_unicodeobject.h" // _PyUnicode_Dedent() @@ -259,8 +261,14 @@ pymain_run_command(wchar_t *command) PyCompilerFlags cf = _PyCompilerFlags_INIT; cf.cf_flags |= PyCF_IGNORE_COOKIE; - ret = _PyRun_SimpleStringFlagsWithName(PyBytes_AsString(bytes), "", &cf); + int exitcode = 0; + ret = _PyRun_SimpleString(PyBytes_AsString(bytes), "", + &cf, &exitcode); Py_DECREF(bytes); + + if (ret == E_EXITCODE) { + return exitcode; + } return (ret != 0); error: @@ -270,7 +278,7 @@ pymain_run_command(wchar_t *command) static int -pymain_start_pyrepl(int pythonstartup) +pymain_run_pyrepl(int pythonstartup) { int res = 0; PyObject *console = NULL; @@ -282,37 +290,37 @@ 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 *pythonstartup_obj = pythonstartup ? Py_True : Py_False; + if (PyDict_SetItemString(kwargs, "mainmodule", main_module) < 0 + || PyDict_SetItemString(kwargs, "pythonstartup", pythonstartup_obj) < 0) + { + goto error; + } + + console_result = PyObject_Call(console, empty_tuple, kwargs); + if (console_result == NULL) { + goto error; } + done: Py_XDECREF(console_result); Py_XDECREF(kwargs); @@ -321,6 +329,10 @@ pymain_start_pyrepl(int pythonstartup) Py_XDECREF(pyrepl); Py_XDECREF(main_module); return res; + +error: + res = pymain_exit_err_print(); + goto done; } @@ -329,19 +341,19 @@ pymain_run_module(const wchar_t *modname, int set_argv0) { PyObject *module, *runmodule, *runargs, *result; if (PySys_Audit("cpython.run_module", "u", modname) < 0) { - return pymain_exit_err_print(); + goto error; } runmodule = PyImport_ImportModuleAttrString("runpy", "_run_module_as_main"); if (runmodule == NULL) { fprintf(stderr, "Could not import runpy._run_module_as_main\n"); - return pymain_exit_err_print(); + goto error; } module = PyUnicode_FromWideChar(modname, -1); if (module == NULL) { fprintf(stderr, "Could not convert module name to unicode\n"); Py_DECREF(runmodule); - return pymain_exit_err_print(); + goto error; } runargs = _PyTuple_FromPair(module, set_argv0 ? Py_True : Py_False); if (runargs == NULL) { @@ -349,17 +361,20 @@ pymain_run_module(const wchar_t *modname, int set_argv0) "Could not create arguments for runpy._run_module_as_main\n"); Py_DECREF(runmodule); Py_DECREF(module); - return pymain_exit_err_print(); + goto error; } result = PyObject_Call(runmodule, runargs, NULL); Py_DECREF(runmodule); Py_DECREF(module); Py_DECREF(runargs); if (result == NULL) { - return pymain_exit_err_print(); + goto error; } Py_DECREF(result); return 0; + +error: + return pymain_exit_err_print(); } @@ -406,9 +421,13 @@ 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); + int exitcode = 0; + int run = _PyRun_AnyFile(fp, filename, 1, &cf, &exitcode); + if (run == E_EXITCODE) { + return exitcode; + } return (run != 0); } @@ -417,14 +436,12 @@ 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, @@ -478,7 +495,7 @@ pymain_run_startup(PyConfig *config, int *exitcode) } PyCompilerFlags cf = _PyCompilerFlags_INIT; - (void) _PyRun_SimpleFileObject(fp, startup, 0, &cf); + (void) _PyRun_SimpleFile(fp, startup, 0, &cf, NULL); PyErr_Clear(); fclose(fp); ret = 0; @@ -537,6 +554,49 @@ _Py_COMP_DIAG_POP } +static int +pymain_run_repl(PyConfig *config, int pythonstartup) +{ + if (PySys_Audit("cpython.run_stdin", NULL) < 0) { + goto error; + } + + if (isatty(fileno(stdin)) + && !_Py_GetEnv(config->use_environment, "PYTHON_BASIC_REPL")) + { + PyObject *pyrepl = PyImport_ImportModule("_pyrepl"); + if (pyrepl != NULL) { + int run = pymain_run_pyrepl(pythonstartup); + Py_DECREF(pyrepl); + return run; + } + + if (!PyErr_ExceptionMatches(PyExc_ModuleNotFoundError)) { + fprintf(stderr, "Could not import _pyrepl.main\n"); + goto error; + } + PyErr_Clear(); + } + + PyCompilerFlags cf = _PyCompilerFlags_INIT; + PyObject *filename = PyUnicode_FromString(""); + if (filename == NULL) { + goto error; + } + + int exitcode = 0; + int run = _PyRun_AnyFile(stdin, filename, 0, &cf, &exitcode); + Py_DECREF(filename); + if (run == E_EXITCODE) { + return exitcode; + } + return (run != 0); + +error: + return pymain_exit_err_print(); +} + + static int pymain_run_stdin(PyConfig *config) { @@ -559,29 +619,7 @@ pymain_run_stdin(PyConfig *config) return pymain_exit_err_print(); } - if (PySys_Audit("cpython.run_stdin", NULL) < 0) { - return pymain_exit_err_print(); - } - - int run; - if (isatty(fileno(stdin)) - && !_Py_GetEnv(config->use_environment, "PYTHON_BASIC_REPL")) { - PyObject *pyrepl = PyImport_ImportModule("_pyrepl"); - if (pyrepl != NULL) { - run = pymain_start_pyrepl(0); - Py_DECREF(pyrepl); - return run; - } - if (!PyErr_ExceptionMatches(PyExc_ModuleNotFoundError)) { - fprintf(stderr, "Could not import _pyrepl.main\n"); - return pymain_exit_err_print(); - } - PyErr_Clear(); - } - - PyCompilerFlags cf = _PyCompilerFlags_INIT; - run = PyRun_AnyFileExFlags(stdin, "", 0, &cf); - return (run != 0); + return pymain_run_repl(config, 0); } @@ -595,38 +633,17 @@ pymain_repl(PyConfig *config, int *exitcode) } if (!(config->inspect && stdin_is_interactive(config) && config_run_code(config))) { + // leave exitcode unchanged return; } pymain_set_inspect(config, 0); if (pymain_run_interactive_hook(exitcode)) { + // leave exitcode unchanged return; } - if (PySys_Audit("cpython.run_stdin", NULL) < 0) { - return; - } - - if (isatty(fileno(stdin)) - && !_Py_GetEnv(config->use_environment, "PYTHON_BASIC_REPL")) { - PyObject *pyrepl = PyImport_ImportModule("_pyrepl"); - if (pyrepl != NULL) { - int run = pymain_start_pyrepl(1); - *exitcode = (run != 0); - Py_DECREF(pyrepl); - return; - } - if (!PyErr_ExceptionMatches(PyExc_ModuleNotFoundError)) { - PyErr_Clear(); - fprintf(stderr, "Could not import _pyrepl.main\n"); - return; - } - PyErr_Clear(); - } - PyCompilerFlags cf = _PyCompilerFlags_INIT; - int run = PyRun_AnyFileExFlags(stdin, "", 0, &cf); - *exitcode = (run != 0); - return; + *exitcode = pymain_run_repl(config, 1); } @@ -789,10 +806,9 @@ pymain_exit_error(PyStatus status) int Py_RunMain(void) { - int exitcode = 0; - _PyRuntime.signals.unhandled_keyboard_interrupt = 0; + int exitcode = 0; pymain_run_python(&exitcode); if (Py_FinalizeEx() < 0) { diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 74d0fe37ddaeadb..418609abc5f6b82 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -64,6 +64,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); @@ -1564,14 +1611,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); @@ -1597,14 +1638,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); @@ -1626,14 +1661,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); @@ -1651,14 +1680,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; } @@ -1672,7 +1695,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; } @@ -1997,6 +2020,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_init_main(void) { PyConfig config; @@ -2938,6 +3037,10 @@ static struct TestCase TestCases[] = { {"test_preinit_parse_argv", test_preinit_parse_argv}, {"test_preinit_dont_parse_argv", test_preinit_dont_parse_argv}, {"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_main", test_init_main}, {"test_init_sys_add", test_init_sys_add}, {"test_init_setpath", test_init_setpath}, diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 971ab064777a418..8d93469219dca72 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_InteractiveLoop() #include "pycore_sysmodule.h" // _PySys_SetAttr() #include "pycore_traceback.h" // _PyTraceBack_Print() #include "pycore_unicodeobject.h" // _PyUnicode_Equal() @@ -41,7 +41,7 @@ # 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); @@ -55,16 +55,20 @@ static PyObject * _PyRun_StringFlagsWithName(const char *str, PyObject* name, int start, PyObject *globals, PyObject *locals, PyCompilerFlags *flags, int generate_new_source); +static int _PyErr_PrintWithExitcode(int *exitcode); + int -_PyRun_AnyFileObject(FILE *fp, PyObject *filename, int closeit, - PyCompilerFlags *flags) +_PyRun_AnyFile(FILE *fp, PyObject *filename, int closeit, + PyCompilerFlags *flags, int *exitcode) { int decref_filename = 0; if (filename == NULL) { filename = PyUnicode_FromString("???"); if (filename == NULL) { - PyErr_Print(); + if (_PyErr_PrintWithExitcode(exitcode)) { + return E_EXITCODE; + } return -1; } decref_filename = 1; @@ -72,13 +76,13 @@ _PyRun_AnyFileObject(FILE *fp, PyObject *filename, int closeit, int res; if (_Py_FdIsInteractive(fp, filename)) { - res = _PyRun_InteractiveLoopObject(fp, filename, flags); + res = _PyRun_InteractiveLoop(fp, filename, flags, exitcode); if (closeit) { fclose(fp); } } else { - res = _PyRun_SimpleFileObject(fp, filename, closeit, flags); + res = _PyRun_SimpleFile(fp, filename, closeit, flags, exitcode); } if (decref_filename) { @@ -99,14 +103,15 @@ PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit, return -1; } } - int res = _PyRun_AnyFileObject(fp, filename_obj, closeit, flags); + int res = _PyRun_AnyFile(fp, filename_obj, closeit, flags, NULL); Py_XDECREF(filename_obj); return res; } int -_PyRun_InteractiveLoopObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags) +_PyRun_InteractiveLoop(FILE *fp, PyObject *filename, + PyCompilerFlags *flags, int *exitcode) { PyCompilerFlags local_flags = _PyCompilerFlags_INIT; if (flags == NULL) { @@ -115,8 +120,7 @@ _PyRun_InteractiveLoopObject(FILE *fp, PyObject *filename, PyCompilerFlags *flag PyObject *v; if (PySys_GetOptionalAttr(&_Py_ID(ps1), &v) < 0) { - PyErr_Print(); - return -1; + goto error; } if (v == NULL) { v = PyUnicode_FromString(">>> "); @@ -129,8 +133,7 @@ _PyRun_InteractiveLoopObject(FILE *fp, PyObject *filename, PyCompilerFlags *flag } Py_XDECREF(v); if (PySys_GetOptionalAttr(&_Py_ID(ps2), &v) < 0) { - PyErr_Print(); - return -1; + goto error; } if (v == NULL) { v = PyUnicode_FromString("... "); @@ -164,7 +167,10 @@ _PyRun_InteractiveLoopObject(FILE *fp, PyObject *filename, PyCompilerFlags *flag } else { nomem_count = 0; } - PyErr_Print(); + if (_PyErr_PrintWithExitcode(exitcode)) { + err = E_EXITCODE; + break; + } flush_io(); } else { nomem_count = 0; @@ -176,6 +182,12 @@ _PyRun_InteractiveLoopObject(FILE *fp, PyObject *filename, PyCompilerFlags *flag #endif } while (ret != E_EOF); return err; + +error: + if (_PyErr_PrintWithExitcode(exitcode)) { + return E_EXITCODE; + } + return -1; } @@ -188,7 +200,7 @@ PyRun_InteractiveLoopFlags(FILE *fp, const char *filename, PyCompilerFlags *flag return -1; } - int err = _PyRun_InteractiveLoopObject(fp, filename_obj, flags); + int err = _PyRun_InteractiveLoop(fp, filename_obj, flags, NULL); Py_DECREF(filename_obj); return err; @@ -459,8 +471,8 @@ set_main_loader(PyObject *d, PyObject *filename, const char *loader_name) int -_PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit, - PyCompilerFlags *flags) +_PyRun_SimpleFile(FILE *fp, PyObject *filename, int closeit, + PyCompilerFlags *flags, int *exitcode) { int ret = -1; @@ -521,7 +533,9 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit, flush_io(); if (v == NULL) { Py_CLEAR(main_module); - PyErr_Print(); + if (_PyErr_PrintWithExitcode(exitcode)) { + ret = E_EXITCODE; + } goto done; } Py_DECREF(v); @@ -530,7 +544,9 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit, done: if (set_file_name) { if (PyDict_PopString(dict, "__file__", NULL) < 0) { - PyErr_Print(); + if (_PyErr_PrintWithExitcode(exitcode)) { + ret = E_EXITCODE; + } } } Py_XDECREF(main_module); @@ -546,14 +562,16 @@ PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit, if (filename_obj == NULL) { return -1; } - int res = _PyRun_SimpleFileObject(fp, filename_obj, closeit, flags); + int res = _PyRun_SimpleFile(fp, filename_obj, closeit, flags, NULL); Py_DECREF(filename_obj); return res; } int -_PyRun_SimpleStringFlagsWithName(const char *command, const char* name, PyCompilerFlags *flags) { +_PyRun_SimpleString(const char *command, const char* name, + PyCompilerFlags *flags, int *exitcode) +{ PyObject *main_module = PyImport_AddModuleRef("__main__"); if (main_module == NULL) { return -1; @@ -566,8 +584,10 @@ _PyRun_SimpleStringFlagsWithName(const char *command, const char* name, PyCompil } else { PyObject* the_name = PyUnicode_FromString(name); if (!the_name) { - PyErr_Print(); Py_DECREF(main_module); + if (_PyErr_PrintWithExitcode(exitcode)) { + return E_EXITCODE; + } return -1; } res = _PyRun_StringFlagsWithName(command, the_name, Py_file_input, dict, dict, flags, 0); @@ -575,7 +595,9 @@ _PyRun_SimpleStringFlagsWithName(const char *command, const char* name, PyCompil } Py_DECREF(main_module); if (res == NULL) { - PyErr_Print(); + if (_PyErr_PrintWithExitcode(exitcode)) { + return E_EXITCODE; + } return -1; } @@ -586,7 +608,7 @@ _PyRun_SimpleStringFlagsWithName(const char *command, const char* name, PyCompil int PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags) { - return _PyRun_SimpleStringFlagsWithName(command, NULL, flags); + return _PyRun_SimpleString(command, NULL, flags, NULL); } static int @@ -675,21 +697,36 @@ _Py_HandleSystemExitAndKeyboardInterrupt(int *exitcode_p) } -static void -handle_system_exit(void) +static int +handle_system_exit(int *exitcode_p) { - int exitcode; - if (_Py_HandleSystemExitAndKeyboardInterrupt(&exitcode)) { - Py_Exit(exitcode); + if (exitcode_p != NULL) { + if (_Py_HandleSystemExitAndKeyboardInterrupt(exitcode_p)) { + return 1; + } + } + else { + int exitcode; + if (_Py_HandleSystemExitAndKeyboardInterrupt(&exitcode)) { + Py_Exit(exitcode); + } } + return 0; } -static void -_PyErr_PrintEx(PyThreadState *tstate, int set_sys_last_vars) +// Print the current exception. +// Return 1 if the exitcode is not NULL and was set to a value. +// Return 0 otherwise. +static int +_PyErr_PrintEx(PyThreadState *tstate, int set_sys_last_vars, int *exitcode) { + int res = 0; + PyObject *typ = NULL, *tb = NULL, *hook = NULL; - handle_system_exit(); + if (handle_system_exit(exitcode)) { + return 1; + } PyObject *exc = _PyErr_GetRaisedException(tstate); if (exc == NULL) { @@ -732,7 +769,9 @@ _PyErr_PrintEx(PyThreadState *tstate, int set_sys_last_vars) PyObject* args[3] = {typ, exc, tb}; PyObject *result = PyObject_Vectorcall(hook, args, 3, NULL); if (result == NULL) { - handle_system_exit(); + if (handle_system_exit(exitcode)) { + res = 1; + } PyObject *exc2 = _PyErr_GetRaisedException(tstate); assert(exc2 && PyExceptionInstance_Check(exc2)); @@ -757,19 +796,27 @@ _PyErr_PrintEx(PyThreadState *tstate, int set_sys_last_vars) Py_XDECREF(typ); Py_XDECREF(exc); Py_XDECREF(tb); + return res; +} + +static int +_PyErr_PrintWithExitcode(int *exitcode) +{ + PyThreadState *tstate = _PyThreadState_GET(); + return _PyErr_PrintEx(tstate, 1, exitcode); } void _PyErr_Print(PyThreadState *tstate) { - _PyErr_PrintEx(tstate, 1); + (void)_PyErr_PrintEx(tstate, 1, NULL); } void PyErr_PrintEx(int set_sys_last_vars) { PyThreadState *tstate = _PyThreadState_GET(); - _PyErr_PrintEx(tstate, set_sys_last_vars); + (void)_PyErr_PrintEx(tstate, set_sys_last_vars, NULL); } void