From 12083284c54be25abadd85781d36b63731dc1f0c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 17 May 2019 23:05:29 +0200 Subject: [PATCH] bpo-36763: _Py_RunMain() doesn't call Py_Exit() anymore (GH-13390) Py_Main() and _Py_RunMain() now return the exitcode rather than calling Py_Exit(exitcode) when calling PyErr_Print() if the current exception type is SystemExit. * Add _Py_HandleSystemExit(). * Add pymain_exit_err_print(). * Add pymain_exit_print(). --- Include/internal/pycore_pylifecycle.h | 2 + .../2019-05-17-19-23-24.bpo-36763.TswmDy.rst | 3 + Modules/main.c | 123 +++++++++++------- Python/pythonrun.c | 52 +++++--- 4 files changed, 117 insertions(+), 63 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2019-05-17-19-23-24.bpo-36763.TswmDy.rst diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h index 0d83acdf28a..4684ebea44a 100644 --- a/Include/internal/pycore_pylifecycle.h +++ b/Include/internal/pycore_pylifecycle.h @@ -100,6 +100,8 @@ PyAPI_FUNC(_PyInitError) _Py_PreInitializeFromCoreConfig( const _PyCoreConfig *coreconfig, const _PyArgv *args); +PyAPI_FUNC(int) _Py_HandleSystemExit(int *exitcode_p); + #ifdef __cplusplus } #endif diff --git a/Misc/NEWS.d/next/C API/2019-05-17-19-23-24.bpo-36763.TswmDy.rst b/Misc/NEWS.d/next/C API/2019-05-17-19-23-24.bpo-36763.TswmDy.rst new file mode 100644 index 00000000000..29c016600db --- /dev/null +++ b/Misc/NEWS.d/next/C API/2019-05-17-19-23-24.bpo-36763.TswmDy.rst @@ -0,0 +1,3 @@ +``Py_Main()`` now returns the exitcode rather than calling +``Py_Exit(exitcode)`` when calling ``PyErr_Print()`` if the current +exception type is ``SystemExit``. diff --git a/Modules/main.c b/Modules/main.c index 72546a21cac..6d4b351e5e1 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -94,8 +94,34 @@ stdin_is_interactive(const _PyCoreConfig *config) } -static PyObject * -pymain_get_importer(const wchar_t *filename) +/* Display the current Python exception and return an exitcode */ +static int +pymain_err_print(int *exitcode_p) +{ + int exitcode; + if (_Py_HandleSystemExit(&exitcode)) { + *exitcode_p = exitcode; + return 1; + } + + PyErr_Print(); + return 0; +} + + +static int +pymain_exit_err_print(void) +{ + int exitcode = 1; + pymain_err_print(&exitcode); + return exitcode; +} + + +/* 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) { PyObject *sys_path0 = NULL, *importer; @@ -112,17 +138,18 @@ pymain_get_importer(const wchar_t *filename) if (importer == Py_None) { Py_DECREF(sys_path0); Py_DECREF(importer); - return NULL; + return 0; } Py_DECREF(importer); - return sys_path0; + *importer_p = sys_path0; + return 0; error: Py_XDECREF(sys_path0); + PySys_WriteStderr("Failed checking if argv[0] is an import path entry\n"); - PyErr_Print(); - return NULL; + return pymain_err_print(exitcode); } @@ -217,8 +244,7 @@ pymain_run_command(wchar_t *command, PyCompilerFlags *cf) error: PySys_WriteStderr("Unable to decode the command from the command line:\n"); - PyErr_Print(); - return 1; + return pymain_exit_err_print(); } @@ -229,44 +255,37 @@ pymain_run_module(const wchar_t *modname, int set_argv0) runpy = PyImport_ImportModule("runpy"); if (runpy == NULL) { fprintf(stderr, "Could not import runpy module\n"); - PyErr_Print(); - return -1; + return pymain_exit_err_print(); } runmodule = PyObject_GetAttrString(runpy, "_run_module_as_main"); if (runmodule == NULL) { fprintf(stderr, "Could not access runpy._run_module_as_main\n"); - PyErr_Print(); Py_DECREF(runpy); - return -1; + return pymain_exit_err_print(); } module = PyUnicode_FromWideChar(modname, wcslen(modname)); if (module == NULL) { fprintf(stderr, "Could not convert module name to unicode\n"); - PyErr_Print(); Py_DECREF(runpy); Py_DECREF(runmodule); - return -1; + return pymain_exit_err_print(); } runargs = Py_BuildValue("(Oi)", module, set_argv0); if (runargs == NULL) { fprintf(stderr, "Could not create arguments for runpy._run_module_as_main\n"); - PyErr_Print(); Py_DECREF(runpy); Py_DECREF(runmodule); Py_DECREF(module); - return -1; + return pymain_exit_err_print(); } result = PyObject_Call(runmodule, runargs, NULL); - if (result == NULL) { - PyErr_Print(); - } Py_DECREF(runpy); Py_DECREF(runmodule); Py_DECREF(module); Py_DECREF(runargs); if (result == NULL) { - return -1; + return pymain_exit_err_print(); } Py_DECREF(result); return 0; @@ -315,9 +334,8 @@ pymain_run_file(_PyCoreConfig *config, PyCompilerFlags *cf) /* call pending calls like signal handlers (SIGINT) */ if (Py_MakePendingCalls() == -1) { - PyErr_Print(); fclose(fp); - return 1; + return pymain_exit_err_print(); } PyObject *unicode, *bytes = NULL; @@ -343,34 +361,36 @@ pymain_run_file(_PyCoreConfig *config, PyCompilerFlags *cf) } -static void -pymain_run_startup(_PyCoreConfig *config, PyCompilerFlags *cf) +static int +pymain_run_startup(_PyCoreConfig *config, PyCompilerFlags *cf, int *exitcode) { const char *startup = _Py_GetEnv(config->use_environment, "PYTHONSTARTUP"); if (startup == NULL) { - return; + return 0; } FILE *fp = _Py_fopen(startup, "r"); if (fp == NULL) { int save_errno = errno; PySys_WriteStderr("Could not open PYTHONSTARTUP\n"); - errno = save_errno; - PyErr_SetFromErrnoWithFilename(PyExc_OSError, - startup); - PyErr_Print(); - return; + errno = save_errno; + PyErr_SetFromErrnoWithFilename(PyExc_OSError, startup); + + return pymain_err_print(exitcode); } (void) PyRun_SimpleFileExFlags(fp, startup, 0, cf); PyErr_Clear(); fclose(fp); + return 0; } -static void -pymain_run_interactive_hook(void) +/* Write an exitcode into *exitcode and return 1 if we have to exit Python. + Return 0 otherwise. */ +static int +pymain_run_interactive_hook(int *exitcode) { PyObject *sys, *hook, *result; sys = PyImport_ImportModule("sys"); @@ -382,7 +402,7 @@ pymain_run_interactive_hook(void) Py_DECREF(sys); if (hook == NULL) { PyErr_Clear(); - return; + return 0; } result = _PyObject_CallNoArg(hook); @@ -392,11 +412,11 @@ pymain_run_interactive_hook(void) } Py_DECREF(result); - return; + return 0; error: PySys_WriteStderr("Failed calling sys.__interactivehook__\n"); - PyErr_Print(); + return pymain_err_print(exitcode); } @@ -406,14 +426,20 @@ pymain_run_stdin(_PyCoreConfig *config, PyCompilerFlags *cf) if (stdin_is_interactive(config)) { config->inspect = 0; Py_InspectFlag = 0; /* do exit on SystemExit */ - pymain_run_startup(config, cf); - pymain_run_interactive_hook(); + + int exitcode; + if (pymain_run_startup(config, cf, &exitcode)) { + return exitcode; + } + + if (pymain_run_interactive_hook(&exitcode)) { + return exitcode; + } } /* call pending calls like signal handlers (SIGINT) */ if (Py_MakePendingCalls() == -1) { - PyErr_Print(); - return 1; + return pymain_exit_err_print(); } int run = PyRun_AnyFileExFlags(stdin, "", 0, cf); @@ -437,7 +463,9 @@ pymain_repl(_PyCoreConfig *config, PyCompilerFlags *cf, int *exitcode) config->inspect = 0; Py_InspectFlag = 0; - pymain_run_interactive_hook(); + if (pymain_run_interactive_hook(exitcode)) { + return; + } int res = PyRun_AnyFileFlags(stdin, "", cf); *exitcode = (res != 0); @@ -457,8 +485,11 @@ pymain_run_python(int *exitcode) __main__.py, main_importer_path is set to filename and will be prepended to sys.path. - Otherwise, main_importer_path is set to NULL. */ - main_importer_path = pymain_get_importer(config->run_filename); + Otherwise, main_importer_path is left unchanged. */ + if (pymain_get_importer(config->run_filename, &main_importer_path, + exitcode)) { + return; + } } if (main_importer_path != NULL) { @@ -491,11 +522,10 @@ pymain_run_python(int *exitcode) *exitcode = pymain_run_command(config->run_command, &cf); } else if (config->run_module) { - *exitcode = (pymain_run_module(config->run_module, 1) != 0); + *exitcode = pymain_run_module(config->run_module, 1); } else if (main_importer_path != NULL) { - int sts = pymain_run_module(L"__main__", 0); - *exitcode = (sts != 0); + *exitcode = pymain_run_module(L"__main__", 0); } else if (config->run_filename != NULL) { *exitcode = pymain_run_file(config, &cf); @@ -508,8 +538,7 @@ pymain_run_python(int *exitcode) goto done; error: - PyErr_Print(); - *exitcode = 1; + *exitcode = pymain_exit_err_print(); done: Py_XDECREF(main_importer_path); diff --git a/Python/pythonrun.c b/Python/pythonrun.c index bc131fd7e5e..d2b27615903 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -585,23 +585,31 @@ print_error_text(PyObject *f, int offset, PyObject *text_obj) PyFile_WriteString("^\n", f); } -static void -handle_system_exit(void) -{ - PyObject *exception, *value, *tb; - int exitcode = 0; +int +_Py_HandleSystemExit(int *exitcode_p) +{ int inspect = _PyInterpreterState_GET_UNSAFE()->core_config.inspect; if (inspect) { /* Don't exit if -i flag was given. This flag is set to 0 * when entering interactive mode for inspecting. */ - return; + return 0; } + if (!PyErr_ExceptionMatches(PyExc_SystemExit)) { + return 0; + } + + PyObject *exception, *value, *tb; PyErr_Fetch(&exception, &value, &tb); + fflush(stdout); - if (value == NULL || value == Py_None) + + int exitcode = 0; + if (value == NULL || value == Py_None) { goto done; + } + if (PyExceptionInstance_Check(value)) { /* The error code should be in the `code' attribute. */ _Py_IDENTIFIER(code); @@ -615,8 +623,10 @@ handle_system_exit(void) /* If we failed to dig out the 'code' attribute, just let the else clause below print the error. */ } - if (PyLong_Check(value)) + + if (PyLong_Check(value)) { exitcode = (int)PyLong_AsLong(value); + } else { PyObject *sys_stderr = _PySys_GetObjectId(&PyId_stderr); /* We clear the exception here to avoid triggering the assertion @@ -633,6 +643,7 @@ handle_system_exit(void) PySys_WriteStderr("\n"); exitcode = 1; } + done: /* Restore and clear the exception info, in order to properly decref * the exception, value, and traceback. If we just exit instead, @@ -641,18 +652,28 @@ handle_system_exit(void) */ PyErr_Restore(exception, value, tb); PyErr_Clear(); - Py_Exit(exitcode); - /* NOTREACHED */ + *exitcode_p = exitcode; + return 1; } + +static void +handle_system_exit(void) +{ + int exitcode; + if (_Py_HandleSystemExit(&exitcode)) { + Py_Exit(exitcode); + } +} + + void PyErr_PrintEx(int set_sys_last_vars) { PyObject *exception, *v, *tb, *hook; - if (PyErr_ExceptionMatches(PyExc_SystemExit)) { - handle_system_exit(); - } + handle_system_exit(); + PyErr_Fetch(&exception, &v, &tb); if (exception == NULL) return; @@ -686,10 +707,9 @@ PyErr_PrintEx(int set_sys_last_vars) stack[2] = tb; result = _PyObject_FastCall(hook, stack, 3); if (result == NULL) { + handle_system_exit(); + PyObject *exception2, *v2, *tb2; - if (PyErr_ExceptionMatches(PyExc_SystemExit)) { - handle_system_exit(); - } PyErr_Fetch(&exception2, &v2, &tb2); PyErr_NormalizeException(&exception2, &v2, &tb2); /* It should not be possible for exception2 or v2