summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrandt Bucher <brandtbucher@microsoft.com>2022-07-18 17:10:22 (GMT)
committerGitHub <noreply@github.com>2022-07-18 17:10:22 (GMT)
commitdaf68ba92f315bfd239a0c993f9f683fb90325fb (patch)
tree41e81bc487e49cdb08ea51dc13801d983209a431
parentc41d4d09172bf28ba9e5760498bd494a3521a123 (diff)
downloadcpython-daf68ba92f315bfd239a0c993f9f683fb90325fb.zip
cpython-daf68ba92f315bfd239a0c993f9f683fb90325fb.tar.gz
cpython-daf68ba92f315bfd239a0c993f9f683fb90325fb.tar.bz2
GH-94822: Don't specialize when metaclasses are involved (GH-94892)
-rw-r--r--Lib/test/test_opcache.py344
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2022-07-15-22-16-08.gh-issue-94822.zRRzBN.rst2
-rw-r--r--Python/specialize.c11
3 files changed, 351 insertions, 6 deletions
diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py
index 61f337d..5c032d5 100644
--- a/Lib/test/test_opcache.py
+++ b/Lib/test/test_opcache.py
@@ -1,5 +1,6 @@
import unittest
+
class TestLoadAttrCache(unittest.TestCase):
def test_descriptor_added_after_optimization(self):
class Descriptor:
@@ -21,3 +22,346 @@ class TestLoadAttrCache(unittest.TestCase):
Descriptor.__set__ = lambda *args: None
self.assertEqual(f(o), 2)
+
+ def test_metaclass_descriptor_added_after_optimization(self):
+ class Descriptor:
+ pass
+
+ class Metaclass(type):
+ attribute = Descriptor()
+
+ class Class(metaclass=Metaclass):
+ attribute = True
+
+ def __get__(self, instance, owner):
+ return False
+
+ def __set__(self, instance, value):
+ return None
+
+ def f():
+ return Class.attribute
+
+ for _ in range(1025):
+ self.assertTrue(f())
+
+ Descriptor.__get__ = __get__
+ Descriptor.__set__ = __set__
+
+ for _ in range(1025):
+ self.assertFalse(f())
+
+ def test_metaclass_descriptor_shadows_class_attribute(self):
+ class Metaclass(type):
+ @property
+ def attribute(self):
+ return True
+
+ class Class(metaclass=Metaclass):
+ attribute = False
+
+ def f():
+ return Class.attribute
+
+ for _ in range(1025):
+ self.assertTrue(f())
+
+ def test_metaclass_set_descriptor_after_optimization(self):
+ class Metaclass(type):
+ pass
+
+ class Class(metaclass=Metaclass):
+ attribute = True
+
+ @property
+ def attribute(self):
+ return False
+
+ def f():
+ return Class.attribute
+
+ for _ in range(1025):
+ self.assertTrue(f())
+
+ Metaclass.attribute = attribute
+
+ for _ in range(1025):
+ self.assertFalse(f())
+
+ def test_metaclass_del_descriptor_after_optimization(self):
+ class Metaclass(type):
+ @property
+ def attribute(self):
+ return True
+
+ class Class(metaclass=Metaclass):
+ attribute = False
+
+ def f():
+ return Class.attribute
+
+ for _ in range(1025):
+ self.assertTrue(f())
+
+ del Metaclass.attribute
+
+ for _ in range(1025):
+ self.assertFalse(f())
+
+ def test_type_descriptor_shadows_attribute_method(self):
+ class Class:
+ mro = None
+
+ def f():
+ return Class.mro
+
+ for _ in range(1025):
+ self.assertIsNone(f())
+
+ def test_type_descriptor_shadows_attribute_member(self):
+ class Class:
+ __base__ = None
+
+ def f():
+ return Class.__base__
+
+ for _ in range(1025):
+ self.assertIs(f(), object)
+
+ def test_type_descriptor_shadows_attribute_getset(self):
+ class Class:
+ __name__ = "Spam"
+
+ def f():
+ return Class.__name__
+
+ for _ in range(1025):
+ self.assertEqual(f(), "Class")
+
+ def test_metaclass_getattribute(self):
+ class Metaclass(type):
+ def __getattribute__(self, name):
+ return True
+
+ class Class(metaclass=Metaclass):
+ attribute = False
+
+ def f():
+ return Class.attribute
+
+ for _ in range(1025):
+ self.assertTrue(f())
+
+ def test_metaclass_swap(self):
+ class OldMetaclass(type):
+ @property
+ def attribute(self):
+ return True
+
+ class NewMetaclass(type):
+ @property
+ def attribute(self):
+ return False
+
+ class Class(metaclass=OldMetaclass):
+ pass
+
+ def f():
+ return Class.attribute
+
+ for _ in range(1025):
+ self.assertTrue(f())
+
+ Class.__class__ = NewMetaclass
+
+ for _ in range(1025):
+ self.assertFalse(f())
+
+
+class TestLoadMethodCache(unittest.TestCase):
+ def test_descriptor_added_after_optimization(self):
+ class Descriptor:
+ pass
+
+ class Class:
+ attribute = Descriptor()
+
+ def __get__(self, instance, owner):
+ return lambda: False
+
+ def __set__(self, instance, value):
+ return None
+
+ def attribute():
+ return True
+
+ instance = Class()
+ instance.attribute = attribute
+
+ def f():
+ return instance.attribute()
+
+ for _ in range(1025):
+ self.assertTrue(f())
+
+ Descriptor.__get__ = __get__
+ Descriptor.__set__ = __set__
+
+ for _ in range(1025):
+ self.assertFalse(f())
+
+ def test_metaclass_descriptor_added_after_optimization(self):
+ class Descriptor:
+ pass
+
+ class Metaclass(type):
+ attribute = Descriptor()
+
+ class Class(metaclass=Metaclass):
+ def attribute():
+ return True
+
+ def __get__(self, instance, owner):
+ return lambda: False
+
+ def __set__(self, instance, value):
+ return None
+
+ def f():
+ return Class.attribute()
+
+ for _ in range(1025):
+ self.assertTrue(f())
+
+ Descriptor.__get__ = __get__
+ Descriptor.__set__ = __set__
+
+ for _ in range(1025):
+ self.assertFalse(f())
+
+ def test_metaclass_descriptor_shadows_class_attribute(self):
+ class Metaclass(type):
+ @property
+ def attribute(self):
+ return lambda: True
+
+ class Class(metaclass=Metaclass):
+ def attribute():
+ return False
+
+ def f():
+ return Class.attribute()
+
+ for _ in range(1025):
+ self.assertTrue(f())
+
+ def test_metaclass_set_descriptor_after_optimization(self):
+ class Metaclass(type):
+ pass
+
+ class Class(metaclass=Metaclass):
+ def attribute():
+ return True
+
+ @property
+ def attribute(self):
+ return lambda: False
+
+ def f():
+ return Class.attribute()
+
+ for _ in range(1025):
+ self.assertTrue(f())
+
+ Metaclass.attribute = attribute
+
+ for _ in range(1025):
+ self.assertFalse(f())
+
+ def test_metaclass_del_descriptor_after_optimization(self):
+ class Metaclass(type):
+ @property
+ def attribute(self):
+ return lambda: True
+
+ class Class(metaclass=Metaclass):
+ def attribute():
+ return False
+
+ def f():
+ return Class.attribute()
+
+ for _ in range(1025):
+ self.assertTrue(f())
+
+ del Metaclass.attribute
+
+ for _ in range(1025):
+ self.assertFalse(f())
+
+ def test_type_descriptor_shadows_attribute_method(self):
+ class Class:
+ def mro():
+ return ["Spam", "eggs"]
+
+ def f():
+ return Class.mro()
+
+ for _ in range(1025):
+ self.assertEqual(f(), ["Spam", "eggs"])
+
+ def test_type_descriptor_shadows_attribute_member(self):
+ class Class:
+ def __base__():
+ return "Spam"
+
+ def f():
+ return Class.__base__()
+
+ for _ in range(1025):
+ self.assertNotEqual(f(), "Spam")
+
+ def test_metaclass_getattribute(self):
+ class Metaclass(type):
+ def __getattribute__(self, name):
+ return lambda: True
+
+ class Class(metaclass=Metaclass):
+ def attribute():
+ return False
+
+ def f():
+ return Class.attribute()
+
+ for _ in range(1025):
+ self.assertTrue(f())
+
+ def test_metaclass_swap(self):
+ class OldMetaclass(type):
+ @property
+ def attribute(self):
+ return lambda: True
+
+ class NewMetaclass(type):
+ @property
+ def attribute(self):
+ return lambda: False
+
+ class Class(metaclass=OldMetaclass):
+ pass
+
+ def f():
+ return Class.attribute()
+
+ for _ in range(1025):
+ self.assertTrue(f())
+
+ Class.__class__ = NewMetaclass
+
+ for _ in range(1025):
+ self.assertFalse(f())
+
+
+if __name__ == "__main__":
+ import unittest
+ unittest.main()
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-07-15-22-16-08.gh-issue-94822.zRRzBN.rst b/Misc/NEWS.d/next/Core and Builtins/2022-07-15-22-16-08.gh-issue-94822.zRRzBN.rst
new file mode 100644
index 0000000..5b24918
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-07-15-22-16-08.gh-issue-94822.zRRzBN.rst
@@ -0,0 +1,2 @@
+Fix an issue where lookups of metaclass descriptors may be ignored when an
+identically-named attribute also exists on the class itself.
diff --git a/Python/specialize.c b/Python/specialize.c
index 66cae44..6f1f3cb 100644
--- a/Python/specialize.c
+++ b/Python/specialize.c
@@ -945,6 +945,10 @@ specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr,
PyObject *name)
{
_PyLoadMethodCache *cache = (_PyLoadMethodCache *)(instr + 1);
+ if (!PyType_CheckExact(owner) || _PyType_Lookup(Py_TYPE(owner), name)) {
+ SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_METACLASS_ATTRIBUTE);
+ return -1;
+ }
PyObject *descr = NULL;
DescriptorClassification kind = 0;
kind = analyze_descriptor((PyTypeObject *)owner, name, &descr, 0);
@@ -957,12 +961,7 @@ specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr,
return 0;
#ifdef Py_STATS
case ABSENT:
- if (_PyType_Lookup(Py_TYPE(owner), name) != NULL) {
- SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_METACLASS_ATTRIBUTE);
- }
- else {
- SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_EXPECTED_ERROR);
- }
+ SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_EXPECTED_ERROR);
return -1;
#endif
default: