diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index fba78fa4439551..7d766ec0178750 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -608,6 +608,25 @@ def test_heaptype_with_setattro(self): del obj.value self.assertEqual(obj.pvalue, 0) + 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_pynumber_tobase(self): from _testcapi import pynumber_tobase self.assertEqual(pynumber_tobase(123, 2), '0b1111011') diff --git a/Misc/NEWS.d/next/C API/2022-08-03-14-39-08.gh-issue-92678.ozFTEx.rst b/Misc/NEWS.d/next/C API/2022-08-03-14-39-08.gh-issue-92678.ozFTEx.rst new file mode 100644 index 00000000000000..6bf3d4b1abbf5a --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-08-03-14-39-08.gh-issue-92678.ozFTEx.rst @@ -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. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 9022ee4d49a83d..a394f46471565b 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -7321,6 +7321,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}, @@ -7380,6 +7388,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."); @@ -7757,6 +7773,12 @@ PyInit__testcapi(void) } 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 NULL; @@ -7775,6 +7797,12 @@ PyInit__testcapi(void) } PyModule_AddObject(m, "HeapCTypeWithBuffer", HeapCTypeWithBuffer); + PyObject *HeapCTypeWithWeakref2 = PyType_FromSpec(&HeapCTypeWithWeakref2_spec); + if (HeapCTypeWithWeakref2 == NULL) { + return -1; + } + PyModule_AddObject(m, "HeapCTypeWithWeakref2", HeapCTypeWithWeakref2); + PyObject *HeapCTypeSetattr = PyType_FromSpec(&HeapCTypeSetattr_spec); if (HeapCTypeSetattr == NULL) { return NULL; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index c5e832df7af2ce..03cc72e742b285 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2224,6 +2224,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) { @@ -2236,10 +2241,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; }