Skip to content
24 changes: 24 additions & 0 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,30 @@ def test_heaptype_with_dict(self):
inst = _testcapi.HeapCTypeWithDict()
self.assertEqual({}, inst.__dict__)

def test_heaptype_with_managed_dict(self):
inst = _testcapi.HeapCTypeWithManagedDict()
inst.foo = 42
self.assertEqual(inst.foo, 42)
self.assertEqual(inst.__dict__, {"foo": 42})

inst = _testcapi.HeapCTypeWithManagedDict()
self.assertEqual({}, inst.__dict__)

a = _testcapi.HeapCTypeWithManagedDict()
b = _testcapi.HeapCTypeWithManagedDict()
a.b = b
b.a = a
del a, b

def test_sublclassing_managed_dict(self):

class C(_testcapi.HeapCTypeWithManagedDict):
pass

i = C()
i.spam = i
del i

def test_heaptype_with_negative_dict(self):
inst = _testcapi.HeapCTypeWithNegativeDict()
inst.foo = 42
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1287,7 +1287,7 @@ class OverflowSizeof(int):
def __sizeof__(self):
return int(self)
self.assertEqual(sys.getsizeof(OverflowSizeof(sys.maxsize)),
sys.maxsize + self.gc_headsize)
sys.maxsize + self.gc_headsize*2)
Comment thread
markshannon marked this conversation as resolved.
with self.assertRaises(OverflowError):
sys.getsizeof(OverflowSizeof(sys.maxsize + 1))
with self.assertRaises(ValueError):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Support C extensions using managed dictionaries by setting the
``Py_TPFLAGS_MANAGED_DICT`` flag.
45 changes: 45 additions & 0 deletions Modules/_testcapi/heaptype.c
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,45 @@ static PyType_Spec HeapCTypeWithDict2_spec = {
HeapCTypeWithDict_slots
};

static int
heapmanaged_traverse(HeapCTypeObject *self, visitproc visit, void *arg)
{
Py_VISIT(Py_TYPE(self));
return _PyObject_VisitManagedDict((PyObject *)self, visit, arg);
}

static void
heapmanaged_clear(HeapCTypeObject *self)
{
_PyObject_ClearManagedDict((PyObject *)self);
}

static void
heapmanaged_dealloc(HeapCTypeObject *self)
{
PyTypeObject *tp = Py_TYPE(self);
_PyObject_ClearManagedDict((PyObject *)self);
PyObject_GC_UnTrack(self);
PyObject_GC_Del(self);
Py_DECREF(tp);
}
Comment thread
markshannon marked this conversation as resolved.

static PyType_Slot HeapCTypeWithManagedDict_slots[] = {
{Py_tp_traverse, heapmanaged_traverse},
{Py_tp_getset, heapctypewithdict_getsetlist},
{Py_tp_clear, heapmanaged_clear},
{Py_tp_dealloc, heapmanaged_dealloc},
{0, 0},
};

