Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitf653caa

Browse files
authored
gh-89811: Check for valid tp_version_tag in specializer (GH-113558)
1 parentc65ae26 commitf653caa

File tree

4 files changed

+243
-3
lines changed

4 files changed

+243
-3
lines changed

‎Lib/test/test_type_cache.py‎

Lines changed: 183 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
""" Tests for the internal type cache in CPython. """
22
importunittest
3+
importdis
34
fromtestimportsupport
45
fromtest.supportimportimport_helper
56
try:
@@ -8,8 +9,11 @@
89
_clear_type_cache=None
910

1011
# Skip this test if the _testcapi module isn't available.
11-
type_get_version=import_helper.import_module('_testcapi').type_get_version
12-
type_assign_version=import_helper.import_module('_testcapi').type_assign_version
12+
_testcapi=import_helper.import_module("_testcapi")
13+
type_get_version=_testcapi.type_get_version
14+
type_assign_specific_version_unsafe=_testcapi.type_assign_specific_version_unsafe
15+
type_assign_version=_testcapi.type_assign_version
16+
type_modified=_testcapi.type_modified
1317

1418

1519
@support.cpython_only
@@ -56,6 +60,183 @@ class C:
5660
self.assertNotEqual(type_get_version(C),0)
5761
self.assertNotEqual(type_get_version(C),c_ver)
5862

63+
deftest_type_assign_specific_version(self):
64+
"""meta-test for type_assign_specific_version_unsafe"""
65+
classC:
66+
pass
67+
68+
type_assign_version(C)
69+
orig_version=type_get_version(C)
70+
self.assertNotEqual(orig_version,0)
71+
72+
type_modified(C)
73+
type_assign_specific_version_unsafe(C,orig_version+5)
74+
type_assign_version(C)# this should do nothing
75+
76+
new_version=type_get_version(C)
77+
self.assertEqual(new_version,orig_version+5)
78+
79+
_clear_type_cache()
80+
81+
82+
@support.cpython_only
83+
classTypeCacheWithSpecializationTests(unittest.TestCase):
84+
deftearDown(self):
85+
_clear_type_cache()
86+
87+
def_assign_and_check_valid_version(self,user_type):
88+
type_modified(user_type)
89+
type_assign_version(user_type)
90+
self.assertNotEqual(type_get_version(user_type),0)
91+
92+
def_assign_and_check_version_0(self,user_type):
93+
type_modified(user_type)
94+
type_assign_specific_version_unsafe(user_type,0)
95+
self.assertEqual(type_get_version(user_type),0)
96+
97+
def_all_opnames(self,func):
98+
returnset(instr.opnameforinstrindis.Bytecode(func,adaptive=True))
99+
100+
def_check_specialization(self,func,arg,opname,*,should_specialize):
101+
self.assertIn(opname,self._all_opnames(func))
102+
103+
for_inrange(100):
104+
func(arg)
105+
106+
ifshould_specialize:
107+
self.assertNotIn(opname,self._all_opnames(func))
108+
else:
109+
self.assertIn(opname,self._all_opnames(func))
110+
111+
deftest_class_load_attr_specialization_user_type(self):
112+
classA:
113+
deffoo(self):
114+
pass
115+
116+
self._assign_and_check_valid_version(A)
117+
118+
defload_foo_1(type_):
119+
type_.foo
120+
121+
self._check_specialization(load_foo_1,A,"LOAD_ATTR",should_specialize=True)
122+
delload_foo_1
123+
124+
self._assign_and_check_version_0(A)
125+
126+
defload_foo_2(type_):
127+
returntype_.foo
128+
129+
self._check_specialization(load_foo_2,A,"LOAD_ATTR",should_specialize=False)
130+
131+
deftest_class_load_attr_specialization_static_type(self):
132+
self._assign_and_check_valid_version(str)
133+
self._assign_and_check_valid_version(bytes)
134+
135+
defget_capitalize_1(type_):
136+
returntype_.capitalize
137+
138+
self._check_specialization(get_capitalize_1,str,"LOAD_ATTR",should_specialize=True)
139+
self.assertEqual(get_capitalize_1(str)('hello'),'Hello')
140+
self.assertEqual(get_capitalize_1(bytes)(b'hello'),b'Hello')
141+
delget_capitalize_1
142+
143+
# Permanently overflow the static type version counter, and force str and bytes
144+
# to have tp_version_tag == 0
145+
for_inrange(2**16):
146+
type_modified(str)
147+
type_assign_version(str)
148+
type_modified(bytes)
149+
type_assign_version(bytes)
150+
151+
self.assertEqual(type_get_version(str),0)
152+
self.assertEqual(type_get_version(bytes),0)
153+
154+
defget_capitalize_2(type_):
155+
returntype_.capitalize
156+
157+
self._check_specialization(get_capitalize_2,str,"LOAD_ATTR",should_specialize=False)
158+
self.assertEqual(get_capitalize_2(str)('hello'),'Hello')
159+
self.assertEqual(get_capitalize_2(bytes)(b'hello'),b'Hello')
160+
161+
deftest_property_load_attr_specialization_user_type(self):
162+
classG:
163+
@property
164+
defx(self):
165+
return9
166+
167+
self._assign_and_check_valid_version(G)
168+
169+
defload_x_1(instance):
170+
instance.x
171+
172+
self._check_specialization(load_x_1,G(),"LOAD_ATTR",should_specialize=True)
173+
delload_x_1
174+
175+
self._assign_and_check_version_0(G)
176+
177+
defload_x_2(instance):
178+
instance.x
179+
180+
self._check_specialization(load_x_2,G(),"LOAD_ATTR",should_specialize=False)
181+
182+
deftest_store_attr_specialization_user_type(self):
183+
classB:
184+
__slots__= ("bar",)
185+
186+
self._assign_and_check_valid_version(B)
187+
188+
defstore_bar_1(type_):
189+
type_.bar=10
190+
191+
self._check_specialization(store_bar_1,B(),"STORE_ATTR",should_specialize=True)
192+
delstore_bar_1
193+
194+
self._assign_and_check_version_0(B)
195+
196+
defstore_bar_2(type_):
197+
type_.bar=10
198+
199+
self._check_specialization(store_bar_2,B(),"STORE_ATTR",should_specialize=False)
200+
201+
deftest_class_call_specialization_user_type(self):
202+
classF:
203+
def__init__(self):
204+
pass
205+
206+
self._assign_and_check_valid_version(F)
207+
208+
defcall_class_1(type_):
209+
type_()
210+
211+
self._check_specialization(call_class_1,F,"CALL",should_specialize=True)
212+
delcall_class_1
213+
214+
self._assign_and_check_version_0(F)
215+
216+
defcall_class_2(type_):
217+
type_()
218+
219+
self._check_specialization(call_class_2,F,"CALL",should_specialize=False)
220+
221+
deftest_to_bool_specialization_user_type(self):
222+
classH:
223+
pass
224+
225+
self._assign_and_check_valid_version(H)
226+
227+
defto_bool_1(instance):
228+
notinstance
229+
230+
self._check_specialization(to_bool_1,H(),"TO_BOOL",should_specialize=True)
231+
delto_bool_1
232+
233+
self._assign_and_check_version_0(H)
234+
235+
defto_bool_2(instance):
236+
notinstance
237+
238+
self._check_specialization(to_bool_2,H(),"TO_BOOL",should_specialize=False)
239+
59240

