mirror of
https://github.com/python/cpython.git
synced 2026-05-06 12:49:07 -04:00
[3.13] gh-144601: Avoid sharing exception objects raised in a PyInit function across multiple interpreters (GH-144602) (GH-144880)
(cherry picked from commit fd6b639a49)
This commit is contained in:
@@ -42,6 +42,7 @@ from test.support import (
|
||||
requires_gil_enabled,
|
||||
Py_GIL_DISABLED,
|
||||
force_not_colorized_test_class,
|
||||
catch_unraisable_exception
|
||||
)
|
||||
from test.support.import_helper import (
|
||||
forget, make_legacy_pyc, unlink, unload, ready_to_import,
|
||||
@@ -2559,6 +2560,32 @@ class SubinterpImportTests(unittest.TestCase):
|
||||
excsnap = _interpreters.run_string(interpid, script)
|
||||
self.assertIsNot(excsnap, None)
|
||||
|
||||
@requires_subinterpreters
|
||||
def test_pyinit_function_raises_exception(self):
|
||||
# gh-144601: PyInit functions that raised exceptions would cause a
|
||||
# crash when imported from a subinterpreter.
|
||||
import _testsinglephase
|
||||
filename = _testsinglephase.__file__
|
||||
script = f"""if True:
|
||||
from test.test_import import import_extension_from_file
|
||||
|
||||
import_extension_from_file('_testsinglephase_raise_exception', {filename!r})"""
|
||||
|
||||
interp = _interpreters.create()
|
||||
try:
|
||||
with catch_unraisable_exception() as cm:
|
||||
exception = _interpreters.run_string(interp, script)
|
||||
unraisable = cm.unraisable
|
||||
finally:
|
||||
_interpreters.destroy(interp)
|
||||
|
||||
self.assertIsNotNone(exception)
|
||||
self.assertIsNotNone(exception.type.__name__, "ImportError")
|
||||
self.assertIsNotNone(exception.msg, "failed to import from subinterpreter due to exception")
|
||||
self.assertIsNotNone(unraisable)
|
||||
self.assertIs(unraisable.exc_type, RuntimeError)
|
||||
self.assertEqual(str(unraisable.exc_value), "evil")
|
||||
|
||||
|
||||
class TestSinglePhaseSnapshot(ModuleSnapshot):
|
||||
"""A representation of a single-phase init module for testing.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Fix crash when importing a module whose ``PyInit`` function raises an
|
||||
exception from a subinterpreter.
|
||||
@@ -799,3 +799,11 @@ PyInit__testsinglephase_circular(void)
|
||||
}
|
||||
return Py_NewRef(static_module_circular);
|
||||
}
|
||||
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit__testsinglephase_raise_exception(void)
|
||||
{
|
||||
PyErr_SetString(PyExc_RuntimeError, "evil");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+16
-1
@@ -2089,13 +2089,29 @@ import_run_extension(PyThreadState *tstate, PyModInitFunction p0,
|
||||
}
|
||||
|
||||
main_finally:
|
||||
if (rc < 0) {
|
||||
_Py_ext_module_loader_result_apply_error(&res, name_buf);
|
||||
}
|
||||
|
||||
/* Switch back to the subinterpreter. */
|
||||
if (switched) {
|
||||
// gh-144601: The exception object can't be transferred across
|
||||
// interpreters. Instead, we print out an unraisable exception, and
|
||||
// then raise a different exception for the calling interpreter.
|
||||
if (rc < 0) {
|
||||
assert(PyErr_Occurred());
|
||||
PyErr_FormatUnraisable("Exception while importing from subinterpreter");
|
||||
}
|
||||
assert(main_tstate != tstate);
|
||||
switch_back_from_main_interpreter(tstate, main_tstate, mod);
|
||||
/* Any module we got from the init function will have to be
|
||||
* reloaded in the subinterpreter. */
|
||||
mod = NULL;
|
||||
if (rc < 0) {
|
||||
PyErr_SetString(PyExc_ImportError,
|
||||
"failed to import from subinterpreter due to exception");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************/
|
||||
@@ -2104,7 +2120,6 @@ main_finally:
|
||||
|
||||
/* Finally we handle the error return from _PyImport_RunModInitFunc(). */
|
||||
if (rc < 0) {
|
||||
_Py_ext_module_loader_result_apply_error(&res, name_buf);
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user