Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Doc/c-api/module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,9 @@ Feature slots
When creating a module, Python checks the value of this slot
using :c:func:`PyABIInfo_Check`.

This slot is required, except for modules created from
:c:struct:`PyModuleDef`.

.. versionadded:: 3.15

.. c:macro:: Py_mod_multiple_interpreters
Expand Down Expand Up @@ -620,9 +623,9 @@ rather than from an extension's :ref:`export hook <extension-export-hook>`.
and the :py:class:`~importlib.machinery.ModuleSpec` *spec*.

The *slots* argument must point to an array of :c:type:`PyModuleDef_Slot`
structures, terminated by an entry slot with slot ID of 0
structures, terminated by an entry with slot ID of 0
(typically written as ``{0}`` or ``{0, NULL}`` in C).
The *slots* argument may not be ``NULL``.
The array must include a :c:data:`Py_mod_abi` entry.

The *spec* argument may be any ``ModuleSpec``-like object, as described
in :c:macro:`Py_mod_create` documentation.
Expand Down
7 changes: 7 additions & 0 deletions Doc/extending/first-extension-module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -265,12 +265,19 @@ Define this array just before your export hook:

.. code-block:: c

PyABIInfo_VAR(abi_info);

static PyModuleDef_Slot spam_slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_name, "spam"},
{Py_mod_doc, "A wonderful module with an example function"},
{0, NULL}
};

The ``PyABIInfo_VAR(abi_info);`` macro and the :c:data:`Py_mod_abi` slot
are a bit of boilerplate that helps prevent extensions compiled for
a different version of Python from crashing the interpreter.

For both :c:data:`Py_mod_name` and :c:data:`Py_mod_doc`, the values are C
strings -- that is, NUL-terminated, UTF-8 encoded byte arrays.

Expand Down
3 changes: 3 additions & 0 deletions Doc/includes/capi-extension/spammodule-01.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ static PyMethodDef spam_methods[] = {

/// Module slot table

PyABIInfo_VAR(abi_info);

static PyModuleDef_Slot spam_slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_name, "spam"},
{Py_mod_doc, "A wonderful module with an example function"},
{Py_mod_methods, spam_methods},
Expand Down
18 changes: 16 additions & 2 deletions Lib/test/test_capi/test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ def def_and_token(mod):
)

class TestModFromSlotsAndSpec(unittest.TestCase):
@requires_gil_enabled("empty slots re-enable GIL")
def test_empty(self):
mod = _testcapi.module_from_slots_empty(FakeSpec())
with self.assertRaises(SystemError):
_testcapi.module_from_slots_empty(FakeSpec())

@requires_gil_enabled("minimal slots re-enable GIL")
def test_minimal(self):
mod = _testcapi.module_from_slots_minimal(FakeSpec())
self.assertIsInstance(mod, types.ModuleType)
self.assertEqual(def_and_token(mod), (0, 0))
self.assertEqual(mod.__name__, 'testmod')
Expand Down Expand Up @@ -159,6 +163,16 @@ def test_null_def_slot(self):
self.assertIn(name, str(cm.exception))
self.assertIn("NULL", str(cm.exception))

def test_bad_abiinfo(self):
"""Slots that incompatible ABI is rejected"""
with self.assertRaises(ImportError) as cm:
_testcapi.module_from_bad_abiinfo(FakeSpec())

def test_multiple_abiinfo(self):
"""Slots that Py_mod_abiinfo can be repeated"""
mod = _testcapi.module_from_multiple_abiinfo(FakeSpec())
self.assertEqual(mod.__name__, 'testmod')

def test_def_multiple_exec(self):
"""PyModule_Exec runs all exec slots of PyModuleDef-defined module"""
mod = _testcapi.module_from_def_multiple_exec(FakeSpec())
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_cext/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,10 @@ _Py_COMP_DIAG_PUSH
#endif

PyDoc_STRVAR(_testcext_doc, "C test extension.");
PyABIInfo_VAR(abi_info);

static PyModuleDef_Slot _testcext_slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_name, STR(MODULE_NAME)},
{Py_mod_doc, (void*)(char*)_testcext_doc},
{Py_mod_exec, (void*)_testcext_exec},
Expand Down
16 changes: 12 additions & 4 deletions Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3499,12 +3499,20 @@ class Sub(tp):
pass
self.assertEqual(_testcapi.pytype_getmodulebytoken(Sub, token), module)

@requires_gil_enabled("empty slots re-enable GIL")
def test_from_modexport_empty_slots(self):
# Module to test that Py_mod_abi is mandatory for PyModExport
modname = '_test_from_modexport_empty_slots'
filename = _testmultiphase.__file__
with self.assertRaises(SystemError):
import_extension_from_file(
modname, filename, put_in_sys_modules=False)

@requires_gil_enabled("this module re-enables GIL")
def test_from_modexport_minimal_slots(self):
# Module to test that:
# - no slots are mandatory for PyModExport
# - no slots except Py_mod_abi is mandatory for PyModExport
# - the slots array is used as the default token
modname = '_test_from_modexport_empty_slots'
modname = '_test_from_modexport_minimal_slots'
filename = _testmultiphase.__file__
module = import_extension_from_file(
modname, filename, put_in_sys_modules=False)
Expand All @@ -3516,7 +3524,7 @@ def test_from_modexport_empty_slots(self):
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())
smoke_mod.get_modexport_minimal_slots())

