Skip to content

Commit bf9df37

Browse files
ZeroIntensitythunder-coding
authored andcommitted
pythongh-144601: Avoid sharing exception objects raised in a PyInit function across multiple interpreters (pythonGH-144602)
1 parent e93da18 commit bf9df37

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
@@ -45,6 +45,7 @@
4545
Py_GIL_DISABLED,
4646
no_rerun,
4747
force_not_colorized_test_class,
48+
catch_unraisable_exception
4849
)
4950
from test.support.import_helper import (
5051
forget, make_legacy_pyc, unlink, unload, ready_to_import,
@@ -2517,6 +2518,32 @@ def test_disallowed_reimport(self):
25172518
excsnap = _interpreters.run_string(interpid, script)
25182519
self.assertIsNot(excsnap, None)
25192520

2521+
@requires_subinterpreters
2522+
def test_pyinit_function_raises_exception(self):
2523+
# gh-144601: PyInit functions that raised exceptions would cause a
2524+
# crash when imported from a subinterpreter.
2525+
import _testsinglephase
2526+
filename = _testsinglephase.__file__
2527+
script = f"""if True:
2528+
from test.test_import import import_extension_from_file
2529+
2530+
import_extension_from_file('_testsinglephase_raise_exception', {filename!r})"""
2531+
2532+
interp = _interpreters.create()
2533+
try:
2534+
with catch_unraisable_exception() as cm:
2535+
exception = _interpreters.run_string(interp, script)
2536+
unraisable = cm.unraisable
2537+
finally:
2538+
_interpreters.destroy(interp)
2539+
2540+
self.assertIsNotNone(exception)
2541+
self.assertIsNotNone(exception.type.__name__, "ImportError")
2542+
self.assertIsNotNone(exception.msg, "failed to import from subinterpreter due to exception")
2543+
self.assertIsNotNone(unraisable)
2544+
self.assertIs(unraisable.exc_type, RuntimeError)
2545+
self.assertEqual(str(unraisable.exc_value), "evil")
2546+
25202547

25212548
class TestSinglePhaseSnapshot(ModuleSnapshot):
25222549
"""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
@@ -801,3 +801,11 @@ PyInit__testsinglephase_circular(void)
801801
}
802802
return Py_NewRef(static_module_circular);
803803
}
804+
805+
806+
PyMODINIT_FUNC
807+
PyInit__testsinglephase_raise_exception(void)
808+
{
809+
PyErr_SetString(PyExc_RuntimeError, "evil");
810+
return NULL;
811+
}

Python/import.c

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

21782178
main_finally:
2179+
if (rc < 0) {
2180+
_Py_ext_module_loader_result_apply_error(&res, name_buf);
2181+
}
2182+
21792183
/* Switch back to the subinterpreter. */
21802184
if (switched) {
2185+
// gh-144601: The exception object can't be transferred across
2186+
// interpreters. Instead, we print out an unraisable exception, and
2187+
// then raise a different exception for the calling interpreter.
2188+
if (rc < 0) {
2189+
assert(PyErr_Occurred());
2190+
PyErr_FormatUnraisable("Exception while importing from subinterpreter");
2191+
}
21812192
assert(main_tstate != tstate);
21822193
switch_back_from_main_interpreter(tstate, main_tstate, mod);
21832194
/* Any module we got from the init function will have to be
21842195
* reloaded in the subinterpreter. */
21852196
mod = NULL;
2197+
if (rc < 0) {
2198+
PyErr_SetString(PyExc_ImportError,
2199+
"failed to import from subinterpreter due to exception");
2200+
goto error;
2201+
}
21862202
}
21872203

21882204
/*****************************************************************/
@@ -2191,7 +2207,6 @@ import_run_extension(PyThreadState *tstate, PyModInitFunction p0,
21912207

21922208
/* Finally we handle the error return from _PyImport_RunModInitFunc(). */
21932209
if (rc < 0) {
2194-
_Py_ext_module_loader_result_apply_error(&res, name_buf);
21952210
goto error;
21962211
}
21972212

0 commit comments

Comments
 (0)