Skip to content

Commit 00257c7

Browse files
authored
GH-119462: Enforce invariants of type versioning (GH-120731)
* Remove uses of Py_TPFLAGS_VALID_VERSION_TAG
1 parent f385d99 commit 00257c7

File tree

7 files changed

+87
-103
lines changed

7 files changed

+87
-103
lines changed

Diff for: Doc/c-api/typeobj.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -1328,8 +1328,8 @@ and :c:data:`PyType_Type` effectively act as defaults.)
13281328
To indicate that a class has changed call :c:func:`PyType_Modified`
13291329

13301330
.. warning::
1331-
This flag is present in header files, but is an internal feature and should
1332-
not be used. It will be removed in a future version of CPython
1331+
This flag is present in header files, but is not be used.
1332+
It will be removed in a future version of CPython
13331333

13341334

13351335
.. c:member:: const char* PyTypeObject.tp_doc

Diff for: Include/object.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ given type object has a specified feature.
566566
/* Objects behave like an unbound method */
567567
#define Py_TPFLAGS_METHOD_DESCRIPTOR (1UL << 17)
568568

569-
/* Object has up-to-date type attribute cache */
569+
/* Unused. Legacy flag */
570570
#define Py_TPFLAGS_VALID_VERSION_TAG (1UL << 19)
571571

572572
/* Type is abstract and cannot be instantiated */

Diff for: Lib/test/test_type_cache.py

+23-6
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,21 @@ class C:
9393
new_version = type_get_version(C)
9494
self.assertEqual(new_version, 0)
9595

96+
def test_119462(self):
97+
98+
class Holder:
99+
value = None
100+
101+
@classmethod
102+
def set_value(cls):
103+
cls.value = object()
104+
105+
class HolderSub(Holder):
106+
pass
107+
108+
for _ in range(1050):
109+
Holder.set_value()
110+
HolderSub.value
96111

97112
@support.cpython_only
98113
@requires_specialization
@@ -106,8 +121,10 @@ def _assign_valid_version_or_skip(self, type_):
106121
if type_get_version(type_) == 0:
107122
self.skipTest("Could not assign valid type version")
108123

109-
def _assign_and_check_version_0(self, user_type):
124+
def _no_more_versions(self, user_type):
110125
type_modified(user_type)
126+
for _ in range(1001):
127+
type_assign_specific_version_unsafe(user_type, 1000_000_000)
111128
type_assign_specific_version_unsafe(user_type, 0)
112129
self.assertEqual(type_get_version(user_type), 0)
113130

@@ -136,7 +153,7 @@ def load_foo_1(type_):
136153
self._check_specialization(load_foo_1, A, "LOAD_ATTR", should_specialize=True)
137154
del load_foo_1
138155

139-
self._assign_and_check_version_0(A)
156+
self._no_more_versions(A)
140157

141158
def load_foo_2(type_):
142159
return type_.foo
@@ -187,7 +204,7 @@ def load_x_1(instance):
187204
self._check_specialization(load_x_1, G(), "LOAD_ATTR", should_specialize=True)
188205
del load_x_1
189206

190-
self._assign_and_check_version_0(G)
207+
self._no_more_versions(G)
191208

192209
def load_x_2(instance):
193210
instance.x
@@ -206,7 +223,7 @@ def store_bar_1(type_):
206223
self._check_specialization(store_bar_1, B(), "STORE_ATTR", should_specialize=True)
207224
del store_bar_1
208225

209-
self._assign_and_check_version_0(B)
226+
self._no_more_versions(B)
210227

211228
def store_bar_2(type_):
212229
type_.bar = 10
@@ -226,7 +243,7 @@ def call_class_1(type_):
226243
self._check_specialization(call_class_1, F, "CALL", should_specialize=True)
227244
del call_class_1
228245

229-
self._assign_and_check_version_0(F)
246+
self._no_more_versions(F)
230247

231248
def call_class_2(type_):
232249
type_()
@@ -245,7 +262,7 @@ def to_bool_1(instance):
245262
self._check_specialization(to_bool_1, H(), "TO_BOOL", should_specialize=True)
246263
del to_bool_1
247264

248-
self._assign_and_check_version_0(H)
265+
self._no_more_versions(H)
249266

250267
def to_bool_2(instance):
251268
not instance
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Make sure that invariants of type versioning are maintained:
2+
* Superclasses always have their version number assigned before subclasses
3+
* The version tag is always zero if the tag is not valid.
4+
* The version tag is always non-if the tag is valid.

Diff for: Modules/_testinternalcapi.c

-1
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

Diff for: Modules/pyexpat.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1651,7 +1651,7 @@ PyDoc_STRVAR(pyexpat_module_documentation,
16511651
static int init_handler_descrs(pyexpat_state *state)
16521652
{
16531653
int i;
1654-
assert(!PyType_HasFeature(state->xml_parse_type, Py_TPFLAGS_VALID_VERSION_TAG));
1654+
assert(state->xml_parse_type->tp_version_tag == 0);
16551655
for (i = 0; handler_info[i].name != NULL; i++) {
16561656
struct HandlerInfo *hi = &handler_info[i];
16571657
hi->getset.name = hi->name;

0 commit comments

Comments
 (0)