From 906e4509328917fe9951f85457897f6a841e0529 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 3 Aug 2022 18:56:24 +0100 Subject: [PATCH] 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. --- Lib/test/test_capi.py | 19 +++++++++++++ ...2-08-03-14-39-08.gh-issue-92678.ozFTEx.rst | 2 ++ Modules/_testcapi/heaptype.c | 28 +++++++++++++++++++ Objects/typeobject.c | 19 +++++++++++-- 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2022-08-03-14-39-08.gh-issue-92678.ozFTEx.rst diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index a88a17d3c55788..013229a6cdc97a 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -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): 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/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 9fb0051ca125e4..12889e825d5581 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -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}, @@ -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."); @@ -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; @@ -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; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index d33befc05d7ab8..1980fcbf9323fd 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -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) { @@ -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; }