Skip to content

Commit 7a6ed9f

Browse files
committed
Maintain type version invariants.
1 parent e8752d7 commit 7a6ed9f

File tree

3 files changed

+44
-21
lines changed

3 files changed

+44
-21
lines changed

Lib/test/test_type_cache.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,10 @@ def _assign_valid_version_or_skip(self, type_):
106106
if type_get_version(type_) == 0:
107107
self.skipTest("Could not assign valid type version")
108108

109-
def _assign_and_check_version_0(self, user_type):
109+
def _no_more_versions(self, user_type):
110110
type_modified(user_type)
111+
for _ in range(1001):
112+
type_assign_specific_version_unsafe(user_type, 1000_000_000)
111113
type_assign_specific_version_unsafe(user_type, 0)
112114
self.assertEqual(type_get_version(user_type), 0)
113115

@@ -136,7 +138,7 @@ def load_foo_1(type_):
136138
self._check_specialization(load_foo_1, A, "LOAD_ATTR", should_specialize=True)
137139
del load_foo_1
138140

139-
self._assign_and_check_version_0(A)
141+
self._no_more_versions(A)
140142

141143
def load_foo_2(type_):
142144
return type_.foo
@@ -187,7 +189,7 @@ def load_x_1(instance):
187189
self._check_specialization(load_x_1, G(), "LOAD_ATTR", should_specialize=True)
188190
del load_x_1
189191

190-
self._assign_and_check_version_0(G)
192+
self._no_more_versions(G)
191193

192194
def load_x_2(instance):
193195
instance.x
@@ -206,7 +208,7 @@ def store_bar_1(type_):
206208
self._check_specialization(store_bar_1, B(), "STORE_ATTR", should_specialize=True)
207209
del store_bar_1
208210

209-
self._assign_and_check_version_0(B)
211+
self._no_more_versions(B)
210212

211213
def store_bar_2(type_):
212214
type_.bar = 10
@@ -226,7 +228,7 @@ def call_class_1(type_):
226228
self._check_specialization(call_class_1, F, "CALL", should_specialize=True)
227229
del call_class_1
228230

229-
self._assign_and_check_version_0(F)
231+
self._no_more_versions(F)
230232

231233
def call_class_2(type_):
232234
type_()
@@ -245,7 +247,7 @@ def to_bool_1(instance):
245247
self._check_specialization(to_bool_1, H(), "TO_BOOL", should_specialize=True)
246248
del to_bool_1
247249

248-
self._assign_and_check_version_0(H)
250+
self._no_more_versions(H)
249251

250252
def to_bool_2(instance):
251253
not instance

Modules/_testinternalcapi.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2014,7 +2014,6 @@ type_assign_specific_version_unsafe(PyObject *self, PyObject *args)
20142014
}
20152015
assert(!PyType_HasFeature(type, Py_TPFLAGS_IMMUTABLETYPE));
20162016
_PyType_SetVersion(type, version);
2017-
type->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG;
20182017
Py_RETURN_NONE;
20192018
}
20202019

Objects/typeobject.c

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,19 @@ PyType_ClearWatcher(int watcher_id)
936936
return 0;
937937
}
938938

939+
#ifndef NDEBUG
940+
/* Returns true if tp_flags and tp_version_tag are consistent */
941+
static bool version_tag_consistency(PyTypeObject *type)
942+
{
943+
if (_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
944+
return type->tp_version_tag != 0;
945+
}
946+
else {
947+
return type->tp_version_tag == 0;
948+
}
949+
}
950+
#endif
951+
939952
static int assign_version_tag(PyInterpreterState *interp, PyTypeObject *type);
940953

941954
int
@@ -952,6 +965,7 @@ PyType_Watch(int watcher_id, PyObject* obj)
952965
}
953966
// ensure we will get a callback on the next modification
954967
BEGIN_TYPE_LOCK()
968+
assert(version_tag_consistency(type));
955969
assign_version_tag(interp, type);
956970
type->tp_watched |= (1 << watcher_id);
957971
END_TYPE_LOCK()
@@ -1070,7 +1084,6 @@ type_modified_unlocked(PyTypeObject *type)
10701084
}
10711085
}
10721086