60241
if__name__=="__main__":
61242
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Check for a valid ``tp_version_tag`` before performing bytecode specializations that
2+
rely on this value being usable.

‎Modules/_testcapimodule.c‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2409,6 +2409,32 @@ type_get_version(PyObject *self, PyObject *type)
24092409
returnres;
24102410
}
24112411

2412+
staticPyObject*
2413+
type_modified(PyObject*self,PyObject*type)
2414+
{
2415+
if (!PyType_Check(type)) {
2416+
PyErr_SetString(PyExc_TypeError,"argument must be a type");
2417+
returnNULL;
2418+
}
2419+
PyType_Modified((PyTypeObject*)type);
2420+
Py_RETURN_NONE;
2421+
}
2422+
2423+
// Circumvents standard version assignment machinery - use with caution and only on
2424+
// short-lived heap types
2425+
staticPyObject*
2426+
type_assign_specific_version_unsafe(PyObject*self,PyObject*args)
2427+
{
2428+
PyTypeObject*type;
2429+
unsignedintversion;
2430+
if (!PyArg_ParseTuple(args,"Oi:type_assign_specific_version_unsafe",&type,&version)) {
2431+
returnNULL;
2432+
}
2433+
assert(!PyType_HasFeature(type,Py_TPFLAGS_IMMUTABLETYPE));
2434+
type->tp_version_tag=version;
2435+
type->tp_flags |=Py_TPFLAGS_VALID_VERSION_TAG;
2436+
Py_RETURN_NONE;
2437+
}
24122438

