Skip to content

Commit 72ee7b1

Browse files
vstinnermiss-islington
authored andcommitted
gh-152132: Fix Py_RunMain() to return an exit code (GH-153446)
* 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. (cherry picked from commit fac72f1) Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent 1604c80 commit 72ee7b1

8 files changed

Lines changed: 398 additions & 251 deletions

File tree

Include/internal/pycore_pylifecycle.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,6 @@ extern int _Py_LegacyLocaleDetected(int warn);
111111
// Export for 'readline' shared extension
112112
PyAPI_FUNC(char*) _Py_SetLocaleFromEnv(int category);
113113

114-
// Export for special main.c string compiling with source tracebacks
115-
int _PyRun_SimpleStringFlagsWithName(const char *command, const char* name, PyCompilerFlags *flags);
116-
117114

118115
/* interpreter config */
119116

Include/internal/pycore_pythonrun.h

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,18 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11-
extern int _PyRun_SimpleFileObject(
11+
extern PyObject* _PyRun_SimpleFile(
1212
FILE *fp,
1313
PyObject *filename,
1414
int closeit,
1515
PyCompilerFlags *flags);
1616

17-
extern int _PyRun_AnyFileObject(
17+
extern PyObject* _PyRun_AnyFile(
1818
FILE *fp,
1919
PyObject *filename,
2020
int closeit,
2121
PyCompilerFlags *flags);
2222

23-
extern int _PyRun_InteractiveLoopObject(
24-
FILE *fp,
25-
PyObject *filename,
26-
PyCompilerFlags *flags);
27-
2823
extern int _PyObject_SupportedAsScript(PyObject *);
2924
extern const char* _Py_SourceAsString(
3025
PyObject *cmd,
@@ -39,6 +34,12 @@ extern PyObject * _Py_CompileStringObjectWithModule(
3934
PyCompilerFlags *flags, int optimize,
4035
PyObject *module);
4136

37+
// Export for special main.c string compiling with source tracebacks
38+
extern PyObject* _PyRun_SimpleString(
39+
const char *command,
40+
PyObject* name,
41+
PyCompilerFlags *flags);
42+
4243

4344
/* Stack size, in "pointers". This must be large enough, so
4445
* no two calls to check recursion depth are more than this far

Lib/test/test_embed.py

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@
6868
if not os.path.isfile(os.path.join(STDLIB_INSTALL, 'os.py')):
6969
STDLIB_INSTALL = None
7070

71+
CODE_EXITCODE_123 = 'raise SystemExit(123)'
72+
73+
7174
def debug_build(program):
7275
program = os.path.basename(program)
7376
name = os.path.splitext(program)[0]
@@ -117,12 +120,16 @@ def run_embedded_interpreter(self, *args, env=None,
117120
env = env.copy()
118121
env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
119122

120-
p = subprocess.Popen(cmd,
121-
stdout=subprocess.PIPE,
122-
stderr=subprocess.PIPE,
123-
universal_newlines=True,
124-
env=env,
125-
cwd=cwd)
123+
kwargs = dict(
124+
stdout=subprocess.PIPE,
125+
stderr=subprocess.PIPE,
126+
universal_newlines=True,
127+
env=env,
128+
cwd=cwd,
129+
)
130+
if input is not None:
131+
kwargs['stdin'] = subprocess.PIPE
132+
p = subprocess.Popen(cmd, **kwargs)
126133
try:
127134
(out, err) = p.communicate(input=input, timeout=timeout)
128135
except:
@@ -565,6 +572,55 @@ def _nogil_filtered_err(err: str, mod_name: str) -> str:
565572
]
566573
return "\n".join(filtered_err_lines)
567574

575+
def check_program_exitcode(self, *args, check_stderr=True, **kwargs):
576+
out, err = self.run_embedded_interpreter(*args, **kwargs)
577+
self.assertEqual(out.rstrip(), 'ok! Py_RunMain() returned 123')
578+
if check_stderr:
579+
self.assertEqual(err, '')
580+
581+
def test_init_run_main_code_exitcode(self):
582+
code = CODE_EXITCODE_123
583+
self.check_program_exitcode("test_init_run_main_code_exitcode", code)
584+
585+
def test_init_run_main_script_exitcode(self):
586+
with tempfile.TemporaryDirectory() as tmpdir:
587+
filename = os.path.join(tmpdir, 'script.py')
588+
with open(filename, 'w') as fp:
589+
fp.write(CODE_EXITCODE_123)
590+
591+
self.check_program_exitcode("test_init_run_main_script_exitcode",
592+
filename)
593+
594+
def test_init_run_main_interactive_exitcode(self):
595+
code = CODE_EXITCODE_123
596+
self.check_program_exitcode("test_init_run_main_interactive_exitcode",
597+
input=code,
598+
check_stderr=False)
599+
600+
def test_init_run_main_startup_exitcode(self):
601+
with tempfile.TemporaryDirectory() as tmpdir:
602+
filename = os.path.join(tmpdir, 'startup.py')
603+
with open(filename, 'x') as fp:
604+
fp.write(CODE_EXITCODE_123)
605+
606+
env = dict(os.environ)
607+
env['PYTHONSTARTUP'] = filename
608+
self.check_program_exitcode("test_init_run_main_interactive_exitcode",
609+
env=env,
610+
check_stderr=False)
611+
612+
def test_init_run_main_module_exitcode(self):
613+
with tempfile.TemporaryDirectory() as tmpdir:
614+
modname = '_testembed_testmodule'
615+
filename = os.path.join(tmpdir, modname + '.py')
616+
with open(filename, 'x', encoding='utf8') as fp:
617+
fp.write(CODE_EXITCODE_123)
618+
619+
env = dict(os.environ)
620+
env['PYTHONPATH'] = tmpdir
621+
self.check_program_exitcode("test_init_run_main_module_exitcode",
622+
modname, env=env)
623+
568624

569625
def config_dev_mode(preconfig, config):
570626
preconfig['allocator'] = PYMEM_ALLOCATOR_DEBUG
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :c:func:`Py_RunMain` to return an exit code, rather than calling
2+
:c:func:`Py_Exit`, when running a script, a command, or the REPL. Patch by
3+
Victor Stinner.

Modules/_testcapi/run.c

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,6 @@ run_simplefile(PyObject *mod, PyObject *args)
129129
assert(_Py_IsValidFD(fd));
130130
fclose(fp);
131131

132-
if (res == -1 && PyErr_Occurred()) {
133-
return NULL;
134-
}
135132
assert(!PyErr_Occurred());
136133
return PyLong_FromLong(res);
137134
}
@@ -203,9 +200,6 @@ run_simplefileex(PyObject *mod, PyObject *args)
203200
return NULL;
204201
}
205202

206-
if (res == -1 && PyErr_Occurred()) {
207-
return NULL;
208-
}
209203
assert(!PyErr_Occurred());
210204
return PyLong_FromLong(res);
211205
}
@@ -243,9 +237,6 @@ run_simplefileexflags(PyObject *mod, PyObject *args)
243237
return NULL;
244238
}
245239

246-
if (res == -1 && PyErr_Occurred()) {
247-
return NULL;
248-
}
249240
assert(!PyErr_Occurred());
250241
return PyLong_FromLong(res);
251242
}
@@ -272,9 +263,6 @@ run_anyfile(PyObject *mod, PyObject *args)
272263
assert(_Py_IsValidFD(fd));
273264
fclose(fp);
274265

275-
if (res == -1 && PyErr_Occurred()) {
276-
return NULL;
277-
}
278266
assert(!PyErr_Occurred());
279267
return PyLong_FromLong(res);
280268
}
@@ -310,9 +298,6 @@ run_anyfileflags(PyObject *mod, PyObject *args)
310298
assert(_Py_IsValidFD(fd));
311299
fclose(fp);
312300

313-
if (res == -1 && PyErr_Occurred()) {
314-
return NULL;
315-
}
316301
assert(!PyErr_Occurred());
317302
return PyLong_FromLong(res);
318303
}
@@ -342,9 +327,6 @@ run_anyfileex(PyObject *mod, PyObject *args)
342327
return NULL;
343328
}
344329

345-
if (res == -1 && PyErr_Occurred()) {
346-
return NULL;
347-
}
348330
assert(!PyErr_Occurred());
349331
return PyLong_FromLong(res);
350332
}
@@ -382,9 +364,6 @@ run_anyfileexflags(PyObject *mod, PyObject *args)
382364
return NULL;
383365
}
384366

385-
if (res == -1 && PyErr_Occurred()) {
386-
return NULL;
387-
}
388367
assert(!PyErr_Occurred());
389368
return PyLong_FromLong(res);
390369
}
@@ -664,9 +643,6 @@ run_simplestring(PyObject *mod, PyObject *args)
664643
}
665644

666645
int res = PyRun_SimpleString(str);
667-
if (res == -1 && PyErr_Occurred()) {
668-
return NULL;
669-
}
670646
assert(!PyErr_Occurred());
671647
return PyLong_FromLong(res);
672648
}
@@ -689,9 +665,6 @@ run_simplestringflags(PyObject *mod, PyObject *args)
689665
}
690666

691667
int res = PyRun_SimpleStringFlags(str, pflags);
692-
if (res == -1 && PyErr_Occurred()) {
693-
return NULL;
694-
}
695668
assert(!PyErr_Occurred());
696669
return PyLong_FromLong(res);
697670
}

0 commit comments

Comments
 (0)