Skip to content

Commit

Permalink
gh-93466: Document PyType_Spec doesn't accept repeated slot IDs; rais…
Browse files Browse the repository at this point in the history
…e where this was problematic (GH-93471)
  • Loading branch information
encukou authored Jun 10, 2022
1 parent 3124d9a commit 21a9a85
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Doc/c-api/type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ The following functions and structs are used to create
Array of :c:type:`PyType_Slot` structures.
Terminated by the special slot value ``{0, NULL}``.
Each slot ID should be specified at most once.
.. c:type:: PyType_Slot
Structure defining optional functionality of a type, containing a slot ID
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,12 @@ def test_heaptype_with_custom_metaclass(self):
with self.assertRaisesRegex(TypeError, msg):
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclassCustomNew)

def test_pytype_fromspec_with_repeated_slots(self):
for variant in range(2):
with self.subTest(variant=variant):
with self.assertRaises(SystemError):
_testcapi.create_type_from_repeated_slots(variant)

def test_pynumber_tobase(self):
from _testcapi import pynumber_tobase
self.assertEqual(pynumber_tobase(123, 2), '0b1111011')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Slot IDs in PyType_Spec may not be repeated. The documentation was updated
to mention this. For some cases of repeated slots, PyType_FromSpec and
related functions will now raise an exception.
59 changes: 59 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1482,6 +1482,63 @@ test_type_from_ephemeral_spec(PyObject *self, PyObject *Py_UNUSED(ignored))
return result;
}

PyType_Slot repeated_doc_slots[] = {
{Py_tp_doc, "A class used for tests·"},
{Py_tp_doc, "A class used for tests"},
{0, 0},
};

PyType_Spec repeated_doc_slots_spec = {
.name = "RepeatedDocSlotClass",
.basicsize = sizeof(PyObject),
.slots = repeated_doc_slots,
};

typedef struct {
PyObject_HEAD
int data;
} HeapCTypeWithDataObject;


static struct PyMemberDef members_to_repeat[] = {
{"T_INT", T_INT, offsetof(HeapCTypeWithDataObject, data), 0, NULL},
{NULL}
};

PyType_Slot repeated_members_slots[] = {
{Py_tp_members, members_to_repeat},
{Py_tp_members, members_to_repeat},
{0, 0},
};

PyType_Spec repeated_members_slots_spec = {
.name = "RepeatedMembersSlotClass",
.basicsize = sizeof(HeapCTypeWithDataObject),
.slots = repeated_members_slots,
};

static PyObject *
create_type_from_repeated_slots(PyObject *self, PyObject *variant_obj)
{
PyObject *class = NULL;
int variant = PyLong_AsLong(variant_obj);
if (PyErr_Occurred()) {
return NULL;
}
switch (variant) {
case 0:
class = PyType_FromSpec(&repeated_doc_slots_spec);
break;
case 1:
class = PyType_FromSpec(&repeated_members_slots_spec);
break;
default:
PyErr_SetString(PyExc_ValueError, "bad test variant");
break;
}
return class;
}


static PyObject *
test_get_type_qualname(PyObject *self, PyObject *Py_UNUSED(ignored))
Expand Down Expand Up @@ -6107,6 +6164,8 @@ static PyMethodDef TestMethods[] = {
{"test_get_type_name", test_get_type_name, METH_NOARGS},
{"test_get_type_qualname", test_get_type_qualname, METH_NOARGS},
{"test_type_from_ephemeral_spec", test_type_from_ephemeral_spec, METH_NOARGS},
{"create_type_from_repeated_slots",
create_type_from_repeated_slots, METH_O},
{"test_from_spec_metatype_inheritance", test_from_spec_metatype_inheritance,
METH_NOARGS},
{"test_from_spec_invalid_metatype_inheritance",
Expand Down
16 changes: 14 additions & 2 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3409,14 +3409,20 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
int r;

const PyType_Slot *slot;
Py_ssize_t nmembers, weaklistoffset, dictoffset, vectorcalloffset;
Py_ssize_t nmembers = 0;
Py_ssize_t weaklistoffset, dictoffset, vectorcalloffset;
char *res_start;
short slot_offset, subslot_offset;

nmembers = weaklistoffset = dictoffset = vectorcalloffset = 0;
for (slot = spec->slots; slot->slot; slot++) {
if (slot->slot == Py_tp_members) {
nmembers = 0;
if (nmembers != 0) {
PyErr_SetString(
PyExc_SystemError,
"Multiple Py_tp_members slots are not supported.");
return NULL;
}
for (const PyMemberDef *memb = slot->pfunc; memb->name != NULL; memb++) {
nmembers++;
if (strcmp(memb->name, "__weaklistoffset__") == 0) {
Expand Down Expand Up @@ -3559,6 +3565,12 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
else if (slot->slot == Py_tp_doc) {
/* For the docstring slot, which usually points to a static string
literal, we need to make a copy */
if (type->tp_doc != NULL) {
PyErr_SetString(
PyExc_SystemError,
"Multiple Py_tp_doc slots are not supported.");
return NULL;
}
if (slot->pfunc == NULL) {
type->tp_doc = NULL;
continue;
Expand Down

0 comments on commit 21a9a85

Please sign in to comment.