GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — gh-92678: Fix MRO calculation for C extension types with manual __dict__ by Fidget-Spinner · Pull Request #95242 · python/cpython Conversation
|
|
What issue does this corresponds to?😅 I need a bit of context here to review it 😉 |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| if (!(type->tp_flags & Py_TPFLAGS_MANAGED_DICT) && | ||
| type->tp_dictoffset && base->tp_dictoffset == 0 && | ||
| type->tp_dictoffset + sizeof(PyObject *) == t_size && | ||
| type->tp_flags & Py_TPFLAGS_HEAPTYPE) { | ||
| t_size -= sizeof(PyObject *); | ||
| } |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This reintroduces the old code pre-3.11, but guards it with a check for not Py_TPFLAGS_MANAGED_DICT.
8319114#diff-1decebeef15f4e0b0ce106c665751ec55068d4d1d1825847925ad4f528b5b872L2254
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
The condition is a bit arcane. Could you capture it in a variable or use a macro or inline function so we can name what we are checking for? Currently is a bit difficult to decipher what happens when all of these are true or false
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
So funny thing is I also found the old code arcane and couldn't really understand it well. From my rough guesstimation
// The __dict__ isn't automatically managed by CPython.
if (!(type->tp_flags & Py_TPFLAGS_MANAGED_DICT) &&
// That a dictoffset is set. Meaning it's a manual __dict__.
// And the base class doesn't have a __dict__
type->tp_dictoffset && base->tp_dictoffset == 0 &&
// Huh?
type->tp_dictoffset + sizeof(PyObject *) == t_size &&
// Not a static type.
type->tp_flags & Py_TPFLAGS_HEAPTYPE) {
t_size -= sizeof(PyObject *);
}
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I think this is a really cursed example of Hyrum's law.
The extra_ivars code assumes that __weakreflist__ comes first then __dict__. In mypyc it has to follow this order to work pre-3.11. Now in 3.11 when we remove that assumption, all the C extensions using this with complicated MROs break 🤦 .
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Sorry. gh-92678 |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| type->tp_weaklistoffset + sizeof(PyObject *) == t_size && | ||
| type->tp_flags & Py_TPFLAGS_HEAPTYPE) | ||
| t_size -= sizeof(PyObject *); | ||
| if (!(type->tp_flags & Py_TPFLAGS_MANAGED_DICT) && |
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Is is correct to check Py_TPFLAGS_MANAGED_DICT during class creation?
It is set during class creation: https://github.com/python/cpython/blob/main/Objects/typeobject.c#L2989
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I think setting the flag happens before the MRO check. https://github.com/python/cpython/blob/main/Objects/typeobject.c#L3174-L3186
(type_new_descriptors then PyType_Ready ).
(PyType_Ready -> type_ready -> type_ready_mro)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
If you factor out the check into a macro, the code would be a bit clearer.
#define FIELD_AT_OFFSET(name, offset) type->tp_ ## name && base->tp_## name == 0 ...
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Reopened because this is an alternative fix. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This looks like it should fix the regression in #95589
Could you add some tests please?
You'll need to two classes each for tp_dictoffset and tp_weaklistoffset to the `_testcapi module then test for multiple inheritance of all pairs in test_capi.py
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| type->tp_weaklistoffset + sizeof(PyObject *) == t_size && | ||
| type->tp_flags & Py_TPFLAGS_HEAPTYPE) | ||
| t_size -= sizeof(PyObject *); | ||
| if (!(type->tp_flags & Py_TPFLAGS_MANAGED_DICT) && |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
If you factor out the check into a macro, the code would be a bit clearer.
#define FIELD_AT_OFFSET(name, offset) type->tp_ ## name && base->tp_## name == 0 ...
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| type->tp_weaklistoffset + sizeof(PyObject *) == t_size && | ||
| type->tp_flags & Py_TPFLAGS_HEAPTYPE) | ||
| t_size -= sizeof(PyObject *); | ||
| if (!(type->tp_flags & Py_TPFLAGS_MANAGED_DICT) && |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
We need to check for tp_dictoffset and tp_weaklistoffset, and shouldn't presume the order.
So we need to check for weakrefs, then dict, then weakrefs again (or vice versa).
if (FIELD_AT_OFFSET(dictoffset, t_size)) { t_size -= sizeof(PyObject *); } if (FIELD_AT_OFFSET(weaklistoffset, t_size)) { t_size -= sizeof(PyObject *); } if (FIELD_AT_OFFSET(dictoffset, t_size)) { t_size -= sizeof(PyObject *); }
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@Fidget-Spinner |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.