From 60d2b6be3187c09837e1c31b74346d79acd7f6be Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 9 Jul 2026 18:01:04 +0200 Subject: [PATCH] gh-152132: Fix bugs and enhance Py_RunMain() * Check for signals more often. Previously, a pending exception could be removed by PyErr_Clear(). * Only call _PyInterpreterState_SetNotRunningMain() if _PyInterpreterState_SetRunningMain() has been called. * Add pymain_set_path0() function. * Simplify pymain_run_startup(): avoid strerror() which is decoded from the wrong encoding. Instead display the exception, Py_fopen() raises an OSError with the path object. * Add pymain_error() and pyrun_error(): write the error message to sys.stderr instead of the C stream stderr. * Add _PySys_FormatV() function. * Convert _PyPathConfig_UpdateGlobal() PyStatus error to an exception. --- Include/internal/pycore_sysmodule.h | 6 + Lib/test/test_cmd_line.py | 2 +- Modules/main.c | 332 +++++++++++++++++----------- Python/pythonrun.c | 18 +- Python/sysmodule.c | 9 +- 5 files changed, 231 insertions(+), 136 deletions(-) diff --git a/Include/internal/pycore_sysmodule.h b/Include/internal/pycore_sysmodule.h index 347b0a7a790c06..d86599cbc3a045 100644 --- a/Include/internal/pycore_sysmodule.h +++ b/Include/internal/pycore_sysmodule.h @@ -21,6 +21,12 @@ extern int _PySys_SetIntMaxStrDigits(int maxdigits); extern int _PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path); +extern void _PySys_FormatV( + PyObject *key, + FILE *fp, + const char *format, + va_list va); + #ifdef __cplusplus } #endif diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index a8645af26b25d8..7640e50f19b783 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -314,7 +314,7 @@ def test_undecodable_code(self): # decodable from ASCII) and run_command() failed on # PyUnicode_AsUTF8String(). This is the expected behaviour on # Linux. - pattern = b"Unable to decode the command from the command line:" + pattern = b"python: Unable to decode the command from the command line:" elif p.returncode == 0: # _Py_char2wchar() decoded b'\xff' as '\xff' even if the locale is # C and the locale encoding is ASCII. It occurs on FreeBSD, Solaris diff --git a/Modules/main.c b/Modules/main.c index a65247dc1e5c61..ef22331760906c 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -11,6 +11,7 @@ #include "pycore_pylifecycle.h" // _Py_PreInitializeFromPyArgv() #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_pythonrun.h" // _PyRun_AnyFile() +#include "pycore_sysmodule.h" // _PySys_FormatV() #include "pycore_tuple.h" // _PyTuple_FromPair #include "pycore_unicodeobject.h" // _PyUnicode_Dedent() @@ -80,6 +81,32 @@ pymain_init(const _PyArgv *args) /* --- pymain_run_python() ---------------------------------------- */ +static void +pymain_error_format(const char *format, ...) +{ + va_list vargs; + va_start(vargs, format); + + PySys_WriteStderr("python: "); + _PySys_FormatV(&_Py_ID(stderr), stderr, format, vargs); + PySys_WriteStderr("\n"); + + va_end(vargs); +} + +// See also pyrun_error() +static void +pymain_error(const char *msg) +{ + PySys_FormatStderr("python: %s\n", msg); +} + +static int +pymain_check_signals(void) +{ + return Py_MakePendingCalls(); +} + /* Non-zero if filename, command (-c) or module (-m) is set on the command line */ static inline int config_run_code(const PyConfig *config) @@ -122,19 +149,29 @@ pymain_exit_err_print(void) } -/* Write an exitcode into *exitcode and return 1 if we have to exit Python. - Return 0 otherwise. */ +// If filename is a package (ex: directory or ZIP file) which contains +// __main__.py, main_importer_path is set to filename and will be prepended to +// sys.path. +// +// Otherwise, main_importer_path is left unchanged. +// +// Write an exitcode into *exitcode and return 1 if we have to exit Python. +// Return 0 otherwise. static int -pymain_get_importer(const wchar_t *filename, PyObject **importer_p, int *exitcode) +pymain_get_importer(const PyConfig *config, PyObject **importer_p, + int *exitcode) { - PyObject *sys_path0 = NULL, *importer; + const wchar_t *filename = config->run_filename; + if (filename == NULL) { + return 0; + } - sys_path0 = PyUnicode_FromWideChar(filename, -1); + PyObject *sys_path0 = PyUnicode_FromWideChar(filename, -1); if (sys_path0 == NULL) { goto error; } - importer = PyImport_GetImporter(sys_path0); + PyObject *importer = PyImport_GetImporter(sys_path0); if (importer == NULL) { goto error; } @@ -152,15 +189,16 @@ pymain_get_importer(const wchar_t *filename, PyObject **importer_p, int *exitcod error: Py_XDECREF(sys_path0); - PySys_WriteStderr("Failed checking if argv[0] is an import path entry\n"); + pymain_error("Failed checking if argv[0] is an import path entry:"); return pymain_err_print(exitcode); } static int -pymain_sys_path_add_path0(PyInterpreterState *interp, PyObject *path0) +pymain_sys_path_add_path0(PyObject *path0) { PyObject *sys_path; + PyInterpreterState *interp = _PyInterpreterState_GET(); PyObject *sysdict = interp->sysdict; if (sysdict != NULL) { sys_path = PyDict_GetItemWithError(sysdict, &_Py_ID(path)); @@ -201,17 +239,21 @@ pymain_header(const PyConfig *config) } -static void +static int pymain_import_readline(const PyConfig *config) { + if (pymain_check_signals() < 0) { + return -1; + } + if (config->isolated) { - return; + return 0; } if (!config->inspect && config_run_code(config)) { - return; + return 0; } if (!isatty(fileno(stdin))) { - return; + return 0; } PyObject *mod = PyImport_ImportModule("readline"); @@ -221,6 +263,7 @@ pymain_import_readline(const PyConfig *config) else { Py_DECREF(mod); } + mod = PyImport_ImportModule("rlcompleter"); if (mod == NULL) { PyErr_Clear(); @@ -228,6 +271,7 @@ pymain_import_readline(const PyConfig *config) else { Py_DECREF(mod); } + return 0; } @@ -272,7 +316,7 @@ pymain_run_command(wchar_t *command) return 0; error: - PySys_WriteStderr("Unable to decode the command from the command line:\n"); + pymain_error("Unable to decode the command from the command line:"); return pymain_exit_err_print(); } @@ -289,13 +333,13 @@ pymain_run_pyrepl(int pythonstartup) PyObject *pyrepl = PyImport_ImportModule("_pyrepl.main"); if (pyrepl == NULL) { - fprintf(stderr, "Could not import _pyrepl.main\n"); + pymain_error("Could not import _pyrepl.main"); goto error; } console = PyObject_GetAttrString(pyrepl, "interactive_console"); if (console == NULL) { - fprintf(stderr, "Could not access _pyrepl.main.interactive_console\n"); + pymain_error("Could not access _pyrepl.main.interactive_console"); goto error; } @@ -343,45 +387,56 @@ pymain_run_pyrepl(int pythonstartup) static int pymain_run_module(const wchar_t *modname, int set_argv0) { - PyObject *module, *runmodule, *runargs, *result; + int exitcode = 0; + PyObject *module = NULL; + PyObject *runmodule = NULL; + PyObject *runargs = NULL; + PyObject *result = NULL; + 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(); + pymain_error("Could not import runpy._run_module_as_main"); + 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(); + pymain_error("Could not convert module name to unicode"); + goto error; } + runargs = _PyTuple_FromPair(module, set_argv0 ? Py_True : Py_False); if (runargs == NULL) { - fprintf(stderr, - "Could not create arguments for runpy._run_module_as_main\n"); - Py_DECREF(runmodule); - Py_DECREF(module); - return pymain_exit_err_print(); + pymain_error("Could not create arguments " + "for runpy._run_module_as_main"); + 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; + +done: + Py_XDECREF(module); + Py_XDECREF(runmodule); + Py_XDECREF(runargs); + Py_XDECREF(result); + return exitcode; + +error: + exitcode = pymain_exit_err_print(); + goto done; } static int -pymain_run_file_obj(PyObject *program_name, PyObject *filename, - int skip_source_first_line) +pymain_run_file_obj(PyObject *filename, int skip_source_first_line) { if (PySys_Audit("cpython.run_file", "O", filename) < 0) { return pymain_exit_err_print(); @@ -389,11 +444,8 @@ pymain_run_file_obj(PyObject *program_name, PyObject *filename, FILE *fp = Py_fopen(filename, "rb"); if (fp == NULL) { - // Ignore the OSError - PyErr_Clear(); - // TODO(picnixz): strerror() is locale dependent but not PySys_FormatStderr(). - PySys_FormatStderr("%S: can't open file %R: [Errno %d] %s\n", - program_name, filename, errno, strerror(errno)); + pymain_error_format("can't open file %R:", filename); + pymain_exit_err_print(); return 2; } @@ -410,14 +462,13 @@ pymain_run_file_obj(PyObject *program_name, PyObject *filename, struct _Py_stat_struct sb; if (_Py_fstat_noraise(fileno(fp), &sb) == 0 && S_ISDIR(sb.st_mode)) { - PySys_FormatStderr("%S: %R is a directory, cannot continue\n", - program_name, filename); + pymain_error_format("%R is a directory, cannot continue", + filename); fclose(fp); return 1; } - // Call pending calls like signal handlers (SIGINT) - if (Py_MakePendingCalls() == -1) { + if (pymain_check_signals() < 0) { fclose(fp); return pymain_exit_err_print(); } @@ -439,16 +490,9 @@ pymain_run_file(const PyConfig *config) if (filename == NULL) { return pymain_exit_err_print(); } - PyObject *program_name = PyUnicode_FromWideChar(config->program_name, -1); - if (program_name == NULL) { - Py_DECREF(filename); - return pymain_exit_err_print(); - } - int exitcode = pymain_run_file_obj(program_name, filename, - config->skip_source_first_line); - Py_DECREF(filename); - Py_DECREF(program_name); + int exitcode = pymain_run_file_obj(filename, config->skip_source_first_line); + Py_XDECREF(filename); return exitcode; } @@ -457,15 +501,17 @@ static int pymain_run_startup(PyConfig *config, int *exitcode) { int ret = 0; + PyObject *startup = NULL; + if (!config->use_environment) { - return 0; + goto done; } - PyObject *startup = NULL; #ifdef MS_WINDOWS const wchar_t *env = _wgetenv(L"PYTHONSTARTUP"); if (env == NULL || env[0] == L'\0') { - return 0; + goto done; } + startup = PyUnicode_FromWideChar(env, -1); if (startup == NULL) { goto error; @@ -473,8 +519,9 @@ pymain_run_startup(PyConfig *config, int *exitcode) #else const char *env = _Py_GetEnv(config->use_environment, "PYTHONSTARTUP"); if (env == NULL) { - return 0; + goto done; } + startup = PyUnicode_DecodeFSDefault(env); if (startup == NULL) { goto error; @@ -486,12 +533,7 @@ pymain_run_startup(PyConfig *config, int *exitcode) FILE *fp = Py_fopen(startup, "r"); if (fp == NULL) { - int save_errno = errno; - PyErr_Clear(); - PySys_WriteStderr("Could not open PYTHONSTARTUP\n"); - - errno = save_errno; - PyErr_SetFromErrnoWithFilenameObjects(PyExc_OSError, startup, NULL); + pymain_error("Could not open PYTHONSTARTUP"); goto error; } @@ -537,53 +579,55 @@ pymain_run_interactive_hook(int *exitcode) goto error; } Py_DECREF(result); - return 0; error: - PySys_WriteStderr("Failed calling sys.__interactivehook__\n"); + pymain_error("Failed calling sys.__interactivehook__:"); return pymain_err_print(exitcode); } -static void -pymain_set_inspect(PyConfig *config, int inspect) +static int +pymain_set_inspect(PyConfig *config, int inspect, int *exitcode) { PyObject *value = PyLong_FromLong(inspect); if (value == NULL || PyConfig_Set("inspect", value) < 0) { - fprintf(stderr, "Could not set the inspect flag\n"); - PyErr_Print(); + Py_XDECREF(value); + pymain_error("Could not set the inspect flag"); + return pymain_err_print(exitcode); } else { assert(config->inspect == inspect); } Py_XDECREF(value); + return 0; } 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 (pymain_check_signals() < 0) { + goto error; } if (PySys_Audit("cpython.run_stdin", NULL) < 0) { - return pymain_exit_err_print(); + goto error; } if (isatty(fileno(stdin)) - && !_Py_GetEnv(config->use_environment, "PYTHON_BASIC_REPL")) { + && !_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(); + pymain_error("Could not import _pyrepl.main"); + goto error; } PyErr_Clear(); } @@ -596,10 +640,13 @@ _pymain_run_repl(PyConfig *config, int startup) Py_DECREF(stdin_obj); } if (result == NULL) { - return pymain_exit_err_print(); + goto error; } Py_DECREF(result); return 0; + +error: + return pymain_exit_err_print(); } static int @@ -607,9 +654,11 @@ pymain_run_stdin(PyConfig *config) { if (stdin_is_interactive(config)) { // do exit on SystemExit - pymain_set_inspect(config, 0); - int exitcode; + if (pymain_set_inspect(config, 0, &exitcode)) { + return exitcode; + } + if (pymain_run_startup(config, &exitcode)) { return exitcode; } @@ -629,14 +678,18 @@ pymain_repl(PyConfig *config, int *exitcode) /* Check this environment variable at the end, to give programs the opportunity to set it from Python. */ if (!config->inspect && _Py_GetEnv(config->use_environment, "PYTHONINSPECT")) { - pymain_set_inspect(config, 1); + if (pymain_set_inspect(config, 1, exitcode)) { + return; + } } if (!(config->inspect && stdin_is_interactive(config) && config_run_code(config))) { return; } - pymain_set_inspect(config, 0); + if (pymain_set_inspect(config, 0, exitcode)) { + return; + } if (pymain_run_interactive_hook(exitcode)) { return; } @@ -645,16 +698,68 @@ pymain_repl(PyConfig *config, int *exitcode) } +static int +pymain_set_path0(PyObject *main_importer_path) +{ + PyConfig *config = (PyConfig *)_Py_GetConfig(); + + PyObject *path0 = NULL; + if (main_importer_path != NULL) { + path0 = Py_NewRef(main_importer_path); + } + else if (!config->safe_path) { + int res = _PyPathConfig_ComputeSysPath0(&config->argv, &path0); + if (res < 0) { + return -1; + } + else if (res == 0) { + Py_CLEAR(path0); + } + } + + if (path0 == NULL) { + return 0; + } + + // XXX Apply config->sys_path_0 in init_interp_main(). We have + // to be sure to get readline/rlcompleter imported at the correct time. + wchar_t *wstr = PyUnicode_AsWideCharString(path0, NULL); + if (wstr == NULL) { + goto error; + } + + PyStatus status = PyConfig_SetString(config, &config->sys_path_0, wstr); + PyMem_Free(wstr); + if (_PyStatus_EXCEPTION(status)) { + _PyErr_SetFromPyStatus(status); + goto error; + } + + int res = pymain_sys_path_add_path0(path0); + Py_DECREF(path0); + + return res; + +error: + Py_DECREF(path0); + return -1; +} + + static void pymain_run_python(int *exitcode) { + int set_running_main = 0; + PyObject *main_importer_path = NULL; PyInterpreterState *interp = _PyInterpreterState_GET(); /* pymain_repl() and pymain_run_stdin() modify the config */ PyConfig *config = (PyConfig*)_PyInterpreterState_GetConfig(interp); /* ensure path config is written into global variables */ - if (_PyStatus_EXCEPTION(_PyPathConfig_UpdateGlobal(config))) { + PyStatus status = _PyPathConfig_UpdateGlobal(config); + if (_PyStatus_EXCEPTION(status)) { + _PyErr_SetFromPyStatus(status); goto error; } @@ -663,60 +768,29 @@ pymain_run_python(int *exitcode) // at that point. assert(config->sys_path_0 == NULL); - if (config->run_filename != NULL) { - /* If filename is a package (ex: directory or ZIP file) which contains - __main__.py, main_importer_path is set to filename and will be - prepended to sys.path. - - Otherwise, main_importer_path is left unchanged. */ - if (pymain_get_importer(config->run_filename, &main_importer_path, - exitcode)) { - return; - } + if (pymain_get_importer(config, &main_importer_path, exitcode)) { + return; } // import readline and rlcompleter before script dir is added to sys.path - pymain_import_readline(config); - - PyObject *path0 = NULL; - if (main_importer_path != NULL) { - path0 = Py_NewRef(main_importer_path); - } - else if (!config->safe_path) { - int res = _PyPathConfig_ComputeSysPath0(&config->argv, &path0); - if (res < 0) { - goto error; - } - else if (res == 0) { - Py_CLEAR(path0); - } + if (pymain_import_readline(config) < 0) { + goto error; } - // XXX Apply config->sys_path_0 in init_interp_main(). We have - // to be sure to get readline/rlcompleter imported at the correct time. - if (path0 != NULL) { - wchar_t *wstr = PyUnicode_AsWideCharString(path0, NULL); - if (wstr == NULL) { - Py_DECREF(path0); - goto error; - } - config->sys_path_0 = _PyMem_RawWcsdup(wstr); - PyMem_Free(wstr); - if (config->sys_path_0 == NULL) { - Py_DECREF(path0); - goto error; - } - int res = pymain_sys_path_add_path0(interp, path0); - Py_DECREF(path0); - if (res < 0) { - goto error; - } + + if (pymain_set_path0(main_importer_path) < 0) { + goto error; } pymain_header(config); _PyInterpreterState_SetRunningMain(interp); + set_running_main = 1; assert(!PyErr_Occurred()); + if (pymain_check_signals() < 0) { + goto error; + } + if (config->run_command) { *exitcode = pymain_run_command(config->run_command); } @@ -733,6 +807,10 @@ pymain_run_python(int *exitcode) *exitcode = pymain_run_stdin(config); } + if (pymain_check_signals() < 0) { + goto error; + } + pymain_repl(config, exitcode); goto done; @@ -740,7 +818,9 @@ pymain_run_python(int *exitcode) *exitcode = pymain_exit_err_print(); done: - _PyInterpreterState_SetNotRunningMain(interp); + if (set_running_main) { + _PyInterpreterState_SetNotRunningMain(interp); + } Py_XDECREF(main_importer_path); } diff --git a/Python/pythonrun.c b/Python/pythonrun.c index e0a752720dfcc2..5abfdc04538c36 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -60,6 +60,14 @@ _PyRun_File(FILE *fp, PyObject *filename, int start, PyObject *globals, PyObject *locals, int closeit, PyCompilerFlags *flags); +// See also pymain_error() +static void +pyrun_error(const char *msg) +{ + PySys_FormatStderr("python: %s\n", msg); +} + + PyObject* _PyRun_AnyFile(FILE *fp, PyObject *filename, int closeit, PyCompilerFlags *flags) @@ -496,7 +504,7 @@ _PyRun_SimpleFile(FILE *fp, PyObject *filename, int closeit, } if (!has_file) { if (PyDict_SetItemString(dict, "__file__", filename) < 0) { - fprintf(stderr, "python: failed to set __main__.__file__\n"); + pyrun_error("failed to set __main__.__file__"); goto done; } set_file_name = 1; @@ -516,12 +524,12 @@ _PyRun_SimpleFile(FILE *fp, PyObject *filename, int closeit, pyc_fp = Py_fopen(filename, "rb"); if (pyc_fp == NULL) { - fprintf(stderr, "python: Can't reopen .pyc file\n"); + pyrun_error("Can't reopen .pyc file"); goto done; } if (set_main_loader(dict, filename, "SourcelessFileLoader") < 0) { - fprintf(stderr, "python: failed to set __main__.__loader__\n"); + pyrun_error("failed to set __main__.__loader__"); fclose(pyc_fp); goto done; } @@ -530,7 +538,7 @@ _PyRun_SimpleFile(FILE *fp, PyObject *filename, int closeit, /* 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"); + pyrun_error("failed to set __main__.__loader__"); goto done; } res = _PyRun_File(fp, filename, Py_file_input, dict, dict, @@ -541,7 +549,7 @@ _PyRun_SimpleFile(FILE *fp, PyObject *filename, int closeit, done: if (set_file_name) { if (PyDict_PopString(dict, "__file__", NULL) < 0) { - fprintf(stderr, "python: failed to delete __main__.__file__\n"); + pyrun_error("failed to delete __main__.__file__"); Py_CLEAR(res); } } diff --git a/Python/sysmodule.c b/Python/sysmodule.c index d9f7b9c449cfb9..9442472b53abbe 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -4641,8 +4641,8 @@ PySys_WriteStderr(const char *format, ...) va_end(va); } -static void -sys_format(PyObject *key, FILE *fp, const char *format, va_list va) +void +_PySys_FormatV(PyObject *key, FILE *fp, const char *format, va_list va) { PyObject *file, *message; const char *utf8; @@ -4664,13 +4664,14 @@ sys_format(PyObject *key, FILE *fp, const char *format, va_list va) _PyErr_SetRaisedException(tstate, exc); } + void PySys_FormatStdout(const char *format, ...) { va_list va; va_start(va, format); - sys_format(&_Py_ID(stdout), stdout, format, va); + _PySys_FormatV(&_Py_ID(stdout), stdout, format, va); va_end(va); } @@ -4680,7 +4681,7 @@ PySys_FormatStderr(const char *format, ...) va_list va; va_start(va, format); - sys_format(&_Py_ID(stderr), stderr, format, va); + _PySys_FormatV(&_Py_ID(stderr), stderr, format, va); va_end(va); }