From 21a9a85ff4d62e8fc5a51b8eb56154f32c319b77 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 10 Jun 2022 15:55:09 +0200 Subject: [PATCH] gh-93466: Document PyType_Spec doesn't accept repeated slot IDs; raise where this was problematic (GH-93471) --- Doc/c-api/type.rst | 2 + Lib/test/test_capi.py | 6 ++ ...2-06-03-14-54-41.gh-issue-93466.DDtH0X.rst | 3 + Modules/_testcapimodule.c | 59 +++++++++++++++++++ Objects/typeobject.c | 16 ++++- 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2022-06-03-14-54-41.gh-issue-93466.DDtH0X.rst diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index fece3e6e642..aa77c285e3b 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -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 diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index 95930ba40ce..cd6a4de47a7 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -618,6 +618,12 @@ class CAPITest(unittest.TestCase): 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') diff --git a/Misc/NEWS.d/next/C API/2022-06-03-14-54-41.gh-issue-93466.DDtH0X.rst b/Misc/NEWS.d/next/C API/2022-06-03-14-54-41.gh-issue-93466.DDtH0X.rst new file mode 100644 index 00000000000..0bb65ea2b38 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-06-03-14-54-41.gh-issue-93466.DDtH0X.rst @@ -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. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index b75e03c0c06..33dc3dbe493 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -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)) @@ -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", diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 51dc5e34c81..e57651446f8 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -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) { @@ -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;