From cef28f1c62a75b6ba867ecc7aa989f51ea0930e9 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 22 Jun 2023 22:19:48 +0200 Subject: [PATCH 1/3] gh-105922: PyImport_AddModule() uses Py_DECREF() Rewrite PyImport_AddModule() to simply call Py_DECREF(), rather than creating a weak reference, to get a borrowed reference to the module. --- Python/import.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Python/import.c b/Python/import.c index 969902afea1cd6..f3174e9f12f348 100644 --- a/Python/import.c +++ b/Python/import.c @@ -372,16 +372,7 @@ PyImport_AddModuleObject(PyObject *name) if (!mod) { return NULL; } - - // gh-86160: PyImport_AddModuleObject() returns a borrowed reference - PyObject *ref = PyWeakref_NewRef(mod, NULL); - Py_DECREF(mod); - if (ref == NULL) { - return NULL; - } - - mod = PyWeakref_GetObject(ref); - Py_DECREF(ref); + Py_DECREF(mod); // sys.modules holds a strong reference return mod; /* borrowed reference */ } From c2490a482dee57f154c15bf246c57b96cd29b49c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 23 Jun 2023 00:22:50 +0200 Subject: [PATCH 2/3] Fix for custom sys.modules type --- Python/import.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Python/import.c b/Python/import.c index f3174e9f12f348..bdf90d6bf11cf8 100644 --- a/Python/import.c +++ b/Python/import.c @@ -372,8 +372,21 @@ PyImport_AddModuleObject(PyObject *name) if (!mod) { return NULL; } - Py_DECREF(mod); // sys.modules holds a strong reference - return mod; /* borrowed reference */ + + // Convert to a borrowed reference + Py_ssize_t old_refcnt = Py_REFCNT(mod); + Py_DECREF(mod); + if (old_refcnt == 1) { + // gh-86160, gh-105922: The module got deleted. It can happen if + // sys.modules does not hold a strong reference to the module. For + // example, if sys.modules is not a regular dict but a custom type. + PyErr_SetString(PyExc_RuntimeError, + "sys.modules does not hold a strong reference " + "to the module"); + return NULL; + } + + return mod; // borrowed reference, sys.modules holds a strong reference } From 326246876a43427b3a873af7c341a2ed825dc7d6 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 23 Jun 2023 00:37:32 +0200 Subject: [PATCH 3/3] Add test --- Lib/test/test_import/__init__.py | 23 +++++++++++++++++++++++ Modules/_testcapimodule.c | 21 +++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index f2726da203efbd..d07e3784362b1e 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -2644,6 +2644,29 @@ def test_pyimport_addmodule_create(self): mod = _testcapi.check_pyimport_addmodule(name) self.assertIs(mod, sys.modules[name]) + def test_pyimport_addmodule_borrowed_ref(self): + # gh-105922: Test PyImport_AddModule() with a custom sys.modules which + # doesn't keep a strong reference to the newly created module + import _testcapi + module_name = 'dontexist' + + self.assertNotIn(module_name, sys.modules) + self.addCleanup(unload, module_name) + + class CustomModules(dict): + def __setitem__(self, key, value): + if key == module_name: + value = "REPLACE_VALUE" + super().__setitem__(key, value) + + custom_modules = CustomModules() + with swap_attr(sys, 'modules', custom_modules): + _testcapi.pyimport_addmodule(module_name) + + if module_name in sys.modules: + print("sys.modules CANNOT be overriden: " + "{module_name} is in sys.modules") + if __name__ == '__main__': # Test needs to be a package, so we can do relative imports. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index d847539f6608dd..fb46148f26ad0c 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3372,6 +3372,26 @@ check_pyimport_addmodule(PyObject *self, PyObject *args) } +static PyObject * +test_pyimport_addmodule(PyObject *self, PyObject *args) +{ + const char *name; + if (!PyArg_ParseTuple(args, "s", &name)) { + return NULL; + } + PyObject *mod = PyImport_AddModule(name); + if (mod == NULL) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_AssertionError, + "PyImport_AddModule returns NULL " + "without setting an exception"); + } + return NULL; + } + return Py_NewRef(mod); +} + + static PyObject * test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) { @@ -3610,6 +3630,7 @@ static PyMethodDef TestMethods[] = { {"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL}, {"test_atexit", test_atexit, METH_NOARGS}, {"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS}, + {"pyimport_addmodule", test_pyimport_addmodule, METH_VARARGS}, {"test_weakref_capi", test_weakref_capi, METH_NOARGS}, {NULL, NULL} /* sentinel */ };