]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-140550: Initial implementation of PEP 793 – PyModExport (GH-140556)
authorPetr Viktorin <encukou@gmail.com>
Wed, 5 Nov 2025 11:31:42 +0000 (12:31 +0100)
committerGitHub <noreply@github.com>
Wed, 5 Nov 2025 11:31:42 +0000 (12:31 +0100)
Co-authored-by: Victor Stinner <vstinner@python.org>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
32 files changed:
Doc/data/stable_abi.dat
Include/exports.h
Include/internal/pycore_importdl.h
Include/internal/pycore_moduleobject.h
Include/moduleobject.h
Include/object.h
Lib/test/test_capi/test_module.py [new file with mode: 0644]
Lib/test/test_capi/test_type.py
Lib/test/test_cext/__init__.py
Lib/test/test_cext/create_moduledef.c [deleted file]
Lib/test/test_cext/extension.c
Lib/test/test_cext/setup.py
Lib/test/test_import/__init__.py
Lib/test/test_stable_abi_ctypes.py
Lib/test/test_sys.py
Misc/NEWS.d/next/C_API/2025-10-26-16-45-28.gh-issue-140556.s__Dae.rst [new file with mode: 0644]
Misc/stable_abi.toml
Modules/Setup.stdlib.in
Modules/_testcapi/heaptype.c
Modules/_testcapi/module.c [new file with mode: 0644]
Modules/_testcapi/parts.h
Modules/_testcapimodule.c
Modules/_testinternalcapi.c
Modules/_testmultiphase.c
Modules/_testsinglephase.c
Objects/moduleobject.c
Objects/typeobject.c
PC/python3dll.c
PCbuild/_testcapi.vcxproj
PCbuild/_testcapi.vcxproj.filters
Python/import.c
Python/importdl.c

index 67b498c4268e2ec1e519b8783ab08d211f9e26e8..1359cfa4fbf72ebb600d1fae3b3ea6b8110dc145 100644 (file)
@@ -425,6 +425,7 @@ func,PyLong_FromUnsignedNativeBytes,3.14,,
 func,PyLong_FromVoidPtr,3.2,,
 func,PyLong_GetInfo,3.2,,
 data,PyLong_Type,3.2,,
+macro,PyMODEXPORT_FUNC,3.15,,
 data,PyMap_Type,3.2,,
 func,PyMapping_Check,3.2,,
 func,PyMapping_GetItemString,3.2,,
@@ -471,8 +472,10 @@ func,PyModule_AddObjectRef,3.10,,
 func,PyModule_AddStringConstant,3.2,,
 func,PyModule_AddType,3.10,,
 func,PyModule_Create2,3.2,,
+func,PyModule_Exec,3.15,,
 func,PyModule_ExecDef,3.7,,
 func,PyModule_FromDefAndSpec2,3.7,,
+func,PyModule_FromSlotsAndSpec,3.15,,
 func,PyModule_GetDef,3.2,,
 func,PyModule_GetDict,3.2,,
 func,PyModule_GetFilename,3.2,,
@@ -480,6 +483,8 @@ func,PyModule_GetFilenameObject,3.2,,
 func,PyModule_GetName,3.2,,
 func,PyModule_GetNameObject,3.7,,
 func,PyModule_GetState,3.2,,
+func,PyModule_GetStateSize,3.15,,
+func,PyModule_GetToken,3.15,,
 func,PyModule_New,3.2,,
 func,PyModule_NewObject,3.7,,
 func,PyModule_SetDocString,3.7,,
@@ -738,6 +743,7 @@ func,PyType_GetFlags,3.2,,
 func,PyType_GetFullyQualifiedName,3.13,,
 func,PyType_GetModule,3.10,,
 func,PyType_GetModuleByDef,3.13,,
+func,PyType_GetModuleByToken,3.15,,
 func,PyType_GetModuleName,3.13,,
 func,PyType_GetModuleState,3.10,,
 func,PyType_GetName,3.11,,
index 0c646d5beb6ad6be5e04b90a0031a10c8f610896..62feb09ed2b43363b516036a735898f5d2fdee31 100644 (file)
@@ -9,6 +9,7 @@
                     inside the Python core, they are private to the core.
                     If in an extension module, it may be declared with
                     external linkage depending on the platform.
+  PyMODEXPORT_FUNC: Like PyMODINIT_FUNC, but for a slots array
 
   As a number of platforms support/require "__declspec(dllimport/dllexport)",
   we support a HAVE_DECLSPEC_DLL macro to save duplication.
@@ -62,9 +63,9 @@
         /* module init functions inside the core need no external linkage */
         /* except for Cygwin to handle embedding */
 #                       if defined(__CYGWIN__)
-#                               define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject*
+#                               define _PyINIT_FUNC_DECLSPEC Py_EXPORTED_SYMBOL
 #                       else /* __CYGWIN__ */
-#                               define PyMODINIT_FUNC PyObject*
+#                               define _PyINIT_FUNC_DECLSPEC
 #                       endif /* __CYGWIN__ */
 #               else /* Py_BUILD_CORE */
         /* Building an extension module, or an embedded situation */
@@ -78,9 +79,9 @@
 #                       define PyAPI_DATA(RTYPE) extern Py_IMPORTED_SYMBOL RTYPE
         /* module init functions outside the core must be exported */
 #                       if defined(__cplusplus)
-#                               define PyMODINIT_FUNC extern "C" Py_EXPORTED_SYMBOL PyObject*
+#                               define _PyINIT_FUNC_DECLSPEC extern "C" Py_EXPORTED_SYMBOL
 #                       else /* __cplusplus */
-#                               define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject*
+#                               define _PyINIT_FUNC_DECLSPEC Py_EXPORTED_SYMBOL
 #                       endif /* __cplusplus */
 #               endif /* Py_BUILD_CORE */
 #       endif /* HAVE_DECLSPEC_DLL */
 #ifndef PyAPI_DATA
 #       define PyAPI_DATA(RTYPE) extern Py_EXPORTED_SYMBOL RTYPE
 #endif
-#ifndef PyMODINIT_FUNC
+#ifndef _PyINIT_FUNC_DECLSPEC
 #       if defined(__cplusplus)
-#               define PyMODINIT_FUNC extern "C" Py_EXPORTED_SYMBOL PyObject*
+#               define _PyINIT_FUNC_DECLSPEC extern "C" Py_EXPORTED_SYMBOL
 #       else /* __cplusplus */
-#               define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject*
+#               define _PyINIT_FUNC_DECLSPEC Py_EXPORTED_SYMBOL
 #       endif /* __cplusplus */
 #endif
 
+#define PyMODINIT_FUNC _PyINIT_FUNC_DECLSPEC PyObject*
+#define PyMODEXPORT_FUNC _PyINIT_FUNC_DECLSPEC PyModuleDef_Slot*
 
 #endif /* Py_EXPORTS_H */
index 3ba9229cc21378700b861aa8a0e0a60501f0ee6c..12a32a5f70e51f11e988544a30aadaf47d0d0667 100644 (file)
@@ -28,6 +28,11 @@ typedef enum ext_module_origin {
     _Py_ext_module_origin_DYNAMIC = 3,
 } _Py_ext_module_origin;
 
