]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-133912: Fix `PyObject_GenericSetDict` to handle inline values (GH-134725)
authorMark Shannon <mark@hotpy.org>
Wed, 28 May 2025 18:03:41 +0000 (19:03 +0100)
committerGitHub <noreply@github.com>
Wed, 28 May 2025 18:03:41 +0000 (19:03 +0100)
Include/internal/pycore_object.h
Lib/test/test_capi/test_type.py
Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-15-55-50.gh-issue-133912.-xAguL.rst [new file with mode: 0644]
Modules/_testcapimodule.c
Objects/object.c
Objects/typeobject.c

index 3fc1e7d7dcbd1531e6e922dbc277ba8eb423f8ad..50225623fe52db74b66fa3a13d1afb6b218fb167 100644 (file)
@@ -1010,6 +1010,8 @@ enum _PyAnnotateFormat {
     _Py_ANNOTATE_FORMAT_STRING = 4,
 };
 
+int _PyObject_SetDict(PyObject *obj, PyObject *value);
+
 #ifdef __cplusplus
 }
 #endif
index 3c9974c738738824814026e586f9a574246a73ce..15fb4a93e2ad7460d196006995f469d2399716be 100644 (file)
@@ -264,3 +264,13 @@ class TypeTests(unittest.TestCase):
         ManualHeapType = _testcapi.ManualHeapType
         for i in range(100):
             self.assertIsInstance(ManualHeapType(), ManualHeapType)
+
+    def test_extension_managed_dict_type(self):
+        ManagedDictType = _testcapi.ManagedDictType
+        obj = ManagedDictType()
+        obj.foo = 42
+        self.assertEqual(obj.foo, 42)
+        self.assertEqual(obj.__dict__, {'foo': 42})
+        obj.__dict__ = {'bar': 3}
+        self.assertEqual(obj.__dict__, {'bar': 3})
+        self.assertEqual(obj.bar, 3)
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-15-55-50.gh-issue-133912.-xAguL.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-15-55-50.gh-issue-133912.-xAguL.rst
new file mode 100644 (file)
index 0000000..2118f3d
--- /dev/null
@@ -0,0 +1,2 @@
+Fix the C API function ``PyObject_GenericSetDict`` to handle extension
+classes with inline values.
index 3aa6e4c9e43a26322069e6e4e233e3f5bcffe791..281c5b41137ac2f0b2687eeb3c74fb1befecce8e 100644 (file)
@@ -3175,6 +3175,48 @@ create_manual_heap_type(void)
     return (PyObject *)type;
 }
 
