diff options
author | Mark Shannon <mark@hotpy.org> | 2024-06-19 16:38:45 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-19 16:38:45 (GMT) |
commit | 00257c746c447a2e026b5a2a618f0e033fb90111 (patch) | |
tree | 7e30b04574aa0886d5bba242d4002840e2ad9e32 /Objects/typeobject.c | |
parent | f385d99f57773e48285e0bcdbcd66dcbfdc647b3 (diff) | |
download | cpython-00257c746c447a2e026b5a2a618f0e033fb90111.zip cpython-00257c746c447a2e026b5a2a618f0e033fb90111.tar.gz cpython-00257c746c447a2e026b5a2a618f0e033fb90111.tar.bz2 |
GH-119462: Enforce invariants of type versioning (GH-120731)
* Remove uses of Py_TPFLAGS_VALID_VERSION_TAG
Diffstat (limited to 'Objects/typeobject.c')
-rw-r--r-- | Objects/typeobject.c | 148 |
1 files changed, 56 insertions, 92 deletions
diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 0dcf1d3..1cc6ca7 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -974,43 +974,37 @@ PyType_Unwatch(int watcher_id, PyObject* obj) return 0; } -#ifdef Py_GIL_DISABLED - static void -type_modification_starting_unlocked(PyTypeObject *type) +set_version_unlocked(PyTypeObject *tp, unsigned int version) { ASSERT_TYPE_LOCK_HELD(); - - /* Clear version tags on all types, but leave the valid - version tag intact. This prepares for a modification so that - any concurrent readers of the type cache will not see invalid - values. - */ - if (!_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) { - return; +#ifndef Py_GIL_DISABLED + PyInterpreterState *interp = _PyInterpreterState_GET(); + // lookup the old version and set to null + if (tp->tp_version_tag != 0) { + PyTypeObject **slot = + interp->types.type_version_cache + + (tp->tp_version_tag % TYPE_VERSION_CACHE_SIZE); + *slot = NULL; } - - PyObject *subclasses = lookup_tp_subclasses(type); - if (subclasses != NULL) { - assert(PyDict_CheckExact(subclasses)); - - Py_ssize_t i = 0; - PyObject *ref; - while (PyDict_Next(subclasses, &i, NULL, &ref)) { - PyTypeObject *subclass = type_from_ref(ref); - if (subclass == NULL) { - continue; - } - type_modification_starting_unlocked(subclass); - Py_DECREF(subclass); - } + if (version) { + tp->tp_versions_used++; + } +#else + if (version) { + _Py_atomic_add_uint16(&tp->tp_versions_used, 1); } - - /* 0 is not a valid version tag */ - _PyType_SetVersion(type, 0); -} - #endif + FT_ATOMIC_STORE_UINT32_RELAXED(tp->tp_version_tag, version); +#ifndef Py_GIL_DISABLED + if (version != 0) { + PyTypeObject **slot = + interp->types.type_version_cache + + (version % TYPE_VERSION_CACHE_SIZE); + *slot = tp; + } +#endif +} static void type_modified_unlocked(PyTypeObject *type) @@ -1021,16 +1015,16 @@ type_modified_unlocked(PyTypeObject *type) Invariants: - - before Py_TPFLAGS_VALID_VERSION_TAG can be set on a type, + - before tp_version_tag can be set on a type, it must first be set on all super types. - This function clears the Py_TPFLAGS_VALID_VERSION_TAG of a + This function clears the tp_version_tag of a type (so it must first clear it on all subclasses). The - tp_version_tag value is meaningless unless this flag is set. + tp_version_tag value is meaningless when equal to zero. We don't assign new version tags eagerly, but only as needed. */ - if (!_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) { + if (type->tp_version_tag == 0) { return; } @@ -1070,8 +1064,7 @@ type_modified_unlocked(PyTypeObject *type) } } - type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG; - _PyType_SetVersion(type, 0); /* 0 is not a valid version tag */ + set_version_unlocked(type, 0); /* 0 is not a valid version tag */ if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { // This field *must* be invalidated if the type is modified (see the // comment on struct _specialization_cache): @@ -1083,7 +1076,7 @@ void PyType_Modified(PyTypeObject *type) { // Quick check without the lock held - if (!_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) { + if (type->tp_version_tag == 0) { return; } @@ -1147,8 +1140,7 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) { clear: assert(!(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)); - type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG; - _PyType_SetVersion(type, 0); /* 0 is not a valid version tag */ + set_version_unlocked(type, 0); /* 0 is not a valid version tag */ if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { // This field *must* be invalidated if the type is modified (see the // comment on struct _specialization_cache): @@ -1168,25 +1160,10 @@ This is similar to func_version_cache. void _PyType_SetVersion(PyTypeObject *tp, unsigned int version) { -#ifndef Py_GIL_DISABLED - PyInterpreterState *interp = _PyInterpreterState_GET(); - // lookup the old version and set to null - if (tp->tp_version_tag != 0) { - PyTypeObject **slot = - interp->types.type_version_cache - + (tp->tp_version_tag % TYPE_VERSION_CACHE_SIZE); - *slot = NULL; - } -#endif - FT_ATOMIC_STORE_UINT32_RELAXED(tp->tp_version_tag, version); -#ifndef Py_GIL_DISABLED - if (version != 0) { - PyTypeObject **slot = - interp->types.type_version_cache - + (version % TYPE_VERSION_CACHE_SIZE); - *slot = tp; - } -#endif + + BEGIN_TYPE_LOCK() + set_version_unlocked(tp, version); + END_TYPE_LOCK() } PyTypeObject * @@ -1221,12 +1198,11 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type) { ASSERT_TYPE_LOCK_HELD(); - /* Ensure that the tp_version_tag is valid and set - Py_TPFLAGS_VALID_VERSION_TAG. To respect the invariant, this - must first be done on all super classes. Return 0 if this - cannot be done, 1 if Py_TPFLAGS_VALID_VERSION_TAG. + /* Ensure that the tp_version_tag is valid. + * To respect the invariant, this must first be done on all super classes. + * Return 0 if this cannot be done, 1 if tp_version_tag is set. */ - if (_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) { + if (type->tp_version_tag != 0) { return 1; } if (!_PyType_HasFeature(type, Py_TPFLAGS_READY)) { @@ -1235,14 +1211,22 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type) if (type->tp_versions_used >= MAX_VERSIONS_PER_CLASS) { return 0; } - type->tp_versions_used++; + + PyObject *bases = lookup_tp_bases(type); + Py_ssize_t n = PyTuple_GET_SIZE(bases); + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *b = PyTuple_GET_ITEM(bases, i); + if (!assign_version_tag(interp, _PyType_CAST(b))) { + return 0; + } + } if (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) { /* static types */ if (NEXT_GLOBAL_VERSION_TAG > _Py_MAX_GLOBAL_TYPE_VERSION_TAG) { /* We have run out of version numbers */ return 0; } - _PyType_SetVersion(type, NEXT_GLOBAL_VERSION_TAG++); + set_version_unlocked(type, NEXT_GLOBAL_VERSION_TAG++); assert (type->tp_version_tag <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG); } else { @@ -1251,18 +1235,9 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type) /* We have run out of version numbers */ return 0; } - _PyType_SetVersion(type, NEXT_VERSION_TAG(interp)++); + set_version_unlocked(type, NEXT_VERSION_TAG(interp)++); assert (type->tp_version_tag != 0); } - - PyObject *bases = lookup_tp_bases(type); - Py_ssize_t n = PyTuple_GET_SIZE(bases); - for (Py_ssize_t i = 0; i < n; i++) { - PyObject *b = PyTuple_GET_ITEM(bases, i); - if (!assign_version_tag(interp, _PyType_CAST(b))) - return 0; - } - type->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG; return 1; } @@ -3318,7 +3293,7 @@ mro_internal_unlocked(PyTypeObject *type, int initial, PyObject **p_old_mro) else { /* For static builtin types, this is only called during init before the method cache has been populated. */ - assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)); + assert(type->tp_version_tag); } if (p_old_mro != NULL) @@ -5463,7 +5438,7 @@ _PyType_LookupRef(PyTypeObject *type, PyObject *name) #else if (entry->version == type->tp_version_tag && entry->name == name) { - assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)); + assert(type->tp_version_tag); OBJECT_STAT_INC_COND(type_cache_hits, !is_dunder_name(name)); OBJECT_STAT_INC_COND(type_cache_dunder_hits, is_dunder_name(name)); Py_XINCREF(entry->value); @@ -5486,7 +5461,6 @@ _PyType_LookupRef(PyTypeObject *type, PyObject *name) if (MCACHE_CACHEABLE_NAME(name)) { has_version = assign_version_tag(interp, type); version = type->tp_version_tag; - assert(!has_version || _PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)); } END_TYPE_LOCK() @@ -5770,16 +5744,6 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value) return -1; } -#ifdef Py_GIL_DISABLED - // In free-threaded builds readers can race with the lock-free portion - // of the type cache and the assignment into the dict. We clear all of the - // versions initially so no readers will succeed in the lock-free case. - // They'll then block on the type lock until the update below is done. - type_modification_starting_unlocked(type); -#endif - - res = _PyDict_SetItem_LockHeld((PyDictObject *)dict, name, value); - /* Clear the VALID_VERSION flag of 'type' and all its subclasses. This could possibly be unified with the update_subclasses() recursion in update_slot(), but carefully: @@ -5787,6 +5751,8 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value) recursing into subclasses. */ type_modified_unlocked(type); + res = _PyDict_SetItem_LockHeld((PyDictObject *)dict, name, value); + if (res == 0) { if (is_dunder_name(name)) { res = update_slot(type, name); @@ -5898,7 +5864,6 @@ fini_static_type(PyInterpreterState *interp, PyTypeObject *type, if (final) { type->tp_flags &= ~Py_TPFLAGS_READY; - type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG; _PyType_SetVersion(type, 0); } @@ -8516,12 +8481,11 @@ init_static_type(PyInterpreterState *interp, PyTypeObject *self, assert(NEXT_GLOBAL_VERSION_TAG <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG); _PyType_SetVersion(self, NEXT_GLOBAL_VERSION_TAG++); - self->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG; } else { assert(!initial); assert(self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN); - assert(self->tp_flags & Py_TPFLAGS_VALID_VERSION_TAG); + assert(self->tp_version_tag != 0); } managed_static_type_state_init(interp, self, isbuiltin, initial); |