Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 53093f3

Browse files
committedAug 3, 2022
pythonGH-92678: Fix tp_dictoffset inheritance. (pythonGH-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 72cad6c commit 53093f3

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
@@ -608,6 +608,25 @@ def test_heaptype_with_setattro(self):
608608
del obj.value
609609
self.assertEqual(obj.pvalue, 0)
610610

611+
def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):
612+
613+
class Both1(_testcapi.HeapCTypeWithWeakref, _testcapi.HeapCTypeWithDict):
614+
pass
615+
class Both2(_testcapi.HeapCTypeWithDict, _testcapi.HeapCTypeWithWeakref):
616+
pass
617+
618+
for cls in (_testcapi.HeapCTypeWithDict, _testcapi.HeapCTypeWithDict2,
619+
_testcapi.HeapCTypeWithWeakref, _testcapi.HeapCTypeWithWeakref2):
620+
for cls2 in (_testcapi.HeapCTypeWithDict, _testcapi.HeapCTypeWithDict2,
621+
_testcapi.HeapCTypeWithWeakref, _testcapi.HeapCTypeWithWeakref2):
622+
if cls is not cls2:
623+
class S(cls, cls2):
624+
pass
625+
class B1(Both1, cls):
626+
pass
627+
class B2(Both1, cls):
628+
pass
629+
611630
def test_pynumber_tobase(self):
612631
from _testcapi import pynumber_tobase
613632
self.assertEqual(pynumber_tobase(123, 2), '0b1111011')
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/_testcapimodule.c

+28
Original file line numberDiff line numberDiff line change
@@ -7321,6 +7321,14 @@ static PyType_Spec HeapCTypeWithDict_spec = {
73217321
HeapCTypeWithDict_slots
73227322
};
73237323

7324+
static PyType_Spec HeapCTypeWithDict2_spec = {
7325+
"_testcapi.HeapCTypeWithDict2",
7326+
sizeof(HeapCTypeWithDictObject),
7327+
0,
7328+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
7329+
HeapCTypeWithDict_slots
7330+
};
7331+
73247332
static struct PyMemberDef heapctypewithnegativedict_members[] = {
73257333
{"dictobj", T_OBJECT, offsetof(HeapCTypeWithDictObject, dict)},
73267334
{"__dictoffset__", T_PYSSIZET, -(Py_ssize_t)sizeof(void*), READONLY},
@@ -7380,6 +7388,14 @@ static PyType_Spec HeapCTypeWithWeakref_spec = {
73807388
HeapCTypeWithWeakref_slots
73817389
};
73827390

7391+
static PyType_Spec HeapCTypeWithWeakref2_spec = {
7392+
"_testcapi.HeapCTypeWithWeakref2",
7393+
sizeof(HeapCTypeWithWeakrefObject),
7394+
0,
7395+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
7396+
HeapCTypeWithWeakref_slots
7397+
};
7398+
73837399
PyDoc_STRVAR(heapctypesetattr__doc__,
73847400
"A heap type without GC, but with overridden __setattr__.\n\n"
73857401
"The 'value' attribute is set to 10 in __init__ and updated via attribute setting.");
@@ -7757,6 +7773,12 @@ PyInit__testcapi(void)
77577773
}
77587774
PyModule_AddObject(m, "HeapCTypeWithDict", HeapCTypeWithDict);
77597775

7776+
PyObject *HeapCTypeWithDict2 = PyType_FromSpec(&HeapCTypeWithDict2_spec);
7777+
if (HeapCTypeWithDict2 == NULL) {
7778+
return -1;
7779+
}
7780+
PyModule_AddObject(m, "HeapCTypeWithDict2", HeapCTypeWithDict2);
7781+
77607782
PyObject *HeapCTypeWithNegativeDict = PyType_FromSpec(&HeapCTypeWithNegativeDict_spec);
77617783
if (HeapCTypeWithNegativeDict == NULL) {
77627784
return NULL;
@@ -7775,6 +7797,12 @@ PyInit__testcapi(void)
77757797
}
77767798
PyModule_AddObject(m, "HeapCTypeWithBuffer", HeapCTypeWithBuffer);
77777799

7800+
PyObject *HeapCTypeWithWeakref2 = PyType_FromSpec(&HeapCTypeWithWeakref2_spec);
7801+
if (HeapCTypeWithWeakref2 == NULL) {
7802+
return -1;
7803+
}
7804+
PyModule_AddObject(m, "HeapCTypeWithWeakref2", HeapCTypeWithWeakref2);
7805+
77787806
PyObject *HeapCTypeSetattr = PyType_FromSpec(&HeapCTypeSetattr_spec);
77797807
if (HeapCTypeSetattr == NULL) {
77807808
return NULL;

‎Objects/typeobject.c

+16-3
Original file line numberDiff line numberDiff line change
@@ -2224,6 +2224,11 @@ best_base(PyObject *bases)
22242224
return base;
22252225
}
22262226

2227+
#define ADDED_FIELD_AT_OFFSET(name, offset) \
2228+
(type->tp_ ## name && (base->tp_ ##name == 0) && \
2229+
type->tp_ ## name + sizeof(PyObject *) == (offset) && \
2230+
type->tp_flags & Py_TPFLAGS_HEAPTYPE)
2231+
22272232
static int
22282233
extra_ivars(PyTypeObject *type, PyTypeObject *base)
22292234
{
@@ -2236,10 +2241,18 @@ extra_ivars(PyTypeObject *type, PyTypeObject *base)
22362241
return t_size != b_size ||
22372242
type->tp_itemsize != base->tp_itemsize;
22382243
}
2239-
if (type->tp_weaklistoffset && base->tp_weaklistoffset == 0 &&
2240-
type->tp_weaklistoffset + sizeof(PyObject *) == t_size &&
2241-
type->tp_flags & Py_TPFLAGS_HEAPTYPE)
2244+
/* Check for __dict__ and __weakrefs__ slots in either order */
2245+
if (ADDED_FIELD_AT_OFFSET(weaklistoffset, t_size)) {
2246+
t_size -= sizeof(PyObject *);
2247+
}
2248+
if ((type->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0 &&
2249+
ADDED_FIELD_AT_OFFSET(dictoffset, t_size)) {
22422250
t_size -= sizeof(PyObject *);
2251+
}
2252+
/* Check __weakrefs__ again, in case it precedes __dict__ */
2253+
if (ADDED_FIELD_AT_OFFSET(weaklistoffset, t_size)) {
2254+
t_size -= sizeof(PyObject *);
2255+
}
22432256
return t_size != b_size;
22442257
}
22452258

0 commit comments

Comments
 (0)
Please sign in to comment.