24132439
staticPyObject*
24142440
type_assign_version(PyObject*self,PyObject*type)
@@ -3342,6 +3368,9 @@ static PyMethodDef TestMethods[] = {
33423368
{"test_py_is_macros",test_py_is_macros,METH_NOARGS},
33433369
{"test_py_is_funcs",test_py_is_funcs,METH_NOARGS},
33443370
{"type_get_version",type_get_version,METH_O,PyDoc_STR("type->tp_version_tag")},
3371+
{"type_modified",type_modified,METH_O,PyDoc_STR("PyType_Modified")},
3372+
{"type_assign_specific_version_unsafe",type_assign_specific_version_unsafe,METH_VARARGS,
3373+
PyDoc_STR("forcefully assign type->tp_version_tag")},
33453374
{"type_assign_version",type_assign_version,METH_O,PyDoc_STR("PyUnstable_Type_AssignVersionTag")},
33463375
{"type_get_tp_bases",type_get_tp_bases,METH_O},
33473376
{"type_get_tp_mro",type_get_tp_mro,METH_O},

‎Python/specialize.c‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,7 @@ _PyCode_Quicken(PyCodeObject *code)
586586
staticintfunction_kind(PyCodeObject*code);
587587
staticboolfunction_check_args(PyObject*o,intexpected_argcount,intopcode);
588588
staticuint32_tfunction_get_version(PyObject*o,intopcode);
589+
staticuint32_ttype_get_version(PyTypeObject*t,intopcode);
589590

590591
staticint
591592
specialize_module_load_attr(
@@ -874,6 +875,9 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name)
874875
PyObject*descr=NULL;
875876
DescriptorClassificationkind=analyze_descriptor(type,name,&descr,0);
876877
assert(descr!=NULL||kind==ABSENT||kind==GETSET_OVERRIDDEN);
878+
if (type_get_version(type,LOAD_ATTR)==0) {
879+
gotofail;
880+
}
877881
switch(kind) {
878882
caseOVERRIDING:
879883
SPECIALIZATION_FAIL(LOAD_ATTR,SPEC_FAIL_ATTR_OVERRIDING_DESCRIPTOR);
@@ -1057,6 +1061,9 @@ _Py_Specialize_StoreAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name)
10571061
}
10581062
PyObject*descr;
10591063
DescriptorClassificationkind=analyze_descriptor(type,name,&descr,1);
1064+
if (type_get_version(type,STORE_ATTR)==0) {
1065+
gotofail;
1066+
}
10601067
switch(kind) {
10611068
caseOVERRIDING:
10621069
SPECIALIZATION_FAIL(STORE_ATTR,SPEC_FAIL_ATTR_OVERRIDING_DESCRIPTOR);
@@ -1183,6 +1190,9 @@ specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr,
11831190
PyObject*descr=NULL;
11841191
DescriptorClassificationkind=0;
11851192
kind=analyze_descriptor((PyTypeObject*)owner,name,&descr,0);
1193+
if (type_get_version((PyTypeObject*)owner,LOAD_ATTR)==0) {
1194+
return-1;
1195+
}
11861196
switch (kind) {
11871197
caseMETHOD:
11881198
caseNON_DESCRIPTOR:
@@ -1455,6 +1465,18 @@ function_get_version(PyObject *o, int opcode)
14551465
returnversion;
14561466
}
14571467

1468+
/* Returning 0 indicates a failure. */
1469+
staticuint32_t
1470+
type_get_version(PyTypeObject*t,intopcode)
1471+
{
1472+
uint32_tversion=t->tp_version_tag;
1473+
if (version==0) {
1474+
SPECIALIZATION_FAIL(opcode,SPEC_FAIL_OUT_OF_VERSIONS);
1475+
return0;
1476+
}
1477+
returnversion;
1478+
}
1479+
14581480
void
14591481
_Py_Specialize_BinarySubscr(
14601482
PyObject*container,PyObject*sub,_Py_CODEUNIT*instr)
@@ -1726,6 +1748,9 @@ specialize_class_call(PyObject *callable, _Py_CODEUNIT *instr, int nargs)
17261748
}
17271749
if (tp->tp_new==PyBaseObject_Type.tp_new) {
17281750
PyFunctionObject*init=get_init_for_simple_managed_python_class(tp);
1751+
if (type_get_version(tp,CALL)==0) {
1752+
return-1;
1753+
}
17291754
if (init!=NULL) {
17301755
if (((PyCodeObject*)init->func_code)->co_argcount!=nargs+1) {
17311756
SPECIALIZATION_FAIL(CALL,SPEC_FAIL_WRONG_NUMBER_ARGUMENTS);
@@ -2466,7 +2491,10 @@ _Py_Specialize_ToBool(PyObject *value, _Py_CODEUNIT *instr)
24662491
SPECIALIZATION_FAIL(TO_BOOL,SPEC_FAIL_OUT_OF_VERSIONS);
24672492
gotofailure;
24682493
}
2469-
uint32_tversion=Py_TYPE(value)->tp_version_tag;
2494+
uint32_tversion=type_get_version(Py_TYPE(value),TO_BOOL);
2495+
if (version==0) {
2496+
gotofailure;
2497+
}
24702498
instr->op.code=TO_BOOL_ALWAYS_TRUE;
24712499
write_u32(cache->version,version);
24722500
assert(version);

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp