mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
gh-127773: Disable attribute cache on incompatible MRO entries (GH-127924)
This commit is contained in:
parent
76ffaef729
commit
aa6579cb60
4 changed files with 50 additions and 2 deletions
|
@ -221,7 +221,9 @@ struct _typeobject {
|
|||
PyObject *tp_weaklist; /* not used for static builtin types */
|
||||
destructor tp_del;
|
||||
|
||||
/* Type attribute cache version tag. Added in version 2.6 */
|
||||
/* Type attribute cache version tag. Added in version 2.6.
|
||||
* If zero, the cache is invalid and must be initialized.
|
||||
*/
|
||||
unsigned int tp_version_tag;
|
||||
|
||||
destructor tp_finalize;
|
||||
|
@ -229,9 +231,17 @@ struct _typeobject {
|
|||
|
||||
/* bitset of which type-watchers care about this type */
|
||||
unsigned char tp_watched;
|
||||
|
||||
/* Number of tp_version_tag values used.
|
||||
* Set to _Py_ATTR_CACHE_UNUSED if the attribute cache is
|
||||
* disabled for this type (e.g. due to custom MRO entries).
|
||||
* Otherwise, limited to MAX_VERSIONS_PER_CLASS (defined elsewhere).
|
||||
*/
|
||||
uint16_t tp_versions_used;
|
||||
};
|
||||
|
||||
#define _Py_ATTR_CACHE_UNUSED (30000) // (see tp_versions_used)
|
||||
|
||||
/* This struct is used by the specializer
|
||||
* It should be treated as an opaque blob
|
||||
* by code other than the specializer and interpreter. */
|
||||
|
|
|
@ -254,6 +254,33 @@ Test failures in looking up the __prepare__ method work.
|
|||
[...]
|
||||
test.test_metaclass.ObscureException
|
||||
|
||||
Test setting attributes with a non-base type in mro() (gh-127773).
|
||||
|
||||
>>> class Base:
|
||||
... value = 1
|
||||
...
|
||||
>>> class Meta(type):
|
||||
... def mro(cls):
|
||||
... return (cls, Base, object)
|
||||
...
|
||||
>>> class WeirdClass(metaclass=Meta):
|
||||
... pass
|
||||
...
|
||||
>>> Base.value
|
||||
1
|
||||
>>> WeirdClass.value
|
||||
1
|
||||
>>> Base.value = 2
|
||||
>>> Base.value
|
||||
2
|
||||
>>> WeirdClass.value
|
||||
2
|
||||
>>> Base.value = 3
|
||||
>>> Base.value
|
||||
3
|
||||
>>> WeirdClass.value
|
||||
3
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Do not use the type attribute cache for types with incompatible :term:`MRO`.
|
|
@ -992,6 +992,7 @@ static void
|
|||
set_version_unlocked(PyTypeObject *tp, unsigned int version)
|
||||
{
|
||||
ASSERT_TYPE_LOCK_HELD();
|
||||
assert(version == 0 || (tp->tp_versions_used != _Py_ATTR_CACHE_UNUSED));
|
||||
#ifndef Py_GIL_DISABLED
|
||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||
// lookup the old version and set to null
|
||||
|
@ -1148,6 +1149,10 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
|
|||
PyObject *b = PyTuple_GET_ITEM(bases, i);
|
||||
PyTypeObject *cls = _PyType_CAST(b);
|
||||
|
||||
if (cls->tp_versions_used >= _Py_ATTR_CACHE_UNUSED) {
|
||||
goto clear;
|
||||
}
|
||||
|
||||
if (!is_subtype_with_mro(lookup_tp_mro(type), type, cls)) {
|
||||
goto clear;
|
||||
}
|
||||
|
@ -1156,7 +1161,8 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
|
|||
|
||||
clear:
|
||||
assert(!(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN));
|
||||
set_version_unlocked(type, 0); /* 0 is not a valid version tag */
|
||||
set_version_unlocked(type, 0); /* 0 is not a valid version tag */
|
||||
type->tp_versions_used = _Py_ATTR_CACHE_UNUSED;
|
||||
if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
|
||||
// This field *must* be invalidated if the type is modified (see the
|
||||
// comment on struct _specialization_cache):
|
||||
|
@ -1208,6 +1214,9 @@ _PyType_GetVersionForCurrentState(PyTypeObject *tp)
|
|||
|
||||
|
||||
#define MAX_VERSIONS_PER_CLASS 1000
|
||||
#if _Py_ATTR_CACHE_UNUSED < MAX_VERSIONS_PER_CLASS
|
||||
#error "_Py_ATTR_CACHE_UNUSED must be bigger than max"
|
||||
#endif
|
||||
|
||||
static int
|
||||
assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
|
||||
|
@ -1225,6 +1234,7 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
|
|||
return 0;
|
||||
}
|
||||
if (type->tp_versions_used >= MAX_VERSIONS_PER_CLASS) {
|
||||
/* (this includes `tp_versions_used == _Py_ATTR_CACHE_UNUSED`) */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue