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,,
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,,
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,,
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,,
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.
/* 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 */
# 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 */
_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;
* 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(
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;
/* 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,
#ifndef Py_INTERNAL_MODULEOBJECT_H
#define Py_INTERNAL_MODULEOBJECT_H
+
+#include <stdbool.h>
+
#ifdef __cplusplus
extern "C" {
#endif
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
#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 */
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 {
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
--- /dev/null
+# 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)
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
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')
+++ /dev/null
-// 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);
-}
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
_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
# 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')
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'
# * 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):
"PyModule_AddStringConstant",
"PyModule_AddType",
"PyModule_Create2",
+ "PyModule_Exec",
"PyModule_ExecDef",
"PyModule_FromDefAndSpec2",
+ "PyModule_FromSlotsAndSpec",
"PyModule_GetDef",
"PyModule_GetDict",
"PyModule_GetFilename",
"PyModule_GetName",
"PyModule_GetNameObject",
"PyModule_GetState",
+ "PyModule_GetStateSize",
+ "PyModule_GetToken",
"PyModule_New",
"PyModule_NewObject",
"PyModule_SetDocString",
"PyType_GetFullyQualifiedName",
"PyType_GetModule",
"PyType_GetModuleByDef",
+ "PyType_GetModuleByToken",
"PyType_GetModuleName",
"PyType_GetModuleState",
"PyType_GetName",
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
--- /dev/null
+:pep:`793`: Add a new entry point for C extension modules,
+``PyModExport_<modulename>``.
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'
@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
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},
{"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},
};
--- /dev/null
+#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);
+}
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
if (_PyTestCapi_Init_Function(m) < 0) {
return -1;
}
+ if (_PyTestCapi_Init_Module(m) < 0) {
+ return -1;
+ }
return 0;
}
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},
#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 */
};
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)
{
{
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;
+}
get_module_state(PyObject *module)
{
PyModuleDef *def = PyModule_GetDef(module);
+ assert(def);
+
if (def->m_size == -1) {
return &global_state.module;
}
#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()
{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)
}
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;
}
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);
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)
{
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;
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;
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",
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) {
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) {
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(
name, cur_slot->slot);
goto error;
}
+#undef COPY_COMMON_SLOT
}
/* By default, multi-phase init modules are expected
}
if (create) {
- m = create(spec, def);
+ m = create(spec, original_def);
if (m == NULL) {
if (!PyErr_Occurred()) {
PyErr_Format(
}
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",
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;
}
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)
}
#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;
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)
{
PyErr_BadArgument();
return NULL;
}
- return _PyModule_GetDef(m);
+ return _PyModule_GetDefOrNull(m);
}
void*
}
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);
}
{
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;
}
{
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;
}
-/* 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));
else {
PyHeapTypeObject *ht = (PyHeapTypeObject*)type;
PyObject *module = ht->ht_module;
- if (module && _PyModule_GetDef(module) == def) {
+ if (module && _PyModule_GetToken(module) == token) {
return module;
}
}
PyHeapTypeObject *ht = (PyHeapTypeObject*)super;
PyObject *module = ht->ht_module;
- if (module && _PyModule_GetDef(module) == def) {
+ if (module && _PyModule_GetToken(module) == token) {
res = module;
break;
}
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)
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)
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)
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)
<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" />
<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>
(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>
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
(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
+
*/
/* 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);
}
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) {
* 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);
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,
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;
return -1;
}
- PyModuleDef *def = PyModule_GetDef(mod);
+ PyModuleDef *def = _PyModule_GetDefOrNull(mod);
if (def == NULL) {
+ assert(!PyErr_Occurred());
PyErr_BadInternalCall();
goto finally;
}
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;
}
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;
}
}
/* 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) {
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;
}
}
#endif
+cleanup:
// XXX Shouldn't this happen in the error cases too (i.e. in "finally")?
if (fp) {
fclose(fp);
#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()
/* 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
* 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;
/* 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();
if (encoded == NULL) {
goto error;
}
- *hook_prefix = nonascii_prefix;
+ *hook_prefixes = &nonascii_prefixes;
} else {
goto error;
}
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;
/* 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;
/********************************************/
#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 */
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. */
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(