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_FromVoidPtr,3.2,,
func,PyLong_GetInfo,3.2,, func,PyLong_GetInfo,3.2,,
data,PyLong_Type,3.2,, data,PyLong_Type,3.2,,
macro,PyMODEXPORT_FUNC,3.15,,
data,PyMap_Type,3.2,, data,PyMap_Type,3.2,,
func,PyMapping_Check,3.2,, func,PyMapping_Check,3.2,,
func,PyMapping_GetItemString,3.2,, func,PyMapping_GetItemString,3.2,,
@ -471,8 +472,10 @@ func,PyModule_AddObjectRef,3.10,,
func,PyModule_AddStringConstant,3.2,, func,PyModule_AddStringConstant,3.2,,
func,PyModule_AddType,3.10,, func,PyModule_AddType,3.10,,
func,PyModule_Create2,3.2,, func,PyModule_Create2,3.2,,
func,PyModule_Exec,3.15,,
func,PyModule_ExecDef,3.7,, func,PyModule_ExecDef,3.7,,
func,PyModule_FromDefAndSpec2,3.7,, func,PyModule_FromDefAndSpec2,3.7,,
func,PyModule_FromSlotsAndSpec,3.15,,
func,PyModule_GetDef,3.2,, func,PyModule_GetDef,3.2,,
func,PyModule_GetDict,3.2,, func,PyModule_GetDict,3.2,,
func,PyModule_GetFilename,3.2,, func,PyModule_GetFilename,3.2,,
@ -480,6 +483,8 @@ func,PyModule_GetFilenameObject,3.2,,
func,PyModule_GetName,3.2,, func,PyModule_GetName,3.2,,
func,PyModule_GetNameObject,3.7,, func,PyModule_GetNameObject,3.7,,
func,PyModule_GetState,3.2,, func,PyModule_GetState,3.2,,
func,PyModule_GetStateSize,3.15,,
func,PyModule_GetToken,3.15,,
func,PyModule_New,3.2,, func,PyModule_New,3.2,,
func,PyModule_NewObject,3.7,, func,PyModule_NewObject,3.7,,
func,PyModule_SetDocString,3.7,, func,PyModule_SetDocString,3.7,,
@ -738,6 +743,7 @@ func,PyType_GetFlags,3.2,,
func,PyType_GetFullyQualifiedName,3.13,, func,PyType_GetFullyQualifiedName,3.13,,
func,PyType_GetModule,3.10,, func,PyType_GetModule,3.10,,
func,PyType_GetModuleByDef,3.13,, func,PyType_GetModuleByDef,3.13,,
func,PyType_GetModuleByToken,3.15,,
func,PyType_GetModuleName,3.13,, func,PyType_GetModuleName,3.13,,
func,PyType_GetModuleState,3.10,, func,PyType_GetModuleState,3.10,,
func,PyType_GetName,3.11,, func,PyType_GetName,3.11,,

View file

