]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-105922: Add PyImport_AddModuleRef() function (#105923)
authorVictor Stinner <vstinner@python.org>
Tue, 20 Jun 2023 06:48:14 +0000 (08:48 +0200)
committerGitHub <noreply@github.com>
Tue, 20 Jun 2023 06:48:14 +0000 (08:48 +0200)
* Add tests on PyImport_AddModuleRef(), PyImport_AddModule() and
  PyImport_AddModuleObject().
* pythonrun.c: Replace Py_XNewRef(PyImport_AddModule(name)) with
  PyImport_AddModuleRef(name).

13 files changed:
Doc/c-api/import.rst
Doc/data/refcounts.dat
Doc/data/stable_abi.dat
Doc/whatsnew/3.13.rst
Include/import.h
Lib/test/test_import/__init__.py
Lib/test/test_stable_abi_ctypes.py
Misc/NEWS.d/next/C API/2023-06-19-20-02-16.gh-issue-105922.o4T6wO.rst [new file with mode: 0644]
Misc/stable_abi.toml
Modules/_testcapimodule.c
PC/python3dll.c
Python/import.c
Python/pythonrun.c

index 6db20237f3fdb068b225ab28d3e38f6aa966685f..7aacc219a2bd619dac8ec362e1ea9ee491f6d102 100644 (file)
@@ -98,27 +98,40 @@ Importing Modules
    an exception set on failure (the module still exists in this case).
 
 
-.. c:function:: PyObject* PyImport_AddModuleObject(PyObject *name)
+.. c:function:: PyObject* PyImport_AddModuleRef(const char *name)
+
+   Return the module object corresponding to a module name.
+
+   The *name* argument may be of the form ``package.module``. First check the
+   modules dictionary if there's one there, and if not, create a new one and
+   insert it in the modules dictionary.
+
+   Return a :term:`strong reference` to the module on success. Return ``NULL``
+   with an exception set on failure.
 
-   Return the module object corresponding to a module name.  The *name* argument
-   may be of the form ``package.module``. First check the modules dictionary if
-   there's one there, and if not, create a new one and insert it in the modules
-   dictionary. Return ``NULL`` with an exception set on failure.
+   The module name *name* is decoded from UTF-8.
 
-   .. note::
+   This function does not load or import the module; if the module wasn't
+   already loaded, you will get an empty module object. Use
+   :c:func:`PyImport_ImportModule` or one of its variants to import a module.
+   Package structures implied by a dotted name for *name* are not created if
+   not already present.
+
+   .. versionadded:: 3.13
+
+
+.. c:function:: PyObject* PyImport_AddModuleObject(PyObject *name)
 
-      This function does not load or import the module; if the module wasn't already
-      loaded, you will get an empty module object. Use :c:func:`PyImport_ImportModule`
-      or one of its variants to import a module.  Package structures implied by a
-      dotted name for *name* are not created if not already present.
+   Similar to :c:func:`PyImport_AddModuleRef`, but return a :term:`borrowed
+   reference` and *name* is a Python :class:`str` object.
 
    .. versionadded:: 3.3
 
 
 .. c:function:: PyObject* PyImport_AddModule(const char *name)
 
-   Similar to :c:func:`PyImport_AddModuleObject`, but the name is a UTF-8
-   encoded string instead of a Unicode object.
+   Similar to :c:func:`PyImport_AddModuleRef`, but return a :term:`borrowed
+   reference`.
 
 
 .. c:function:: PyObject* PyImport_ExecCodeModule(const char *name, PyObject *co)
index f5628abb90443c03126296b2d40140a05180ac2b..d707cc34c98c97a97177b03772e8bcfaf6be76b8 100644 (file)
@@ -974,6 +974,9 @@ PyCoro_New:PyFrameObject*:frame:0:
 PyCoro_New:PyObject*:name:0:
 PyCoro_New:PyObject*:qualname:0:
 
+PyImport_AddModuleRef:PyObject*::+1:
+PyImport_AddModuleRef:const char*:name::
+
 PyImport_AddModule:PyObject*::0:reference borrowed from sys.modules
 PyImport_AddModule:const char*:name::
 
index 80806aedfcdbd5f8916d3535bb3568133b6c445c..a3fde01cf67f3c5af6fb5efe1fa2d5e8e6abfe85 100644 (file)
@@ -298,6 +298,7 @@ type,PyGetSetDef,3.2,,full-abi
 var,PyGetSetDescr_Type,3.2,,
 function,PyImport_AddModule,3.2,,
 function,PyImport_AddModuleObject,3.7,,
+function,PyImport_AddModuleRef,3.13,,
 function,PyImport_AppendInittab,3.2,,
 function,PyImport_ExecCodeModule,3.2,,
 function,PyImport_ExecCodeModuleEx,3.2,,
index 735715f01528984250883303e245edd89e054059..bbe02a9e85b42c22464064e67e139e76cdbf6003 100644 (file)
@@ -426,6 +426,11 @@ New Features
   APIs accepting the format codes always use ``Py_ssize_t`` for ``#`` formats.
   (Contributed by Inada Naoki in :gh:`104922`.)
 
+* Add :c:func:`PyImport_AddModuleRef`: similar to
+  :c:func:`PyImport_AddModule`, but return a :term:`strong reference` instead
+  of a :term:`borrowed reference`.
+  (Contributed by Victor Stinner in :gh:`105922`.)
+
 
 Porting to Python 3.13
 ----------------------
index 6c63744edb063437f94658dea02cf04578e73603..24b23b9119196fcb5908ae70e035a884543bcc05 100644 (file)
@@ -43,6 +43,11 @@ PyAPI_FUNC(PyObject *) PyImport_AddModuleObject(
 PyAPI_FUNC(PyObject *) PyImport_AddModule(
     const char *name            /* UTF-8 encoded string */
     );
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000
+PyAPI_FUNC(PyObject *) PyImport_AddModuleRef(
+    const char *name            /* UTF-8 encoded string */
+    );
+#endif
 PyAPI_FUNC(PyObject *) PyImport_ImportModule(
     const char *name            /* UTF-8 encoded string */
     );
index 71a50bcbeedf9bc7728f75cb94755ba6a964c077..f2726da203efbda408bd9283d4dfd9e51e824bdc 100644 (file)
@@ -2621,6 +2621,30 @@ class SinglephaseInitTests(unittest.TestCase):
         #  * module's global state was initialized, not reset
 
 
+@cpython_only
+class CAPITests(unittest.TestCase):
+    def test_pyimport_addmodule(self):
+        # gh-105922: Test PyImport_AddModuleRef(), PyImport_AddModule()
+        # and PyImport_AddModuleObject()
+        import _testcapi
+        for name in (
+            'sys',     # frozen module
+            'test',    # package
+            __name__,  # package.module
+        ):
+            _testcapi.check_pyimport_addmodule(name)
+
+    def test_pyimport_addmodule_create(self):
+        # gh-105922: Test PyImport_AddModuleRef(), create a new module
+        import _testcapi
+        name = 'dontexist'
+        self.assertNotIn(name, sys.modules)
+        self.addCleanup(unload, name)
+
+        mod = _testcapi.check_pyimport_addmodule(name)
+        self.assertIs(mod, sys.modules[name])
+
+
 if __name__ == '__main__':
     # Test needs to be a package, so we can do relative imports.
     unittest.main()
index 8cad71c7c34545cb0c43545103d251e872e0da1d..c26dff5aaf370bcd7e948a460cd1e333325a65e2 100644 (file)
@@ -328,6 +328,7 @@ SYMBOL_NAMES = (
     "PyGetSetDescr_Type",
     "PyImport_AddModule",
     "PyImport_AddModuleObject",
+    "PyImport_AddModuleRef",
     "PyImport_AppendInittab",
     "PyImport_ExecCodeModule",
     "PyImport_ExecCodeModuleEx",
diff --git a/Misc/NEWS.d/next/C API/2023-06-19-20-02-16.gh-issue-105922.o4T6wO.rst b/Misc/NEWS.d/next/C API/2023-06-19-20-02-16.gh-issue-105922.o4T6wO.rst
new file mode 100644 (file)
index 0000000..7515d68
--- /dev/null
@@ -0,0 +1,3 @@
+Add :c:func:`PyImport_AddModuleRef`: similar to :c:func:`PyImport_AddModule`,
+but return a :term:`strong reference` instead of a :term:`borrowed reference`.
+Patch by Victor Stinner.
index f9100054175fc1e9fbf00e5afc8adff25382727f..7025ed4e66b69859409447a838cbc3062f9f3f89 100644 (file)
     added = '3.12'
 [const.Py_TPFLAGS_ITEMS_AT_END]
     added = '3.12'
+[function.PyImport_AddModuleRef]
+    added = '3.13'
index 35687b49f24a0d105df2b74339cf506905c2016e..9d4517c8f68b09bee6aee7a8f7908d89cd5f2e8e 100644 (file)
@@ -3325,6 +3325,53 @@ test_atexit(PyObject *self, PyObject *Py_UNUSED(args))
 }
 
 
+static PyObject *
+check_pyimport_addmodule(PyObject *self, PyObject *args)
+{
+    const char *name;
+    if (!PyArg_ParseTuple(args, "s", &name)) {
+        return NULL;
+    }
+
+    // test PyImport_AddModuleRef()
+    PyObject *module = PyImport_AddModuleRef(name);
+    if (module == NULL) {
+        return NULL;
+    }
+    assert(PyModule_Check(module));
+    // module is a strong reference
+
+    // test PyImport_AddModule()
+    PyObject *module2 = PyImport_AddModule(name);
+    if (module2 == NULL) {
+        goto error;
+    }
+    assert(PyModule_Check(module2));
+    assert(module2 == module);
+    // module2 is a borrowed ref
+
+    // test PyImport_AddModuleObject()
+    PyObject *name_obj = PyUnicode_FromString(name);
+    if (name_obj == NULL) {
+        goto error;
+    }
+    PyObject *module3 = PyImport_AddModuleObject(name_obj);
+    Py_DECREF(name_obj);
+    if (module3 == NULL) {
+        goto error;
+    }
+    assert(PyModule_Check(module3));
+    assert(module3 == module);
+    // module3 is a borrowed ref
+
+    return module;
+
+error:
+    Py_DECREF(module);
+    return NULL;
+}
+
+
 static PyMethodDef TestMethods[] = {
     {"set_errno",               set_errno,                       METH_VARARGS},
     {"test_config",             test_config,                     METH_NOARGS},
@@ -3468,6 +3515,7 @@ static PyMethodDef TestMethods[] = {
     {"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL},
     {"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},
     {NULL, NULL} /* sentinel */
 };
 
index 505feef4b986c41669d26022d674d337416eea8e..fea19b77185dd544cea9e74ef81dc9f21bea1ed2 100755 (executable)
@@ -288,6 +288,7 @@ EXPORT_FUNC(PyGILState_GetThisThreadState)
 EXPORT_FUNC(PyGILState_Release)
 EXPORT_FUNC(PyImport_AddModule)
 EXPORT_FUNC(PyImport_AddModuleObject)
+EXPORT_FUNC(PyImport_AddModuleRef)
 EXPORT_FUNC(PyImport_AppendInittab)
 EXPORT_FUNC(PyImport_ExecCodeModule)
 EXPORT_FUNC(PyImport_ExecCodeModuleEx)
index 779af55347a6140b5e64385275b3862e80b3264f..969902afea1cd6b353f6fbc8c63fc09e39254215 100644 (file)
@@ -350,20 +350,38 @@ import_add_module(PyThreadState *tstate, PyObject *name)
     return m;
 }
 
+PyObject *
+PyImport_AddModuleRef(const char *name)
+{
+    PyObject *name_obj = PyUnicode_FromString(name);
+    if (name_obj == NULL) {
+        return NULL;
+    }
+    PyThreadState *tstate = _PyThreadState_GET();
+    PyObject *module = import_add_module(tstate, name_obj);
+    Py_DECREF(name_obj);
+    return module;
+}
+
+
 PyObject *
 PyImport_AddModuleObject(PyObject *name)
 {
     PyThreadState *tstate = _PyThreadState_GET();
     PyObject *mod = import_add_module(tstate, name);
-    if (mod) {
-        PyObject *ref = PyWeakref_NewRef(mod, NULL);
-        Py_DECREF(mod);
-        if (ref == NULL) {
-            return NULL;
-        }
-        mod = PyWeakref_GetObject(ref);
-        Py_DECREF(ref);
+    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);
     return mod; /* borrowed reference */
 }
 
@@ -2240,11 +2258,12 @@ init_importlib(PyThreadState *tstate, PyObject *sysmod)
     if (PyImport_ImportFrozenModule("_frozen_importlib") <= 0) {
         return -1;
     }
-    PyObject *importlib = PyImport_AddModule("_frozen_importlib"); // borrowed
+
+    PyObject *importlib = PyImport_AddModuleRef("_frozen_importlib");
     if (importlib == NULL) {
         return -1;
     }
-    IMPORTLIB(interp) = Py_NewRef(importlib);
+    IMPORTLIB(interp) = importlib;
 
     // Import the _imp module
     if (verbose) {
index f9e798ebcd65962da1af9e8dd12973ffad29a1ef..af82fa491511c10b42f37e8afa303e12aa3d9f8c 100644 (file)
@@ -406,7 +406,7 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit,
 {
     int ret = -1;
 
-    PyObject *main_module = Py_XNewRef(PyImport_AddModule("__main__"));
+    PyObject *main_module = PyImport_AddModuleRef("__main__");
     if (main_module == NULL)
         return -1;
     PyObject *dict = PyModule_GetDict(main_module);  // borrowed ref
@@ -502,7 +502,7 @@ PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
 int
 PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags)
 {
-    PyObject *main_module = Py_XNewRef(PyImport_AddModule("__main__"));
+    PyObject *main_module = PyImport_AddModuleRef("__main__");
     if (main_module == NULL) {
         return -1;
     }