Skip to content

Commit 62c34a6

Browse files
authored
[3.14] gh-152132: Fix Py_RunMain() to return an exit code (#153446) (#153455)
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. (cherry picked from commit fac72f1)
1 parent 5e08cb2 commit 62c34a6

8 files changed

Lines changed: 406 additions & 236 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,
@@ -33,6 +28,12 @@ extern const char* _Py_SourceAsString(
3328
PyCompilerFlags *cf,
3429
PyObject **cmd_copy);
3530

31+
// Export for special main.c string compiling with source tracebacks
32+
extern PyObject* _PyRun_SimpleString(
33+
const char *command,
34+
PyObject* name,
35+
PyCompilerFlags *flags);
36+
3637

3738
/* Stack size, in "pointers". This must be large enough, so
3839
* 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:
@@ -554,6 +561,55 @@ def _nogil_filtered_err(err: str, mod_name: str) -> str:
554561
]
555562
return "\n".join(filtered_err_lines)
556563

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

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