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
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.
.. 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.
/// 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},
)
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')
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())
#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},
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)
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):
--- /dev/null
+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).
* Lib/test/test_capi/test_module.py
*/
+PyABIInfo_VAR(abi_info);
+
static PyObject *
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)
{
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},
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},
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},
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},
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},
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},
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},
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},
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},
}
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},
}
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},
.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},
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)
{
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},
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},
{"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},
/* PyModExport_* hooks */
+PyABIInfo_VAR(abi_info);
+
PyMODEXPORT_FUNC
PyModExport__test_from_modexport(void)
{
static PyModuleDef_Slot slots[] = {
+ {Py_mod_abi, &abi_info},
{Py_mod_name, "_test_from_modexport"},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
PyModExport__test_from_modexport_gil_used(void)
{
static PyModuleDef_Slot slots[] = {
+ {Py_mod_abi, &abi_info},
{Py_mod_name, "_test_from_modexport_gil_used"},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_USED},
PyModExport__test_from_modexport_create_nonmodule(void)
{
static PyModuleDef_Slot slots[] = {
+ {Py_mod_abi, &abi_info},
{Py_mod_name, "_test_from_modexport_create_nonmodule"},
{Py_mod_create, modexport_create_string},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
PyModExport__test_from_modexport_create_nonmodule_gil_used(void)
{
static PyModuleDef_Slot slots[] = {
+ {Py_mod_abi, &abi_info},
{Py_mod_name, "_test_from_modexport_create_nonmodule"},
{Py_mod_create, modexport_create_string},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
return modexport_empty_slots;
}
+
+static PyModuleDef_Slot modexport_minimal_slots[] = {
+ {Py_mod_abi, &abi_info},
+ {0},
+};
+
+PyMODEXPORT_FUNC
+PyModExport__test_from_modexport_minimal_slots(void)
+{
+ return modexport_minimal_slots;
+}
+
static int
modexport_smoke_exec(PyObject *mod)
{
}
static PyObject *
-modexport_get_empty_slots(PyObject *mod, PyObject *arg)
+modexport_get_minimal_slots(PyObject *mod, PyObject *arg)
{
/* Get the address of modexport_empty_slots.
- * This method would be in the `_test_from_modexport_empty_slots` module,
+ * This method would be in the `_test_from_modexport_minimal_slots` module,
* if it had a methods slot.
*/
- return PyLong_FromVoidPtr(&modexport_empty_slots);
+ return PyLong_FromVoidPtr(&modexport_minimal_slots);
}
static 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},
+ {"get_modexport_minimal_slots", modexport_get_minimal_slots, METH_NOARGS},
{0},
};
static PyModuleDef_Slot slots[] = {
+ {Py_mod_abi, &abi_info},
{Py_mod_name, "_test_from_modexport_smoke"},
{Py_mod_doc, "the expected docstring"},
{Py_mod_exec, modexport_smoke_exec},
bool seen_m_traverse_slot = false;
bool seen_m_clear_slot = false;
bool seen_m_free_slot = false;
+ bool seen_m_abi_slot = false;
for (cur_slot = def_like->m_slots; cur_slot && cur_slot->slot; cur_slot++) {
// Macro to copy a non-NULL, non-repeatable slot.
if (PyABIInfo_Check((PyABIInfo *)cur_slot->value, name) < 0) {
goto error;
}
+ seen_m_abi_slot = true;
break;
DEF_SLOT_CASE(Py_mod_name, char*, m_name)
DEF_SLOT_CASE(Py_mod_doc, char*, m_doc)
#undef COPY_NONDEF_SLOT
#undef COPY_NONNULL_SLOT
}
+ if (!original_def && !seen_m_abi_slot) {
+ PyErr_Format(
+ PyExc_SystemError,
+ "module %s does not define Py_mod_abi,"
+ " which is mandatory for modules defined from slots only.",
+ name);
+ goto error;
+ }
#ifdef Py_GIL_DISABLED
// For modules created directly from slots (not from a def), we enable
return _abiinfo_raise(
module_name,
"incompatible future stable ABI version (%d.%d)",
- ((info->abi_version) >> 24) % 0xff,
- ((info->abi_version) >> 16) % 0xff);
+ ((info->abi_version) >> 24) & 0xff,
+ ((info->abi_version) >> 16) & 0xff);
}
if (info->abi_version < Py_PACK_VERSION(3, 2)) {
return _abiinfo_raise(
module_name,
"invalid stable ABI version (%d.%d)",
- ((info->abi_version) >> 24) % 0xff,
- ((info->abi_version) >> 16) % 0xff);
+ ((info->abi_version) >> 24) & 0xff,
+ ((info->abi_version) >> 16) & 0xff);
}
}
if (info->flags & PyABIInfo_INTERNAL) {
return _abiinfo_raise(
module_name,
"incompatible ABI version (%d.%d)",
- ((info->abi_version) >> 24) % 0xff,
- ((info->abi_version) >> 16) % 0xff);
+ ((info->abi_version) >> 24) & 0xff,
+ ((info->abi_version) >> 16) & 0xff);
}
}
}