Skip to content

Commit 907958c

Browse files
[3.14] gh-144601: Avoid sharing exception objects raised in a PyInit function across multiple interpreters (GH-144602) (GH-144633)
gh-144601: Avoid sharing exception objects raised in a `PyInit` function across multiple interpreters (GH-144602) (cherry picked from commit fd6b639) Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
1 parent d148662 commit 907958c

File tree

4 files changed

+53
-1
lines changed

4 files changed

+53
-1
lines changed

Lib/test/test_import/__init__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
Py_GIL_DISABLED,
4444
no_rerun,
4545
force_not_colorized_test_class,
46+
catch_unraisable_exception
4647
)
4748
from test.support.import_helper import (
4849
forget, make_legacy_pyc, unlink, unload, ready_to_import,
@@ -2540,6 +2541,32 @@ def test_disallowed_reimport(self):
25402541
excsnap = _interpreters.run_string(interpid, script)
25412542
self.assertIsNot(excsnap, None)
25422543

2544+
@requires_subinterpreters
2545+
def test_pyinit_function_raises_exception(self):
2546+
# gh-144601: PyInit functions that raised exceptions would cause a
2547+
# crash when imported from a subinterpreter.
2548+
import _testsinglephase
2549+
filename = _testsinglephase.__file__
2550+
script = f"""if True:
2551+
from test.test_import import import_extension_from_file
2552+
2553+
import_extension_from_file('_testsinglephase_raise_exception', {filename!r})"""
2554+
2555+
interp = _interpreters.create()
2556+
try:
2557+
with catch_unraisable_exception() as cm:
2558+
exception = _interpreters.run_string(interp, script)
2559+
unraisable = cm.unraisable
2560+
finally:
2561+
_interpreters.destroy(interp)
2562+
2563+
self.assertIsNotNone(exception)
2564+
self.assertIsNotNone(exception.type.__name__, "ImportError")
2565+
self.assertIsNotNone(exception.msg, "failed to import from subinterpreter due to exception")
2566+
self.assertIsNotNone(unraisable)
2567+
self.assertIs(unraisable.exc_type, RuntimeError)
2568+
self.assertEqual(str(unraisable.exc_value), "evil")
2569+
25432570

25442571
class TestSinglePhaseSnapshot(ModuleSnapshot):
25452572
"""A representation of a single-phase init module for testing.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crash when importing a module whose ``PyInit`` function raises an
2+
exception from a subinterpreter.

Modules/_testsinglephase.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,3 +799,11 @@ PyInit__testsinglephase_circular(void)
799799
}
800800
return Py_NewRef(static_module_circular);
801801
}
802+
803+
804+
PyMODINIT_FUNC
805+
PyInit__testsinglephase_raise_exception(void)
806+
{
807+
PyErr_SetString(PyExc_RuntimeError, "evil");
808+
return NULL;
809+
}

Python/import.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2147,13 +2147,29 @@ import_run_extension(PyThreadState *tstate, PyModInitFunction p0,
21472147
}
21482148

21492149
main_finally:
2150+
if (rc < 0) {
2151+
_Py_ext_module_loader_result_apply_error(&res, name_buf);
2152+
}
2153+
21502154
/* Switch back to the subinterpreter. */
21512155
if (switched) {
2156+
// gh-144601: The exception object can't be transferred across
2157+
// interpreters. Instead, we print out an unraisable exception, and
2158+
// then raise a different exception for the calling interpreter.
2159+
if (rc < 0) {
2160+
assert(PyErr_Occurred());
2161+
PyErr_FormatUnraisable("Exception while importing from subinterpreter");
2162+
}
21522163
assert(main_tstate != tstate);
21532164
switch_back_from_main_interpreter(tstate, main_tstate, mod);
21542165
/* Any module we got from the init function will have to be
21552166
* reloaded in the subinterpreter. */
21562167
mod = NULL;
2168+
if (rc < 0) {
2169+
PyErr_SetString(PyExc_ImportError,
2170+
"failed to import from subinterpreter due to exception");
2171+
goto error;
2172+
}
21572173
}
21582174

21592175
/*****************************************************************/
@@ -2162,7 +2178,6 @@ import_run_extension(PyThreadState *tstate, PyModInitFunction p0,
21622178

21632179
/* Finally we handle the error return from _PyImport_RunModInitFunc(). */
21642180
if (rc < 0) {
2165-
_Py_ext_module_loader_result_apply_error(&res, name_buf);
21662181
goto error;
21672182
}
21682183

0 commit comments

Comments
 (0)