Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-93466: Document PyType_Spec doesn't accept repeated slot IDs; raise where this was problematic #93471

Merged
merged 4 commits into from
Jun 10, 2022
Merged
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
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