Skip to content

Commit fdac87a

Browse files
authored
[3.13] gh-127773: Disable attribute cache on incompatible MRO entries (GH-127924) (GH-143729)
1 parent 01c1876 commit fdac87a

File tree

4 files changed

+50
-2
lines changed

4 files changed

+50
-2
lines changed

Include/cpython/object.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,17 +221,27 @@ struct _typeobject {
221221
PyObject *tp_weaklist; /* not used for static builtin types */
222222
destructor tp_del;
223223

224-
/* Type attribute cache version tag. Added in version 2.6 */
224+
/* Type attribute cache version tag. Added in version 2.6.
225+
* If zero, the cache is invalid and must be initialized.
226+
*/
225227
unsigned int tp_version_tag;
226228

227229
destructor tp_finalize;
228230
vectorcallfunc tp_vectorcall;
229231

230232
/* bitset of which type-watchers care about this type */
231233
unsigned char tp_watched;
234+
235+
/* Number of tp_version_tag values used.
236+
* Set to _Py_ATTR_CACHE_UNUSED if the attribute cache is
237+
* disabled for this type (e.g. due to custom MRO entries).
238+
* Otherwise, limited to MAX_VERSIONS_PER_CLASS (defined elsewhere).
239+
*/
232240
uint16_t tp_versions_used;
233241
};
234242

243+
#define _Py_ATTR_CACHE_UNUSED (30000) // (see tp_versions_used)
244+
235245
/* This struct is used by the specializer
236246
* It should be treated as an opaque blob
237247
* by code other than the specializer and interpreter. */

Lib/test/test_metaclass.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,33 @@
254254
[...]
255255
test.test_metaclass.ObscureException
256256
257+
Test setting attributes with a non-base type in mro() (gh-127773).
258+
259+
>>> class Base:
260+
... value = 1
261+
...
262+
>>> class Meta(type):
263+
... def mro(cls):
264+
... return (cls, Base, object)
265+
...
266+
>>> class WeirdClass(metaclass=Meta):
267+
... pass
268+
...
269+
>>> Base.value
270+
1
271+
>>> WeirdClass.value
272+
1
273+
>>> Base.value = 2
274+
>>> Base.value
275+
2
276+
>>> WeirdClass.value
277+
2
278+
>>> Base.value = 3
279+
>>> Base.value
280+
3
281+
>>> WeirdClass.value
282+
3
283+
257284
"""
258285

259286
import sys
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Do not use the type attribute cache for types with incompatible :term:`MRO`.

Objects/typeobject.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,7 @@ static void
967967
set_version_unlocked(PyTypeObject *tp, unsigned int version)
968968
{
969969
ASSERT_TYPE_LOCK_HELD();
970+
assert(version == 0 || (tp->tp_versions_used != _Py_ATTR_CACHE_UNUSED));
970971
#ifndef Py_GIL_DISABLED
971972
if (version) {
972973
tp->tp_versions_used++;
@@ -1109,6 +1110,10 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
11091110
PyObject *b = PyTuple_GET_ITEM(bases, i);
11101111
PyTypeObject *cls = _PyType_CAST(b);
11111112

1113+
if (cls->tp_versions_used >= _Py_ATTR_CACHE_UNUSED) {
1114+
goto clear;
1115+
}
1116+
11121117
if (!is_subtype_with_mro(lookup_tp_mro(type), type, cls)) {
11131118
goto clear;
11141119
}
@@ -1117,7 +1122,8 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
11171122

11181123
clear:
11191124
assert(!(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN));
1120-
set_version_unlocked(type, 0); /* 0 is not a valid version tag */
1125+
set_version_unlocked(type, 0); /* 0 is not a valid version tag */
1126+
type->tp_versions_used = _Py_ATTR_CACHE_UNUSED;
11211127
if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
11221128
// This field *must* be invalidated if the type is modified (see the
11231129
// comment on struct _specialization_cache):
@@ -1127,6 +1133,9 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
11271133
}
11281134

11291135
#define MAX_VERSIONS_PER_CLASS 1000
1136+
#if _Py_ATTR_CACHE_UNUSED < MAX_VERSIONS_PER_CLASS
1137+
#error "_Py_ATTR_CACHE_UNUSED must be bigger than max"
1138+
#endif
11301139

11311140
static int
11321141
assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
@@ -1144,6 +1153,7 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
11441153
return 0;
11451154
}
11461155
if (type->tp_versions_used >= MAX_VERSIONS_PER_CLASS) {
1156+
/* (this includes `tp_versions_used == _Py_ATTR_CACHE_UNUSED`) */
11471157
return 0;
11481158
}
11491159

0 commit comments

Comments
 (0)