@cpython_only
class TestMagicNumber(unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The :c:data:`Py_mod_abi` slot is now mandatory for modules created from a
slots array (using :c:func:`PyModule_FromSlotsAndSpec` or the
:c:func:`PyModExport_* <PyModExport_modulename>` export hook).
65 changes: 65 additions & 0 deletions Modules/_testcapi/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* Lib/test/test_capi/test_module.py
*/

PyABIInfo_VAR(abi_info);

static PyObject *
module_from_slots_empty(PyObject *self, PyObject *spec)
{
Expand All @@ -17,6 +19,16 @@ module_from_slots_empty(PyObject *self, PyObject *spec)
return PyModule_FromSlotsAndSpec(slots, spec);
}

static PyObject *
module_from_slots_minimal(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_abi, &abi_info},
{0},
};
return PyModule_FromSlotsAndSpec(slots, spec);
}

static PyObject *
module_from_slots_null(PyObject *self, PyObject *spec)
{
Expand All @@ -27,6 +39,7 @@ static PyObject *
module_from_slots_name(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_name, "currently ignored..."},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
Expand All @@ -39,6 +52,7 @@ static PyObject *
module_from_slots_doc(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_doc, "the docstring"},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
Expand All @@ -51,6 +65,7 @@ static PyObject *
module_from_slots_size(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_state_size, (void*)123},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
Expand Down Expand Up @@ -78,6 +93,7 @@ static PyObject *
module_from_slots_methods(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_methods, a_methoddef_array},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
Expand All @@ -96,6 +112,7 @@ static PyObject *
module_from_slots_gc(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_state_traverse, noop_traverse},
{Py_mod_state_clear, noop_clear},
{Py_mod_state_free, noop_free},
Expand Down Expand Up @@ -128,6 +145,7 @@ static PyObject *
module_from_slots_token(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_token, (void*)&test_token},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
Expand Down Expand Up @@ -156,6 +174,7 @@ static PyObject *
module_from_slots_exec(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_exec, simple_exec},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
Expand Down Expand Up @@ -189,6 +208,7 @@ static PyObject *
module_from_slots_create(PyObject *self, PyObject *spec)
{
PyModuleDef_Slot slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_create, create_attr_from_spec},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
Expand Down Expand Up @@ -220,6 +240,7 @@ module_from_slots_repeat_slot(PyObject *self, PyObject *spec)
return NULL;
}
PyModuleDef_Slot slots[] = {
{Py_mod_abi, &abi_info},
{slot_id, "anything"},
{slot_id, "anything else"},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
Expand All @@ -238,6 +259,7 @@ module_from_slots_null_slot(PyObject *self, PyObject *spec)
}
PyModuleDef_Slot slots[] = {
{slot_id, NULL},
{Py_mod_abi, &abi_info},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
Expand All @@ -254,6 +276,7 @@ module_from_def_slot(PyObject *self, PyObject *spec)
}
PyModuleDef_Slot slots[] = {
{slot_id, "anything"},
{Py_mod_abi, &abi_info},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
Expand Down Expand Up @@ -285,6 +308,7 @@ static PyModuleDef parrot_def = {
.m_slots = NULL /* set below */,
};
static PyModuleDef_Slot parrot_slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_name, (void*)parrot_name},
{Py_mod_doc, (void*)parrot_doc},
{Py_mod_state_size, (void*)123},
Expand Down Expand Up @@ -314,6 +338,43 @@ module_from_def_slot_parrot(PyObject *self, PyObject *spec)
return module;
}

static PyObject *
module_from_bad_abiinfo(PyObject *self, PyObject *spec)
{
PyABIInfo bad_abi_info = {
1, 0,
.abi_version=0x02080000,
};
PyModuleDef_Slot slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_abi, &bad_abi_info},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
return PyModule_FromSlotsAndSpec(slots, spec);
}

static PyObject *
module_from_multiple_abiinfo(PyObject *self, PyObject *spec)
{
PyABIInfo extra_abi_info = {
1, 0,
.flags=PyABIInfo_STABLE | PyABIInfo_FREETHREADING_AGNOSTIC,
.abi_version=0x03040000,
};
PyModuleDef_Slot slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_abi, &abi_info},
{Py_mod_abi, &extra_abi_info},
{Py_mod_abi, &extra_abi_info},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0},
};
return PyModule_FromSlotsAndSpec(slots, spec);
}

static int
another_exec(PyObject *module)
{
Expand Down Expand Up @@ -344,6 +405,7 @@ static PyObject *
module_from_def_multiple_exec(PyObject *self, PyObject *spec)
{
static PyModuleDef_Slot slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_exec, simple_exec},
{Py_mod_exec, another_exec},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
Expand Down Expand Up @@ -399,6 +461,7 @@ pymodule_get_state_size(PyObject *self, PyObject *module)

static PyMethodDef test_methods[] = {
{"module_from_slots_empty", module_from_slots_empty, METH_O},
{"module_from_slots_minimal", module_from_slots_minimal, 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},
Expand All @@ -413,6 +476,8 @@ static PyMethodDef test_methods[] = {
{"module_from_def_multiple_exec", module_from_def_multiple_exec, METH_O},
{"module_from_def_slot", module_from_def_slot, METH_O},
{"module_from_def_slot_parrot", module_from_def_slot_parrot, METH_O},
{"module_from_bad_abiinfo", module_from_bad_abiinfo, METH_O},
{"module_from_multiple_abiinfo", module_from_multiple_abiinfo, 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},
Expand Down
Loading
Loading