8000 GH-94822: Don't specialize when metaclasses are involved (GH-94892) · python/cpython@daf68ba · GitHub
[go: up one dir, main page]

Skip to content

Commit daf68ba

Browse files
authored
GH-94822: Don't specialize when metaclasses are involved (GH-94892)
1 parent c41d4d0 commit daf68ba

File tree

3 files changed

+351
-6
lines changed

3 files changed

+351
-6
lines changed

Lib/test/test_opcache.py

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import unittest
22

3+
34
class TestLoadAttrCache(unittest.TestCase):
45
def test_descriptor_added_after_optimization(self):
56
class Descriptor:
@@ -21,3 +22,346 @@ def f(o):
2122
Descriptor.__set__ = lambda *args: None
2223

2324
self.assertEqual(f(o), 2)
25+
26+
def test_metaclass_descriptor_added_after_optimization(self):
27+
class Descriptor:
28+
pass
29+
30+
class Metaclass(type):
31+
attribute = Descriptor()
32+
33+
class Class(metaclass=Metaclass):
34+
attribute = True
35+
36+
def __get__(self, instance, owner):
37+
return False
38+
39+
def __set__(self, instance, value):
40+
return None
41+
42+
def f():
43+
return Class.attribute
44+
45+
for _ in range(1025):
46+
self.assertTrue(f())
47+
48+
Descriptor.__get__ = __get__
49+
Descriptor.__set__ = __set__
50+
51+
for _ in range(1025):
52+
self.assertFalse(f())
53+
54+
def test_metaclass_descriptor_shadows_class_attribute(self):
55+
class Metaclass(type):
56+
@property
57+
def attribute(self):
58+
return True
59+
60+
class Class(metaclass=Metaclass):
61+
attribute = False
62+
63+
def f():
64+
return Class.attribute
65+
66+
for _ in range(1025):
67+
self.assertTrue(f())
68+
69+
def test_metaclass_set_descriptor_after_optimization(self):
70+
class Metaclass(type):
71+
pass
72+
73+
class Class(metaclass=Metaclass):
74+
attribute = True
75+
76+
@property
77+
def attribute(self):
78+
return False
79+
80+
def f():
81+
return Class.attribute
82+
83+
for _ in range(1025):
84+
self.assertTrue(f())
85+
86+
Metaclass.attribute = attribute
87+
88+
for _ in range(1025):
89+
self.assertFalse(f())
90+
91+
def test_metaclass_del_descriptor_after_optimization(self):
92+
class Metaclass(type):
93+
@property
94+
def attribute(self):
95+
return True
96+
97+
class Class(metaclass=Metaclass):
98+
attribute = False
99+
100+
def f():
101+
return Class.attribute
102+
103+
for _ in range(1025):
104+
self.assertTrue(f())
105+
106+
del Metaclass.attribute
107+
108+
for _ in range(1025):
109+
self.assertFalse(f())
110+
111+
def test_type_descriptor_shadows_attribute_method(self):
112+
class Class:
113+
mro = None
114+
115+
def f():
116+
return Class.mro
117+
118+
for _ in range(1025):
119+
self.assertIsNone(f())
120+
121+
def test_type_descriptor_shadows_attribute_member(self):
122+
class Class:
123+
__base__ = None
124+
125+
def f():
126+
return Class.__base__
127+
128+
for _ in range(1025):
129+
self.assertIs(f(), object)
130+
131+
def test_type_descriptor_shadows_attribute_getset(self):
132+
class Class:
133+
__name__ = "Spam"
134+
135+
def f():
136+
return Class.__name__
137+
138+
for _ in range(1025):
139+
self.assertEqual(f(), "Class")
140+
141+
def test_metaclass_getattribute(self):
142+
class Metaclass(type):
143+
def __getattribute__(self, name):
144+
return True
145+
146+
class Class(metaclass=Metaclass):
147+
attribute = False
148+
149+
def f():
150+
return Class.attribute
151+
152+
for _ in range(1025):
153+
self.assertTrue(f())
154+
155+
def test_metaclass_swap(self):
156+
class OldMetaclass(type):
157+
@property
158+
def attribute(self):
159+
return True
160+
161+
class NewMetaclass(type):
162+
@property
163+
def attribute(self):
164+
return False
165+
166+
class Class(metaclass=OldMetaclass):
167+
pass
168+
169+
def f():
170+
return Class.attribute
171+
172+
for _ in range(1025):
173+
self.assertTrue(f())
174+
175+
Class.__class__ = NewMetaclass
176+
177+
for _ in range(1025):
178+
self.assertFalse(f())
179+
180+
181+
class TestLoadMethodCache(unittest.TestCase):
182+
def test_descriptor_added_after_optimization(self):
183+
class Descriptor:
184+
pass
185+
186+
class Class:
187+
attribute = Descriptor()
188+
189+
def __get__(self, instance, owner):
190+
return lambda: False
191+
192+
def __set__(self, instance, value):
193+
return None
194+
195+
def attribute():
196+
return True
197+
198+
instance = Class()
199+
instance.attribute = attribute
200+
201+
def f():
202+
return instance.attribute()
203+
204+
for _ in range(1025):
205+
self.assertTrue(f())
206+
207+
Descriptor.__get__ = __get__
208+
Descriptor.__set__ = __set__
209+
210+
for _ in range(1025):
211+
self.assertFalse(f())
212+
213+
def test_metaclass_descriptor_added_after_optimization(self):
214+
class Descriptor:
215+
pass
216+
217+
class Metaclass(type):
218+
attribute = Descriptor()
219+
220+
class Class(metaclass=Metaclass):
221+
def attribute():
222+
return True
223+
224+
def __get__(self, instance, owner):
225+
return lambda: False
226+
227+
def __set__(self, instance, value):
228+
return None
229+
230+
def f():
231+
return Class.attribute()
232+
233+
for _ in range(1025):
234+
self.assertTrue(f())
235+
236+
Descriptor.__get__ = __get__
237+
Descriptor.__set__ = __set__
238+
239+
for _ in range(1025):
240+
self.assertFalse(f())
241+
242+
def test_metaclass_descriptor_shadows_class_attribute(self):
243+
class Metaclass(type):
244+
@property
245+
def attribute(self):
246+
return lambda: True
247+
248+
class Class(metaclass=Metaclass):
249+
def attribute():
250+
return False
251+
252+
def f():
253+
return Class.attribute()
254+
255+
for _ in range(1025):
256+
self.assertTrue(f())
257+
258+
def test_metaclass_set_descriptor_after_optimization(self):
259+
class Metaclass(type):
260+
pass
261+
262+
class Class(metaclass=Metaclass):
263+
def attribute():
264+
return True
265+
266+
@property
267+
def attribute(self):
268+
return lambda: False
269+
270+
def f():
271+
return Class.attribute()
272+
273+
for _ in range(1025):
274+
self.assertTrue(f())
275+
276+
Metaclass.attribute = attribute
277+
278+
for _ in range(1025):
279+
self.assertFalse(f())
280+
281+
def test_metaclass_del_descriptor_after_optimization(self):
282+
class Metaclass(type):
283+
@property
284+
def attribute(self):
285+
return lambda: True
286+
287+
class Class(metaclass=Metaclass):
288+
def attribute():
289+
return False
290+
291+
def f():
292+
return Class.attribute()
293+
294+
for _ in range(1025):
295+
self.assertTrue(f())
296+
297+
del Metaclass.attribute
298+
299+
for _ in range(1025):
300+
self.assertFalse(f())
301+
302+
def test_type_descriptor_shadows_attribute_method(self):
303+
class Class:
304+
def mro():
305+
return ["Spam", "eggs"]
306+
307+
def f():
308+
return Class.mro()
309+
310+
for _ in range(1025):
311+
self.assertEqual(f(), ["Spam", "eggs"])
312+
313+
def test_type_descriptor_shadows_attribute_member(self):
314+
class Class:
315+
def __base__():
316+
return "Spam"
317+
318+
def f():
319+
return Class.__base__()
320+
321+
for _ in range(1025):
322+
self.assertNotEqual(f(), "Spam")
323+
324+
def test_metaclass_getattribute(self):
325+
class Metaclass(type):
326+
def __getattribute__(self, name):
327+
return lambda: True
328+
329+
class Class(metaclass=Metaclass):
330+
def attribute():
331+
return False
332+
333+
def f():
334+
return Class.attribute()
335+
336+
for _ in range(1025):
337+
self.assertTrue(f())
338+
339+
def test_metaclass_swap(self):
340+
class OldMetaclass(type):
341+
@property
342+
def attribute(self):
343+
return lambda: True
344+
345+
class NewMetaclass(type):
346+
@property
347+
def attribute(self):
348+
return lambda: False
349+
350+
class Class(metaclass=OldMetaclass):
351+
pass
352+
353+
def f():
354+
return Class.attribute()
355+
356+
for _ in range(1025):
357+
self.assertTrue(f())
358+
359+
Class.__class__ = NewMetaclass
360+
361+
for _ in range(1025):
362+
self.assertFalse(f())
363+
364+
365+
if __name__ == "__main__":
366+
import unittest
367+
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix an issue where lookups of metaclass descriptors may be ignored when an
2+
identically-named attribute also exists on the class itself.

Python/specialize.c

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,10 @@ specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr,
945945
PyObject *name)
946946
{
947947
_PyLoadMethodCache *cache = (_PyLoadMethodCache *)(instr + 1);
948+
if (!PyType_CheckExact(owner) || _PyType_Lookup(Py_TYPE(owner), name)) {
949+
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_METACLASS_ATTRIBUTE);
950+
return -1;
951+
}
948952
PyObject *descr = NULL;
949953
DescriptorClassification kind = 0;
950954
kind = analyze_descriptor((PyTypeObject *)owner, name, &descr, 0);
@@ -957,12 +961,7 @@ specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr,
957961
return 0;
958962
#ifdef Py_STATS
959963
case ABSENT:
960-
if (_PyType_Lookup(Py_TYPE(owner), name) != NULL) {
961-
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_METACLASS_ATTRIBUTE);
962-
}
963-
else {
964-
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_EXPECTED_ERROR);
965-
}
964+
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_EXPECTED_ERROR);
966965
return -1;
967966
#endif
968967
default:

0 commit comments

Comments
 (0)
0