Skip to content

Commit

Permalink
GH-92678: Fix tp_dictoffset inheritance. (GH-95596)
Browse files Browse the repository at this point in the history
* Add test for inheriting explicit __dict__ and weakref.

* Restore 3.10 behavior for multiple inheritance of C extension classes that store their dictionary at the end of the struct.
  • Loading branch information
markshannon authored Aug 3, 2022
1 parent 89f5229 commit 906e450
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 3 deletions.
19 changes: 19 additions & 0 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,25 @@ def test_heaptype_with_custom_metaclass(self):
with self.assertRaisesRegex(TypeError, msg):
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclassCustomNew)

def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):

class Both1(_testcapi.HeapCTypeWithWeakref, _testcapi.HeapCTypeWithDict):
pass
class Both2(_testcapi.HeapCTypeWithDict, _testcapi.HeapCTypeWithWeakref):
pass

for cls in (_testcapi.HeapCTypeWithDict, _testcapi.HeapCTypeWithDict2,
_testcapi.HeapCTypeWithWeakref, _testcapi.HeapCTypeWithWeakref2):
for cls2 in (_testcapi.HeapCTypeWithDict, _testcapi.HeapCTypeWithDict2,
_testcapi.HeapCTypeWithWeakref, _testcapi.HeapCTypeWithWeakref2):
if cls is not cls2:
class S(cls, cls2):
pass
class B1(Both1, cls):
pass
class B2(Both1, cls):
pass

def test_pytype_fromspec_with_repeated_slots(self):
for variant in range(2):
with self.subTest(variant=variant):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Restore the 3.10 behavior for multiple inheritance of C extension classes
that store their dictionary at the end of the struct.
28 changes: 28 additions & 0 deletions Modules/_testcapi/heaptype.c
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,14 @@ static PyType_Spec HeapCTypeWithDict_spec = {
HeapCTypeWithDict_slots
};

static PyType_Spec HeapCTypeWithDict2_spec = {
"_testcapi.HeapCTypeWithDict2",
sizeof(HeapCTypeWithDictObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
HeapCTypeWithDict_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 @@ -796,6 +804,14 @@ static PyType_Spec HeapCTypeWithWeakref_spec = {
HeapCTypeWithWeakref_slots
};

static PyType_Spec HeapCTypeWithWeakref2_spec = {
"_testcapi.HeapCTypeWithWeakref2",
sizeof(HeapCTypeWithWeakrefObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
HeapCTypeWithWeakref_slots
};

PyDoc_STRVAR(heapctypesetattr__doc__,
"A heap type without GC, but with overridden __setattr__.\n\n"
"The 'value' attribute is set to 10 in __init__ and updated via attribute setting.");
Expand Down Expand Up @@ -919,6 +935,12 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
}
PyModule_AddObject(m, "HeapCTypeWithDict", HeapCTypeWithDict);

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

PyObject *HeapCTypeWithNegativeDict = PyType_FromSpec(&HeapCTypeWithNegativeDict_spec);
if (HeapCTypeWithNegativeDict == NULL) {
return -1;
Expand All @@ -931,6 +953,12 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
}
PyModule_AddObject(m, "HeapCTypeWithWeakref", HeapCTypeWithWeakref);

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

PyObject *HeapCTypeWithBuffer = PyType_FromSpec(&HeapCTypeWithBuffer_spec);
if (HeapCTypeWithBuffer == NULL) {
return -1;
Expand Down
19 changes: 16 additions & 3 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2316,6 +2316,11 @@ best_base(PyObject *bases)
return base;
}

#define ADDED_FIELD_AT_OFFSET(name, offset) \
(type->tp_ ## name && (base->tp_ ##name == 0) && \
type->tp_ ## name + sizeof(PyObject *) == (offset) && \
type->tp_flags & Py_TPFLAGS_HEAPTYPE)

static int
extra_ivars(PyTypeObject *type, PyTypeObject *base)
{
Expand All @@ -2328,10 +2333,18 @@ extra_ivars(PyTypeObject *type, PyTypeObject *base)
return t_size != b_size ||
type->tp_itemsize != base->tp_itemsize;
}
if (type->tp_weaklistoffset && base->tp_weaklistoffset == 0 &&
type->tp_weaklistoffset + sizeof(PyObject *) == t_size &&
type->tp_flags & Py_TPFLAGS_HEAPTYPE)
/* Check for __dict__ and __weakrefs__ slots in either order */
if (ADDED_FIELD_AT_OFFSET(weaklistoffset, t_size)) {
t_size -= sizeof(PyObject *);
}
if ((type->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0 &&
ADDED_FIELD_AT_OFFSET(dictoffset, t_size)) {
t_size -= sizeof(PyObject *);
}
/* Check __weakrefs__ again, in case it precedes __dict__ */
if (ADDED_FIELD_AT_OFFSET(weaklistoffset, t_size)) {
t_size -= sizeof(PyObject *);
}
return t_size != b_size;
}

Expand Down

0 comments on commit 906e450

Please sign in to comment.