1073-
type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
10741087
_PyType_SetVersion(type, 0); /* 0 is not a valid version tag */
10751088
if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
10761089
// This field *must* be invalidated if the type is modified (see the
@@ -1082,6 +1095,7 @@ type_modified_unlocked(PyTypeObject *type)
10821095
void
10831096
PyType_Modified(PyTypeObject *type)
10841097
{
1098+
assert(version_tag_consistency(type));
10851099
// Quick check without the lock held
10861100
if (!_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
10871101
return;
@@ -1147,7 +1161,6 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
11471161

11481162
clear:
11491163
assert(!(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN));
1150-
type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
11511164
_PyType_SetVersion(type, 0); /* 0 is not a valid version tag */
11521165
if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
11531166
// This field *must* be invalidated if the type is modified (see the
@@ -1168,6 +1181,7 @@ This is similar to func_version_cache.
11681181
void
11691182
_PyType_SetVersion(PyTypeObject *tp, unsigned int version)
11701183
{
1184+
assert(version_tag_consistency(tp));
11711185
#ifndef Py_GIL_DISABLED
11721186
PyInterpreterState *interp = _PyInterpreterState_GET();
11731187
// lookup the old version and set to null
@@ -1178,6 +1192,13 @@ _PyType_SetVersion(PyTypeObject *tp, unsigned int version)
11781192
*slot = NULL;
11791193
}
11801194
#endif
1195+
if (version) {
1196+
tp->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG;
1197+
tp->tp_versions_used++;
1198+
}
1199+
else {
1200+
tp->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
1201+
}
11811202
FT_ATOMIC_STORE_UINT32_RELAXED(tp->tp_version_tag, version);
11821203
#ifndef Py_GIL_DISABLED
11831204
if (version != 0) {
@@ -1219,6 +1240,7 @@ _PyType_GetVersionForCurrentState(PyTypeObject *tp)
12191240
static int
12201241
assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
12211242
{
1243+
assert(version_tag_consistency(type));
12221244
ASSERT_TYPE_LOCK_HELD();
12231245

12241246
/* Ensure that the tp_version_tag is valid and set
@@ -1235,7 +1257,14 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
12351257
if (type->tp_versions_used >= MAX_VERSIONS_PER_CLASS) {
12361258
return 0;
12371259
}
1238-
type->tp_versions_used++;
1260+
1261+
PyObject *bases = lookup_tp_bases(type);
1262+
Py_ssize_t n = PyTuple_GET_SIZE(bases);
1263+
for (Py_ssize_t i = 0; i < n; i++) {
1264+
PyObject *b = PyTuple_GET_ITEM(bases, i);
1265+
if (!assign_version_tag(interp, _PyType_CAST(b)))
1266+
return 0;
1267+
}
12391268
if (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) {
12401269
/* static types */
12411270
if (NEXT_GLOBAL_VERSION_TAG > _Py_MAX_GLOBAL_TYPE_VERSION_TAG) {
@@ -1254,15 +1283,7 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
12541283
_PyType_SetVersion(type, NEXT_VERSION_TAG(interp)++);
12551284
assert (type->tp_version_tag != 0);
12561285
}
1257-
1258-
PyObject *bases = lookup_tp_bases(type);
1259-
Py_ssize_t n = PyTuple_GET_SIZE(bases);
1260-
for (Py_ssize_t i = 0; i < n; i++) {
1261-
PyObject *b = PyTuple_GET_ITEM(bases, i);
1262-
if (!assign_version_tag(interp, _PyType_CAST(b)))
1263-
return 0;
1264-
}
1265-
type->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG;
1286+
assert(version_tag_consistency(type));
12661287
return 1;
12671288
}
12681289

@@ -3284,6 +3305,7 @@ static int
32843305
mro_internal_unlocked(PyTypeObject *type, int initial, PyObject **p_old_mro)
32853306
{
32863307
ASSERT_TYPE_LOCK_HELD();
3308+
assert(version_tag_consistency(type));
32873309

32883310
PyObject *new_mro, *old_mro;
32893311
int reent;
@@ -5432,6 +5454,7 @@ _PyType_LookupRef(PyTypeObject *type, PyObject *name)
54325454
unsigned int h = MCACHE_HASH_METHOD(type, name);
54335455
struct type_cache *cache = get_type_cache();
54345456
struct type_cache_entry *entry = &cache->hashtable[h];
5457+
assert(version_tag_consistency(type));
54355458
#ifdef Py_GIL_DISABLED
54365459
// synchronize-with other writing threads by doing an acquire load on the sequence
54375460
while (1) {
@@ -5898,7 +5921,6 @@ fini_static_type(PyInterpreterState *interp, PyTypeObject *type,
58985921

58995922
if (final) {
59005923
type->tp_flags &= ~Py_TPFLAGS_READY;
5901-
type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
59025924
_PyType_SetVersion(type, 0);
59035925
}
59045926

@@ -8516,11 +8538,11 @@ init_static_type(PyInterpreterState *interp, PyTypeObject *self,
85168538

85178539
assert(NEXT_GLOBAL_VERSION_TAG <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG);
85188540
_PyType_SetVersion(self, NEXT_GLOBAL_VERSION_TAG++);
8519-
self->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG;
85208541
}
85218542
else {
85228543
assert(!initial);
85238544
assert(self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN);
8545+
assert(self->tp_version_tag != 0);
85248546
assert(self->tp_flags & Py_TPFLAGS_VALID_VERSION_TAG);
85258547
}
85268548

0 commit comments

Comments
 (0)