Skip to content

Commit a86963b

Browse files
authored
gh-146636: Py_mod_abi mandatory for modules created from slots array (GH-146855)
1 parent 97babb8 commit a86963b

File tree

11 files changed

+152
-18
lines changed

11 files changed

+152
-18
lines changed

Doc/c-api/module.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ Feature slots
230230
When creating a module, Python checks the value of this slot
231231
using :c:func:`PyABIInfo_Check`.
232232
233+
This slot is required, except for modules created from
234+
:c:struct:`PyModuleDef`.
235+
233236
.. versionadded:: 3.15
234237
235238
.. c:macro:: Py_mod_multiple_interpreters
@@ -620,9 +623,9 @@ rather than from an extension's :ref:`export hook <extension-export-hook>`.
620623
and the :py:class:`~importlib.machinery.ModuleSpec` *spec*.
621624
622625
The *slots* argument must point to an array of :c:type:`PyModuleDef_Slot`
623-
structures, terminated by an entry slot with slot ID of 0
626+
structures, terminated by an entry with slot ID of 0
624627
(typically written as ``{0}`` or ``{0, NULL}`` in C).
625-
The *slots* argument may not be ``NULL``.
628+
The array must include a :c:data:`Py_mod_abi` entry.
626629
627630
The *spec* argument may be any ``ModuleSpec``-like object, as described
628631
in :c:macro:`Py_mod_create` documentation.

Doc/extending/first-extension-module.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,19 @@ Define this array just before your export hook:
265265

266266
.. code-block:: c
267267
268+
PyABIInfo_VAR(abi_info);
269+
268270
static PyModuleDef_Slot spam_slots[] = {
271+
{Py_mod_abi, &abi_info},
269272
{Py_mod_name, "spam"},
270273
{Py_mod_doc, "A wonderful module with an example function"},
271274
{0, NULL}
272275
};
273276
277+
The ``PyABIInfo_VAR(abi_info);`` macro and the :c:data:`Py_mod_abi` slot
278+
are a bit of boilerplate that helps prevent extensions compiled for
279+
a different version of Python from crashing the interpreter.
280+
274281
For both :c:data:`Py_mod_name` and :c:data:`Py_mod_doc`, the values are C
275282
strings -- that is, NUL-terminated, UTF-8 encoded byte arrays.
276283