+typedef struct {
+    PyObject_VAR_HEAD
+} ManagedDictObject;
+
+int ManagedDict_traverse(PyObject *self, visitproc visit, void *arg) {
+    PyObject_VisitManagedDict(self, visit, arg);
+    Py_VISIT(Py_TYPE(self));
+    return 0;
+}
+
+int ManagedDict_clear(PyObject *self) {
+    PyObject_ClearManagedDict(self);
+    return 0;
+}
+
+static PyGetSetDef ManagedDict_getset[] = {
+    {"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict, NULL, NULL},
+    {NULL, NULL, NULL, NULL, NULL},
+};
+
+static PyType_Slot ManagedDict_slots[] = {
+    {Py_tp_new, (void *)PyType_GenericNew},
+    {Py_tp_getset, (void *)ManagedDict_getset},
+    {Py_tp_traverse, (void *)ManagedDict_traverse},
+    {Py_tp_clear, (void *)ManagedDict_clear},
+    {0}
+};
+
+static PyType_Spec ManagedDict_spec = {
+    "_testcapi.ManagedDictType",
+    sizeof(ManagedDictObject),
+    0, // itemsize
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_MANAGED_DICT | Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_HAVE_GC,
+    ManagedDict_slots
+};
+
+static PyObject *
+create_managed_dict_type(void)
+{
+   return PyType_FromSpec(&ManagedDict_spec);
+}
+
 static struct PyModuleDef _testcapimodule = {
     PyModuleDef_HEAD_INIT,
     .m_name = "_testcapi",
@@ -3315,6 +3357,13 @@ PyInit__testcapi(void)
         return NULL;
     }
 
+    PyObject *managed_dict_type = create_managed_dict_type();
+    if (managed_dict_type == NULL) {
+        return NULL;
+    }
+    if (PyModule_Add(m, "ManagedDictType", managed_dict_type) < 0) {
+        return NULL;
+    }
 
     /* Include tests from the _testcapi/ directory */
     if (_PyTestCapi_Init_Vectorcall(m) < 0) {
index 68c8bfeae33e336eb19c4c7f98f9e220a7d203c9..9fe61ba7f1593aab65ab6726d5dec92653b42a98 100644 (file)
@@ -2016,40 +2016,11 @@ PyObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value)
 int
 PyObject_GenericSetDict(PyObject *obj, PyObject *value, void *context)
 {
-    PyObject **dictptr = _PyObject_GetDictPtr(obj);
-    if (dictptr == NULL) {
-        if (_PyType_HasFeature(Py_TYPE(obj), Py_TPFLAGS_INLINE_VALUES) &&
-            _PyObject_GetManagedDict(obj) == NULL
-        ) {
-            /* Was unable to convert to dict */
-            PyErr_NoMemory();
-        }
-        else {
-            PyErr_SetString(PyExc_AttributeError,
-                            "This object has no __dict__");
-        }
-        return -1;
-    }
     if (value == NULL) {
         PyErr_SetString(PyExc_TypeError, "cannot delete __dict__");
         return -1;
     }
-    if (!PyDict_Check(value)) {
-        PyErr_Format(PyExc_TypeError,
-                     "__dict__ must be set to a dictionary, "
-                     "not a '%.200s'", Py_TYPE(value)->tp_name);
-        return -1;
-    }
-    Py_BEGIN_CRITICAL_SECTION(obj);
-    PyObject *olddict = *dictptr;
-    FT_ATOMIC_STORE_PTR_RELEASE(*dictptr, Py_NewRef(value));
-#ifdef Py_GIL_DISABLED
-    _PyObject_XDecRefDelayed(olddict);
-#else
-    Py_XDECREF(olddict);
-#endif
-    Py_END_CRITICAL_SECTION();
-    return 0;
+    return _PyObject_SetDict(obj, value);
 }
 
 
index 07f01e3bcb229c33f643917a1232f63f6256ff26..db923c164774b7a6189eb4ca6b372b9bd3d9e485 100644 (file)
@@ -3948,10 +3948,39 @@ subtype_dict(PyObject *obj, void *context)
     return PyObject_GenericGetDict(obj, context);
 }
 
+int
+_PyObject_SetDict(PyObject *obj, PyObject *value)
+{
+    if (value != NULL && !PyDict_Check(value)) {
+        PyErr_Format(PyExc_TypeError,
+                     "__dict__ must be set to a dictionary, "
+                     "not a '%.200s'", Py_TYPE(value)->tp_name);
+        return -1;
+    }
+    if (Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
+        return _PyObject_SetManagedDict(obj, value);
+    }
+    PyObject **dictptr = _PyObject_ComputedDictPointer(obj);
+    if (dictptr == NULL) {
+        PyErr_SetString(PyExc_AttributeError,
+                        "This object has no __dict__");
+        return -1;
+    }
+    Py_BEGIN_CRITICAL_SECTION(obj);
+    PyObject *olddict = *dictptr;
+    FT_ATOMIC_STORE_PTR_RELEASE(*dictptr, Py_NewRef(value));
+#ifdef Py_GIL_DISABLED
+    _PyObject_XDecRefDelayed(olddict);
+#else
+    Py_XDECREF(olddict);
+#endif
+    Py_END_CRITICAL_SECTION();
+    return 0;
+}
+
 static int
 subtype_setdict(PyObject *obj, PyObject *value, void *context)
 {
-    PyObject **dictptr;
     PyTypeObject *base;
 
     base = get_builtin_base_with_dict(Py_TYPE(obj));
@@ -3969,28 +3998,7 @@ subtype_setdict(PyObject *obj, PyObject *value, void *context)
         }
         return func(descr, obj, value);
     }
-    /* Almost like PyObject_GenericSetDict, but allow __dict__ to be deleted. */
-    if (value != NULL && !PyDict_Check(value)) {
-        PyErr_Format(PyExc_TypeError,
-                     "__dict__ must be set to a dictionary, "
-                     "not a '%.200s'", Py_TYPE(value)->tp_name);
-        return -1;
-    }
-
-    if (Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
-        return _PyObject_SetManagedDict(obj, value);
-    }
-    else {
-        dictptr = _PyObject_ComputedDictPointer(obj);
-        if (dictptr == NULL) {
-            PyErr_SetString(PyExc_AttributeError,
-                            "This object has no __dict__");
-            return -1;
-        }
-        Py_CLEAR(*dictptr);
-        *dictptr = Py_XNewRef(value);
-    }
-    return 0;
+    return _PyObject_SetDict(obj, value);
 }
 
 static PyObject *