Skip to content

[3.14] GH-133912: Fix PyObject_GenericSetDict to handle inline values (GH-134725) #134859

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

Merged
merged 1 commit into from
May 29, 2025
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 Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,8 @@ enum _PyAnnotateFormat {
_Py_ANNOTATE_FORMAT_STRING = 4,
};

int _PyObject_SetDict(PyObject *obj, PyObject *value);

#ifdef __cplusplus
}
#endif
Expand Down
10 changes: 10 additions & 0 deletions Lib/test/test_capi/test_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,13 @@ def test_manual_heap_type(self):
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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix the C API function ``PyObject_GenericSetDict`` to handle extension
classes with inline values.
49 changes: 49 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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) {
Expand Down
31 changes: 1 addition & 30 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -1906,40 +1906,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);
}


Expand Down
54 changes: 31 additions & 23 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3676,10 +3676,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));
Expand All @@ -3697,28 +3726,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 *
Expand Down
Loading