Skip to content

Commit fac72f1

Browse files
authored
gh-152132: Fix Py_RunMain() to return an exit code (#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.
1 parent 6ba3ad5 commit fac72f1

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
@@ -108,9 +108,6 @@ extern int _Py_LegacyLocaleDetected(int warn);
108108
// Export for 'readline' shared extension
109109
PyAPI_FUNC(char*) _Py_SetLocaleFromEnv(int category);
110110

111-
// Export for special main.c string compiling with source tracebacks
112-
int _PyRun_SimpleStringFlagsWithName(const char *command, const char* name, PyCompilerFlags *flags);
113-
114111

115112
/* interpreter config */
116113

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]
@@ -140,12 +143,16 @@ def run_embedded_interpreter(self, *args, env=None,
140143
env = env.copy()
141144
env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
142145

143-
p = subprocess.Popen(cmd,
144-
stdout=subprocess.PIPE,
145-
stderr=subprocess.PIPE,
146-
universal_newlines=True,
147-
env=env,
148-
cwd=cwd)
146+
kwargs = dict(
147+
stdout=subprocess.PIPE,
148+
stderr=subprocess.PIPE,
149+
universal_newlines=True,
150+
env=env,
151+
cwd=cwd,
152+
)
153+
if input is not None:
154+
kwargs['stdin'] = subprocess.PIPE
155+
p = subprocess.Popen(cmd, **kwargs)
149156
try:
150157
(out, err) = p.communicate(input=input, timeout=timeout)
151158
except:
@@ -590,6 +597,55 @@ def _nogil_filtered_err(err: str, mod_name: str) -> str:
590597
]
591598
return "\n".join(filtered_err_lines)
592599

600+
def check_program_exitcode(self, *args, check_stderr=True, **kwargs):
601+
out, err = self.run_embedded_interpreter(*args, **kwargs)
602+
self.assertEqual(out.rstrip(), 'ok! Py_RunMain() returned 123')
603+
if check_stderr:
604+
self.assertEqual(err, '')
605+
606+
def test_init_run_main_code_exitcode(self):
607+
code = CODE_EXITCODE_123
608+
self.check_program_exitcode("test_init_run_main_code_exitcode", code)
609+
610+
def test_init_run_main_script_exitcode(self):
611+
with tempfile.TemporaryDirectory() as tmpdir:
612+
filename = os.path.join(tmpdir, 'script.py')
613+
with open(filename, 'w') as fp:
614+
fp.write(CODE_EXITCODE_123)
615+
616+
self.check_program_exitcode("test_init_run_main_script_exitcode",
617+
filename)
618+
619+
def test_init_run_main_interactive_exitcode(self):
620+
code = CODE_EXITCODE_123
621+
self.check_program_exitcode("test_init_run_main_interactive_exitcode",
622+
input=code,
623+
check_stderr=False)
624+
625+
def test_init_run_main_startup_exitcode(self):
626+
with tempfile.TemporaryDirectory() as tmpdir:
627+
filename = os.path.join(tmpdir, 'startup.py')
628+
with open(filename, 'x') as fp:
629+
fp.write(CODE_EXITCODE_123)
630+
631+
env = dict(os.environ)
632+
env['PYTHONSTARTUP'] = filename
633+
self.check_program_exitcode("test_init_run_main_interactive_exitcode",
634+
env=env,
635+
check_stderr=False)
636+
637+
def test_init_run_main_module_exitcode(self):
638+
with tempfile.TemporaryDirectory() as tmpdir:
639+
modname = '_testembed_testmodule'
640+
filename = os.path.join(tmpdir, modname + '.py')
641+
with open(filename, 'x', encoding='utf8') as fp:
642+
fp.write(CODE_EXITCODE_123)
643+
644+
env = dict(os.environ)
645+
env['PYTHONPATH'] = tmpdir
646+
self.check_program_exitcode("test_init_run_main_module_exitcode",
647+
modname, env=env)
648+
593649

594650
def config_dev_mode(preconfig, config):
595651
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)