Skip to content

Commit 1d7a635

Browse files
committed
Set large tp_versions_used when type attribute cache should be disabled
1 parent 6653d17 commit 1d7a635

File tree

3 files changed

+22
-2
lines changed

3 files changed

+22
-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. */
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 MRO.

Objects/typeobject.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,7 @@ static void
992992
set_version_unlocked(PyTypeObject *tp, unsigned int version)
993993
{
994994
ASSERT_TYPE_LOCK_HELD();
995+
assert(version == 0 || (tp->tp_versions_used != _Py_ATTR_CACHE_UNUSED));
995996
#ifndef Py_GIL_DISABLED
996997
PyInterpreterState *interp = _PyInterpreterState_GET();
997998
// lookup the old version and set to null
@@ -1148,6 +1149,10 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
11481149
PyObject *b = PyTuple_GET_ITEM(bases, i);
11491150
PyTypeObject *cls = _PyType_CAST(b);
11501151

1152+
if (cls->tp_versions_used >= _Py_ATTR_CACHE_UNUSED) {
1153+
goto clear;
1154+
}
1155+
11511156
if (!is_subtype_with_mro(lookup_tp_mro(type), type, cls)) {
11521157
goto clear;
11531158
}
@@ -1156,7 +1161,8 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
11561161

11571162
clear:
11581163
assert(!(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN));
1159-
set_version_unlocked(type, 0); /* 0 is not a valid version tag */
1164+
set_version_unlocked(type, 0); /* 0 is not a valid version tag */
1165+
type->tp_versions_used = _Py_ATTR_CACHE_UNUSED;
11601166
if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
11611167
// This field *must* be invalidated if the type is modified (see the
11621168
// comment on struct _specialization_cache):
@@ -1208,6 +1214,8 @@ _PyType_GetVersionForCurrentState(PyTypeObject *tp)
12081214

12091215

12101216
#define MAX_VERSIONS_PER_CLASS 1000
1217+
_Static_assert(MAX_VERSIONS_PER_CLASS < _Py_ATTR_CACHE_UNUSED,
1218+
"_Py_ATTR_CACHE_UNUSED must be bigger than max");
12111219

12121220
static int
12131221
assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
@@ -1225,6 +1233,7 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
12251233
return 0;
12261234
}
12271235
if (type->tp_versions_used >= MAX_VERSIONS_PER_CLASS) {
1236+
/* (this includes `tp_versions_used == _Py_ATTR_CACHE_UNUSED`) */
12281237
return 0;
12291238
}
12301239

0 commit comments

Comments
 (0)