@ -9,6 +9,7 @@
inside the Python core, they are private to the core. inside the Python core, they are private to the core.
If in an extension module, it may be declared with If in an extension module, it may be declared with
external linkage depending on the platform. 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)", As a number of platforms support/require "__declspec(dllimport/dllexport)",
we support a HAVE_DECLSPEC_DLL macro to save duplication. we support a HAVE_DECLSPEC_DLL macro to save duplication.
@ -62,9 +63,9 @@
/* module init functions inside the core need no external linkage */ /* module init functions inside the core need no external linkage */
/* except for Cygwin to handle embedding */ /* except for Cygwin to handle embedding */
# if defined(__CYGWIN__) # if defined(__CYGWIN__)
# define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject* # define _PyINIT_FUNC_DECLSPEC Py_EXPORTED_SYMBOL
# else /* __CYGWIN__ */ # else /* __CYGWIN__ */
# define PyMODINIT_FUNC PyObject* # define _PyINIT_FUNC_DECLSPEC
# endif /* __CYGWIN__ */ # endif /* __CYGWIN__ */
# else /* Py_BUILD_CORE */ # else /* Py_BUILD_CORE */
/* Building an extension module, or an embedded situation */ /* Building an extension module, or an embedded situation */
@ -78,9 +79,9 @@
# define PyAPI_DATA(RTYPE) extern Py_IMPORTED_SYMBOL RTYPE # define PyAPI_DATA(RTYPE) extern Py_IMPORTED_SYMBOL RTYPE
/* module init functions outside the core must be exported */ /* module init functions outside the core must be exported */
# if defined(__cplusplus) # if defined(__cplusplus)
# define PyMODINIT_FUNC extern "C" Py_EXPORTED_SYMBOL PyObject* # define _PyINIT_FUNC_DECLSPEC extern "C" Py_EXPORTED_SYMBOL
# else /* __cplusplus */ # else /* __cplusplus */
# define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject* # define _PyINIT_FUNC_DECLSPEC Py_EXPORTED_SYMBOL
# endif /* __cplusplus */ # endif /* __cplusplus */
# endif /* Py_BUILD_CORE */ # endif /* Py_BUILD_CORE */
# endif /* HAVE_DECLSPEC_DLL */ # endif /* HAVE_DECLSPEC_DLL */
@ -93,13 +94,15 @@
#ifndef PyAPI_DATA #ifndef PyAPI_DATA
# define PyAPI_DATA(RTYPE) extern Py_EXPORTED_SYMBOL RTYPE # define PyAPI_DATA(RTYPE) extern Py_EXPORTED_SYMBOL RTYPE
#endif #endif
#ifndef PyMODINIT_FUNC #ifndef _PyINIT_FUNC_DECLSPEC
# if defined(__cplusplus) # if defined(__cplusplus)
# define PyMODINIT_FUNC extern "C" Py_EXPORTED_SYMBOL PyObject* # define _PyINIT_FUNC_DECLSPEC extern "C" Py_EXPORTED_SYMBOL
# else /* __cplusplus */ # else /* __cplusplus */
# define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject* # define _PyINIT_FUNC_DECLSPEC Py_EXPORTED_SYMBOL
# endif /* __cplusplus */ # endif /* __cplusplus */
#endif #endif
#define PyMODINIT_FUNC _PyINIT_FUNC_DECLSPEC PyObject*
#define PyMODEXPORT_FUNC _PyINIT_FUNC_DECLSPEC PyModuleDef_Slot*
#endif /* Py_EXPORTS_H */ #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_DYNAMIC = 3,
} _Py_ext_module_origin; } _Py_ext_module_origin;
struct hook_prefixes {
const char *const init_prefix;
const char *const export_prefix;
};
/* Input for loading an extension module. */ /* Input for loading an extension module. */
struct _Py_ext_module_loader_info { struct _Py_ext_module_loader_info {
PyObject *filename; PyObject *filename;
@ -40,7 +45,7 @@ struct _Py_ext_module_loader_info {
* depending on if it's builtin or not. */ * depending on if it's builtin or not. */
PyObject *path; PyObject *path;
_Py_ext_module_origin origin; _Py_ext_module_origin origin;
const char *hook_prefix; const struct hook_prefixes *hook_prefixes;
const char *newcontext; const char *newcontext;
}; };
extern void _Py_ext_module_loader_info_clear( extern void _Py_ext_module_loader_info_clear(
@ -62,7 +67,9 @@ extern int _Py_ext_module_loader_info_init_from_spec(
PyObject *spec); PyObject *spec);
#endif #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 { struct _Py_ext_module_loader_result {
PyModuleDef *def; PyModuleDef *def;
PyObject *module; PyObject *module;
@ -89,10 +96,11 @@ extern void _Py_ext_module_loader_result_apply_error(
/* The module init function. */ /* The module init function. */
typedef PyObject *(*PyModInitFunction)(void); typedef PyObject *(*PyModInitFunction)(void);
typedef PyModuleDef_Slot *(*PyModExportFunction)(void);
#ifdef HAVE_DYNAMIC_LOADING #ifdef HAVE_DYNAMIC_LOADING
extern PyModInitFunction _PyImport_GetModInitFunc( extern int _PyImport_GetModuleExportHooks(
struct _Py_ext_module_loader_info *info, struct _Py_ext_module_loader_info *info,
FILE *fp); FILE *fp, PyModInitFunction *modinit, PyModExportFunction *modexport);
#endif #endif
extern int _PyImport_RunModInitFunc( extern int _PyImport_RunModInitFunc(
PyModInitFunction p0, PyModInitFunction p0,

View file

@ -1,5 +1,8 @@
#ifndef Py_INTERNAL_MODULEOBJECT_H #ifndef Py_INTERNAL_MODULEOBJECT_H
#define Py_INTERNAL_MODULEOBJECT_H #define Py_INTERNAL_MODULEOBJECT_H
#include <stdbool.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -16,32 +19,49 @@ extern int _PyModule_IsPossiblyShadowing(PyObject *);
extern int _PyModule_IsExtension(PyObject *obj); extern int _PyModule_IsExtension(PyObject *obj);
typedef int (*_Py_modexecfunc)(PyObject *);
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
PyObject *md_dict; PyObject *md_dict;
PyModuleDef *md_def;
void *md_state; void *md_state;
PyObject *md_weaklist; PyObject *md_weaklist;
// for logging purposes after md_dict is cleared // for logging purposes after md_dict is cleared
PyObject *md_name; PyObject *md_name;
bool md_token_is_def; /* if true, `md_token` is the PyModuleDef */
#ifdef Py_GIL_DISABLED #ifdef Py_GIL_DISABLED
void *md_gil; void *md_gil;
#endif #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; } PyModuleObject;
static inline PyModuleDef* _PyModule_GetDef(PyObject *mod) { #define _PyModule_CAST(op) \
assert(PyModule_Check(mod)); (assert(PyModule_Check(op)), _Py_CAST(PyModuleObject*, (op)))
return ((PyModuleObject *)mod)->md_def;
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) { static inline void* _PyModule_GetState(PyObject* mod) {
assert(PyModule_Check(mod)); return _PyModule_CAST(mod)->md_state;
return ((PyModuleObject *)mod)->md_state;
} }
static inline PyObject* _PyModule_GetDict(PyObject *mod) { static inline PyObject* _PyModule_GetDict(PyObject *mod) {
assert(PyModule_Check(mod)); PyObject *dict = _PyModule_CAST(mod)->md_dict;
PyObject *dict = ((PyModuleObject *)mod) -> md_dict;
// _PyModule_GetDict(mod) must not be used after calling module_clear(mod) // _PyModule_GetDict(mod) must not be used after calling module_clear(mod)
assert(dict != NULL); assert(dict != NULL);
return dict; // borrowed reference return dict; // borrowed reference

View file

@ -83,11 +83,19 @@ struct PyModuleDef_Slot {
#endif #endif
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15) #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15)
# define Py_mod_abi 5 # 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 #endif
#ifndef Py_LIMITED_API #ifndef Py_LIMITED_API
#define _Py_mod_LAST_SLOT 5 #define _Py_mod_LAST_SLOT 13
#endif #endif
#endif /* New in 3.5 */ #endif /* New in 3.5 */
@ -109,6 +117,13 @@ struct PyModuleDef_Slot {
PyAPI_FUNC(int) PyUnstable_Module_SetGIL(PyObject *module, void *gil); PyAPI_FUNC(int) PyUnstable_Module_SetGIL(PyObject *module, void *gil);
#endif #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 #ifndef _Py_OPAQUE_PYOBJECT
struct PyModuleDef { struct PyModuleDef {

View file

@ -839,6 +839,11 @@ PyAPI_FUNC(PyObject *) PyType_GetModuleByDef(PyTypeObject *, PyModuleDef *);
PyAPI_FUNC(int) PyType_Freeze(PyTypeObject *type); PyAPI_FUNC(int) PyType_Freeze(PyTypeObject *type);
#endif #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 #ifdef __cplusplus
} }
#endif #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): with self.assertRaises(TypeError):
_testcapi.pytype_getmodulebydef(H2) _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): def test_freeze(self):
# test PyType_Freeze() # test PyType_Freeze()
type_freeze = _testcapi.type_freeze type_freeze = _testcapi.type_freeze

View file

@ -14,7 +14,6 @@
SOURCES = [ SOURCES = [
os.path.join(os.path.dirname(__file__), 'extension.c'), 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') 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; return 0;
} }
#define _FUNC_NAME(NAME) PyInit_ ## NAME #define _FUNC_NAME(NAME) PyModExport_ ## NAME
#define FUNC_NAME(NAME) _FUNC_NAME(NAME) #define FUNC_NAME(NAME) _FUNC_NAME(NAME)
// Converting from function pointer to void* has undefined behavior, but // Converting from function pointer to void* has undefined behavior, but
@ -88,58 +88,40 @@ _testcext_exec(
_Py_COMP_DIAG_PUSH _Py_COMP_DIAG_PUSH
#if defined(__GNUC__) #if defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wpedantic" #pragma GCC diagnostic ignored "-Wpedantic"
#pragma GCC diagnostic ignored "-Wcast-qual"
#elif defined(__clang__) #elif defined(__clang__)
#pragma clang diagnostic ignored "-Wpedantic" #pragma clang diagnostic ignored "-Wpedantic"
#pragma clang diagnostic ignored "-Wcast-qual"
#endif #endif
PyDoc_STRVAR(_testcext_doc, "C test extension.");
static PyModuleDef_Slot _testcext_slots[] = { 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_exec, (void*)_testcext_exec},
{Py_mod_methods, _testcext_methods},
{0, NULL} {0, NULL}
}; };
_Py_COMP_DIAG_POP _Py_COMP_DIAG_POP
PyDoc_STRVAR(_testcext_doc, "C test extension."); PyMODEXPORT_FUNC
#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
FUNC_NAME(MODULE_NAME)(void) FUNC_NAME(MODULE_NAME)(void)
{ {
return PyModuleDef_Init(&_testcext_module); return _testcext_slots;
} }
#else // _Py_OPAQUE_PYOBJECT // Also define the soft-deprecated entrypoint to ensure it isn't called
// 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);
#define _INITFUNC_NAME(NAME) PyInit_ ## NAME
#define INITFUNC_NAME(NAME) _INITFUNC_NAME(NAME)
PyMODINIT_FUNC PyMODINIT_FUNC
FUNC_NAME(MODULE_NAME)(void) INITFUNC_NAME(MODULE_NAME)(void)
{ {
return testcext_create_moduledef( PyErr_SetString(
STR(MODULE_NAME), _testcext_doc, _testcext_methods, _testcext_slots); 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 # Define _Py_OPAQUE_PYOBJECT macro
if opaque_pyobject: if opaque_pyobject:
cflags.append(f'-D_Py_OPAQUE_PYOBJECT') cflags.append(f'-D_Py_OPAQUE_PYOBJECT')
sources.append('create_moduledef.c')
if internal: if internal:
cflags.append('-DTEST_INTERNAL_C_API=1') 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( self.check_compatible_here(
modname, filename, strict=False, isolated=False) 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") @unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
def test_python_compat(self): def test_python_compat(self):
module = 'threading' module = 'threading'
@ -3394,6 +3409,83 @@ def test_basic_multiple_interpreters_reset_each(self):
# * module's global state was initialized, not reset # * 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 @cpython_only
class TestMagicNumber(unittest.TestCase): class TestMagicNumber(unittest.TestCase):
def test_magic_number_endianness(self): def test_magic_number_endianness(self):

View file

@ -469,8 +469,10 @@ SYMBOL_NAMES = (
"PyModule_AddStringConstant", "PyModule_AddStringConstant",
"PyModule_AddType", "PyModule_AddType",
"PyModule_Create2", "PyModule_Create2",
"PyModule_Exec",
"PyModule_ExecDef", "PyModule_ExecDef",
"PyModule_FromDefAndSpec2", "PyModule_FromDefAndSpec2",
"PyModule_FromSlotsAndSpec",
"PyModule_GetDef", "PyModule_GetDef",
"PyModule_GetDict", "PyModule_GetDict",
"PyModule_GetFilename", "PyModule_GetFilename",
@ -478,6 +480,8 @@ SYMBOL_NAMES = (
"PyModule_GetName", "PyModule_GetName",
"PyModule_GetNameObject", "PyModule_GetNameObject",
"PyModule_GetState", "PyModule_GetState",
"PyModule_GetStateSize",
"PyModule_GetToken",
"PyModule_New", "PyModule_New",
"PyModule_NewObject", "PyModule_NewObject",
"PyModule_SetDocString", "PyModule_SetDocString",
@ -733,6 +737,7 @@ SYMBOL_NAMES = (
"PyType_GetFullyQualifiedName", "PyType_GetFullyQualifiedName",
"PyType_GetModule", "PyType_GetModule",
"PyType_GetModuleByDef", "PyType_GetModuleByDef",
"PyType_GetModuleByToken",
"PyType_GetModuleName", "PyType_GetModuleName",
"PyType_GetModuleState", "PyType_GetModuleState",
"PyType_GetName", "PyType_GetName",

View file

@ -1725,9 +1725,10 @@ def get_gen(): yield 1
check(int(PyLong_BASE**2), vsize('') + 3*self.longdigit) check(int(PyLong_BASE**2), vsize('') + 3*self.longdigit)
# module # module
if support.Py_GIL_DISABLED: if support.Py_GIL_DISABLED:
check(unittest, size('PPPPPP')) md_gil = 'P'
else: else:
check(unittest, size('PPPPP')) md_gil = ''
check(unittest, size('PPPP?' + md_gil + 'NPPPPP'))
# None # None
check(None, size('')) check(None, size(''))
# NotImplementedType # 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' added = '3.15'
[const.PyABIInfo_FREETHREADING_AGNOSTIC] [const.PyABIInfo_FREETHREADING_AGNOSTIC]
added = '3.15' 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__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.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__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__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_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.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); 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[] = { static PyMethodDef TestMethods[] = {
{"pytype_fromspec_meta", pytype_fromspec_meta, METH_O}, {"pytype_fromspec_meta", pytype_fromspec_meta, METH_O},
@ -546,6 +561,7 @@ static PyMethodDef TestMethods[] = {
{"get_tp_token", get_tp_token, METH_O}, {"get_tp_token", get_tp_token, METH_O},
{"pytype_getbasebytoken", pytype_getbasebytoken, METH_VARARGS}, {"pytype_getbasebytoken", pytype_getbasebytoken, METH_VARARGS},
{"pytype_getmodulebydef", pytype_getmodulebydef, METH_O}, {"pytype_getmodulebydef", pytype_getmodulebydef, METH_O},
{"pytype_getmodulebytoken", pytype_getmodulebytoken, METH_VARARGS},
{NULL}, {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_Frame(PyObject *mod);
int _PyTestCapi_Init_Type(PyObject *mod); int _PyTestCapi_Init_Type(PyObject *mod);
int _PyTestCapi_Init_Function(PyObject *mod); int _PyTestCapi_Init_Function(PyObject *mod);
int _PyTestCapi_Init_Module(PyObject *mod);
#endif // Py_TESTCAPI_PARTS_H #endif // Py_TESTCAPI_PARTS_H

View file

@ -3512,6 +3512,9 @@ _testcapi_exec(PyObject *m)
if (_PyTestCapi_Init_Function(m) < 0) { if (_PyTestCapi_Init_Function(m) < 0) {
return -1; return -1;
} }
if (_PyTestCapi_Init_Module(m) < 0) {
return -1;
}
return 0; return 0;
} }

View file

@ -2418,6 +2418,34 @@ set_vectorcall_nop(PyObject *self, PyObject *func)
Py_RETURN_NONE; 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[] = { static PyMethodDef module_functions[] = {
{"get_configs", get_configs, METH_NOARGS}, {"get_configs", get_configs, METH_NOARGS},
{"get_recursion_depth", get_recursion_depth, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS},
@ -2527,6 +2555,7 @@ static PyMethodDef module_functions[] = {
#endif #endif
{"simple_pending_call", simple_pending_call, METH_O}, {"simple_pending_call", simple_pending_call, METH_O},
{"set_vectorcall_nop", set_vectorcall_nop, METH_O}, {"set_vectorcall_nop", set_vectorcall_nop, METH_O},
{"module_get_gc_hooks", module_get_gc_hooks, METH_O},
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };

View file

@ -850,6 +850,28 @@ PyInit__testmultiphase_exec_unreported_exception(void)
return PyModuleDef_Init(&def_exec_unreported_exception); 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 static int
meth_state_access_exec(PyObject *m) 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); 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) get_module_state(PyObject *module)
{ {
PyModuleDef *def = PyModule_GetDef(module); PyModuleDef *def = PyModule_GetDef(module);
assert(def);
if (def->m_size == -1) { if (def->m_size == -1) {
return &global_state.module; return &global_state.module;
} }

View file

@ -8,7 +8,7 @@
#include "pycore_interp.h" // PyInterpreterState.importlib #include "pycore_interp.h" // PyInterpreterState.importlib
#include "pycore_long.h" // _PyLong_GetOne() #include "pycore_long.h" // _PyLong_GetOne()
#include "pycore_modsupport.h" // _PyModule_CreateInitialized() #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_object.h" // _PyType_AllocNoTrack
#include "pycore_pyerrors.h" // _PyErr_FormatFromCause() #include "pycore_pyerrors.h" // _PyErr_FormatFromCause()
#include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_pystate.h" // _PyInterpreterState_GET()
@ -27,6 +27,27 @@ static PyMemberDef module_members[] = {
{0} {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 = { PyTypeObject PyModuleDef_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0) PyVarObject_HEAD_INIT(&PyType_Type, 0)
@ -44,8 +65,14 @@ _PyModule_IsExtension(PyObject *obj)
} }
PyModuleObject *module = (PyModuleObject*)obj; PyModuleObject *module = (PyModuleObject*)obj;
PyModuleDef *def = module->md_def; if (module->md_exec) {
return (def != NULL && def->m_methods != NULL); 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); m = (PyModuleObject *)_PyType_AllocNoTrack(mt, 0);
if (m == NULL) if (m == NULL)
return NULL; return NULL;
m->md_def = NULL;
m->md_state = NULL; m->md_state = NULL;
m->md_weaklist = NULL; m->md_weaklist = NULL;
m->md_name = 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(); m->md_dict = PyDict_New();
if (m->md_dict == NULL) { if (m->md_dict == NULL) {
Py_DECREF(m); Py_DECREF(m);
@ -264,6 +300,17 @@ PyModule_Create2(PyModuleDef* module, int module_api_version)
return _PyModule_CreateInitialized(module, 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 * PyObject *
_PyModule_CreateInitialized(PyModuleDef* module, int module_api_version) _PyModule_CreateInitialized(PyModuleDef* module, int module_api_version)
{ {
@ -310,15 +357,21 @@ _PyModule_CreateInitialized(PyModuleDef* module, int module_api_version)
return NULL; 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 #ifdef Py_GIL_DISABLED
m->md_gil = Py_MOD_GIL_USED; m->md_gil = Py_MOD_GIL_USED;
#endif #endif
return (PyObject*)m; return (PyObject*)m;
} }
PyObject * static PyObject *
PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_version) 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; PyModuleDef_Slot* cur_slot;
PyObject *(*create)(PyObject *, PyModuleDef*) = NULL; PyObject *(*create)(PyObject *, PyModuleDef*) = NULL;
@ -331,10 +384,10 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
int has_execution_slots = 0; int has_execution_slots = 0;
const char *name; const char *name;
int ret; int ret;
void *token = NULL;
_Py_modexecfunc m_exec = NULL;
PyInterpreterState *interp = _PyInterpreterState_GET(); PyInterpreterState *interp = _PyInterpreterState_GET();
PyModuleDef_Init(def);
nameobj = PyObject_GetAttrString(spec, "name"); nameobj = PyObject_GetAttrString(spec, "name");
if (nameobj == NULL) { if (nameobj == NULL) {
return NULL; return NULL;
@ -348,7 +401,7 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
goto error; goto error;
} }
if (def->m_size < 0) { if (def_like->m_size < 0) {
PyErr_Format( PyErr_Format(
PyExc_SystemError, PyExc_SystemError,
"module %s: m_size may not be negative for multi-phase initialization", "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; 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) { switch (cur_slot->slot) {
case Py_mod_create: case Py_mod_create:
if (create) { if (create) {
@ -370,6 +451,9 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
break; break;
case Py_mod_exec: case Py_mod_exec:
has_execution_slots = 1; has_execution_slots = 1;
if (!original_def) {
COPY_COMMON_SLOT(Py_mod_exec, _Py_modexecfunc, m_exec);
}
break; break;
case Py_mod_multiple_interpreters: case Py_mod_multiple_interpreters:
if (has_multiple_interpreters_slot) { if (has_multiple_interpreters_slot) {
@ -398,6 +482,35 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
goto error; goto error;
} }
break; 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: default:
assert(cur_slot->slot < 0 || cur_slot->slot > _Py_mod_LAST_SLOT); assert(cur_slot->slot < 0 || cur_slot->slot > _Py_mod_LAST_SLOT);
PyErr_Format( PyErr_Format(
@ -406,6 +519,7 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
name, cur_slot->slot); name, cur_slot->slot);
goto error; goto error;
} }
#undef COPY_COMMON_SLOT
} }
/* By default, multi-phase init modules are expected /* By default, multi-phase init modules are expected
@ -429,7 +543,7 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
} }
if (create) { if (create) {
m = create(spec, def); m = create(spec, original_def);
if (m == NULL) { if (m == NULL) {
if (!PyErr_Occurred()) { if (!PyErr_Occurred()) {
PyErr_Format( PyErr_Format(
@ -455,15 +569,27 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
} }
if (PyModule_Check(m)) { if (PyModule_Check(m)) {
((PyModuleObject*)m)->md_state = NULL; PyModuleObject *mod = (PyModuleObject*)m;
((PyModuleObject*)m)->md_def = def; 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 #ifdef Py_GIL_DISABLED
((PyModuleObject*)m)->md_gil = gil_slot; mod->md_gil = gil_slot;
#else #else
(void)gil_slot; (void)gil_slot;
#endif #endif
mod->md_exec = m_exec;
} else { } 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( PyErr_Format(
PyExc_SystemError, PyExc_SystemError,
"module %s is not a module object, but requests module state", "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); name);
goto error; 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) { if (def_like->m_methods != NULL) {
ret = _add_methods_to_object(m, nameobj, def->m_methods); ret = _add_methods_to_object(m, nameobj, def_like->m_methods);
if (ret != 0) { if (ret != 0) {
goto error; goto error;
} }
} }
if (def->m_doc != NULL) { if (def_like->m_doc != NULL) {
ret = PyModule_SetDocString(m, def->m_doc); ret = PyModule_SetDocString(m, def_like->m_doc);
if (ret != 0) { if (ret != 0) {
goto error; goto error;
} }
@ -503,6 +637,29 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
return NULL; 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 #ifdef Py_GIL_DISABLED
int int
PyUnstable_Module_SetGIL(PyObject *module, void *gil) PyUnstable_Module_SetGIL(PyObject *module, void *gil)
@ -516,71 +673,94 @@ PyUnstable_Module_SetGIL(PyObject *module, void *gil)
} }
#endif #endif
int static int
PyModule_ExecDef(PyObject *module, PyModuleDef *def) run_exec_func(PyObject *module, int (*exec)(PyObject *))
{ {
PyModuleDef_Slot *cur_slot; int ret = exec(module);
const char *name; if (ret != 0) {
int ret; if (!PyErr_Occurred()) {
PyErr_Format(
name = PyModule_GetName(module); PyExc_SystemError,
if (name == NULL) { "execution of %R failed without setting an exception",
module);
}
return -1; 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) { static int
PyModuleObject *md = (PyModuleObject*)module; 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) { if (md->md_state == NULL) {
/* Always set a state pointer; this serves as a marker to skip /* Always set a state pointer; this serves as a marker to skip
* multiple initialization (importlib.reload() is no-op) */ * 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) { if (!md->md_state) {
PyErr_NoMemory(); PyErr_NoMemory();
return -1; 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) { if (def->m_slots == NULL) {
return 0; return 0;
} }
for (cur_slot = def->m_slots; cur_slot && cur_slot->slot; cur_slot++) { for (cur_slot = def->m_slots; cur_slot && cur_slot->slot; cur_slot++) {
switch (cur_slot->slot) { if (cur_slot->slot == Py_mod_exec) {
case Py_mod_create: int (*func)(PyObject *) = cur_slot->value;
/* handled in PyModule_FromDefAndSpec2 */ if (run_exec_func(module, func) < 0) {
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);
return -1; return -1;
}
continue;
} }
} }
return 0; return 0;
@ -624,6 +804,31 @@ PyModule_GetDict(PyObject *m)
return _PyModule_GetDict(m); // borrowed reference 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* PyObject*
PyModule_GetNameObject(PyObject *mod) PyModule_GetNameObject(PyObject *mod)
{ {
@ -764,7 +969,7 @@ PyModule_GetDef(PyObject* m)
PyErr_BadArgument(); PyErr_BadArgument();
return NULL; return NULL;
} }
return _PyModule_GetDef(m); return _PyModule_GetDefOrNull(m);
} }
void* void*
@ -888,17 +1093,18 @@ module_dealloc(PyObject *self)
} }
FT_CLEAR_WEAKREFS(self, m->md_weaklist); 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 */ /* bpo-39824: Don't call m_free() if m_size > 0 and md_state=NULL */
if (m->md_def && m->md_def->m_free if (m->md_state_free && (m->md_state_size <= 0 || m->md_state != NULL))
&& (m->md_def->m_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_dict);
Py_XDECREF(m->md_name); Py_XDECREF(m->md_name);
if (m->md_state != NULL) if (m->md_state != NULL) {
PyMem_Free(m->md_state); PyMem_Free(m->md_state);
}
Py_TYPE(m)->tp_free((PyObject *)m); Py_TYPE(m)->tp_free((PyObject *)m);
} }
@ -1206,11 +1412,11 @@ module_traverse(PyObject *self, visitproc visit, void *arg)
{ {
PyModuleObject *m = _PyModule_CAST(self); 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 */ /* bpo-39824: Don't call m_traverse() if m_size > 0 and md_state=NULL */
if (m->md_def && m->md_def->m_traverse if (m->md_state_traverse && (m->md_state_size <= 0 || m->md_state != NULL))
&& (m->md_def->m_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) if (res)
return res; return res;
} }
@ -1224,18 +1430,19 @@ module_clear(PyObject *self)
{ {
PyModuleObject *m = _PyModule_CAST(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 */ /* bpo-39824: Don't call m_clear() if m_size > 0 and md_state=NULL */
if (m->md_def && m->md_def->m_clear if (m->md_state_clear && (m->md_state_size <= 0 || m->md_state != NULL))
&& (m->md_def->m_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()) { if (PyErr_Occurred()) {
PyErr_FormatUnraisable("Exception ignored in m_clear of module%s%V", PyErr_FormatUnraisable("Exception ignored in m_clear of module%s%V",
m->md_name ? " " : "", m->md_name ? " " : "",
m->md_name, ""); m->md_name, "");
} }
if (res) if (res) {
return res; return res;
}
} }
Py_CLEAR(m->md_dict); Py_CLEAR(m->md_dict);
return 0; return 0;

View file

@ -5764,11 +5764,11 @@ PyType_GetModuleState(PyTypeObject *type)
} }
/* Get the module of the first superclass where the module has the /* Return borrowed ref to the module of the first superclass where the module
* given PyModuleDef. * has the given token.
*/ */
PyObject * static PyObject *
PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def) borrow_module_by_token(PyTypeObject *type, const void *token)
{ {
assert(PyType_Check(type)); assert(PyType_Check(type));
@ -5780,7 +5780,7 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
else { else {
PyHeapTypeObject *ht = (PyHeapTypeObject*)type; PyHeapTypeObject *ht = (PyHeapTypeObject*)type;
PyObject *module = ht->ht_module; PyObject *module = ht->ht_module;
if (module && _PyModule_GetDef(module) == def) { if (module && _PyModule_GetToken(module) == token) {
return module; return module;
} }
} }
@ -5808,7 +5808,7 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
PyHeapTypeObject *ht = (PyHeapTypeObject*)super; PyHeapTypeObject *ht = (PyHeapTypeObject*)super;
PyObject *module = ht->ht_module; PyObject *module = ht->ht_module;
if (module && _PyModule_GetDef(module) == def) { if (module && _PyModule_GetToken(module) == token) {
res = module; res = module;
break; break;
} }
@ -5826,6 +5826,18 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
return NULL; 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 * static PyTypeObject *
get_base_by_token_recursive(PyObject *bases, void *token) 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_AddStringConstant)
EXPORT_FUNC(PyModule_AddType) EXPORT_FUNC(PyModule_AddType)
EXPORT_FUNC(PyModule_Create2) EXPORT_FUNC(PyModule_Create2)
EXPORT_FUNC(PyModule_Exec)
EXPORT_FUNC(PyModule_ExecDef) EXPORT_FUNC(PyModule_ExecDef)
EXPORT_FUNC(PyModule_FromDefAndSpec2) EXPORT_FUNC(PyModule_FromDefAndSpec2)
EXPORT_FUNC(PyModule_FromSlotsAndSpec)
EXPORT_FUNC(PyModule_GetDef) EXPORT_FUNC(PyModule_GetDef)
EXPORT_FUNC(PyModule_GetDict) EXPORT_FUNC(PyModule_GetDict)
EXPORT_FUNC(PyModule_GetFilename) EXPORT_FUNC(PyModule_GetFilename)
@ -425,6 +427,8 @@ EXPORT_FUNC(PyModule_GetFilenameObject)
EXPORT_FUNC(PyModule_GetName) EXPORT_FUNC(PyModule_GetName)
EXPORT_FUNC(PyModule_GetNameObject) EXPORT_FUNC(PyModule_GetNameObject)
EXPORT_FUNC(PyModule_GetState) EXPORT_FUNC(PyModule_GetState)
EXPORT_FUNC(PyModule_GetStateSize)
EXPORT_FUNC(PyModule_GetToken)
EXPORT_FUNC(PyModule_New) EXPORT_FUNC(PyModule_New)
EXPORT_FUNC(PyModule_NewObject) EXPORT_FUNC(PyModule_NewObject)
EXPORT_FUNC(PyModule_SetDocString) EXPORT_FUNC(PyModule_SetDocString)
@ -668,6 +672,7 @@ EXPORT_FUNC(PyType_GetFlags)
EXPORT_FUNC(PyType_GetFullyQualifiedName) EXPORT_FUNC(PyType_GetFullyQualifiedName)
EXPORT_FUNC(PyType_GetModule) EXPORT_FUNC(PyType_GetModule)
EXPORT_FUNC(PyType_GetModuleByDef) EXPORT_FUNC(PyType_GetModuleByDef)
EXPORT_FUNC(PyType_GetModuleByToken)
EXPORT_FUNC(PyType_GetModuleName) EXPORT_FUNC(PyType_GetModuleName)
EXPORT_FUNC(PyType_GetModuleState) EXPORT_FUNC(PyType_GetModuleState)
EXPORT_FUNC(PyType_GetName) EXPORT_FUNC(PyType_GetName)

View file

@ -126,6 +126,7 @@
<ClCompile Include="..\Modules\_testcapi\gc.c" /> <ClCompile Include="..\Modules\_testcapi\gc.c" />
<ClCompile Include="..\Modules\_testcapi\run.c" /> <ClCompile Include="..\Modules\_testcapi\run.c" />
<ClCompile Include="..\Modules\_testcapi\modsupport.c" /> <ClCompile Include="..\Modules\_testcapi\modsupport.c" />
<ClCompile Include="..\Modules\_testcapi\module.c" />
<ClCompile Include="..\Modules\_testcapi\monitoring.c" /> <ClCompile Include="..\Modules\_testcapi\monitoring.c" />
<ClCompile Include="..\Modules\_testcapi\config.c" /> <ClCompile Include="..\Modules\_testcapi\config.c" />
<ClCompile Include="..\Modules\_testcapi\import.c" /> <ClCompile Include="..\Modules\_testcapi\import.c" />

View file

@ -111,6 +111,9 @@
<ClCompile Include="..\Modules\_testcapi\modsupport.c"> <ClCompile Include="..\Modules\_testcapi\modsupport.c">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\Modules\_testcapi\module.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Modules\_testcapi\monitoring.c"> <ClCompile Include="..\Modules\_testcapi\monitoring.c">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>

View file

@ -672,8 +672,8 @@ _PyImport_ClearModulesByIndex(PyInterpreterState *interp)
(6). first time (not found in _PyRuntime.imports.extensions): (6). first time (not found in _PyRuntime.imports.extensions):
A. _imp_create_dynamic_impl() -> import_find_extension() A. _imp_create_dynamic_impl() -> import_find_extension()
B. _imp_create_dynamic_impl() -> _PyImport_GetModInitFunc() B. _imp_create_dynamic_impl() -> _PyImport_GetModuleExportHooks()
C. _PyImport_GetModInitFunc(): load <module init func> C. _PyImport_GetModuleExportHooks(): load <module init func>
D. _imp_create_dynamic_impl() -> import_run_extension() D. _imp_create_dynamic_impl() -> import_run_extension()
E. import_run_extension() -> _PyImport_RunModInitFunc() E. import_run_extension() -> _PyImport_RunModInitFunc()
F. _PyImport_RunModInitFunc(): call <module init func> F. _PyImport_RunModInitFunc(): call <module init func>
@ -743,16 +743,19 @@ _PyImport_ClearModulesByIndex(PyInterpreterState *interp)
A. noop A. noop
...for multi-phase init modules: ...for multi-phase init modules from PyModInit_* (PyModuleDef):
(6). every time: (6). every time:
A. _imp_create_dynamic_impl() -> import_find_extension() (not found) A. _imp_create_dynamic_impl() -> import_find_extension() (not found)
B. _imp_create_dynamic_impl() -> _PyImport_GetModInitFunc() B. _imp_create_dynamic_impl() -> _PyImport_GetModuleExportHooks()
C. _PyImport_GetModInitFunc(): load <module init func> C. _PyImport_GetModuleExportHooks(): load <module init func>
D. _imp_create_dynamic_impl() -> import_run_extension() D. _imp_create_dynamic_impl() -> import_run_extension()
E. import_run_extension() -> _PyImport_RunModInitFunc() E. import_run_extension() -> _PyImport_RunModInitFunc()
F. _PyImport_RunModInitFunc(): call <module init func> F. _PyImport_RunModInitFunc(): call <module init func>
G. import_run_extension() -> PyModule_FromDefAndSpec() G. import_run_extension() -> PyModule_FromDefAndSpec()
PyModule_FromDefAndSpec():
H. PyModule_FromDefAndSpec(): gather/check moduledef slots H. PyModule_FromDefAndSpec(): gather/check moduledef slots
I. if there's a Py_mod_create slot: I. if there's a Py_mod_create slot:
1. PyModule_FromDefAndSpec(): call its function 1. PyModule_FromDefAndSpec(): call its function
@ -765,10 +768,29 @@ _PyImport_ClearModulesByIndex(PyInterpreterState *interp)
(10). every time: (10). every time:
A. _imp_exec_dynamic_impl() -> exec_builtin_or_dynamic() A. _imp_exec_dynamic_impl() -> exec_builtin_or_dynamic()
B. if mod->md_state == NULL (including if m_size == 0): B. if mod->md_state == NULL (including if m_size == 0):
1. exec_builtin_or_dynamic() -> PyModule_ExecDef() 1. exec_builtin_or_dynamic() -> PyModule_Exec()
2. PyModule_ExecDef(): allocate mod->md_state 2. PyModule_Exec(): allocate mod->md_state
3. if there's a Py_mod_exec slot: 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 */ /* Common implementation for _imp.exec_dynamic and _imp.exec_builtin */
static int static int
exec_builtin_or_dynamic(PyObject *mod) { exec_builtin_or_dynamic(PyObject *mod) {
PyModuleDef *def;
void *state; void *state;
if (!PyModule_Check(mod)) { if (!PyModule_Check(mod)) {
return 0; return 0;
} }
def = PyModule_GetDef(mod);
if (def == NULL) {
return 0;
}
state = PyModule_GetState(mod); state = PyModule_GetState(mod);
if (state) { if (state) {
/* Already initialized; skip reload */ /* Already initialized; skip reload */
return 0; 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) PyObject *name, PyObject *modules)
{ {
assert(mod != NULL && PyModule_Check(mod)); 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); Py_ssize_t index = _get_cached_module_index(cached);
if (_modules_by_index_set(tstate->interp, index, mod) < 0) { if (_modules_by_index_set(tstate->interp, index, mod) < 0) {
@ -1865,8 +1881,8 @@ reload_singlephase_extension(PyThreadState *tstate,
* due to violating interpreter isolation. * due to violating interpreter isolation.
* See the note in set_cached_m_dict(). * See the note in set_cached_m_dict().
* Until that is solved, we leave md_def set to NULL. */ * Until that is solved, we leave md_def set to NULL. */
assert(_PyModule_GetDef(mod) == NULL assert(_PyModule_GetDefOrNull(mod) == NULL
|| _PyModule_GetDef(mod) == def); || _PyModule_GetDefOrNull(mod) == def);
} }
else { else {
assert(cached->m_dict == NULL); assert(cached->m_dict == NULL);
@ -1953,6 +1969,43 @@ import_find_extension(PyThreadState *tstate,
return mod; 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 * static PyObject *
import_run_extension(PyThreadState *tstate, PyModInitFunction p0, import_run_extension(PyThreadState *tstate, PyModInitFunction p0,
struct _Py_ext_module_loader_info *info, struct _Py_ext_module_loader_info *info,
@ -2125,7 +2178,7 @@ import_run_extension(PyThreadState *tstate, PyModInitFunction p0,
assert_multiphase_def(def); assert_multiphase_def(def);
assert(mod == NULL); assert(mod == NULL);
/* Note that we cheat a little by not repeating the calls /* 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); mod = PyModule_FromDefAndSpec(def, spec);
if (mod == NULL) { if (mod == NULL) {
goto error; goto error;
@ -2239,8 +2292,9 @@ _PyImport_FixupBuiltin(PyThreadState *tstate, PyObject *mod, const char *name,
return -1; return -1;
} }
PyModuleDef *def = PyModule_GetDef(mod); PyModuleDef *def = _PyModule_GetDefOrNull(mod);
if (def == NULL) { if (def == NULL) {
assert(!PyErr_Occurred());
PyErr_BadInternalCall(); PyErr_BadInternalCall();
goto finally; goto finally;
} }
@ -2322,8 +2376,8 @@ create_builtin(PyThreadState *tstate, PyObject *name, PyObject *spec)
assert(!_PyErr_Occurred(tstate)); assert(!_PyErr_Occurred(tstate));
assert(cached != NULL); assert(cached != NULL);
/* The module might not have md_def set in certain reload cases. */ /* The module might not have md_def set in certain reload cases. */
assert(_PyModule_GetDef(mod) == NULL assert(_PyModule_GetDefOrNull(mod) == NULL
|| cached->def == _PyModule_GetDef(mod)); || cached->def == _PyModule_GetDefOrNull(mod));
assert_singlephase(cached); assert_singlephase(cached);
goto finally; goto finally;
} }
@ -4653,8 +4707,8 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
assert(!_PyErr_Occurred(tstate)); assert(!_PyErr_Occurred(tstate));
assert(cached != NULL); assert(cached != NULL);
/* The module might not have md_def set in certain reload cases. */ /* The module might not have md_def set in certain reload cases. */
assert(_PyModule_GetDef(mod) == NULL assert(_PyModule_GetDefOrNull(mod) == NULL
|| cached->def == _PyModule_GetDef(mod)); || cached->def == _PyModule_GetDefOrNull(mod));
assert_singlephase(cached); assert_singlephase(cached);
goto finally; 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 /* 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. */ * code relies on fp still being open. */
FILE *fp; FILE *fp;
if (file != NULL) { if (file != NULL) {
@ -4692,7 +4746,13 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
fp = 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) { if (p0 == NULL) {
goto finally; goto finally;
} }
@ -4714,6 +4774,7 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
} }
#endif #endif
cleanup:
// XXX Shouldn't this happen in the error cases too (i.e. in "finally")? // XXX Shouldn't this happen in the error cases too (i.e. in "finally")?
if (fp) { if (fp) {
fclose(fp); fclose(fp);

View file

@ -5,7 +5,7 @@
#include "pycore_call.h" // _PyObject_CallMethod() #include "pycore_call.h" // _PyObject_CallMethod()
#include "pycore_import.h" // _PyImport_SwapPackageContext() #include "pycore_import.h" // _PyImport_SwapPackageContext()
#include "pycore_importdl.h" #include "pycore_importdl.h"
#include "pycore_moduleobject.h" // _PyModule_GetDef() #include "pycore_moduleobject.h" // _PyModule_GetDefOrNull()
#include "pycore_pyerrors.h" // _PyErr_FormatFromCause() #include "pycore_pyerrors.h" // _PyErr_FormatFromCause()
#include "pycore_runtime.h" // _Py_ID() #include "pycore_runtime.h" // _Py_ID()
@ -35,8 +35,10 @@ extern dl_funcptr _PyImport_FindSharedFuncptr(const char *prefix,
/* module info to use when loading */ /* module info to use when loading */
/***********************************/ /***********************************/
static const char * const ascii_only_prefix = "PyInit"; static const struct hook_prefixes ascii_only_prefixes = {
static const char * const nonascii_prefix = "PyInitU"; "PyInit", "PyModExport"};
static const struct hook_prefixes nonascii_prefixes = {
"PyInitU", "PyModExportU"};
/* Get the variable part of a module's export symbol name. /* Get the variable part of a module's export symbol name.
* Returns a bytes instance. For non-ASCII-named modules, the name is * 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. * nonascii_prefix, as appropriate.
*/ */
static PyObject * 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 *tmp;
PyObject *encoded = NULL; PyObject *encoded = NULL;
PyObject *modname = NULL; PyObject *modname = NULL;
@ -72,7 +74,7 @@ get_encoded_name(PyObject *name, const char **hook_prefix) {
/* Encode to ASCII or Punycode, as needed */ /* Encode to ASCII or Punycode, as needed */
encoded = PyUnicode_AsEncodedString(name, "ascii", NULL); encoded = PyUnicode_AsEncodedString(name, "ascii", NULL);
if (encoded != NULL) { if (encoded != NULL) {
*hook_prefix = ascii_only_prefix; *hook_prefixes = &ascii_only_prefixes;
} else { } else {
if (PyErr_ExceptionMatches(PyExc_UnicodeEncodeError)) { if (PyErr_ExceptionMatches(PyExc_UnicodeEncodeError)) {
PyErr_Clear(); PyErr_Clear();
@ -80,7 +82,7 @@ get_encoded_name(PyObject *name, const char **hook_prefix) {
if (encoded == NULL) { if (encoded == NULL) {
goto error; goto error;
} }
*hook_prefix = nonascii_prefix; *hook_prefixes = &nonascii_prefixes;
} else { } else {
goto error; 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); assert(PyUnicode_GetLength(name) > 0);
info.name = Py_NewRef(name); 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) { if (info.name_encoded == NULL) {
_Py_ext_module_loader_info_clear(&info); _Py_ext_module_loader_info_clear(&info);
return -1; return -1;
@ -189,7 +191,7 @@ _Py_ext_module_loader_info_init_for_builtin(
/* We won't need filename. */ /* We won't need filename. */
.path=name, .path=name,
.origin=_Py_ext_module_origin_BUILTIN, .origin=_Py_ext_module_origin_BUILTIN,
.hook_prefix=ascii_only_prefix, .hook_prefixes=&ascii_only_prefixes,
.newcontext=NULL, .newcontext=NULL,
}; };
return 0; return 0;
@ -377,39 +379,63 @@ _Py_ext_module_loader_result_apply_error(
/********************************************/ /********************************************/
#ifdef HAVE_DYNAMIC_LOADING #ifdef HAVE_DYNAMIC_LOADING
PyModInitFunction static dl_funcptr
_PyImport_GetModInitFunc(struct _Py_ext_module_loader_info *info, findfuncptr(const char *prefix, const char *name_buf,
FILE *fp) 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); const char *name_buf = PyBytes_AS_STRING(info->name_encoded);
dl_funcptr exportfunc; 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) { exportfunc = findfuncptr(
if (!PyErr_Occurred()) { info->hook_prefixes->export_prefix,
PyObject *msg; name_buf, info, fp);
msg = PyUnicode_FromFormat( if (exportfunc) {
"dynamic module does not define " *modexport = (PyModExportFunction)exportfunc;
"module export function (%s_%s)", return 2;
info->hook_prefix, name_buf); }
if (msg != NULL) {
PyErr_SetImportError(msg, info->name, info->filename); exportfunc = findfuncptr(
Py_DECREF(msg); 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 -1;
return (PyModInitFunction)exportfunc;
} }
#endif /* HAVE_DYNAMIC_LOADING */ #endif /* HAVE_DYNAMIC_LOADING */
@ -477,7 +503,7 @@ _PyImport_RunModInitFunc(PyModInitFunction p0,
res.def = (PyModuleDef *)m; res.def = (PyModuleDef *)m;
/* Run PyModule_FromDefAndSpec() to finish loading the module. */ /* 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. */ /* Non-ASCII is only supported for multi-phase init. */
res.kind = _Py_ext_module_kind_MULTIPHASE; res.kind = _Py_ext_module_kind_MULTIPHASE;
/* Don't allow legacy init for non-ASCII module names. */ /* Don't allow legacy init for non-ASCII module names. */
@ -496,7 +522,7 @@ _PyImport_RunModInitFunc(PyModInitFunction p0,
goto error; goto error;
} }
res.def = _PyModule_GetDef(m); res.def = _PyModule_GetDefOrNull(m);
if (res.def == NULL) { if (res.def == NULL) {
PyErr_Clear(); PyErr_Clear();
_Py_ext_module_loader_result_set_error( _Py_ext_module_loader_result_set_error(