[Python-checkins] gh-93466: Document PyType_Spec doesn't accept repeated slot IDs; raise where this was problematic (GH-93471)

encukou webhook-mailer at python.org
Fri Jun 10 09:55:18 EDT 2022


https://github.com/python/cpython/commit/21a9a85ff4d62e8fc5a51b8eb56154f32c319b77
commit: 21a9a85ff4d62e8fc5a51b8eb56154f32c319b77
branch: main
author: Petr Viktorin <encukou at gmail.com>
committer: encukou <encukou at gmail.com>
date: 2022-06-10T15:55:09+02:00
summary:

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

files:
A Misc/NEWS.d/next/C API/2022-06-03-14-54-41.gh-issue-93466.DDtH0X.rst
M Doc/c-api/type.rst
M Lib/test/test_capi.py
M Modules/_testcapimodule.c
M Objects/typeobject.c

diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst
index fece3e6e642ff..aa77c285e3b82 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 95930ba40ceb5..cd6a4de47a73d 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -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')
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 0000000000000..0bb65ea2b3878
--- /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 b75e03c0c068d..33dc3dbe493c5 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 51dc5e34c81a2..e57651446f888 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;



More information about the Python-checkins mailing list