]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-144601: Avoid sharing exception objects raised in a `PyInit` function acros...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 16 Feb 2026 15:37:46 +0000 (16:37 +0100)
committerGitHub <noreply@github.com>
Mon, 16 Feb 2026 15:37:46 +0000 (10:37 -0500)
gh-144601: Avoid sharing exception objects raised in a `PyInit` function across multiple interpreters (GH-144602)
(cherry picked from commit fd6b639a49dd1143c6fd8729fc49f17b3114a965)

Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
Lib/test/test_import/__init__.py
Misc/NEWS.d/next/Core_and_Builtins/2026-02-08-12-47-27.gh-issue-144601.E4Yi9J.rst [new file with mode: 0644]
Modules/_testsinglephase.c
Python/import.c

index 95121debbbfa74e9d69f6a34e95e41004ec6b414..2e1c6d72f549f9444df95f2d8666a968db6e64ef 100644 (file)
@@ -43,6 +43,7 @@ from test.support import (
     Py_GIL_DISABLED,
     no_rerun,
     force_not_colorized_test_class,
+    catch_unraisable_exception
 )
 from test.support.import_helper import (
     forget, make_legacy_pyc, unlink, unload, ready_to_import,
@@ -2540,6 +2541,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.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-08-12-47-27.gh-issue-144601.E4Yi9J.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-08-12-47-27.gh-issue-144601.E4Yi9J.rst
new file mode 100644 (file)
index 0000000..1c7772e
--- /dev/null
@@ -0,0 +1,2 @@
+Fix crash when importing a module whose ``PyInit`` function raises an
+exception from a subinterpreter.
index 2c59085d15b5beed94a289206eac0df35a558957..f74b964faf35fbc3a7fd6cc59aa7aaaaf48a0fa3 100644 (file)
@@ -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;
+}
index c7f10f7e4304db7d1049678f16d1061ef9258249..5914722c8f75177e111dd9f3dd69fead54d0db19 100644 (file)
@@ -2147,13 +2147,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;
+        }
     }
 
     /*****************************************************************/
@@ -2162,7 +2178,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;
     }