summaryrefslogtreecommitdiffstats
path: root/Objects
diff options
context:
space:
mode:
authorMark Shannon <mark@hotpy.org>2024-06-20 14:09:32 (GMT)
committerGitHub <noreply@github.com>2024-06-20 14:09:32 (GMT)
commitb8fd80f91b980598cb378dba224cdb595b132fb4 (patch)
tree0a6ad47a84ca82f94cefeb2a8a5f4d5edb654458 /Objects
parentd0a5e40f01c9e05100fb2548613805653fb71864 (diff)
downloadcpython-b8fd80f91b980598cb378dba224cdb595b132fb4.zip
cpython-b8fd80f91b980598cb378dba224cdb595b132fb4.tar.gz
cpython-b8fd80f91b980598cb378dba224cdb595b132fb4.tar.bz2
[3.13] GH-119462: Enforce invariants of type versioning. Backport of GH-120731. (#120748)
* Remove uses of Py_TPFLAGS_VALID_VERSION_TAG
Diffstat (limited to 'Objects')
-rw-r--r--Objects/typeobject.c111
1 files changed, 36 insertions, 75 deletions
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 5bf2bc2..7c11d87 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -973,43 +973,21 @@ 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
+ if (version) {
+ tp->tp_versions_used++;
}
-
- 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);
- }
+#else
+ if (version) {
+ _Py_atomic_add_uint16(&tp->tp_versions_used, 1);
}
-
- /* 0 is not a valid version tag */
- _Py_atomic_store_uint32_release(&type->tp_version_tag, 0);
-}
-
#endif
+ FT_ATOMIC_STORE_UINT32_RELAXED(tp->tp_version_tag, version);
+}
static void
type_modified_unlocked(PyTypeObject *type)
@@ -1020,16 +998,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;
}
@@ -1069,8 +1047,7 @@ type_modified_unlocked(PyTypeObject *type)
}
}
- type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
- FT_ATOMIC_STORE_UINT32_RELAXED(type->tp_version_tag, 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):
@@ -1082,7 +1059,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;
}
@@ -1146,8 +1123,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;
- FT_ATOMIC_STORE_UINT32_RELAXED(type->tp_version_tag, 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):
@@ -1162,12 +1138,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)) {
@@ -1176,15 +1151,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;
}
- FT_ATOMIC_STORE_UINT32_RELAXED(type->tp_version_tag,
- NEXT_GLOBAL_VERSION_TAG++);
+ set_version_unlocked(type, NEXT_GLOBAL_VERSION_TAG++);
assert (type->tp_version_tag <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG);
}
else {
@@ -1193,19 +1175,9 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
/* We have run out of version numbers */
return 0;
}
- FT_ATOMIC_STORE_UINT32_RELAXED(type->tp_version_tag,
- 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;
}
@@ -3126,7 +3098,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)
@@ -5275,7 +5247,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);
@@ -5298,7 +5270,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()
@@ -5582,16 +5553,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:
@@ -5599,6 +5560,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);
@@ -5710,7 +5673,6 @@ fini_static_type(PyInterpreterState *interp, PyTypeObject *type,
if (final) {
type->tp_flags &= ~Py_TPFLAGS_READY;
- type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
type->tp_version_tag = 0;
}
@@ -8329,12 +8291,11 @@ init_static_type(PyInterpreterState *interp, PyTypeObject *self,
assert(NEXT_GLOBAL_VERSION_TAG <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG);
self->tp_version_tag = 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);