gh-140550: Initial implementation of PEP 793 – PyModExport (GH-140556)

Co-authored-by: Victor Stinner <vstinner@python.org>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
This commit is contained in:
Petr Viktorin 2025-11-05 12:31:42 +01:00 committed by GitHub
parent f2bce51b98
commit 589a03a8ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 1494 additions and 236 deletions

View 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,,

View 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 */
@ -93,13 +94,15 @@
#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 */

View 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,

View 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

View 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 {

View 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

View file

@ -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)

View file

@ -195,6 +195,24 @@ class H2(int): pass
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

View file

@ -14,7 +14,6 @@
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')

View file

@ -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);
}

View 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

View 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')

View file

@ -2497,6 +2497,21 @@ def test_multi_init_extension_per_interpreter_gil_compat(self):
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 @@ def test_basic_multiple_interpreters_reset_each(self):
# * 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):

View 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",

View file

@ -1725,9 +1725,10 @@ def get_gen(): yield 1
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

View file

@ -0,0 +1,2 @@
:pep:`793`: Add a new entry point for C extension modules,
``PyModExport_<modulename>``.

View file

@ -2611,3 +2611,31 @@
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'

View file

@ -175,7 +175,7 @@
@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

View 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},
};

378
Modules/_testcapi/module.c Normal file
View file

@ -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);
}

View 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

View 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;
}

View 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 */
};

View 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;
}

View 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;
}

View 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 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
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;
name = PyModule_GetName(module);
if (name == NULL) {
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;
}
if (def->m_size >= 0) {
PyModuleObject *md = (PyModuleObject*)module;
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 (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;

View 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 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
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)

5
PC/python3dll.c generated
View file

@ -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)

View file

@ -126,6 +126,7 @@
<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" />

View file

@ -111,6 +111,9 @@
<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>

View 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 @@ import_run_extension(PyThreadState *tstate, PyModInitFunction p0,
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);

View 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)
{
#ifdef MS_WINDOWS
return _PyImport_FindSharedFuncptrWindows(
prefix, name_buf, info->filename, fp);
#else
const char *path_buf = PyBytes_AS_STRING(info->filename_encoded);
return _PyImport_FindSharedFuncptr(
prefix, name_buf, path_buf, fp);
#endif
}
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;
#ifdef MS_WINDOWS
exportfunc = _PyImport_FindSharedFuncptrWindows(
info->hook_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);
}
#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);
}
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;
}
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 NULL;
}
return (PyModInitFunction)exportfunc;
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(