Skip to content

Commit 906e450

Browse files
authored
GH-92678: Fix tp_dictoffset inheritance. (GH-95596)
* 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.
1 parent 89f5229 commit 906e450

File tree

4 files changed

+65
-3
lines changed

4 files changed

+65
-3
lines changed

Lib/test/test_capi.py

+19
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,25 @@ def test_heaptype_with_custom_metaclass(self):
619619
with self.assertRaisesRegex(TypeError, msg):
620620
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclassCustomNew)
621621

622+
def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):
623+
624+
class Both1(_testcapi.HeapCTypeWithWeakref, _testcapi.HeapCTypeWithDict):
625+
pass
626+
class Both2(_testcapi.HeapCTypeWithDict, _testcapi.HeapCTypeWithWeakref):
627+
pass
628+
629+
for cls in (_testcapi.HeapCTypeWithDict, _testcapi.HeapCTypeWithDict2,
630+
_testcapi.HeapCTypeWithWeakref, _testcapi.HeapCTypeWithWeakref2):
631+
for cls2 in (_testcapi.HeapCTypeWithDict, _testcapi.HeapCTypeWithDict2,
632+
_testcapi.HeapCTypeWithWeakref, _testcapi.HeapCTypeWithWeakref2):
633+
if cls is not cls2:
634+
class S(cls, cls2):
635+
pass
636+
class B1(Both1, cls):
637+
pass
638+
class B2(Both1, cls):
639+
pass
640+
622641
def test_pytype_fromspec_with_repeated_slots(self):
623642
for variant in range(2):
624643
with self.subTest(variant=variant):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Restore the 3.10 behavior for multiple inheritance of C extension classes
2+
that store their dictionary at the end of the struct.

Modules/_testcapi/heaptype.c

+28
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,14 @@ static PyType_Spec HeapCTypeWithDict_spec = {
737737
HeapCTypeWithDict_slots
738738
};
739739

740+
static PyType_Spec HeapCTypeWithDict2_spec = {
741+
"_testcapi.HeapCTypeWithDict2",
742+
sizeof(HeapCTypeWithDictObject),
743+
0,
744+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
745+
HeapCTypeWithDict_slots
746+
};
747+
740748
static struct PyMemberDef heapctypewithnegativedict_members[] = {
741749
{"dictobj", T_OBJECT, offsetof(HeapCTypeWithDictObject, dict)},
742750
{"__dictoffset__", T_PYSSIZET, -(Py_ssize_t)sizeof(void*), READONLY},
@@ -796,6 +804,14 @@ static PyType_Spec HeapCTypeWithWeakref_spec = {
796804
HeapCTypeWithWeakref_slots
797805
};
798806

807+
static PyType_Spec HeapCTypeWithWeakref2_spec = {
808+
"_testcapi.HeapCTypeWithWeakref2",
809+
sizeof(HeapCTypeWithWeakrefObject),
810+
0,
811+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
812+
HeapCTypeWithWeakref_slots
813+
};
814+
799815
PyDoc_STRVAR(heapctypesetattr__doc__,
800816
"A heap type without GC, but with overridden __setattr__.\n\n"
801817
"The 'value' attribute is set to 10 in __init__ and updated via attribute setting.");
@@ -919,6 +935,12 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
919935
}
920936
PyModule_AddObject(m, "HeapCTypeWithDict", HeapCTypeWithDict);
921937

938+
PyObject *HeapCTypeWithDict2 = PyType_FromSpec(&HeapCTypeWithDict2_spec);
939+
if (HeapCTypeWithDict2 == NULL) {
940+
return -1;
941+
}
942+
PyModule_AddObject(m, "HeapCTypeWithDict2", HeapCTypeWithDict2);
943+
922944
PyObject *HeapCTypeWithNegativeDict = PyType_FromSpec(&HeapCTypeWithNegativeDict_spec);
923945
if (HeapCTypeWithNegativeDict == NULL) {
924946
return -1;
@@ -931,6 +953,12 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
931953
}
932954
PyModule_AddObject(m, "HeapCTypeWithWeakref", HeapCTypeWithWeakref);
933955

956+
PyObject *HeapCTypeWithWeakref2 = PyType_FromSpec(&HeapCTypeWithWeakref2_spec);
957+
if (HeapCTypeWithWeakref2 == NULL) {
958+
return -1;
959+
}
960+
PyModule_AddObject(m, "HeapCTypeWithWeakref2", HeapCTypeWithWeakref2);
961+
934962
PyObject *HeapCTypeWithBuffer = PyType_FromSpec(&HeapCTypeWithBuffer_spec);
935963
if (HeapCTypeWithBuffer == NULL) {
936964
return -1;

Objects/typeobject.c

+16-3
Original file line numberDiff line numberDiff line change
@@ -2316,6 +2316,11 @@ best_base(PyObject *bases)
23162316
return base;
23172317
}
23182318

2319+
#define ADDED_FIELD_AT_OFFSET(name, offset) \
2320+
(type->tp_ ## name && (base->tp_ ##name == 0) && \
2321+
type->tp_ ## name + sizeof(PyObject *) == (offset) && \
2322+
type->tp_flags & Py_TPFLAGS_HEAPTYPE)
2323+
23192324
static int
23202325
extra_ivars(PyTypeObject *type, PyTypeObject *base)
23212326
{
@@ -2328,10 +2333,18 @@ extra_ivars(PyTypeObject *type, PyTypeObject *base)
23282333
return t_size != b_size ||
23292334
type->tp_itemsize != base->tp_itemsize;
23302335
}
2331-
if (type->tp_weaklistoffset && base->tp_weaklistoffset == 0 &&
2332-
type->tp_weaklistoffset + sizeof(PyObject *) == t_size &&
2333-
type->tp_flags & Py_TPFLAGS_HEAPTYPE)
2336+
/* Check for __dict__ and __weakrefs__ slots in either order */
2337+
if (ADDED_FIELD_AT_OFFSET(weaklistoffset, t_size)) {
2338+
t_size -= sizeof(PyObject *);
2339+
}
2340+
if ((type->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0 &&
2341+
ADDED_FIELD_AT_OFFSET(dictoffset, t_size)) {
23342342
t_size -= sizeof(PyObject *);
2343+
}
2344+
/* Check __weakrefs__ again, in case it precedes __dict__ */
2345+
if (ADDED_FIELD_AT_OFFSET(weaklistoffset, t_size)) {
2346+
t_size -= sizeof(PyObject *);
2347+
}
23352348
return t_size != b_size;
23362349
}
23372350

0 commit comments

Comments
 (0)