static PyType_Spec HeapCTypeWithManagedDict_spec = {
"_testcapi.HeapCTypeWithManagedDict",
sizeof(PyObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_MANAGED_DICT,
HeapCTypeWithManagedDict_slots
};

static struct PyMemberDef heapctypewithnegativedict_members[] = {
{"dictobj", T_OBJECT, offsetof(HeapCTypeWithDictObject, dict)},
{"__dictoffset__", T_PYSSIZET, -(Py_ssize_t)sizeof(void*), READONLY},
Expand Down Expand Up @@ -963,6 +1002,12 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
}
PyModule_AddObject(m, "HeapCTypeWithNegativeDict", HeapCTypeWithNegativeDict);

PyObject *HeapCTypeWithManagedDict = PyType_FromSpec(&HeapCTypeWithManagedDict_spec);
if (HeapCTypeWithManagedDict == NULL) {
return -1;
}
PyModule_AddObject(m, "HeapCTypeWithManagedDict", HeapCTypeWithManagedDict);

PyObject *HeapCTypeWithWeakref = PyType_FromSpec(&HeapCTypeWithWeakref_spec);
if (HeapCTypeWithWeakref == NULL) {
return -1;
Expand Down
1 change: 1 addition & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,7 @@ _PyObject_ComputedDictPointer(PyObject *obj)
if (dictoffset == 0)
return NULL;
if (dictoffset < 0) {
assert(dictoffset != -1);
Py_ssize_t tsize = Py_SIZE(obj);
if (tsize < 0) {
tsize = -tsize;
Expand Down
70 changes: 53 additions & 17 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1312,16 +1312,21 @@ subtype_traverse(PyObject *self, visitproc visit, void *arg)
assert(base);
}

if (type->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
int err = _PyObject_VisitManagedDict(self, visit, arg);
if (err) {
return err;
if (type->tp_dictoffset != base->tp_dictoffset) {
assert(base->tp_dictoffset == 0);
Comment thread
markshannon marked this conversation as resolved.
if (type->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
assert(type->tp_dictoffset == -1);
int err = _PyObject_VisitManagedDict(self, visit, arg);
if (err) {
return err;
}
}
else {
PyObject **dictptr = _PyObject_ComputedDictPointer(self);
if (dictptr && *dictptr) {
Py_VISIT(*dictptr);
}
}
}
else if (type->tp_dictoffset != base->tp_dictoffset) {
PyObject **dictptr = _PyObject_ComputedDictPointer(self);
if (dictptr && *dictptr)
Py_VISIT(*dictptr);
}

if (type->tp_flags & Py_TPFLAGS_HEAPTYPE
Expand Down Expand Up @@ -1380,7 +1385,9 @@ subtype_clear(PyObject *self)
/* Clear the instance dict (if any), to break cycles involving only
__dict__ slots (as in the case 'self.__dict__ is self'). */
if (type->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
_PyObject_ClearManagedDict(self);
if ((base->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
_PyObject_ClearManagedDict(self);
}
}
else if (type->tp_dictoffset != base->tp_dictoffset) {
PyObject **dictptr = _PyObject_ComputedDictPointer(self);
Expand Down Expand Up @@ -3085,20 +3092,15 @@ type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type)
}
}

if (ctx->add_dict && ctx->base->tp_itemsize) {
type->tp_dictoffset = -(long)sizeof(PyObject *);
slotoffset += sizeof(PyObject *);
}

if (ctx->add_weak) {
assert(!ctx->base->tp_itemsize);
type->tp_weaklistoffset = slotoffset;
slotoffset += sizeof(PyObject *);
}
if (ctx->add_dict && ctx->base->tp_itemsize == 0) {
if (ctx->add_dict) {
assert((type->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0);
type->tp_flags |= Py_TPFLAGS_MANAGED_DICT;
type->tp_dictoffset = -slotoffset - sizeof(PyObject *)*3;
type->tp_dictoffset = -1;
}
Comment thread
ericsnowcurrently marked this conversation as resolved.

type->tp_basicsize = slotoffset;
Expand Down Expand Up @@ -6161,6 +6163,7 @@ inherit_special(PyTypeObject *type, PyTypeObject *base)
COPYVAL(tp_itemsize);
COPYVAL(tp_weaklistoffset);
COPYVAL(tp_dictoffset);

#undef COPYVAL

/* Setup fast subclass flags */
Expand Down Expand Up @@ -6567,6 +6570,21 @@ type_ready_fill_dict(PyTypeObject *type)
return 0;
}

static int
type_ready_dict_offset(PyTypeObject *type)
{
if (type->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
if (type->tp_dictoffset > 0 || type->tp_dictoffset < -1) {
PyErr_Format(PyExc_TypeError,
"type %s has the Py_TPFLAGS_MANAGED_DICT flag "
"but tp_dictoffset is set",
type->tp_name);
return -1;
}
type->tp_dictoffset = -1;
}
return 0;
}

static int
type_ready_mro(PyTypeObject *type)
Expand Down Expand Up @@ -6775,6 +6793,21 @@ type_ready_post_checks(PyTypeObject *type)
type->tp_name);
return -1;
}
if (type->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
if (type->tp_dictoffset != -1) {
PyErr_Format(PyExc_SystemError,
"type %s has the Py_TPFLAGS_MANAGED_DICT flag "
"but tp_dictoffset is set to incompatible value",
type->tp_name);
return -1;
}
}
else if (type->tp_dictoffset < sizeof(PyObject)) {
if (type->tp_dictoffset + type->tp_basicsize <= 0) {
PyErr_Format(PyExc_SystemError,
"type %s has a tp_dictoffset that is too small");
}
Comment thread
markshannon marked this conversation as resolved.
}
return 0;
}

Expand Down Expand Up @@ -6814,6 +6847,9 @@ type_ready(PyTypeObject *type)
if (type_ready_inherit(type) < 0) {
return -1;
}
if (type_ready_dict_offset(type) < 0) {
return -1;
}
if (type_ready_set_hash(type) < 0) {
return -1;
}
Expand Down
18 changes: 11 additions & 7 deletions Tools/gdb/libpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,13 +478,17 @@ def get_attr_dict(self):
dictoffset = int_from_int(typeobj.field('tp_dictoffset'))
if dictoffset != 0:
if dictoffset < 0:
type_PyVarObject_ptr = gdb.lookup_type('PyVarObject').pointer()
tsize = int_from_int(self._gdbval.cast(type_PyVarObject_ptr)['ob_size'])
if tsize < 0:
tsize = -tsize
size = _PyObject_VAR_SIZE(typeobj, tsize)
dictoffset += size
assert dictoffset % _sizeof_void_p() == 0
if int_from_int(typeobj.field('tp_flags')) & Py_TPFLAGS_MANAGED_DICT:
assert dictoffset == -1
dictoffset = -3 * _sizeof_void_p()
Comment thread
markshannon marked this conversation as resolved.
else:
type_PyVarObject_ptr = gdb.lookup_type('PyVarObject').pointer()
tsize = int_from_int(self._gdbval.cast(type_PyVarObject_ptr)['ob_size'])
if tsize < 0:
tsize = -tsize
size = _PyObject_VAR_SIZE(typeobj, tsize)
dictoffset += size
assert dictoffset % _sizeof_void_p() == 0

dictptr = self._gdbval.cast(_type_char_ptr()) + dictoffset
PyObjectPtrPtr = PyObjectPtr.get_gdb_type().pointer()
Expand Down