]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-152132: Fix Py_RunMain() to return an exit code (#153446)
authorVictor Stinner <vstinner@python.org>
Thu, 9 Jul 2026 18:28:15 +0000 (20:28 +0200)
committerGitHub <noreply@github.com>
Thu, 9 Jul 2026 18:28:15 +0000 (18:28 +0000)
* 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.

Include/internal/pycore_pylifecycle.h
Include/internal/pycore_pythonrun.h
Lib/test/test_embed.py
Misc/NEWS.d/next/C_API/2026-07-09-17-33-29.gh-issue-152132.Ma8MJZ.rst [new file with mode: 0644]
Modules/_testcapi/run.c
Modules/main.c
Programs/_testembed.c
Python/pythonrun.c

index 12e1e78526db35aa2e3dd0af6077055b820eecfc..ab627c28c1fa5ee4204fc74710cf135e155e7659 100644 (file)
@@ -108,9 +108,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 */
 
index 66dd7cd843b04fc6f2452c8053951744fb99d4d5..b2126f3f71bb46eb2139b12a969ae1b5696418ec 100644 (file)
@@ -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,
@@ -39,6 +34,12 @@ extern PyObject * _Py_CompileStringObjectWithModule(
     PyCompilerFlags *flags, int optimize,
     PyObject *module);
 
+// 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
index d0ac1d1c0cb1a15d0ca5cfe5d41ee144e6cd5ded..40036609a1905510cd3a47b5abfba52561ae0430 100644 (file)
@@ -68,6 +68,9 @@ STDLIB_INSTALL = os.path.join(sys.prefix, sys.platlibdir,
 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 @@ class EmbeddingTestsMixin:
             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,55 @@ class EmbeddingTests(EmbeddingTestsMixin, unittest.TestCase):
         ]
         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 (file)
index 0000000..633c41e
--- /dev/null
@@ -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.
index 4a63c0f98a74d4c8dadc4f81949fcb6cfd872962..7fc180e136559bab7d17c1b624871c044cf2bcb4 100644 (file)
@@ -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);
 }
index 2f23513cc2403dcfab9627e34c067492b82d0c70..a65247dc1e5c61756e601495e0875d1a4a59288f 100644 (file)
@@ -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_tuple.h"         // _PyTuple_FromPair
 #include "pycore_unicodeobject.h" // _PyUnicode_Dedent()
 
@@ -235,7 +235,6 @@ static int
 pymain_run_command(wchar_t *command)
 {
     PyObject *unicode, *bytes;
-    int ret;
 
     unicode = PyUnicode_FromWideChar(command, -1);
     if (unicode == NULL) {
@@ -259,9 +258,18 @@ pymain_run_command(wchar_t *command)
 
     PyCompilerFlags cf = _PyCompilerFlags_INIT;
     cf.cf_flags |= PyCF_IGNORE_COOKIE;
-    ret = _PyRun_SimpleStringFlagsWithName(PyBytes_AsString(bytes), "<string>", &cf);
+    PyObject *result = NULL;
+    PyObject *string_obj = PyUnicode_FromString("<string>");
+    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");
@@ -270,9 +278,9 @@ error:
 
 
 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;
@@ -282,37 +290,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);
@@ -320,7 +332,11 @@ done:
     Py_XDECREF(console);
     Py_XDECREF(pyrepl);
     Py_XDECREF(main_module);
-    return res;
+    return exitcode;
+
+error:
+    exitcode = pymain_exit_err_print();
+    goto done;
 }
 
 
@@ -406,10 +422,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
@@ -417,28 +437,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;
     }
@@ -478,10 +496,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);
@@ -542,22 +562,8 @@ pymain_set_inspect(PyConfig *config, int inspect)
 
 
 static int
-pymain_run_stdin(PyConfig *config)
+_pymain_run_repl(PyConfig *config, int startup)
 {
-    if (stdin_is_interactive(config)) {
-        // do exit on SystemExit
-        pymain_set_inspect(config, 0);
-
-        int exitcode;
-        if (pymain_run_startup(config, &exitcode)) {
-            return exitcode;
-        }
-
-        if (pymain_run_interactive_hook(&exitcode)) {
-            return exitcode;
-        }
-    }
-
     /* call pending calls like signal handlers (SIGINT) */
     if (Py_MakePendingCalls() == -1) {
         return pymain_exit_err_print();
@@ -567,14 +573,13 @@ pymain_run_stdin(PyConfig *config)
         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);
+            int exitcode = pymain_run_pyrepl(startup);
             Py_DECREF(pyrepl);
-            return run;
+            return exitcode;
         }
         if (!PyErr_ExceptionMatches(PyExc_ModuleNotFoundError)) {
             fprintf(stderr, "Could not import _pyrepl.main\n");
@@ -584,8 +589,37 @@ pymain_run_stdin(PyConfig *config)
     }
 
     PyCompilerFlags cf = _PyCompilerFlags_INIT;
-    run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, &cf);
-    return (run != 0);
+    PyObject *result = NULL;
+    PyObject *stdin_obj = PyUnicode_FromString("<stdin>");
+    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)
+{
+    if (stdin_is_interactive(config)) {
+        // do exit on SystemExit
+        pymain_set_inspect(config, 0);
+
+        int exitcode;
+        if (pymain_run_startup(config, &exitcode)) {
+            return exitcode;
+        }
+
+        if (pymain_run_interactive_hook(&exitcode)) {
+            return exitcode;
+        }
+    }
+
+    return _pymain_run_repl(config, 0);
 }
 
 
@@ -607,30 +641,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")) {
-        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, "<stdin>", 0, &cf);
-    *exitcode = (run != 0);
-    return;
+    *exitcode = _pymain_run_repl(config, 1);
 }
 
 
index 74d0fe37ddaeadb14e04000305ead4cd8e9aabc1..418609abc5f6b82b190b0e633e78a47d5e606576 100644 (file)
@@ -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 @@ fail:
 
 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},
index 8c441e7ebd3c79f4c83c8454d04a25a69c228595..e0a752720dfcc2debea27212b82ee68a19a665f2 100644 (file)
@@ -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()
 #  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 @@ error:
 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;
         }
         set_file_name = 1;
@@ -492,7 +507,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 */
@@ -508,39 +522,31 @@ _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, "<stdin>")) &&
             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);
         }
     }
     Py_XDECREF(main_module);
-    return ret;
+    return res;
 }
 
 
@@ -550,51 +556,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();
-            Py_DECREF(main_module);
-            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)
 {
@@ -1231,9 +1233,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;
@@ -1277,12 +1279,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) {
@@ -1316,11 +1318,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;