+struct hook_prefixes {
+    const char *const init_prefix;
+    const char *const export_prefix;
+};
+
 /* Input for loading an extension module. */
 struct _Py_ext_module_loader_info {
     PyObject *filename;
@@ -40,7 +45,7 @@ struct _Py_ext_module_loader_info {
      * depending on if it's builtin or not. */
     PyObject *path;
     _Py_ext_module_origin origin;
-    const char *hook_prefix;
+    const struct hook_prefixes *hook_prefixes;
     const char *newcontext;
 };
 extern void _Py_ext_module_loader_info_clear(
@@ -62,7 +67,9 @@ extern int _Py_ext_module_loader_info_init_from_spec(
     PyObject *spec);
 #endif
 
-/* The result from running an extension module's init function. */
+/* The result from running an extension module's init function.
+ * Not used for modules defined via PyModExport (slots array).
+ */
 struct _Py_ext_module_loader_result {
     PyModuleDef *def;
     PyObject *module;
@@ -89,10 +96,11 @@ extern void _Py_ext_module_loader_result_apply_error(
 
 /* The module init function. */
 typedef PyObject *(*PyModInitFunction)(void);
+typedef PyModuleDef_Slot *(*PyModExportFunction)(void);
 #ifdef HAVE_DYNAMIC_LOADING
-extern PyModInitFunction _PyImport_GetModInitFunc(
+extern int _PyImport_GetModuleExportHooks(
     struct _Py_ext_module_loader_info *info,
-    FILE *fp);
+    FILE *fp, PyModInitFunction *modinit, PyModExportFunction *modexport);
 #endif
 extern int _PyImport_RunModInitFunc(
     PyModInitFunction p0,
index b170d7bce702c62f53b5677c1cfb975e4919e4df..c34e42e826e476a0cea55c1ceea4083880343e29 100644 (file)
@@ -1,5 +1,8 @@
 #ifndef Py_INTERNAL_MODULEOBJECT_H
 #define Py_INTERNAL_MODULEOBJECT_H
+
+#include <stdbool.h>
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -16,32 +19,49 @@ extern int _PyModule_IsPossiblyShadowing(PyObject *);
 
 extern int _PyModule_IsExtension(PyObject *obj);
 
+typedef int (*_Py_modexecfunc)(PyObject *);
+
 typedef struct {
     PyObject_HEAD
     PyObject *md_dict;
-    PyModuleDef *md_def;
     void *md_state;
     PyObject *md_weaklist;
     // for logging purposes after md_dict is cleared
     PyObject *md_name;
+    bool md_token_is_def;  /* if true, `md_token` is the PyModuleDef */
 #ifdef Py_GIL_DISABLED
     void *md_gil;
 #endif
+    Py_ssize_t md_state_size;
+    traverseproc md_state_traverse;
+    inquiry md_state_clear;
+    freefunc md_state_free;
+    void *md_token;
+    _Py_modexecfunc md_exec;  /* only set if md_token_is_def is true */
 } PyModuleObject;
 
-static inline PyModuleDef* _PyModule_GetDef(PyObject *mod) {
-    assert(PyModule_Check(mod));
-    return ((PyModuleObject *)mod)->md_def;
+#define _PyModule_CAST(op) \
+    (assert(PyModule_Check(op)), _Py_CAST(PyModuleObject*, (op)))
+
+static inline PyModuleDef *_PyModule_GetDefOrNull(PyObject *arg) {
+    PyModuleObject *mod = _PyModule_CAST(arg);
+    if (mod->md_token_is_def) {
+        return (PyModuleDef *)mod->md_token;
+    }
+    return NULL;
+}
+
+static inline PyModuleDef *_PyModule_GetToken(PyObject *arg) {
+    PyModuleObject *mod = _PyModule_CAST(arg);
+    return mod->md_token;
 }
 
 static inline void* _PyModule_GetState(PyObject* mod) {
-    assert(PyModule_Check(mod));
-    return ((PyModuleObject *)mod)->md_state;
+    return _PyModule_CAST(mod)->md_state;
 }
 
 static inline PyObject* _PyModule_GetDict(PyObject *mod) {
-    assert(PyModule_Check(mod));
-    PyObject *dict = ((PyModuleObject *)mod) -> md_dict;
+    PyObject *dict = _PyModule_CAST(mod)->md_dict;
     // _PyModule_GetDict(mod) must not be used after calling module_clear(mod)
     assert(dict != NULL);
     return dict;  // borrowed reference
index e3afac0a343be1b2f647f71e7151b0c42869ea15..e83bc395aa46188553aadbc64f48336a4ca5f025 100644 (file)
@@ -83,11 +83,19 @@ struct PyModuleDef_Slot {
 #endif
 #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15)
 #  define Py_mod_abi 5
+#  define Py_mod_name 6
+#  define Py_mod_doc 7
+#  define Py_mod_state_size 8
+#  define Py_mod_methods 9
+#  define Py_mod_state_traverse 10
+#  define Py_mod_state_clear 11
+#  define Py_mod_state_free 12
+#  define Py_mod_token 13
 #endif
 
 
 #ifndef Py_LIMITED_API
-#define _Py_mod_LAST_SLOT 5
+#define _Py_mod_LAST_SLOT 13
 #endif
 
 #endif /* New in 3.5 */
@@ -109,6 +117,13 @@ struct PyModuleDef_Slot {
 PyAPI_FUNC(int) PyUnstable_Module_SetGIL(PyObject *module, void *gil);
 #endif
 
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15)
+PyAPI_FUNC(PyObject *) PyModule_FromSlotsAndSpec(const PyModuleDef_Slot *,
+                                                 PyObject *spec);
+PyAPI_FUNC(int) PyModule_Exec(PyObject *mod);
+PyAPI_FUNC(int) PyModule_GetStateSize(PyObject *mod, Py_ssize_t *result);
+PyAPI_FUNC(int) PyModule_GetToken(PyObject *, void **result);
+#endif
 
 #ifndef _Py_OPAQUE_PYOBJECT
 struct PyModuleDef {
index 291e4f0a7ed2dd54bbfb0d105150e7a718bc76c0..f17dcba4f476b05297888499f6a212896c26000d 100644 (file)
@@ -839,6 +839,11 @@ PyAPI_FUNC(PyObject *) PyType_GetModuleByDef(PyTypeObject *, PyModuleDef *);
 PyAPI_FUNC(int) PyType_Freeze(PyTypeObject *type);
 #endif
 
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15)
+PyAPI_FUNC(PyObject *) PyType_GetModuleByToken(PyTypeObject *type,
+                                               const void *token);
+#endif
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/Lib/test/test_capi/test_module.py b/Lib/test/test_capi/test_module.py
new file mode 100644 (file)
index 0000000..7ec23e6
--- /dev/null
@@ -0,0 +1,185 @@
+# The C functions used by this module are in:
+#   Modules/_testcapi/module.c
+
+import unittest
+import types
+from test.support import import_helper, subTests
+
+# Skip this test if the _testcapi module isn't available.
+_testcapi = import_helper.import_module('_testcapi')
+
+
+class FakeSpec:
+    name = 'testmod'
+
+DEF_SLOTS = (
+    'Py_mod_name', 'Py_mod_doc', 'Py_mod_state_size', 'Py_mod_methods',
+    'Py_mod_state_traverse', 'Py_mod_state_clear', 'Py_mod_state_free',
+    'Py_mod_token',
+)
+
+def def_and_token(mod):
+    return (
+        _testcapi.pymodule_get_def(mod),
+        _testcapi.pymodule_get_token(mod),
+    )
+
+class TestModFromSlotsAndSpec(unittest.TestCase):
+    def test_empty(self):
+        mod = _testcapi.module_from_slots_empty(FakeSpec())
+        self.assertIsInstance(mod, types.ModuleType)
+        self.assertEqual(def_and_token(mod), (0, 0))
+        self.assertEqual(mod.__name__, 'testmod')
+        size = _testcapi.pymodule_get_state_size(mod)
+        self.assertEqual(size, 0)
+
+    def test_null_slots(self):
+        with self.assertRaises(SystemError):
+            _testcapi.module_from_slots_null(FakeSpec())
+
+    def test_none_spec(self):
+        # The spec currently must contain a name
+        with self.assertRaises(AttributeError):
+            _testcapi.module_from_slots_empty(None)
+        with self.assertRaises(AttributeError):
+            _testcapi.module_from_slots_name(None)
+
+    def test_name(self):
+        # Py_mod_name (and PyModuleDef.m_name) are currently ignored when
+        # spec is given.
+        # We still test that it's accepted.
+        mod = _testcapi.module_from_slots_name(FakeSpec())
+        self.assertIsInstance(mod, types.ModuleType)
+        self.assertEqual(def_and_token(mod), (0, 0))
+        self.assertEqual(mod.__name__, 'testmod')
+        self.assertEqual(mod.__doc__, None)
+
+    def test_doc(self):
+        mod = _testcapi.module_from_slots_doc(FakeSpec())
+        self.assertIsInstance(mod, types.ModuleType)
+        self.assertEqual(def_and_token(mod), (0, 0))
+        self.assertEqual(mod.__name__, 'testmod')
+        self.assertEqual(mod.__doc__, 'the docstring')
+
+    def test_size(self):
+        mod = _testcapi.module_from_slots_size(FakeSpec())
+        self.assertIsInstance(mod, types.ModuleType)
+        self.assertEqual(def_and_token(mod), (0, 0))
+        self.assertEqual(mod.__name__, 'testmod')
+        self.assertEqual(mod.__doc__, None)
+        size = _testcapi.pymodule_get_state_size(mod)
+        self.assertEqual(size, 123)
+
+    def test_methods(self):
+        mod = _testcapi.module_from_slots_methods(FakeSpec())
+        self.assertIsInstance(mod, types.ModuleType)
+        self.assertEqual(def_and_token(mod), (0, 0))
+        self.assertEqual(mod.__name__, 'testmod')
+        self.assertEqual(mod.__doc__, None)
+        self.assertEqual(mod.a_method(456), (mod, 456))
+
+    def test_gc(self):
+        mod = _testcapi.module_from_slots_gc(FakeSpec())
+        self.assertIsInstance(mod, types.ModuleType)
+        self.assertEqual(def_and_token(mod), (0, 0))
+        self.assertEqual(mod.__name__, 'testmod')
+        self.assertEqual(mod.__doc__, None)
+
+        # Check that the requested hook functions (which module_from_slots_gc
+        # stores as attributes) match what's in the module (as retrieved by
+        # _testinternalcapi.module_get_gc_hooks)
+        _testinternalcapi = import_helper.import_module('_testinternalcapi')
+        traverse, clear, free = _testinternalcapi.module_get_gc_hooks(mod)
+        self.assertEqual(traverse, mod.traverse)
+        self.assertEqual(clear, mod.clear)
+        self.assertEqual(free, mod.free)
+
+    def test_token(self):
+        mod = _testcapi.module_from_slots_token(FakeSpec())
+        self.assertIsInstance(mod, types.ModuleType)
+        self.assertEqual(def_and_token(mod), (0, _testcapi.module_test_token))
+        self.assertEqual(mod.__name__, 'testmod')
+        self.assertEqual(mod.__doc__, None)
+
+    def test_exec(self):
+        mod = _testcapi.module_from_slots_exec(FakeSpec())
+        self.assertIsInstance(mod, types.ModuleType)
+        self.assertEqual(def_and_token(mod), (0, 0))
+        self.assertEqual(mod.__name__, 'testmod')
+        self.assertEqual(mod.__doc__, None)
+        self.assertEqual(mod.a_number, 456)
+
+    def test_create(self):
+        spec = FakeSpec()
+        spec._gimme_this = "not a module object"
+        mod = _testcapi.module_from_slots_create(spec)
+        self.assertIsInstance(mod, str)
+        self.assertEqual(mod, "not a module object")
+        with self.assertRaises(TypeError):
+            _testcapi.pymodule_get_def(mod),
+        with self.assertRaises(TypeError):
+            _testcapi.pymodule_get_token(mod)
+
+    def test_def_slot(self):
+        """Slots that replace PyModuleDef fields can't be used with PyModuleDef
+        """
+        for name in DEF_SLOTS:
+            with self.subTest(name):
+                spec = FakeSpec()
+                spec._test_slot_id = getattr(_testcapi, name)
+                with self.assertRaises(SystemError) as cm:
+                    _testcapi.module_from_def_slot(spec)
+                self.assertIn(name, str(cm.exception))
+                self.assertIn("PyModuleDef", str(cm.exception))
+
+    def test_repeated_def_slot(self):
+        """Slots that replace PyModuleDef fields can't be repeated"""
+        for name in (*DEF_SLOTS, 'Py_mod_exec'):
+            with self.subTest(name):
+                spec = FakeSpec()
+                spec._test_slot_id = getattr(_testcapi, name)
+                with self.assertRaises(SystemError) as cm:
+                    _testcapi.module_from_slots_repeat_slot(spec)
+                self.assertIn(name, str(cm.exception))
+                self.assertIn("more than one", str(cm.exception))
+
+    def test_null_def_slot(self):
+        """Slots that replace PyModuleDef fields can't be NULL"""
+        for name in (*DEF_SLOTS, 'Py_mod_exec'):
+            with self.subTest(name):
+                spec = FakeSpec()
+                spec._test_slot_id = getattr(_testcapi, name)
+                with self.assertRaises(SystemError) as cm:
+                    _testcapi.module_from_slots_null_slot(spec)
+                self.assertIn(name, str(cm.exception))
+                self.assertIn("NULL", str(cm.exception))
+
+    def test_def_multiple_exec(self):
+        """PyModule_Exec runs all exec slots of PyModuleDef-defined module"""
+        mod = _testcapi.module_from_def_multiple_exec(FakeSpec())
+        self.assertFalse(hasattr(mod, 'a_number'))
+        _testcapi.pymodule_exec(mod)
+        self.assertEqual(mod.a_number, 456)
+        self.assertEqual(mod.another_number, 789)
+        _testcapi.pymodule_exec(mod)
+        self.assertEqual(mod.a_number, 456)
+        self.assertEqual(mod.another_number, -789)
+        def_ptr, token = def_and_token(mod)
+        self.assertEqual(def_ptr, token)
+
+    def test_def_token(self):
+        """In PyModuleDef-defined modules, the def is the token"""
+        mod = _testcapi.module_from_def_multiple_exec(FakeSpec())
+        def_ptr, token = def_and_token(mod)
+        self.assertEqual(def_ptr, token)
+        self.assertGreater(def_ptr, 0)
+
+    @subTests('name, expected_size', [
+        (__name__, 0),  # Python module
+        ('_testsinglephase', -1),  # single-phase init
+        ('sys', -1),
+    ])
+    def test_get_state_size(self, name, expected_size):
+        mod = import_helper.import_module(name)
+        size = _testcapi.pymodule_get_state_size(mod)
+        self.assertEqual(size, expected_size)
index 93874fbee326dd080f5cf993be8b647edfd895e4..e6a8ef9eed6fc4299d09eafb9877b9a8c1218c2b 100644 (file)
@@ -195,6 +195,24 @@ class TypeTests(unittest.TestCase):
         with self.assertRaises(TypeError):
             _testcapi.pytype_getmodulebydef(H2)
 
+    def test_get_module_by_token(self):
+        token = _testcapi.pymodule_get_token(_testcapi)
+
+        heaptype = _testcapi.create_type_with_token('_testcapi.H', 0)
+        mod = _testcapi.pytype_getmodulebytoken(heaptype, token)
+        self.assertIs(mod, _testcapi)
+
+        class H1(heaptype): pass
+        mod = _testcapi.pytype_getmodulebytoken(H1, token)
+        self.assertIs(mod, _testcapi)
+
+        with self.assertRaises(TypeError):
+            _testcapi.pytype_getmodulebytoken(int, token)
+
+        class H2(int): pass
+        with self.assertRaises(TypeError):
+            _testcapi.pytype_getmodulebytoken(H2, token)
+
     def test_freeze(self):
         # test PyType_Freeze()
         type_freeze = _testcapi.type_freeze
index fb93c6ccbb637d4b22eaef6d8eef1f8ab95c671d..a52c2241f5d9d40bd606b9ce7a19ec1d1ad797f4 100644 (file)
@@ -14,7 +14,6 @@ from test import support
 
 SOURCES = [
     os.path.join(os.path.dirname(__file__), 'extension.c'),
-    os.path.join(os.path.dirname(__file__), 'create_moduledef.c'),
 ]
 SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')
 
diff --git a/Lib/test/test_cext/create_moduledef.c b/Lib/test/test_cext/create_moduledef.c
deleted file mode 100644 (file)
index 249c316..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-// Workaround for testing _Py_OPAQUE_PYOBJECT.
-// See end of 'extension.c'
-
-
-#undef _Py_OPAQUE_PYOBJECT
-#undef Py_LIMITED_API
-#include "Python.h"
-
-
-// (repeated definition to avoid creating a header)
-extern PyObject *testcext_create_moduledef(
-    const char *name, const char *doc,
-    PyMethodDef *methods, PyModuleDef_Slot *slots);
-
-PyObject *testcext_create_moduledef(
-    const char *name, const char *doc,
-    PyMethodDef *methods, PyModuleDef_Slot *slots) {
-
-    static struct PyModuleDef _testcext_module = {
-        PyModuleDef_HEAD_INIT,
-    };
-    if (!_testcext_module.m_name) {
-        _testcext_module.m_name = name;
-        _testcext_module.m_doc = doc;
-        _testcext_module.m_methods = methods;
-        _testcext_module.m_slots = slots;
-    }
-    return PyModuleDef_Init(&_testcext_module);
-}
index 73fc67ae59d18f81664bd6e60fba098bc982e04f..0f668c1da32d6e7cea603535d7cf1ffe8ba88f45 100644 (file)
@@ -78,7 +78,7 @@ _testcext_exec(
     return 0;
 }
 
-#define _FUNC_NAME(NAME) PyInit_ ## NAME
+#define _FUNC_NAME(NAME) PyModExport_ ## NAME
 #define FUNC_NAME(NAME) _FUNC_NAME(NAME)
 
 // Converting from function pointer to void* has undefined behavior, but
@@ -88,58 +88,40 @@ _testcext_exec(
 _Py_COMP_DIAG_PUSH
 #if defined(__GNUC__)
 #pragma GCC diagnostic ignored "-Wpedantic"
+#pragma GCC diagnostic ignored "-Wcast-qual"
 #elif defined(__clang__)
 #pragma clang diagnostic ignored "-Wpedantic"
+#pragma clang diagnostic ignored "-Wcast-qual"
 #endif
 
+PyDoc_STRVAR(_testcext_doc, "C test extension.");
+
 static PyModuleDef_Slot _testcext_slots[] = {
+    {Py_mod_name, STR(MODULE_NAME)},
+    {Py_mod_doc, (void*)(char*)_testcext_doc},
     {Py_mod_exec, (void*)_testcext_exec},
+    {Py_mod_methods, _testcext_methods},
     {0, NULL}
 };
 
 _Py_COMP_DIAG_POP
 
-PyDoc_STRVAR(_testcext_doc, "C test extension.");
-
-#ifndef _Py_OPAQUE_PYOBJECT
-
-static struct PyModuleDef _testcext_module = {
-    PyModuleDef_HEAD_INIT,  // m_base
-    STR(MODULE_NAME),  // m_name
-    _testcext_doc,  // m_doc
-    0,  // m_size
-    _testcext_methods,  // m_methods
-    _testcext_slots,  // m_slots
-    NULL,  // m_traverse
-    NULL,  // m_clear
-    NULL,  // m_free
-};
-
-
-PyMODINIT_FUNC
+PyMODEXPORT_FUNC
 FUNC_NAME(MODULE_NAME)(void)
 {
-    return PyModuleDef_Init(&_testcext_module);
+    return _testcext_slots;
 }
 
-#else  // _Py_OPAQUE_PYOBJECT
-
-// Opaque PyObject means that PyModuleDef is also opaque and cannot be
-// declared statically. See PEP 793.
-// So, this part of module creation is split into a separate source file
-// which uses non-limited API.
-
-// (repeated definition to avoid creating a header)
-extern PyObject *testcext_create_moduledef(
-    const char *name, const char *doc,
-    PyMethodDef *methods, PyModuleDef_Slot *slots);
+// Also define the soft-deprecated entrypoint to ensure it isn't called
 
+#define _INITFUNC_NAME(NAME) PyInit_ ## NAME
+#define INITFUNC_NAME(NAME) _INITFUNC_NAME(NAME)
 
 PyMODINIT_FUNC
-FUNC_NAME(MODULE_NAME)(void)
+INITFUNC_NAME(MODULE_NAME)(void)
 {
-    return testcext_create_moduledef(
-        STR(MODULE_NAME), _testcext_doc, _testcext_methods, _testcext_slots);
+    PyErr_SetString(
+        PyExc_AssertionError,
+        "PyInit_* function called while a PyModExport_* one is available");
+    return NULL;
 }
-
-#endif  // _Py_OPAQUE_PYOBJECT
index 4d71e4751f7afd90cb525eafc08e35689e83a0a5..67dfddec751791a27694b77513ad5fbfe9ef4137 100644 (file)
@@ -99,7 +99,6 @@ def main():
     # Define _Py_OPAQUE_PYOBJECT macro
     if opaque_pyobject:
         cflags.append(f'-D_Py_OPAQUE_PYOBJECT')
-        sources.append('create_moduledef.c')
 
     if internal:
         cflags.append('-DTEST_INTERNAL_C_API=1')
index 072021e595975a7ebd4c85ac025266f113cc47fb..e87d8b7e7bbb1f88cbdb9f48544621c55c5ead4f 100644 (file)
@@ -2497,6 +2497,21 @@ class SubinterpImportTests(unittest.TestCase):
                         self.check_compatible_here(
                             modname, filename, strict=False, isolated=False)
 
+    @unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
+    def test_testmultiphase_exec_multiple(self):
+        modname = '_testmultiphase_exec_multiple'
+        filename = _testmultiphase.__file__
+        module = import_extension_from_file(modname, filename,
+                                            put_in_sys_modules=False)
+        # All three exec's were called.
+        self.assertEqual(module.a, 1)
+        self.assertEqual(module.b, 2)
+        self.assertEqual(module.c, 3)
+        # They were called in order.
+        keys = list(module.__dict__)
+        self.assertLess(keys.index('a'), keys.index('b'))
+        self.assertLess(keys.index('b'), keys.index('c'))
+
     @unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
     def test_python_compat(self):
         module = 'threading'
@@ -3394,6 +3409,83 @@ class SinglephaseInitTests(unittest.TestCase):
         #  * module's global state was initialized, not reset
 
 
+@unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
+class ModexportTests(unittest.TestCase):
+    def test_from_modexport(self):
+        modname = '_test_from_modexport'
+        filename = _testmultiphase.__file__
+        module = import_extension_from_file(modname, filename,
+                                            put_in_sys_modules=False)
+
+        self.assertEqual(module.__name__, modname)
+
+    def test_from_modexport_null(self):
+        modname = '_test_from_modexport_null'
+        filename = _testmultiphase.__file__
+        with self.assertRaises(SystemError):
+            import_extension_from_file(modname, filename,
+                                       put_in_sys_modules=False)
+
+    def test_from_modexport_exception(self):
+        modname = '_test_from_modexport_exception'
+        filename = _testmultiphase.__file__
+        with self.assertRaises(ValueError):
+            import_extension_from_file(modname, filename,
+                                       put_in_sys_modules=False)
+
+    def test_from_modexport_create_nonmodule(self):
+        modname = '_test_from_modexport_create_nonmodule'
+        filename = _testmultiphase.__file__
+        module = import_extension_from_file(modname, filename,
+                                            put_in_sys_modules=False)
+        self.assertIsInstance(module, str)
+
+    def test_from_modexport_smoke(self):
+        # General positive test for sundry features
+        # (PyModule_FromSlotsAndSpec tests exercise these more carefully)
+        modname = '_test_from_modexport_smoke'
+        filename = _testmultiphase.__file__
+        module = import_extension_from_file(modname, filename,
+                                            put_in_sys_modules=False)
+        self.assertEqual(module.__doc__, "the expected docstring")
+        self.assertEqual(module.number, 147)
+        self.assertEqual(module.get_state_int(), 258)
+        self.assertGreater(module.get_test_token(), 0)
+
+    def test_from_modexport_smoke_token(self):
+        _testcapi = import_module("_testcapi")
+
+        modname = '_test_from_modexport_smoke'
+        filename = _testmultiphase.__file__
+        module = import_extension_from_file(modname, filename,
+                                            put_in_sys_modules=False)
+        token = module.get_test_token()
+        self.assertEqual(_testcapi.pymodule_get_token(module), token)
+
+        tp = module.Example
+        self.assertEqual(_testcapi.pytype_getmodulebytoken(tp, token), module)
+        class Sub(tp):
+            pass
+        self.assertEqual(_testcapi.pytype_getmodulebytoken(Sub, token), module)
+
+    def test_from_modexport_empty_slots(self):
+        # Module to test that:
+        # - no slots are mandatory for PyModExport
+        # - the slots array is used as the default token
+        modname = '_test_from_modexport_empty_slots'
+        filename = _testmultiphase.__file__
+        module = import_extension_from_file(
+            modname, filename, put_in_sys_modules=False)
+
+        self.assertEqual(module.__name__, modname)
+        self.assertEqual(module.__doc__, None)
+
+        _testcapi = import_module("_testcapi")
+        smoke_mod = import_extension_from_file(
+            '_test_from_modexport_smoke', filename, put_in_sys_modules=False)
+        self.assertEqual(_testcapi.pymodule_get_token(module),
+                         smoke_mod.get_modexport_empty_slots())
+
 @cpython_only
 class TestMagicNumber(unittest.TestCase):
     def test_magic_number_endianness(self):
index cbec7e43a7c9fbd8ac3a222f23133b0752820eb1..7167646ecc6734d45fad14faf68cbb30db6fd19a 100644 (file)
@@ -469,8 +469,10 @@ SYMBOL_NAMES = (
     "PyModule_AddStringConstant",
     "PyModule_AddType",
     "PyModule_Create2",
+    "PyModule_Exec",
     "PyModule_ExecDef",
     "PyModule_FromDefAndSpec2",
+    "PyModule_FromSlotsAndSpec",
     "PyModule_GetDef",
     "PyModule_GetDict",
     "PyModule_GetFilename",
@@ -478,6 +480,8 @@ SYMBOL_NAMES = (
     "PyModule_GetName",
     "PyModule_GetNameObject",
     "PyModule_GetState",
+    "PyModule_GetStateSize",
+    "PyModule_GetToken",
     "PyModule_New",
     "PyModule_NewObject",
     "PyModule_SetDocString",
@@ -733,6 +737,7 @@ SYMBOL_NAMES = (
     "PyType_GetFullyQualifiedName",
     "PyType_GetModule",
     "PyType_GetModuleByDef",
+    "PyType_GetModuleByToken",
     "PyType_GetModuleName",
     "PyType_GetModuleState",
     "PyType_GetName",
index 1198c6d35113c81b0689a60528ef65ef30b52807..3ceed019ac43cfb867fb14e95a1ce4d57267334f 100644 (file)
@@ -1725,9 +1725,10 @@ class SizeofTest(unittest.TestCase):
         check(int(PyLong_BASE**2), vsize('') + 3*self.longdigit)
         # module
         if support.Py_GIL_DISABLED:
-            check(unittest, size('PPPPPP'))
+            md_gil = 'P'
         else:
-            check(unittest, size('PPPPP'))
+            md_gil = ''
+        check(unittest, size('PPPP?' + md_gil + 'NPPPPP'))
         # None
         check(None, size(''))
         # NotImplementedType
diff --git a/Misc/NEWS.d/next/C_API/2025-10-26-16-45-28.gh-issue-140556.s__Dae.rst b/Misc/NEWS.d/next/C_API/2025-10-26-16-45-28.gh-issue-140556.s__Dae.rst
new file mode 100644 (file)
index 0000000..61da609
--- /dev/null
@@ -0,0 +1,2 @@
+:pep:`793`: Add a new entry point for C extension modules,
+``PyModExport_<modulename>``.
index ad0f370459910af1a5c6ef59eacededf448ecc4f..7ee6cf1dae5a3310bce26ab2368d7f8ab7c04b61 100644 (file)
     added = '3.15'
 [const.PyABIInfo_FREETHREADING_AGNOSTIC]
     added = '3.15'
+[function.PyModule_FromSlotsAndSpec]
+    added = '3.15'
+[function.PyModule_Exec]
+    added = '3.15'
+[function.PyModule_GetToken]
+    added = '3.15'
+[function.PyType_GetModuleByToken]
+    added = '3.15'
+[function.PyModule_GetStateSize]
+    added = '3.15'
+[macro.PyMODEXPORT_FUNC]
+    added = '3.15'
+[const.Py_mod_name]
+    added = '3.15'
+[const.Py_mod_doc]
+    added = '3.15'
+[const.Py_mod_state_size]
+    added = '3.15'
+[const.Py_mod_methods]
+    added = '3.15'
+[const.Py_mod_state_traverse]
+    added = '3.15'
+[const.Py_mod_state_clear]
+    added = '3.15'
+[const.Py_mod_state_free]
+    added = '3.15'
+[const.Py_mod_token]
+    added = '3.15'
index b9ffdcc65d1313e1bb537a1dcfb8eeca8be19723..2c3013e3d0c1445ed017c89c117b6ad84fa979d7 100644 (file)
 @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
 @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
 @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c
 @MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c
 @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
 @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
index 69dcf072da1815366209f35ca35435df84813570..4fdcc850a339b4009bd2dee2c88ca9a9676bbf5a 100644 (file)
@@ -528,6 +528,21 @@ pytype_getmodulebydef(PyObject *self, PyObject *type)
     return Py_XNewRef(mod);
 }
 
+static PyObject *
+pytype_getmodulebytoken(PyObject *self, PyObject *args)
+{
+    PyObject *type;
+    PyObject *py_token;
+    if (!PyArg_ParseTuple(args, "OO", &type, &py_token)) {
+        return NULL;
+    }
+    void *token = PyLong_AsVoidPtr(py_token);
+    if ((!token) && PyErr_Occurred()) {
+        return NULL;
+    }
+    return PyType_GetModuleByToken((PyTypeObject *)type, token);
+}
+
 
 static PyMethodDef TestMethods[] = {
     {"pytype_fromspec_meta",    pytype_fromspec_meta,            METH_O},
@@ -546,6 +561,7 @@ static PyMethodDef TestMethods[] = {
     {"get_tp_token", get_tp_token, METH_O},
     {"pytype_getbasebytoken", pytype_getbasebytoken, METH_VARARGS},
     {"pytype_getmodulebydef", pytype_getmodulebydef, METH_O},
+    {"pytype_getmodulebytoken", pytype_getmodulebytoken, METH_VARARGS},
     {NULL},
 };
 
diff --git a/Modules/_testcapi/module.c b/Modules/_testcapi/module.c
new file mode 100644 (file)
index 0000000..9349445
--- /dev/null
@@ -0,0 +1,378 @@
+#include "parts.h"
+#include "util.h"
+
+// Test PyModule_* API
+
+/* unittest Cases that use these functions are in:
+ * Lib/test/test_capi/test_module.py
+ */
+
+static PyObject *
+module_from_slots_empty(PyObject *self, PyObject *spec)
+{
+    PyModuleDef_Slot slots[] = {
+        {0},
+    };
+    return PyModule_FromSlotsAndSpec(slots, spec);
+}
+
+static PyObject *
+module_from_slots_null(PyObject *self, PyObject *spec)
+{
+    return PyModule_FromSlotsAndSpec(NULL, spec);
+}
+
+static PyObject *
+module_from_slots_name(PyObject *self, PyObject *spec)
+{
+    PyModuleDef_Slot slots[] = {
+        {Py_mod_name, "currently ignored..."},
+        {0},
+    };
+    return PyModule_FromSlotsAndSpec(slots, spec);
+}
+
+static PyObject *
+module_from_slots_doc(PyObject *self, PyObject *spec)
+{
+    PyModuleDef_Slot slots[] = {
+        {Py_mod_doc, "the docstring"},
+        {0},
+    };
+    return PyModule_FromSlotsAndSpec(slots, spec);
+}
+
+static PyObject *
+module_from_slots_size(PyObject *self, PyObject *spec)
+{
+    PyModuleDef_Slot slots[] = {
+        {Py_mod_state_size, (void*)123},
+        {0},
+    };
+    PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
+    if (!mod) {
+        return NULL;
+    }
+    return mod;
+}
+
+static PyObject *
+a_method(PyObject *self, PyObject *arg)
+{
+    return PyTuple_Pack(2, self, arg);
+}
+
+static PyMethodDef a_methoddef_array[] = {
+    {"a_method", a_method, METH_O},
+    {0},
+};
+
+static PyObject *
+module_from_slots_methods(PyObject *self, PyObject *spec)
+{
+    PyModuleDef_Slot slots[] = {
+        {Py_mod_methods, a_methoddef_array},
+        {0},
+    };
+    return PyModule_FromSlotsAndSpec(slots, spec);
+}
+
+static int noop_traverse(PyObject *self, visitproc visit, void *arg) {
+    return 0;
+}
+static int noop_clear(PyObject *self) { return 0; }
+static void noop_free(void *self) { }
+
+static PyObject *
+module_from_slots_gc(PyObject *self, PyObject *spec)
+{
+    PyModuleDef_Slot slots[] = {
+        {Py_mod_state_traverse, noop_traverse},
+        {Py_mod_state_clear, noop_clear},
+        {Py_mod_state_free, noop_free},
+        {0},
+    };
+    PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
+    if (!mod) {
+        return NULL;
+    }
+    if (PyModule_Add(mod, "traverse", PyLong_FromVoidPtr(&noop_traverse)) < 0) {
+        Py_DECREF(mod);
+        return NULL;
+    }
+    if (PyModule_Add(mod, "clear", PyLong_FromVoidPtr(&noop_clear)) < 0) {
+        Py_DECREF(mod);
+        return NULL;
+    }
+    if (PyModule_Add(mod, "free", PyLong_FromVoidPtr(&noop_free)) < 0) {
+        Py_DECREF(mod);
+        return NULL;
+    }
+    return mod;
+}
+
+static const char test_token;
+
+static PyObject *
+module_from_slots_token(PyObject *self, PyObject *spec)
+{
+    PyModuleDef_Slot slots[] = {
+        {Py_mod_token, (void*)&test_token},
+        {0},
+    };
+    PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
+    if (!mod) {
+        return NULL;
+    }
+    void *got_token;
+    if (PyModule_GetToken(mod, &got_token) < 0) {
+        Py_DECREF(mod);
+        return NULL;
+    }
+    assert(got_token == &test_token);
+    return mod;
+}
+
+static int
+simple_exec(PyObject *module)
+{
+    return PyModule_AddIntConstant(module, "a_number", 456);
+}
+
+static PyObject *
+module_from_slots_exec(PyObject *self, PyObject *spec)
+{
+    PyModuleDef_Slot slots[] = {
+        {Py_mod_exec, simple_exec},
+        {0},
+    };
+    PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
+    if (!mod) {
+        return NULL;
+    }
+    int res = PyObject_HasAttrStringWithError(mod, "a_number");
+    if (res < 0) {
+        Py_DECREF(mod);
+        return NULL;
+    }
+    assert(res == 0);
+    if (PyModule_Exec(mod) < 0) {
+        Py_DECREF(mod);
+        return NULL;
+    }
+    return mod;
+}
+
+static PyObject *
+create_attr_from_spec(PyObject *spec, PyObject *def)
+{
+    assert(!def);
+    return PyObject_GetAttrString(spec, "_gimme_this");
+}
+
+static PyObject *
+module_from_slots_create(PyObject *self, PyObject *spec)
+{
+    PyModuleDef_Slot slots[] = {
+        {Py_mod_create, create_attr_from_spec},
+        {0},
+    };
+    return PyModule_FromSlotsAndSpec(slots, spec);
+}
+
+
+static int
+slot_from_object(PyObject *obj)
+{
+    PyObject *slot_id_obj = PyObject_GetAttrString(obj, "_test_slot_id");
+    if (slot_id_obj == NULL) {
+        return -1;
+    }
+    int slot_id = PyLong_AsInt(slot_id_obj);
+    if (PyErr_Occurred()) {
+        return -1;
+    }
+    return slot_id;
+}
+
+static PyObject *
+module_from_slots_repeat_slot(PyObject *self, PyObject *spec)
+{
+    int slot_id = slot_from_object(spec);
+    if (slot_id < 0) {
+        return NULL;
+    }
+    PyModuleDef_Slot slots[] = {
+        {slot_id, "anything"},
+        {slot_id, "anything else"},
+        {0},
+    };
+    return PyModule_FromSlotsAndSpec(slots, spec);
+}
+
+static PyObject *
+module_from_slots_null_slot(PyObject *self, PyObject *spec)
+{
+    int slot_id = slot_from_object(spec);
+    if (slot_id < 0) {
+        return NULL;
+    }
+    PyModuleDef_Slot slots[] = {
+        {slot_id, NULL},
+        {0},
+    };
+    return PyModule_FromSlotsAndSpec(slots, spec);
+}
+
+static PyObject *
+module_from_def_slot(PyObject *self, PyObject *spec)
+{
+    int slot_id = slot_from_object(spec);
+    if (slot_id < 0) {
+        return NULL;
+    }
+    PyModuleDef_Slot slots[] = {
+        {slot_id, "anything"},
+        {0},
+    };
+    PyModuleDef def = {
+        PyModuleDef_HEAD_INIT,
+        .m_name = "currently ignored",
+        .m_slots = slots,
+    };
+    // PyModuleDef is normally static; the real requirement is that it
+    // must outlive its module.
+    // Here, module creation fails, so it's fine on the stack.
+    PyObject *result = PyModule_FromDefAndSpec(&def, spec);
+    assert(result == NULL);
+    return result;
+}
+
+static int
+another_exec(PyObject *module)
+{
+    /* Make sure simple_exec was called */
+    assert(PyObject_HasAttrString(module, "a_number"));
+
+    /* Add or negate a global called 'another_number'  */
+    PyObject *another_number;
+    if (PyObject_GetOptionalAttrString(module, "another_number",
+                                       &another_number) < 0) {
+        return -1;
+    }
+    if (!another_number) {
+        return PyModule_AddIntConstant(module, "another_number", 789);
+    }
+    PyObject *neg_number = PyNumber_Negative(another_number);
+    Py_DECREF(another_number);
+    if (!neg_number) {
+        return -1;
+    }
+    int result = PyObject_SetAttrString(module, "another_number",
+                                        neg_number);
+    Py_DECREF(neg_number);
+    return result;
+}
+
+static PyObject *
+module_from_def_multiple_exec(PyObject *self, PyObject *spec)
+{
+    static PyModuleDef_Slot slots[] = {
+        {Py_mod_exec, simple_exec},
+        {Py_mod_exec, another_exec},
+        {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+        {0},
+    };
+    static PyModuleDef def = {
+        PyModuleDef_HEAD_INIT,
+        .m_name = "currently ignored",
+        .m_slots = slots,
+    };
+    return PyModule_FromDefAndSpec(&def, spec);
+}
+
+static PyObject *
+pymodule_exec(PyObject *self, PyObject *module)
+{
+    if (PyModule_Exec(module) < 0) {
+        return NULL;
+    }
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+pymodule_get_token(PyObject *self, PyObject *module)
+{
+    void *token;
+    if (PyModule_GetToken(module, &token) < 0) {
+        return NULL;
+    }
+    return PyLong_FromVoidPtr(token);
+}
+
+static PyObject *
+pymodule_get_def(PyObject *self, PyObject *module)
+{
+    PyModuleDef *def = PyModule_GetDef(module);
+    if (!def && PyErr_Occurred()) {
+        return NULL;
+    }
+    return PyLong_FromVoidPtr(def);
+}
+
+static PyObject *
+pymodule_get_state_size(PyObject *self, PyObject *module)
+{
+    Py_ssize_t size;
+    if (PyModule_GetStateSize(module, &size) < 0) {
+        return NULL;
+    }
+    return PyLong_FromSsize_t(size);
+}
+
+static PyMethodDef test_methods[] = {
+    {"module_from_slots_empty", module_from_slots_empty, METH_O},
+    {"module_from_slots_null", module_from_slots_null, METH_O},
+    {"module_from_slots_name", module_from_slots_name, METH_O},
+    {"module_from_slots_doc", module_from_slots_doc, METH_O},
+    {"module_from_slots_size", module_from_slots_size, METH_O},
+    {"module_from_slots_methods", module_from_slots_methods, METH_O},
+    {"module_from_slots_gc", module_from_slots_gc, METH_O},
+    {"module_from_slots_token", module_from_slots_token, METH_O},
+    {"module_from_slots_exec", module_from_slots_exec, METH_O},
+    {"module_from_slots_create", module_from_slots_create, METH_O},
+    {"module_from_slots_repeat_slot", module_from_slots_repeat_slot, METH_O},
+    {"module_from_slots_null_slot", module_from_slots_null_slot, METH_O},
+    {"module_from_def_multiple_exec", module_from_def_multiple_exec, METH_O},
+    {"module_from_def_slot", module_from_def_slot, METH_O},
+    {"pymodule_get_token", pymodule_get_token, METH_O},
+    {"pymodule_get_def", pymodule_get_def, METH_O},
+    {"pymodule_get_state_size", pymodule_get_state_size, METH_O},
+    {"pymodule_exec", pymodule_exec, METH_O},
+    {NULL},
+};
+
+int
+_PyTestCapi_Init_Module(PyObject *m)
+{
+#define ADD_INT_MACRO(C) if (PyModule_AddIntConstant(m, #C, C) < 0) return -1;
+    ADD_INT_MACRO(Py_mod_create);
+    ADD_INT_MACRO(Py_mod_exec);
+    ADD_INT_MACRO(Py_mod_multiple_interpreters);
+    ADD_INT_MACRO(Py_mod_gil);
+    ADD_INT_MACRO(Py_mod_name);
+    ADD_INT_MACRO(Py_mod_doc);
+    ADD_INT_MACRO(Py_mod_state_size);
+    ADD_INT_MACRO(Py_mod_methods);
+    ADD_INT_MACRO(Py_mod_state_traverse);
+    ADD_INT_MACRO(Py_mod_state_clear);
+    ADD_INT_MACRO(Py_mod_state_free);
+    ADD_INT_MACRO(Py_mod_token);
+#undef ADD_INT_MACRO
+    if (PyModule_Add(m, "module_test_token",
+                     PyLong_FromVoidPtr((void*)&test_token)) < 0)
+    {
+        return -1;
+    }
+    return PyModule_AddFunctions(m, test_methods);
+}
index 32915d04bd3635c71f0614c472ed9cad3544c501..a7feca5bd960705498ca89fab4e3125203e74a5a 100644 (file)
@@ -66,5 +66,6 @@ int _PyTestCapi_Init_Import(PyObject *mod);
 int _PyTestCapi_Init_Frame(PyObject *mod);
 int _PyTestCapi_Init_Type(PyObject *mod);
 int _PyTestCapi_Init_Function(PyObject *mod);
+int _PyTestCapi_Init_Module(PyObject *mod);
 
 #endif // Py_TESTCAPI_PARTS_H
index e29b9ae354bc1d00ad033d9b533651eacb8ab9e1..22cd731d4100828c059de6d6c58a44a84ff5d5fd 100644 (file)
@@ -3512,6 +3512,9 @@ _testcapi_exec(PyObject *m)
     if (_PyTestCapi_Init_Function(m) < 0) {
         return -1;
     }
+    if (_PyTestCapi_Init_Module(m) < 0) {
+        return -1;
+    }
 
     return 0;
 }
index c2647d405e25bcb09fae6192fd2498f67815af43..dede05960d78b6ea3af4a83b0aaabab243f6f47b 100644 (file)
@@ -2418,6 +2418,34 @@ set_vectorcall_nop(PyObject *self, PyObject *func)
     Py_RETURN_NONE;
 }
 
+static PyObject *
+module_get_gc_hooks(PyObject *self, PyObject *arg)
+{
+    PyModuleObject *mod = (PyModuleObject *)arg;
+    PyObject *traverse = NULL;
+    PyObject *clear = NULL;
+    PyObject *free = NULL;
+    PyObject *result = NULL;
+    traverse = PyLong_FromVoidPtr(mod->md_state_traverse);
+    if (!traverse) {
+        goto finally;
+    }
+    clear = PyLong_FromVoidPtr(mod->md_state_clear);
+    if (!clear) {
+        goto finally;
+    }
+    free = PyLong_FromVoidPtr(mod->md_state_free);
+    if (!free) {
+        goto finally;
+    }
+    result = PyTuple_FromArray((PyObject*[]){ traverse, clear, free }, 3);
+finally:
+    Py_XDECREF(traverse);
+    Py_XDECREF(clear);
+    Py_XDECREF(free);
+    return result;
+}
+
 static PyMethodDef module_functions[] = {
     {"get_configs", get_configs, METH_NOARGS},
     {"get_recursion_depth", get_recursion_depth, METH_NOARGS},
@@ -2527,6 +2555,7 @@ static PyMethodDef module_functions[] = {
 #endif
     {"simple_pending_call", simple_pending_call, METH_O},
     {"set_vectorcall_nop", set_vectorcall_nop, METH_O},
+    {"module_get_gc_hooks", module_get_gc_hooks, METH_O},
     {NULL, NULL} /* sentinel */
 };
 
index bfec0678e2c6695677cd21fb958018078288b9e5..220fa888e49a5275d517ac85d05c0e889257b450 100644 (file)
@@ -850,6 +850,28 @@ PyInit__testmultiphase_exec_unreported_exception(void)
     return PyModuleDef_Init(&def_exec_unreported_exception);
 }
 
+static int execfn_a1(PyObject*m) { return PyModule_AddIntConstant(m, "a", 1); }
+static int execfn_b2(PyObject*m) { return PyModule_AddIntConstant(m, "b", 2); }
+static int execfn_c3(PyObject*m) { return PyModule_AddIntConstant(m, "c", 3); }
+
+PyMODINIT_FUNC
+PyInit__testmultiphase_exec_multiple(void)
+{
+    static PyModuleDef_Slot slots[] = {
+        {Py_mod_exec, execfn_a1},
+        {Py_mod_exec, execfn_b2},
+        {Py_mod_exec, execfn_c3},
+        {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+        {0}
+    };
+    static PyModuleDef def = {
+        PyModuleDef_HEAD_INIT,
+        .m_name="_testmultiphase_exec_multiple",
+        .m_slots=slots,
+    };
+    return PyModuleDef_Init(&def);
+}
+
 static int
 meth_state_access_exec(PyObject *m)
 {
@@ -993,3 +1015,156 @@ PyInit__test_no_multiple_interpreter_slot(void)
 {
     return PyModuleDef_Init(&no_multiple_interpreter_slot_def);
 }
+
+
+/* PyModExport_* hooks */
+
+PyMODEXPORT_FUNC
+PyModExport__test_from_modexport(void)
+{
+    static PyModuleDef_Slot slots[] = {
+        {Py_mod_name, "_test_from_modexport"},
+        {0},
+    };
+    return slots;
+}
+
+PyMODEXPORT_FUNC
+PyModExport__test_from_modexport_null(void)
+{
+    return NULL;
+}
+
+PyMODINIT_FUNC
+PyModInit__test_from_modexport_null(void)
+{
+    // This is not called as fallback for failed PyModExport_*
+    assert(0);
+    PyErr_SetString(PyExc_AssertionError, "PyInit_ fallback called");
+    return NULL;
+}
+
+PyMODEXPORT_FUNC
+PyModExport__test_from_modexport_exception(void)
+{
+    PyErr_SetString(PyExc_ValueError, "failed as requested");
+    return NULL;
+}
+
+PyMODINIT_FUNC
+PyModInit__test_from_modexport_exception(void)
+{
+    // This is not called as fallback for failed PyModExport_*
+    assert(0);
+    PyErr_SetString(PyExc_AssertionError, "PyInit_ fallback called");
+    return NULL;
+}
+
+static PyObject *
+modexport_create_string(PyObject *spec, PyObject *def)
+{
+    assert(def == NULL);
+    return PyUnicode_FromString("is this \xf0\x9f\xa6\x8b... a module?");
+}
+
+PyMODEXPORT_FUNC
+PyModExport__test_from_modexport_create_nonmodule(void)
+{
+    static PyModuleDef_Slot slots[] = {
+        {Py_mod_name, "_test_from_modexport_create_nonmodule"},
+        {Py_mod_create, modexport_create_string},
+        {0},
+    };
+    return slots;
+}
+
+static PyModuleDef_Slot modexport_empty_slots[] = {
+    {0},
+};
+
+PyMODEXPORT_FUNC
+PyModExport__test_from_modexport_empty_slots(void)
+{
+    return modexport_empty_slots;
+}
+
+static int
+modexport_smoke_exec(PyObject *mod)
+{
+    // "magic" values 147 & 258 are expected in the test
+    if (PyModule_AddIntConstant(mod, "number", 147) < 0) {
+        return 0;
+    }
+    int *state = PyModule_GetState(mod);
+    if (!state) {
+        return -1;
+    }
+    *state = 258;
+
+    PyObject *tp = PyType_FromModuleAndSpec(mod, &StateAccessType_spec, NULL);
+    if (PyModule_Add(mod, "Example", tp) < 0) {
+        return -1;
+    }
+
+    return 0;
+}
+
+static PyObject *
+modexport_smoke_get_state_int(PyObject *mod, PyObject *arg)
+{
+    int *state = PyModule_GetState(mod);
+    if (!state) {
+        return NULL;
+    }
+    return PyLong_FromLong(*state);
+}
+
+static const char modexport_smoke_test_token;
+
+static PyObject *
+modexport_smoke_get_test_token(PyObject *mod, PyObject *arg)
+{
+    return PyLong_FromVoidPtr((void*)&modexport_smoke_test_token);
+}
+
+static PyObject *
+modexport_get_empty_slots(PyObject *mod, PyObject *arg)
+{
+    /* Get the address of modexport_empty_slots.
+     * This method would be in the `_test_from_modexport_empty_slots` module,
+     * if it had a methods slot.
+     */
+    return PyLong_FromVoidPtr(&modexport_empty_slots);
+}
+
+static void
+modexport_smoke_free(PyObject *mod)
+{
+    int *state = PyModule_GetState(mod);
+    if (!state) {
+        PyErr_FormatUnraisable("Exception ignored in module %R free", mod);
+    }
+    assert(*state == 258);
+}
+
+PyMODEXPORT_FUNC
+PyModExport__test_from_modexport_smoke(void)
+{
+    static PyMethodDef methods[] = {
+        {"get_state_int", modexport_smoke_get_state_int, METH_NOARGS},
+        {"get_test_token", modexport_smoke_get_test_token, METH_NOARGS},
+        {"get_modexport_empty_slots", modexport_get_empty_slots, METH_NOARGS},
+        {0},
+    };
+    static PyModuleDef_Slot slots[] = {
+        {Py_mod_name, "_test_from_modexport_smoke"},
+        {Py_mod_doc, "the expected docstring"},
+        {Py_mod_exec, modexport_smoke_exec},
+        {Py_mod_state_size, (void*)sizeof(int)},
+        {Py_mod_methods, methods},
+        {Py_mod_state_free, modexport_smoke_free},
+        {Py_mod_token, (void*)&modexport_smoke_test_token},
+        {0},
+    };
+    return slots;
+}
index 2c59085d15b5beed94a289206eac0df35a558957..ee38d61b43a82a068961cff55bfcbb76930168b5 100644 (file)
@@ -244,6 +244,8 @@ static inline module_state *
 get_module_state(PyObject *module)
 {
     PyModuleDef *def = PyModule_GetDef(module);
+    assert(def);
+
     if (def->m_size == -1) {
         return &global_state.module;
     }
index 0d45c1171688abd7403ddf915e6913f6f022ebbe..9dee03bdb5ee551227e3d89d1b0805cda149f382 100644 (file)
@@ -8,7 +8,7 @@
 #include "pycore_interp.h"        // PyInterpreterState.importlib
 #include "pycore_long.h"          // _PyLong_GetOne()
 #include "pycore_modsupport.h"    // _PyModule_CreateInitialized()
-#include "pycore_moduleobject.h"  // _PyModule_GetDef()
+#include "pycore_moduleobject.h"  // _PyModule_GetDefOrNull()
 #include "pycore_object.h"        // _PyType_AllocNoTrack
 #include "pycore_pyerrors.h"      // _PyErr_FormatFromCause()
 #include "pycore_pystate.h"       // _PyInterpreterState_GET()
@@ -27,6 +27,27 @@ static PyMemberDef module_members[] = {
     {0}
 };
 
+static void
+assert_def_missing_or_redundant(PyModuleObject *m)
+{
+    /* We copy all relevant info into the module object.
+     * Modules created using a def keep a reference to that (statically
+     * allocated) def; the info there should match what we have in the module.
+     */
+#ifndef NDEBUG
+    if (m->md_token_is_def) {
+        PyModuleDef *def = (PyModuleDef *)m->md_token;
+        assert(def);
+#define DO_ASSERT(F) assert (def->m_ ## F == m->md_state_ ## F);
+        DO_ASSERT(size);
+        DO_ASSERT(traverse);
+        DO_ASSERT(clear);
+        DO_ASSERT(free);
+#undef DO_ASSERT
+    }
+#endif // NDEBUG
+}
+
 
 PyTypeObject PyModuleDef_Type = {
     PyVarObject_HEAD_INIT(&PyType_Type, 0)
@@ -44,8 +65,14 @@ _PyModule_IsExtension(PyObject *obj)
     }
     PyModuleObject *module = (PyModuleObject*)obj;
 
-    PyModuleDef *def = module->md_def;
-    return (def != NULL && def->m_methods != NULL);
+    if (module->md_exec) {
+        return 1;
+    }
+    if (module->md_token_is_def) {
+        PyModuleDef *def = (PyModuleDef *)module->md_token;
+        return (module->md_token_is_def && def->m_methods != NULL);
+    }
+    return 0;
 }
 
 
@@ -146,10 +173,19 @@ new_module_notrack(PyTypeObject *mt)
     m = (PyModuleObject *)_PyType_AllocNoTrack(mt, 0);
     if (m == NULL)
         return NULL;
-    m->md_def = NULL;
     m->md_state = NULL;
     m->md_weaklist = NULL;
     m->md_name = NULL;
+    m->md_token_is_def = false;
+#ifdef Py_GIL_DISABLED
+    m->md_gil = Py_MOD_GIL_USED;
+#endif
+    m->md_state_size = 0;
+    m->md_state_traverse = NULL;
+    m->md_state_clear = NULL;
+    m->md_state_free = NULL;
+    m->md_exec = NULL;
+    m->md_token = NULL;
     m->md_dict = PyDict_New();
     if (m->md_dict == NULL) {
         Py_DECREF(m);
@@ -264,6 +300,17 @@ PyModule_Create2(PyModuleDef* module, int module_api_version)
     return _PyModule_CreateInitialized(module, module_api_version);
 }
 
+static void
+module_copy_members_from_deflike(
+    PyModuleObject *md,
+    PyModuleDef *def_like /* not necessarily a valid Python object */)
+{
+    md->md_state_size = def_like->m_size;
+    md->md_state_traverse = def_like->m_traverse;
+    md->md_state_clear = def_like->m_clear;
+    md->md_state_free = def_like->m_free;
+}
+
 PyObject *
 _PyModule_CreateInitialized(PyModuleDef* module, int module_api_version)
 {
@@ -310,15 +357,21 @@ _PyModule_CreateInitialized(PyModuleDef* module, int module_api_version)
             return NULL;
         }
     }
-    m->md_def = module;
+    m->md_token = module;
+    m->md_token_is_def = true;
+    module_copy_members_from_deflike(m, module);
 #ifdef Py_GIL_DISABLED
     m->md_gil = Py_MOD_GIL_USED;
 #endif
     return (PyObject*)m;
 }
 
-PyObject *
-PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_version)
+static PyObject *
+module_from_def_and_spec(
+    PyModuleDef* def_like, /* not necessarily a valid Python object */
+    PyObject *spec,
+    int module_api_version,
+    PyModuleDef* original_def /* NULL if not defined by a def */)
 {
     PyModuleDef_Slot* cur_slot;
     PyObject *(*create)(PyObject *, PyModuleDef*) = NULL;
@@ -331,10 +384,10 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
     int has_execution_slots = 0;
     const char *name;
     int ret;
+    void *token = NULL;
+    _Py_modexecfunc m_exec = NULL;
     PyInterpreterState *interp = _PyInterpreterState_GET();
 
-    PyModuleDef_Init(def);
-
     nameobj = PyObject_GetAttrString(spec, "name");
     if (nameobj == NULL) {
         return NULL;
@@ -348,7 +401,7 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
         goto error;
     }
 
-    if (def->m_size < 0) {
+    if (def_like->m_size < 0) {
         PyErr_Format(
             PyExc_SystemError,
             "module %s: m_size may not be negative for multi-phase initialization",
@@ -356,7 +409,35 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
         goto error;
     }
 
-    for (cur_slot = def->m_slots; cur_slot && cur_slot->slot; cur_slot++) {
+    for (cur_slot = def_like->m_slots; cur_slot && cur_slot->slot; cur_slot++) {
+        // Macro to copy a non-NULL, non-repeatable slot that's unusable with
+        // PyModuleDef. The destination must be initially NULL.
+#define COPY_COMMON_SLOT(SLOT, TYPE, DEST)                              \
+        do {                                                            \
+            if (!(TYPE)(cur_slot->value)) {                             \
+                PyErr_Format(                                           \
+                    PyExc_SystemError,                                  \
+                    "module %s: " #SLOT " must not be NULL",            \
+                    name);                                              \
+                goto error;                                             \
+            }                                                           \
+            if (original_def) {                                         \
+                PyErr_Format(                                           \
+                    PyExc_SystemError,                                  \
+                    "module %s: " #SLOT " used with PyModuleDef",       \
+                    name);                                              \
+                goto error;                                             \
+            }                                                           \
+            if (DEST) {                                                 \
+                PyErr_Format(                                           \
+                    PyExc_SystemError,                                  \
+                    "module %s has more than one " #SLOT " slot",       \
+                    name);                                              \
+                goto error;                                             \
+            }                                                           \
+            DEST = (TYPE)(cur_slot->value);                             \
+        } while (0);                                                    \
+        /////////////////////////////////////////////////////////////////
         switch (cur_slot->slot) {
             case Py_mod_create:
                 if (create) {
@@ -370,6 +451,9 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
                 break;
             case Py_mod_exec:
                 has_execution_slots = 1;
+                if (!original_def) {
+                    COPY_COMMON_SLOT(Py_mod_exec, _Py_modexecfunc, m_exec);
+                }
                 break;
             case Py_mod_multiple_interpreters:
                 if (has_multiple_interpreters_slot) {
@@ -398,6 +482,35 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
                     goto error;
                 }
                 break;
+            case Py_mod_name:
+                COPY_COMMON_SLOT(Py_mod_name, char*, def_like->m_name);
+                break;
+            case Py_mod_doc:
+                COPY_COMMON_SLOT(Py_mod_doc, char*, def_like->m_doc);
+                break;
+            case Py_mod_state_size:
+                COPY_COMMON_SLOT(Py_mod_state_size, Py_ssize_t,
+                                 def_like->m_size);
+                break;
+            case Py_mod_methods:
+                COPY_COMMON_SLOT(Py_mod_methods, PyMethodDef*,
+                                 def_like->m_methods);
+                break;
+            case Py_mod_state_traverse:
+                COPY_COMMON_SLOT(Py_mod_state_traverse, traverseproc,
+                                 def_like->m_traverse);
+                break;
+            case Py_mod_state_clear:
+                COPY_COMMON_SLOT(Py_mod_state_clear, inquiry,
+                                 def_like->m_clear);
+                break;
+            case Py_mod_state_free:
+                COPY_COMMON_SLOT(Py_mod_state_free, freefunc,
+                                 def_like->m_free);
+                break;
+            case Py_mod_token:
+                COPY_COMMON_SLOT(Py_mod_token, void*, token);
+                break;
             default:
                 assert(cur_slot->slot < 0 || cur_slot->slot > _Py_mod_LAST_SLOT);
                 PyErr_Format(
@@ -406,6 +519,7 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
                     name, cur_slot->slot);
                 goto error;
         }
+#undef COPY_COMMON_SLOT
     }
 
     /* By default, multi-phase init modules are expected
@@ -429,7 +543,7 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
     }
 
     if (create) {
-        m = create(spec, def);
+        m = create(spec, original_def);
         if (m == NULL) {
             if (!PyErr_Occurred()) {
                 PyErr_Format(
@@ -455,15 +569,27 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
     }
 
     if (PyModule_Check(m)) {
-        ((PyModuleObject*)m)->md_state = NULL;
-        ((PyModuleObject*)m)->md_def = def;
+        PyModuleObject *mod = (PyModuleObject*)m;
+        mod->md_state = NULL;
+        module_copy_members_from_deflike(mod, def_like);
+        if (original_def) {
+            assert (!token);
+            mod->md_token = original_def;
+            mod->md_token_is_def = 1;
+        }
+        else {
+            mod->md_token = token;
+        }
 #ifdef Py_GIL_DISABLED
-        ((PyModuleObject*)m)->md_gil = gil_slot;
+        mod->md_gil = gil_slot;
 #else
         (void)gil_slot;
 #endif
+        mod->md_exec = m_exec;
     } else {
-        if (def->m_size > 0 || def->m_traverse || def->m_clear || def->m_free) {
+        if (def_like->m_size > 0 || def_like->m_traverse || def_like->m_clear
+            || def_like->m_free)
+        {
             PyErr_Format(
                 PyExc_SystemError,
                 "module %s is not a module object, but requests module state",
@@ -478,17 +604,25 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
                 name);
             goto error;
         }
+        if (token) {
+            PyErr_Format(
+                PyExc_SystemError,
+                "module %s specifies a token, but did not create "
+                    "a ModuleType instance",
+                name);
+            goto error;
+        }
     }
 
-    if (def->m_methods != NULL) {
-        ret = _add_methods_to_object(m, nameobj, def->m_methods);
+    if (def_like->m_methods != NULL) {
+        ret = _add_methods_to_object(m, nameobj, def_like->m_methods);
         if (ret != 0) {
             goto error;
         }
     }
 
-    if (def->m_doc != NULL) {
-        ret = PyModule_SetDocString(m, def->m_doc);
+    if (def_like->m_doc != NULL) {
+        ret = PyModule_SetDocString(m, def_like->m_doc);
         if (ret != 0) {
             goto error;
         }
@@ -503,6 +637,29 @@ error:
     return NULL;
 }
 
+PyObject *
+PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_version)
+{
+    PyModuleDef_Init(def);
+    return module_from_def_and_spec(def, spec, module_api_version, def);
+}
+
+PyObject *
+PyModule_FromSlotsAndSpec(const PyModuleDef_Slot *slots, PyObject *spec)
+{
+    if (!slots) {
+        PyErr_SetString(
+            PyExc_SystemError,
+            "PyModule_FromSlotsAndSpec called with NULL slots");
+        return NULL;
+    }
+    // Fill in enough of a PyModuleDef to pass to common machinery
+    PyModuleDef def_like = {.m_slots = (PyModuleDef_Slot *)slots};
+
+    return module_from_def_and_spec(&def_like, spec, PYTHON_API_VERSION,
+                                    NULL);
+}
+
 #ifdef Py_GIL_DISABLED
 int
 PyUnstable_Module_SetGIL(PyObject *module, void *gil)
@@ -516,71 +673,94 @@ PyUnstable_Module_SetGIL(PyObject *module, void *gil)
 }
 #endif
 
-int
-PyModule_ExecDef(PyObject *module, PyModuleDef *def)
+static int
+run_exec_func(PyObject *module, int (*exec)(PyObject *))
 {
-    PyModuleDef_Slot *cur_slot;
-    const char *name;
-    int ret;
+    int ret = exec(module);
+    if (ret != 0) {
+        if (!PyErr_Occurred()) {
+            PyErr_Format(
+                PyExc_SystemError,
+                "execution of %R failed without setting an exception",
+                module);
+        }
+        return -1;
+    }
+    if (PyErr_Occurred()) {
+        _PyErr_FormatFromCause(
+            PyExc_SystemError,
+            "execution of module %R raised unreported exception",
+            module);
+        return -1;
+    }
+    return 0;
+}
 
-    name = PyModule_GetName(module);
-    if (name == NULL) {
+static int
+alloc_state(PyObject *module)
+{
+    if (!PyModule_Check(module)) {
+        PyErr_Format(PyExc_TypeError, "expected module, got %T", module);
         return -1;
     }
+    PyModuleObject *md = (PyModuleObject*)module;
 
-    if (def->m_size >= 0) {
-        PyModuleObject *md = (PyModuleObject*)module;
+    if (md->md_state_size >= 0) {
         if (md->md_state == NULL) {
             /* Always set a state pointer; this serves as a marker to skip
              * multiple initialization (importlib.reload() is no-op) */
-            md->md_state = PyMem_Malloc(def->m_size);
+            md->md_state = PyMem_Malloc(md->md_state_size);
             if (!md->md_state) {
                 PyErr_NoMemory();
                 return -1;
             }
-            memset(md->md_state, 0, def->m_size);
+            memset(md->md_state, 0, md->md_state_size);
         }
     }
+    return 0;
+}
+
+int
+PyModule_Exec(PyObject *module)
+{
+    if (alloc_state(module) < 0) {
+        return -1;
+    }
+    PyModuleObject *md = (PyModuleObject*)module;
+    if (md->md_exec) {
+        assert(!md->md_token_is_def);
+        return run_exec_func(module, md->md_exec);
+    }
+
+    PyModuleDef *def = _PyModule_GetDefOrNull(module);
+    if (def) {
+        return PyModule_ExecDef(module, def);
+    }
+    return 0;
+}
+
+int
+PyModule_ExecDef(PyObject *module, PyModuleDef *def)
+{
+    PyModuleDef_Slot *cur_slot;
+
+    if (alloc_state(module) < 0) {
+        return -1;
+    }
+
+    assert(PyModule_Check(module));
 
     if (def->m_slots == NULL) {
         return 0;
     }
 
     for (cur_slot = def->m_slots; cur_slot && cur_slot->slot; cur_slot++) {
-        switch (cur_slot->slot) {
-            case Py_mod_create:
-                /* handled in PyModule_FromDefAndSpec2 */
-                break;
-            case Py_mod_exec:
-                ret = ((int (*)(PyObject *))cur_slot->value)(module);
-                if (ret != 0) {
-                    if (!PyErr_Occurred()) {
-                        PyErr_Format(
-                            PyExc_SystemError,
-                            "execution of module %s failed without setting an exception",
-                            name);
-                    }
-                    return -1;
-                }
-                if (PyErr_Occurred()) {
-                    _PyErr_FormatFromCause(
-                        PyExc_SystemError,
-                        "execution of module %s raised unreported exception",
-                        name);
-                    return -1;
-                }
-                break;
-            case Py_mod_multiple_interpreters:
-            case Py_mod_gil:
-            case Py_mod_abi:
-                /* handled in PyModule_FromDefAndSpec2 */
-                break;
-            default:
-                PyErr_Format(
-                    PyExc_SystemError,
-                    "module %s initialized with unknown slot %i",
-                    name, cur_slot->slot);
+        if (cur_slot->slot == Py_mod_exec) {
+            int (*func)(PyObject *) = cur_slot->value;
+            if (run_exec_func(module, func) < 0) {
                 return -1;
+            }
+            continue;
         }
     }
     return 0;
@@ -624,6 +804,31 @@ PyModule_GetDict(PyObject *m)
     return _PyModule_GetDict(m);  // borrowed reference
 }
 
+int
+PyModule_GetStateSize(PyObject *m, Py_ssize_t *size_p)
+{
+    *size_p = -1;
+    if (!PyModule_Check(m)) {
+        PyErr_Format(PyExc_TypeError, "expected module, got %T", m);
+        return -1;
+    }
+    PyModuleObject *mod = (PyModuleObject *)m;
+    *size_p = mod->md_state_size;
+    return 0;
+}
+
+int
+PyModule_GetToken(PyObject *m, void **token_p)
+{
+    *token_p = NULL;
+    if (!PyModule_Check(m)) {
+        PyErr_Format(PyExc_TypeError, "expected module, got %T", m);
+        return -1;
+    }
+    *token_p = _PyModule_GetToken(m);
+    return 0;
+}
+
 PyObject*
 PyModule_GetNameObject(PyObject *mod)
 {
@@ -764,7 +969,7 @@ PyModule_GetDef(PyObject* m)
         PyErr_BadArgument();
         return NULL;
     }
-    return _PyModule_GetDef(m);
+    return _PyModule_GetDefOrNull(m);
 }
 
 void*
@@ -888,17 +1093,18 @@ module_dealloc(PyObject *self)
     }
     FT_CLEAR_WEAKREFS(self, m->md_weaklist);
 
+    assert_def_missing_or_redundant(m);
     /* bpo-39824: Don't call m_free() if m_size > 0 and md_state=NULL */
-    if (m->md_def && m->md_def->m_free
-        && (m->md_def->m_size <= 0 || m->md_state != NULL))
+    if (m->md_state_free && (m->md_state_size <= 0 || m->md_state != NULL))
     {
-        m->md_def->m_free(m);
+        m->md_state_free(m);
     }
 
     Py_XDECREF(m->md_dict);
     Py_XDECREF(m->md_name);
-    if (m->md_state != NULL)
+    if (m->md_state != NULL) {
         PyMem_Free(m->md_state);
+    }
     Py_TYPE(m)->tp_free((PyObject *)m);
 }
 
@@ -1206,11 +1412,11 @@ module_traverse(PyObject *self, visitproc visit, void *arg)
 {
     PyModuleObject *m = _PyModule_CAST(self);
 
+    assert_def_missing_or_redundant(m);
     /* bpo-39824: Don't call m_traverse() if m_size > 0 and md_state=NULL */
-    if (m->md_def && m->md_def->m_traverse
-        && (m->md_def->m_size <= 0 || m->md_state != NULL))
+    if (m->md_state_traverse && (m->md_state_size <= 0 || m->md_state != NULL))
     {
-        int res = m->md_def->m_traverse((PyObject*)m, visit, arg);
+        int res = m->md_state_traverse((PyObject*)m, visit, arg);
         if (res)
             return res;
     }
@@ -1224,18 +1430,19 @@ module_clear(PyObject *self)
 {
     PyModuleObject *m = _PyModule_CAST(self);
 
+    assert_def_missing_or_redundant(m);
     /* bpo-39824: Don't call m_clear() if m_size > 0 and md_state=NULL */
-    if (m->md_def && m->md_def->m_clear
-        && (m->md_def->m_size <= 0 || m->md_state != NULL))
+    if (m->md_state_clear && (m->md_state_size <= 0 || m->md_state != NULL))
     {
-        int res = m->md_def->m_clear((PyObject*)m);
+        int res = m->md_state_clear((PyObject*)m);
         if (PyErr_Occurred()) {
             PyErr_FormatUnraisable("Exception ignored in m_clear of module%s%V",
                                    m->md_name ? " " : "",
                                    m->md_name, "");
         }
-        if (res)
+        if (res) {
             return res;
+        }
     }
     Py_CLEAR(m->md_dict);
     return 0;
index d56950158073ead601cf099dcc94832c615d68b0..326f4add896bab6295a9eb143b7b3f4ec441522c 100644 (file)
@@ -5764,11 +5764,11 @@ PyType_GetModuleState(PyTypeObject *type)
 }
 
 
-/* Get the module of the first superclass where the module has the
- * given PyModuleDef.
+/* Return borrowed ref to the module of the first superclass where the module
+ * has the given token.
  */
-PyObject *
-PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
+static PyObject *
+borrow_module_by_token(PyTypeObject *type, const void *token)
 {
     assert(PyType_Check(type));
 
@@ -5780,7 +5780,7 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
     else {
         PyHeapTypeObject *ht = (PyHeapTypeObject*)type;
         PyObject *module = ht->ht_module;
-        if (module && _PyModule_GetDef(module) == def) {
+        if (module && _PyModule_GetToken(module) == token) {
             return module;
         }
     }
@@ -5808,7 +5808,7 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
 
         PyHeapTypeObject *ht = (PyHeapTypeObject*)super;
         PyObject *module = ht->ht_module;
-        if (module && _PyModule_GetDef(module) == def) {
+        if (module && _PyModule_GetToken(module) == token) {
             res = module;
             break;
         }
@@ -5826,6 +5826,18 @@ error:
     return NULL;
 }
 
+PyObject *
+PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
+{
+    return borrow_module_by_token(type, def);
+}
+
+PyObject *
+PyType_GetModuleByToken(PyTypeObject *type, const void *token)
+{
+    return Py_XNewRef(borrow_module_by_token(type, token));
+}
+
 
 static PyTypeObject *
 get_base_by_token_recursive(PyObject *bases, void *token)
index 05c86e6d5924d48d65d858e0e552445dcbafad46..99e0f05fe03209d1e58bf05dd735f947d8788e10 100755 (executable)
@@ -416,8 +416,10 @@ EXPORT_FUNC(PyModule_AddObjectRef)
 EXPORT_FUNC(PyModule_AddStringConstant)
 EXPORT_FUNC(PyModule_AddType)
 EXPORT_FUNC(PyModule_Create2)
+EXPORT_FUNC(PyModule_Exec)
 EXPORT_FUNC(PyModule_ExecDef)
 EXPORT_FUNC(PyModule_FromDefAndSpec2)
+EXPORT_FUNC(PyModule_FromSlotsAndSpec)
 EXPORT_FUNC(PyModule_GetDef)
 EXPORT_FUNC(PyModule_GetDict)
 EXPORT_FUNC(PyModule_GetFilename)
@@ -425,6 +427,8 @@ EXPORT_FUNC(PyModule_GetFilenameObject)
 EXPORT_FUNC(PyModule_GetName)
 EXPORT_FUNC(PyModule_GetNameObject)
 EXPORT_FUNC(PyModule_GetState)
+EXPORT_FUNC(PyModule_GetStateSize)
+EXPORT_FUNC(PyModule_GetToken)
 EXPORT_FUNC(PyModule_New)
 EXPORT_FUNC(PyModule_NewObject)
 EXPORT_FUNC(PyModule_SetDocString)
@@ -668,6 +672,7 @@ EXPORT_FUNC(PyType_GetFlags)
 EXPORT_FUNC(PyType_GetFullyQualifiedName)
 EXPORT_FUNC(PyType_GetModule)
 EXPORT_FUNC(PyType_GetModuleByDef)
+EXPORT_FUNC(PyType_GetModuleByToken)
 EXPORT_FUNC(PyType_GetModuleName)
 EXPORT_FUNC(PyType_GetModuleState)
 EXPORT_FUNC(PyType_GetName)
index a355a5fc25707a40323d1bf418c3d49659a3ada3..68707a54ff6b874f9b3fa19c54b54b1d6ae6f828 100644 (file)
     <ClCompile Include="..\Modules\_testcapi\gc.c" />
     <ClCompile Include="..\Modules\_testcapi\run.c" />
     <ClCompile Include="..\Modules\_testcapi\modsupport.c" />
+    <ClCompile Include="..\Modules\_testcapi\module.c" />
     <ClCompile Include="..\Modules\_testcapi\monitoring.c" />
     <ClCompile Include="..\Modules\_testcapi\config.c" />
     <ClCompile Include="..\Modules\_testcapi\import.c" />
index 05128d3ac36efccfbd865edcb8fa1f7260271358..b0e75ce433ab14d994ad7707936567aa5c07b10b 100644 (file)
     <ClCompile Include="..\Modules\_testcapi\modsupport.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\Modules\_testcapi\module.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\Modules\_testcapi\monitoring.c">
       <Filter>Source Files</Filter>
     </ClCompile>
index d4b574a8828dc5de1c304c133de9cadfd3d3b2ad..6cf4a061ca610f668e078c25870b2cc0084bf2e2 100644 (file)
@@ -672,8 +672,8 @@ _PyImport_ClearModulesByIndex(PyInterpreterState *interp)
 
     (6). first time  (not found in _PyRuntime.imports.extensions):
        A. _imp_create_dynamic_impl() -> import_find_extension()
-       B. _imp_create_dynamic_impl() -> _PyImport_GetModInitFunc()
-       C.   _PyImport_GetModInitFunc():  load <module init func>
+       B. _imp_create_dynamic_impl() -> _PyImport_GetModuleExportHooks()
+       C.   _PyImport_GetModuleExportHooks():  load <module init func>
        D. _imp_create_dynamic_impl() -> import_run_extension()
        E.   import_run_extension() -> _PyImport_RunModInitFunc()
        F.     _PyImport_RunModInitFunc():  call <module init func>
@@ -743,16 +743,19 @@ _PyImport_ClearModulesByIndex(PyInterpreterState *interp)
        A. noop
 
 
-    ...for multi-phase init modules:
+    ...for multi-phase init modules from PyModInit_* (PyModuleDef):
 
     (6). every time:
        A. _imp_create_dynamic_impl() -> import_find_extension()  (not found)
-       B. _imp_create_dynamic_impl() -> _PyImport_GetModInitFunc()
-       C.   _PyImport_GetModInitFunc():  load <module init func>
+       B. _imp_create_dynamic_impl() -> _PyImport_GetModuleExportHooks()
+       C.   _PyImport_GetModuleExportHooks():  load <module init func>
        D. _imp_create_dynamic_impl() -> import_run_extension()
        E.   import_run_extension() -> _PyImport_RunModInitFunc()
        F.     _PyImport_RunModInitFunc():  call <module init func>
        G.   import_run_extension() -> PyModule_FromDefAndSpec()
+
+       PyModule_FromDefAndSpec():
+
        H.      PyModule_FromDefAndSpec(): gather/check moduledef slots
        I.      if there's a Py_mod_create slot:
                  1. PyModule_FromDefAndSpec():  call its function
@@ -765,10 +768,29 @@ _PyImport_ClearModulesByIndex(PyInterpreterState *interp)
     (10). every time:
        A. _imp_exec_dynamic_impl() -> exec_builtin_or_dynamic()
        B.   if mod->md_state == NULL (including if m_size == 0):
-            1. exec_builtin_or_dynamic() -> PyModule_ExecDef()
-            2.   PyModule_ExecDef():  allocate mod->md_state
+            1. exec_builtin_or_dynamic() -> PyModule_Exec()
+            2.   PyModule_Exec():  allocate mod->md_state
             3.   if there's a Py_mod_exec slot:
-                 1. PyModule_ExecDef():  call its function
+                 1. PyModule_Exec():  call its function
+
+
+    ...for multi-phase init modules from PyModExport_* (slots array):
+
+    (6). every time:
+
+       A. _imp_create_dynamic_impl() -> import_find_extension()  (not found)
+       B. _imp_create_dynamic_impl() -> _PyImport_GetModuleExportHooks()
+       C.   _PyImport_GetModuleExportHooks():  load <module export func>
+       D. _imp_create_dynamic_impl() -> import_run_modexport()
+       E.     import_run_modexport():  call <module init func>
+       F.   import_run_modexport() -> PyModule_FromSlotsAndSpec()
+       G.     PyModule_FromSlotsAndSpec(): create temporary PyModuleDef-like
+       H.       PyModule_FromSlotsAndSpec() -> PyModule_FromDefAndSpec()
+
+       (PyModule_FromDefAndSpec behaves as for PyModInit_*, above)
+
+    (10). every time: as for PyModInit_*, above
+
  */
 
 
@@ -825,25 +847,19 @@ _PyImport_SetDLOpenFlags(PyInterpreterState *interp, int new_val)
 /* Common implementation for _imp.exec_dynamic and _imp.exec_builtin */
 static int
 exec_builtin_or_dynamic(PyObject *mod) {
-    PyModuleDef *def;
     void *state;
 
     if (!PyModule_Check(mod)) {
         return 0;
     }
 
-    def = PyModule_GetDef(mod);
-    if (def == NULL) {
-        return 0;
-    }
-
     state = PyModule_GetState(mod);
     if (state) {
         /* Already initialized; skip reload */
         return 0;
     }
 
-    return PyModule_ExecDef(mod, def);
+    return PyModule_Exec(mod);
 }
 
 
@@ -1787,7 +1803,7 @@ finish_singlephase_extension(PyThreadState *tstate, PyObject *mod,
                              PyObject *name, PyObject *modules)
 {
     assert(mod != NULL && PyModule_Check(mod));
-    assert(cached->def == _PyModule_GetDef(mod));
+    assert(cached->def == _PyModule_GetDefOrNull(mod));
 
     Py_ssize_t index = _get_cached_module_index(cached);
     if (_modules_by_index_set(tstate->interp, index, mod) < 0) {
@@ -1865,8 +1881,8 @@ reload_singlephase_extension(PyThreadState *tstate,
          * due to violating interpreter isolation.
          * See the note in set_cached_m_dict().
          * Until that is solved, we leave md_def set to NULL. */
-        assert(_PyModule_GetDef(mod) == NULL
-               || _PyModule_GetDef(mod) == def);
+        assert(_PyModule_GetDefOrNull(mod) == NULL
+               || _PyModule_GetDefOrNull(mod) == def);
     }
     else {
         assert(cached->m_dict == NULL);
@@ -1953,6 +1969,43 @@ import_find_extension(PyThreadState *tstate,
     return mod;
 }
 
+static PyObject *
+import_run_modexport(PyThreadState *tstate, PyModExportFunction ex0,
+                     struct _Py_ext_module_loader_info *info,
+                     PyObject *spec)
+{
+    /* This is like import_run_extension, but avoids interpreter switching
+     * and code for for single-phase modules.
+     */
+    PyModuleDef_Slot *slots = ex0();
+    if (!slots) {
+        if (!PyErr_Occurred()) {
+            PyErr_Format(
+                PyExc_SystemError,
+                "slot export function for module %s failed without setting an exception",
+                info->name);
+        }
+        return NULL;
+    }
+    if (PyErr_Occurred()) {
+        PyErr_Format(
+            PyExc_SystemError,
+            "slot export function for module %s raised unreported exception",
+            info->name);
+    }
+    PyObject *result = PyModule_FromSlotsAndSpec(slots, spec);
+    if (!result) {
+        return NULL;
+    }
+    if (PyModule_Check(result)) {
+        PyModuleObject *mod = (PyModuleObject *)result;
+        if (mod && !mod->md_token) {
+            mod->md_token = slots;
+        }
+    }
+    return result;
+}
+
 static PyObject *
 import_run_extension(PyThreadState *tstate, PyModInitFunction p0,
                      struct _Py_ext_module_loader_info *info,
@@ -2125,7 +2178,7 @@ main_finally:
         assert_multiphase_def(def);
         assert(mod == NULL);
         /* Note that we cheat a little by not repeating the calls
-         * to _PyImport_GetModInitFunc() and _PyImport_RunModInitFunc(). */
+         * to _PyImport_GetModuleExportHooks() and _PyImport_RunModInitFunc(). */
         mod = PyModule_FromDefAndSpec(def, spec);
         if (mod == NULL) {
             goto error;
@@ -2239,8 +2292,9 @@ _PyImport_FixupBuiltin(PyThreadState *tstate, PyObject *mod, const char *name,
         return -1;
     }
 
-    PyModuleDef *def = PyModule_GetDef(mod);
+    PyModuleDef *def = _PyModule_GetDefOrNull(mod);
     if (def == NULL) {
+        assert(!PyErr_Occurred());
         PyErr_BadInternalCall();
         goto finally;
     }
@@ -2322,8 +2376,8 @@ create_builtin(PyThreadState *tstate, PyObject *name, PyObject *spec)
         assert(!_PyErr_Occurred(tstate));
         assert(cached != NULL);
         /* The module might not have md_def set in certain reload cases. */
-        assert(_PyModule_GetDef(mod) == NULL
-                || cached->def == _PyModule_GetDef(mod));
+        assert(_PyModule_GetDefOrNull(mod) == NULL
+                || cached->def == _PyModule_GetDefOrNull(mod));
         assert_singlephase(cached);
         goto finally;
     }
@@ -4653,8 +4707,8 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
         assert(!_PyErr_Occurred(tstate));
         assert(cached != NULL);
         /* The module might not have md_def set in certain reload cases. */
-        assert(_PyModule_GetDef(mod) == NULL
-                || cached->def == _PyModule_GetDef(mod));
+        assert(_PyModule_GetDefOrNull(mod) == NULL
+                || cached->def == _PyModule_GetDefOrNull(mod));
         assert_singlephase(cached);
         goto finally;
     }
@@ -4679,7 +4733,7 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
     }
 
     /* We would move this (and the fclose() below) into
-     * _PyImport_GetModInitFunc(), but it isn't clear if the intervening
+     * _PyImport_GetModuleExportHooks(), but it isn't clear if the intervening
      * code relies on fp still being open. */
     FILE *fp;
     if (file != NULL) {
@@ -4692,7 +4746,13 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
         fp = NULL;
     }
 
-    PyModInitFunction p0 = _PyImport_GetModInitFunc(&info, fp);
+    PyModInitFunction p0 = NULL;
+    PyModExportFunction ex0 = NULL;
+    _PyImport_GetModuleExportHooks(&info, fp, &p0, &ex0);
+    if (ex0) {
+        mod = import_run_modexport(tstate, ex0, &info, spec);
+        goto cleanup;
+    }
     if (p0 == NULL) {
         goto finally;
     }
@@ -4714,6 +4774,7 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
     }
 #endif
 
+cleanup:
     // XXX Shouldn't this happen in the error cases too (i.e. in "finally")?
     if (fp) {
         fclose(fp);
index 802843fe7b9dcebbf40017ae4a2b9af2c46e30cf..23a55c39677100f60134947f0837e0b482ae6f49 100644 (file)
@@ -5,7 +5,7 @@
 #include "pycore_call.h"          // _PyObject_CallMethod()
 #include "pycore_import.h"        // _PyImport_SwapPackageContext()
 #include "pycore_importdl.h"
-#include "pycore_moduleobject.h"  // _PyModule_GetDef()
+#include "pycore_moduleobject.h"  // _PyModule_GetDefOrNull()
 #include "pycore_pyerrors.h"      // _PyErr_FormatFromCause()
 #include "pycore_runtime.h"       // _Py_ID()
 
@@ -35,8 +35,10 @@ extern dl_funcptr _PyImport_FindSharedFuncptr(const char *prefix,
 /* module info to use when loading */
 /***********************************/
 
-static const char * const ascii_only_prefix = "PyInit";
-static const char * const nonascii_prefix = "PyInitU";
+static const struct hook_prefixes ascii_only_prefixes = {
+    "PyInit", "PyModExport"};
+static const struct hook_prefixes nonascii_prefixes = {
+    "PyInitU", "PyModExportU"};
 
 /* Get the variable part of a module's export symbol name.
  * Returns a bytes instance. For non-ASCII-named modules, the name is
@@ -45,7 +47,7 @@ static const char * const nonascii_prefix = "PyInitU";
  * nonascii_prefix, as appropriate.
  */
 static PyObject *
-get_encoded_name(PyObject *name, const char **hook_prefix) {
+get_encoded_name(PyObject *name, const struct hook_prefixes **hook_prefixes) {
     PyObject *tmp;
     PyObject *encoded = NULL;
     PyObject *modname = NULL;
@@ -72,7 +74,7 @@ get_encoded_name(PyObject *name, const char **hook_prefix) {
     /* Encode to ASCII or Punycode, as needed */
     encoded = PyUnicode_AsEncodedString(name, "ascii", NULL);
     if (encoded != NULL) {
-        *hook_prefix = ascii_only_prefix;
+        *hook_prefixes = &ascii_only_prefixes;
     } else {
         if (PyErr_ExceptionMatches(PyExc_UnicodeEncodeError)) {
             PyErr_Clear();
@@ -80,7 +82,7 @@ get_encoded_name(PyObject *name, const char **hook_prefix) {
             if (encoded == NULL) {
                 goto error;
             }
-            *hook_prefix = nonascii_prefix;
+            *hook_prefixes = &nonascii_prefixes;
         } else {
             goto error;
         }
@@ -130,7 +132,7 @@ _Py_ext_module_loader_info_init(struct _Py_ext_module_loader_info *p_info,
     assert(PyUnicode_GetLength(name) > 0);
     info.name = Py_NewRef(name);
 
-    info.name_encoded = get_encoded_name(info.name, &info.hook_prefix);
+    info.name_encoded = get_encoded_name(info.name, &info.hook_prefixes);
     if (info.name_encoded == NULL) {
         _Py_ext_module_loader_info_clear(&info);
         return -1;
@@ -189,7 +191,7 @@ _Py_ext_module_loader_info_init_for_builtin(
         /* We won't need filename. */
         .path=name,
         .origin=_Py_ext_module_origin_BUILTIN,
-        .hook_prefix=ascii_only_prefix,
+        .hook_prefixes=&ascii_only_prefixes,
         .newcontext=NULL,
     };
     return 0;
@@ -377,39 +379,63 @@ _Py_ext_module_loader_result_apply_error(
 /********************************************/
 
 #ifdef HAVE_DYNAMIC_LOADING
-PyModInitFunction
-_PyImport_GetModInitFunc(struct _Py_ext_module_loader_info *info,
-                         FILE *fp)
+static dl_funcptr
+findfuncptr(const char *prefix, const char *name_buf,
+            struct _Py_ext_module_loader_info *info,
+            FILE *fp)
 {
-    const char *name_buf = PyBytes_AS_STRING(info->name_encoded);
-    dl_funcptr exportfunc;
 #ifdef MS_WINDOWS
-    exportfunc = _PyImport_FindSharedFuncptrWindows(
-            info->hook_prefix, name_buf, info->filename, fp);
+    return _PyImport_FindSharedFuncptrWindows(
+            prefix, name_buf, info->filename, fp);
 #else
-    {
-        const char *path_buf = PyBytes_AS_STRING(info->filename_encoded);
-        exportfunc = _PyImport_FindSharedFuncptr(
-                        info->hook_prefix, name_buf, path_buf, fp);
-    }
+    const char *path_buf = PyBytes_AS_STRING(info->filename_encoded);
+    return _PyImport_FindSharedFuncptr(
+            prefix, name_buf, path_buf, fp);
 #endif
+}
 
-    if (exportfunc == NULL) {
-        if (!PyErr_Occurred()) {
-            PyObject *msg;
-            msg = PyUnicode_FromFormat(
-                "dynamic module does not define "
-                "module export function (%s_%s)",
-                info->hook_prefix, name_buf);
-            if (msg != NULL) {
-                PyErr_SetImportError(msg, info->name, info->filename);
-                Py_DECREF(msg);
-            }
-        }
-        return NULL;
+int
+_PyImport_GetModuleExportHooks(
+    struct _Py_ext_module_loader_info *info,
+    FILE *fp,
+    PyModInitFunction *modinit,
+    PyModExportFunction *modexport)
+{
+    *modinit = NULL;
+    *modexport = NULL;
+
+    const char *name_buf = PyBytes_AS_STRING(info->name_encoded);
+    dl_funcptr exportfunc;
+
+    exportfunc = findfuncptr(
+        info->hook_prefixes->export_prefix,
+        name_buf, info, fp);
+    if (exportfunc) {
+        *modexport = (PyModExportFunction)exportfunc;
+        return 2;
+    }
+
+    exportfunc = findfuncptr(
+        info->hook_prefixes->init_prefix,
+        name_buf, info, fp);
+    if (exportfunc) {
+        *modinit = (PyModInitFunction)exportfunc;
+        return 1;
     }
 
-    return (PyModInitFunction)exportfunc;
+    if (!PyErr_Occurred()) {
+        PyObject *msg;
+        msg = PyUnicode_FromFormat(
+            "dynamic module does not define "
+            "module export function (%s_%s or %s_%s)",
+            info->hook_prefixes->export_prefix, name_buf,
+            info->hook_prefixes->init_prefix, name_buf);
+        if (msg != NULL) {
+            PyErr_SetImportError(msg, info->name, info->filename);
+            Py_DECREF(msg);
+        }
+    }
+    return -1;
 }
 #endif /* HAVE_DYNAMIC_LOADING */
 
@@ -477,7 +503,7 @@ _PyImport_RunModInitFunc(PyModInitFunction p0,
         res.def = (PyModuleDef *)m;
         /* Run PyModule_FromDefAndSpec() to finish loading the module. */
     }
-    else if (info->hook_prefix == nonascii_prefix) {
+    else if (info->hook_prefixes == &nonascii_prefixes) {
         /* Non-ASCII is only supported for multi-phase init. */
         res.kind = _Py_ext_module_kind_MULTIPHASE;
         /* Don't allow legacy init for non-ASCII module names. */
@@ -496,7 +522,7 @@ _PyImport_RunModInitFunc(PyModInitFunction p0,
             goto error;
         }
 
-        res.def = _PyModule_GetDef(m);
+        res.def = _PyModule_GetDefOrNull(m);
         if (res.def == NULL) {
             PyErr_Clear();
             _Py_ext_module_loader_result_set_error(