Skip to content

Commit 21a9a85

Browse files
authored
gh-93466: Document PyType_Spec doesn't accept repeated slot IDs; raise where this was problematic (GH-93471)
1 parent 3124d9a commit 21a9a85

File tree

5 files changed

+84
-2
lines changed

5 files changed

+84
-2
lines changed

Doc/c-api/type.rst

+2
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,8 @@ The following functions and structs are used to create
296296
Array of :c:type:`PyType_Slot` structures.
297297
Terminated by the special slot value ``{0, NULL}``.
298298
299+
Each slot ID should be specified at most once.
300+
299301
.. c:type:: PyType_Slot
300302
301303
Structure defining optional functionality of a type, containing a slot ID

Lib/test/test_capi.py

+6
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,12 @@ def test_heaptype_with_custom_metaclass(self):
618618
with self.assertRaisesRegex(TypeError, msg):
619619
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclassCustomNew)
620620

621+
def test_pytype_fromspec_with_repeated_slots(self):
622+
for variant in range(2):
623+
with self.subTest(variant=variant):
624+
with self.assertRaises(SystemError):
625+
_testcapi.create_type_from_repeated_slots(variant)
626+
621627
def test_pynumber_tobase(self):
622628
from _testcapi import pynumber_tobase
623629
self.assertEqual(pynumber_tobase(123, 2), '0b1111011')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Slot IDs in PyType_Spec may not be repeated. The documentation was updated
2+
to mention this. For some cases of repeated slots, PyType_FromSpec and
3+
related functions will now raise an exception.

Modules/_testcapimodule.c

+59
Original file line numberDiff line numberDiff line change
@@ -1482,6 +1482,63 @@ test_type_from_ephemeral_spec(PyObject *self, PyObject *Py_UNUSED(ignored))
14821482
return result;
14831483
}
14841484

1485+
PyType_Slot repeated_doc_slots[] = {
1486+
{Py_tp_doc, "A class used for tests·"},
1487+
{Py_tp_doc, "A class used for tests"},
1488+
{0, 0},
1489+
};
1490+
1491+
PyType_Spec repeated_doc_slots_spec = {
1492+
.name = "RepeatedDocSlotClass",
1493+
.basicsize = sizeof(PyObject),
1494+
.slots = repeated_doc_slots,
1495+
};
1496+
1497+
typedef struct {
1498+
PyObject_HEAD
1499+
int data;
1500+
} HeapCTypeWithDataObject;
1501+
1502+
1503+
static struct PyMemberDef members_to_repeat[] = {
1504+
{"T_INT", T_INT, offsetof(HeapCTypeWithDataObject, data), 0, NULL},
1505+
{NULL}
1506+
};
1507+
1508+
PyType_Slot repeated_members_slots[] = {
1509+
{Py_tp_members, members_to_repeat},
1510+
{Py_tp_members, members_to_repeat},
1511+
{0, 0},
1512+
};
1513+
1514+
PyType_Spec repeated_members_slots_spec = {
1515+
.name = "RepeatedMembersSlotClass",
1516+
.basicsize = sizeof(HeapCTypeWithDataObject),
1517+
.slots = repeated_members_slots,
1518+
};
1519+
1520+
static PyObject *
1521+
create_type_from_repeated_slots(PyObject *self, PyObject *variant_obj)
1522+
{
1523+
PyObject *class = NULL;
1524+
int variant = PyLong_AsLong(variant_obj);
1525+
if (PyErr_Occurred()) {
1526+
return NULL;
1527+
}
1528+
switch (variant) {
1529+
case 0:
1530+
class = PyType_FromSpec(&repeated_doc_slots_spec);
1531+
break;
1532+
case 1:
1533+
class = PyType_FromSpec(&repeated_members_slots_spec);
1534+
break;
1535+
default:
1536+
PyErr_SetString(PyExc_ValueError, "bad test variant");
1537+
break;
1538+
}
1539+
return class;
1540+
}
1541+
14851542

14861543
static PyObject *
14871544
test_get_type_qualname(PyObject *self, PyObject *Py_UNUSED(ignored))
@@ -6107,6 +6164,8 @@ static PyMethodDef TestMethods[] = {
61076164
{"test_get_type_name", test_get_type_name, METH_NOARGS},
61086165
{"test_get_type_qualname", test_get_type_qualname, METH_NOARGS},
61096166
{"test_type_from_ephemeral_spec", test_type_from_ephemeral_spec, METH_NOARGS},
6167+
{"create_type_from_repeated_slots",
6168+
create_type_from_repeated_slots, METH_O},
61106169
{"test_from_spec_metatype_inheritance", test_from_spec_metatype_inheritance,
61116170
METH_NOARGS},
61126171
{"test_from_spec_invalid_metatype_inheritance",

Objects/typeobject.c

+14-2
Original file line numberDiff line numberDiff line change
@@ -3409,14 +3409,20 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
34093409
int r;
34103410

34113411
const PyType_Slot *slot;
3412-
Py_ssize_t nmembers, weaklistoffset, dictoffset, vectorcalloffset;
3412+
Py_ssize_t nmembers = 0;
3413+
Py_ssize_t weaklistoffset, dictoffset, vectorcalloffset;
34133414
char *res_start;
34143415
short slot_offset, subslot_offset;
34153416

34163417
nmembers = weaklistoffset = dictoffset = vectorcalloffset = 0;
34173418
for (slot = spec->slots; slot->slot; slot++) {
34183419
if (slot->slot == Py_tp_members) {
3419-
nmembers = 0;
3420+
if (nmembers != 0) {
3421+
PyErr_SetString(
3422+
PyExc_SystemError,
3423+
"Multiple Py_tp_members slots are not supported.");
3424+
return NULL;
3425+
}
34203426
for (const PyMemberDef *memb = slot->pfunc; memb->name != NULL; memb++) {
34213427
nmembers++;
34223428
if (strcmp(memb->name, "__weaklistoffset__") == 0) {
@@ -3559,6 +3565,12 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
35593565
else if (slot->slot == Py_tp_doc) {
35603566
/* For the docstring slot, which usually points to a static string
35613567
literal, we need to make a copy */
3568+
if (type->tp_doc != NULL) {
3569+
PyErr_SetString(
3570+
PyExc_SystemError,
3571+
"Multiple Py_tp_doc slots are not supported.");
3572+
return NULL;
3573+
}
35623574
if (slot->pfunc == NULL) {
35633575
type->tp_doc = NULL;
35643576
continue;

0 commit comments

Comments
 (0)