Doc/includes/capi-extension/spammodule-01.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ static PyMethodDef spam_methods[] = {
3535

3636
/// Module slot table
3737

38+
PyABIInfo_VAR(abi_info);
39+
3840
static PyModuleDef_Slot spam_slots[] = {
41+
{Py_mod_abi, &abi_info},
3942
{Py_mod_name, "spam"},
4043
{Py_mod_doc, "A wonderful module with an example function"},
4144
{Py_mod_methods, spam_methods},

Lib/test/test_capi/test_module.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@ def def_and_token(mod):
2525
)
2626

2727
class TestModFromSlotsAndSpec(unittest.TestCase):
28-
@requires_gil_enabled("empty slots re-enable GIL")
2928
def test_empty(self):
30-
mod = _testcapi.module_from_slots_empty(FakeSpec())
29+
with self.assertRaises(SystemError):
30+
_testcapi.module_from_slots_empty(FakeSpec())
31+
32+
@requires_gil_enabled("minimal slots re-enable GIL")
33+
def test_minimal(self):
34+
mod = _testcapi.module_from_slots_minimal(FakeSpec())
3135
self.assertIsInstance(mod, types.ModuleType)
3236
self.assertEqual(def_and_token(mod), (0, 0))
3337
self.assertEqual(mod.__name__, 'testmod')
@@ -159,6 +163,16 @@ def test_null_def_slot(self):
159163
self.assertIn(name, str(cm.exception))
160164
self.assertIn("NULL", str(cm.exception))
161165

166+
def test_bad_abiinfo(self):
167+
"""Slots that incompatible ABI is rejected"""
168+
with self.assertRaises(ImportError) as cm:
169+
_testcapi.module_from_bad_abiinfo(FakeSpec())
170+
171+
def test_multiple_abiinfo(self):
172+
"""Slots that Py_mod_abiinfo can be repeated"""
173+
mod = _testcapi.module_from_multiple_abiinfo(FakeSpec())
174+
self.assertEqual(mod.__name__, 'testmod')
175+
162176
def test_def_multiple_exec(self):
163177
"""PyModule_Exec runs all exec slots of PyModuleDef-defined module"""
164178
mod = _testcapi.module_from_def_multiple_exec(FakeSpec())

Lib/test/test_cext/extension.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,10 @@ _Py_COMP_DIAG_PUSH
119119
#endif
120120

121121
PyDoc_STRVAR(_testcext_doc, "C test extension.");
122+
PyABIInfo_VAR(abi_info);
122123

123124
static PyModuleDef_Slot _testcext_slots[] = {
125+
{Py_mod_abi, &abi_info},
124126
{Py_mod_name, STR(MODULE_NAME)},
125127
{Py_mod_doc, (void*)(char*)_testcext_doc},
126128
{Py_mod_exec, (void*)_testcext_exec},

Lib/test/test_import/__init__.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3499,12 +3499,20 @@ class Sub(tp):
34993499
pass
35003500
self.assertEqual(_testcapi.pytype_getmodulebytoken(Sub, token), module)
35013501

3502-
@requires_gil_enabled("empty slots re-enable GIL")
35033502
def test_from_modexport_empty_slots(self):
3503+
# Module to test that Py_mod_abi is mandatory for PyModExport
3504+
modname = '_test_from_modexport_empty_slots'
3505+
filename = _testmultiphase.__file__
3506+
with self.assertRaises(SystemError):
3507+
import_extension_from_file(
3508+
modname, filename, put_in_sys_modules=False)
3509+
3510+
@requires_gil_enabled("this module re-enables GIL")
3511+
def test_from_modexport_minimal_slots(self):
35043512
# Module to test that:
3505-
# - no slots are mandatory for PyModExport
3513+
# - no slots except Py_mod_abi is mandatory for PyModExport
35063514
# - the slots array is used as the default token
3507-
modname = '_test_from_modexport_empty_slots'
3515+
modname = '_test_from_modexport_minimal_slots'
35083516
filename = _testmultiphase.__file__
35093517
module = import_extension_from_file(
35103518
modname, filename, put_in_sys_modules=False)
@@ -3516,7 +3524,7 @@ def test_from_modexport_empty_slots(self):
35163524
smoke_mod = import_extension_from_file(
35173525
'_test_from_modexport_smoke', filename, put_in_sys_modules=False)
35183526
self.assertEqual(_testcapi.pymodule_get_token(module),
3519-
smoke_mod.get_modexport_empty_slots())
3527+
smoke_mod.get_modexport_minimal_slots())
35203528

35213529
@cpython_only
35223530
class TestMagicNumber(unittest.TestCase):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The :c:data:`Py_mod_abi` slot is now mandatory for modules created from a
2+
slots array (using :c:func:`PyModule_FromSlotsAndSpec` or the
3+
:c:func:`PyModExport_* <PyModExport_modulename>` export hook).

Modules/_testcapi/module.c

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
* Lib/test/test_capi/test_module.py
99
*/
1010

11+
PyABIInfo_VAR(abi_info);
12+
1113
static PyObject *
1214
module_from_slots_empty(PyObject *self, PyObject *spec)
1315
{
@@ -17,6 +19,16 @@ module_from_slots_empty(PyObject *self, PyObject *spec)
1719
return PyModule_FromSlotsAndSpec(slots, spec);
1820
}
1921

22+
static PyObject *
23+
module_from_slots_minimal(PyObject *self, PyObject *spec)
24+
{
25+
PyModuleDef_Slot slots[] = {
26+
{Py_mod_abi, &abi_info},
27+
{0},
28+
};
29+
return PyModule_FromSlotsAndSpec(slots, spec);
30+
}
31+
2032
static PyObject *
2133
module_from_slots_null(PyObject *self, PyObject *spec)
2234
{
@@ -27,6 +39,7 @@ static PyObject *
2739
module_from_slots_name(PyObject *self, PyObject *spec)
2840
{
2941
PyModuleDef_Slot slots[] = {
42+
{Py_mod_abi, &abi_info},
3043
{Py_mod_name, "currently ignored..."},
3144
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
3245
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -39,6 +52,7 @@ static PyObject *
3952
module_from_slots_doc(PyObject *self, PyObject *spec)
4053
{
4154
PyModuleDef_Slot slots[] = {
55+
{Py_mod_abi, &abi_info},
4256
{Py_mod_doc, "the docstring"},
4357
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
4458
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -51,6 +65,7 @@ static PyObject *
5165
module_from_slots_size(PyObject *self, PyObject *spec)
5266
{
5367
PyModuleDef_Slot slots[] = {
68+
{Py_mod_abi, &abi_info},
5469
{Py_mod_state_size, (void*)123},
5570
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
5671
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -78,6 +93,7 @@ static PyObject *
7893
module_from_slots_methods(PyObject *self, PyObject *spec)
7994
{
8095
PyModuleDef_Slot slots[] = {
96+
{Py_mod_abi, &abi_info},
8197
{Py_mod_methods, a_methoddef_array},
8298
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
8399
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -96,6 +112,7 @@ static PyObject *
96112
module_from_slots_gc(PyObject *self, PyObject *spec)
97113
{
98114
PyModuleDef_Slot slots[] = {
115+
{Py_mod_abi, &abi_info},
99116
{Py_mod_state_traverse, noop_traverse},
100117
{Py_mod_state_clear, noop_clear},
101118
{Py_mod_state_free, noop_free},
@@ -128,6 +145,7 @@ static PyObject *
128145
module_from_slots_token(PyObject *self, PyObject *spec)
129146
{
130147
PyModuleDef_Slot slots[] = {
148+
{Py_mod_abi, &abi_info},
131149
{Py_mod_token, (void*)&test_token},
132150
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
133151
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -156,6 +174,7 @@ static PyObject *
156174
module_from_slots_exec(PyObject *self, PyObject *spec)
157175
{
158176
PyModuleDef_Slot slots[] = {
177+
{Py_mod_abi, &abi_info},
159178
{Py_mod_exec, simple_exec},
160179
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
161180
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -189,6 +208,7 @@ static PyObject *
189208
module_from_slots_create(PyObject *self, PyObject *spec)
190209
{
191210
PyModuleDef_Slot slots[] = {
211+
{Py_mod_abi, &abi_info},
192212
{Py_mod_create, create_attr_from_spec},
193213
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
194214
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
@@ -220,6 +240,7 @@ module_from_slots_repeat_slot(PyObject *self, PyObject *spec)
220240
return NULL;
221241
}
222242
PyModuleDef_Slot slots[] = {
243+
{Py_mod_abi, &abi_info},
223244
{slot_id, "anything"},
224245
{slot_id, "anything else"},
225246
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
@@ -238,6 +259,7 @@ module_from_slots_null_slot(PyObject *self, PyObject *spec)
238259
}
239260
PyModuleDef_Slot slots[] = {
240261
{slot_id, NULL},
262+
{Py_mod_abi, &abi_info},
241263
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
242264
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
243265
{0},
@@ -254,6 +276,7 @@ module_from_def_slot(PyObject *self, PyObject *spec)
254276
}
255277
PyModuleDef_Slot slots[] = {
256278
{slot_id, "anything"},
279+
{Py_mod_abi, &abi_info},
257280
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
258281
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
259282
{0},
@@ -285,6 +308,7 @@ static PyModuleDef parrot_def = {
285308
.m_slots = NULL /* set below */,
286309
};
287310
static PyModuleDef_Slot parrot_slots[] = {
311+
{Py_mod_abi, &abi_info},
288312
{Py_mod_name, (void*)parrot_name},
289313
{Py_mod_doc, (void*)parrot_doc},
290314
{Py_mod_state_size, (void*)123},
@@ -314,6 +338,43 @@ module_from_def_slot_parrot(PyObject *self, PyObject *spec)
314338
return module;
315339
}
316340

341+
static PyObject *
342+
module_from_bad_abiinfo(PyObject *self, PyObject *spec)
343+
{
344+
PyABIInfo bad_abi_info = {
345+
1, 0,
346+
.abi_version=0x02080000,
347+
};
348+
PyModuleDef_Slot slots[] = {
349+
{Py_mod_abi, &abi_info},
350+
{Py_mod_abi, &bad_abi_info},
351+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
352+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
353+
{0},
354+
};
355+
return PyModule_FromSlotsAndSpec(slots, spec);
356+
}
357+
358+
static PyObject *
359+
module_from_multiple_abiinfo(PyObject *self, PyObject *spec)
360+
{
361+
PyABIInfo extra_abi_info = {
362+
1, 0,
363+
.flags=PyABIInfo_STABLE | PyABIInfo_FREETHREADING_AGNOSTIC,
364+
.abi_version=0x03040000,
365+
};
366+
PyModuleDef_Slot slots[] = {
367+
{Py_mod_abi, &abi_info},
368+
{Py_mod_abi, &abi_info},
369+
{Py_mod_abi, &extra_abi_info},
370+
{Py_mod_abi, &extra_abi_info},
371+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
372+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
373+
{0},
374+
};
375+
return PyModule_FromSlotsAndSpec(slots, spec);
376+
}
377+
317378
static int
318379
another_exec(PyObject *module)
319380
{
@@ -344,6 +405,7 @@ static PyObject *
344405
module_from_def_multiple_exec(PyObject *self, PyObject *spec)
345406
{
346407
static PyModuleDef_Slot slots[] = {
408+
{Py_mod_abi, &abi_info},
347409
{Py_mod_exec, simple_exec},
348410
{Py_mod_exec, another_exec},
349411
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
@@ -399,6 +461,7 @@ pymodule_get_state_size(PyObject *self, PyObject *module)
399461

400462
static PyMethodDef test_methods[] = {
401463
{"module_from_slots_empty", module_from_slots_empty, METH_O},
464+
{"module_from_slots_minimal", module_from_slots_minimal, METH_O},
402465
{"module_from_slots_null", module_from_slots_null, METH_O},
403466
{"module_from_slots_name", module_from_slots_name, METH_O},
404467
{"module_from_slots_doc", module_from_slots_doc, METH_O},
@@ -413,6 +476,8 @@ static PyMethodDef test_methods[] = {
413476
{"module_from_def_multiple_exec", module_from_def_multiple_exec, METH_O},
414477
{"module_from_def_slot", module_from_def_slot, METH_O},
415478
{"module_from_def_slot_parrot", module_from_def_slot_parrot, METH_O},
479+
{"module_from_bad_abiinfo", module_from_bad_abiinfo, METH_O},
480+
{"module_from_multiple_abiinfo", module_from_multiple_abiinfo, METH_O},
416481
{"pymodule_get_token", pymodule_get_token, METH_O},
417482
{"pymodule_get_def", pymodule_get_def, METH_O},
418483
{"pymodule_get_state_size", pymodule_get_state_size, METH_O},

0 commit comments

Comments
 (0)