Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Include/internal/pycore_pylifecycle.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ 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);

// Like _PyRun_SimpleStringFlagsWithName but returns the result object
// instead of calling PyErr_Print() on failure. The caller should handle
// the error with _Py_HandleSystemExitAndKeyboardInterrupt or PyErr_Print.
PyObject *_PyRun_SimpleStringFlagsEx(const char *command, const char* name, PyCompilerFlags *flags);


/* interpreter config */

Expand Down
6 changes: 6 additions & 0 deletions Include/internal/pycore_pythonrun.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ extern int _PyRun_AnyFileObject(
int closeit,
PyCompilerFlags *flags);

extern PyObject * _PyRun_SimpleFileObjectEx(
FILE *fp,
PyObject *filename,
int closeit,
PyCompilerFlags *flags);

extern int _PyRun_InteractiveLoopObject(
FILE *fp,
PyObject *filename,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix :c:func:`Py_RunMain` incorrectly calling ``exit()`` on
:exc:`SystemExit` when running a command or file. The exit code
is now returned to the caller.
22 changes: 15 additions & 7 deletions Modules/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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_SimpleFileObjectEx()
#include "pycore_tuple.h" // _PyTuple_FromPair
#include "pycore_unicodeobject.h" // _PyUnicode_Dedent()

Expand Down Expand Up @@ -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) {
Expand All @@ -259,9 +258,13 @@ 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 *res = _PyRun_SimpleStringFlagsEx(PyBytes_AsString(bytes), "<string>", &cf);
Py_DECREF(bytes);
return (ret != 0);
if (res == NULL) {
return pymain_exit_err_print();
}
Py_DECREF(res);
return 0;

error:
PySys_WriteStderr("Unable to decode the command from the command line:\n");
Expand Down Expand Up @@ -406,10 +409,15 @@ pymain_run_file_obj(PyObject *program_name, PyObject *filename,
return pymain_exit_err_print();
}

/* PyRun_AnyFileExFlags(closeit=1) calls fclose(fp) before running code */
/* Use _PyRun_SimpleFileObjectEx which returns PyObject* without calling
PyErr_Print(), so we can handle SystemExit properly via pymain_exit_err_print. */
PyCompilerFlags cf = _PyCompilerFlags_INIT;
int run = _PyRun_AnyFileObject(fp, filename, 1, &cf);
return (run != 0);
PyObject *v = _PyRun_SimpleFileObjectEx(fp, filename, 1, &cf);
if (v == NULL) {
Comment on lines +415 to +416

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use single-letter variable names.

return pymain_exit_err_print();
}
Py_DECREF(v);
return 0;
}

static int
Expand Down
115 changes: 115 additions & 0 deletions Python/pythonrun.c
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,94 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit,
}


/* Variant of _PyRun_SimpleFileObject that returns the result object
instead of calling PyErr_Print() on failure. The caller (typically
pymain_run_file_obj in Modules/main.c) should handle the error
with _Py_HandleSystemExitAndKeyboardInterrupt or pymain_exit_err_print. */
PyObject *
_PyRun_SimpleFileObjectEx(FILE *fp, PyObject *filename, int closeit,
PyCompilerFlags *flags)
Comment on lines +545 to +547

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This is not a good name -- the Ex suffix existing in CPython is a historical artifact that we try to avoid these days.
  2. Most of this function is copied exactly from _PyRun_SimpleFileObject. Rather than duplicating the logic, let's factor this out into a common helper function.

{
PyObject *v = NULL;

PyObject *main_module = PyImport_AddModuleRef("__main__");
if (main_module == NULL)
return NULL;
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) {
goto done;
}
set_file_name = 1;
}

int pyc = maybe_pyc_file(fp, filename, closeit);
if (pyc < 0) {
goto done;
}

if (pyc) {
FILE *pyc_fp;
/* Try to run a pyc file. First, re-open in binary */
if (closeit) {
fclose(fp);
closeit = 0; // already closed
}

pyc_fp = Py_fopen(filename, "rb");
if (pyc_fp == NULL) {
fprintf(stderr, "python: Can't reopen .pyc file\n");
goto done;
}

if (set_main_loader(dict, filename, "SourcelessFileLoader") < 0) {
fprintf(stderr, "python: failed to set __main__.__loader__\n");
fclose(pyc_fp);
goto done;
}
v = 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");
goto done;
}
v = pyrun_file(fp, filename, Py_file_input, dict, dict,
closeit, flags);
}
flush_io();

done:
if (set_file_name) {
if (v == NULL) {
// Main code failed: save the exception before cleanup
// so PyDict_PopString doesn't overwrite it
PyObject *saved_exc = PyErr_GetRaisedException();
if (PyDict_PopString(dict, "__file__", NULL) < 0) {
/* Non-fatal cleanup error; just clear it */
PyErr_Clear();
}
PyErr_SetRaisedException(saved_exc);
} else {
// Main code succeeded: if cleanup fails, print it
// to match legacy behavior
if (PyDict_PopString(dict, "__file__", NULL) < 0) {
PyErr_Print();
}
}
}
Py_XDECREF(main_module);
return v;
}


int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
PyCompilerFlags *flags)
Expand Down Expand Up @@ -583,6 +671,33 @@ _PyRun_SimpleStringFlagsWithName(const char *command, const char* name, PyCompil
return 0;
}

PyObject *
_PyRun_SimpleStringFlagsEx(const char *command, const char* name, PyCompilerFlags *flags)
Comment on lines +674 to +675

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments as _PyRun_SimpleFileObjectEx.

{
PyObject *main_module = PyImport_AddModuleRef("__main__");
if (main_module == NULL) {
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) {
Py_DECREF(main_module);
return NULL;
}
res = _PyRun_StringFlagsWithName(command, the_name, Py_file_input,
dict, dict, flags, 0);
Py_DECREF(the_name);
}
Py_DECREF(main_module);
return res;
}


int
PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags)
{
Expand Down
Loading