]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-125017: Fix crash on premature access to classmethod/staticmethod annotations...
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Thu, 17 Oct 2024 16:45:25 +0000 (09:45 -0700)
committerGitHub <noreply@github.com>
Thu, 17 Oct 2024 16:45:25 +0000 (09:45 -0700)
Lib/test/test_descr.py
Misc/NEWS.d/next/Core_and_Builtins/2024-10-16-23-06-06.gh-issue-125017.fcltj0.rst [new file with mode: 0644]
Objects/funcobject.c

index 9d15ab3a96bad64fe3daf6aa33899a5dbcd98396..b7e0f4d6d640180a1e7654ad8e735ff8e2a2d873 100644 (file)
@@ -1618,6 +1618,9 @@ class ClassPropertiesAndMethods(unittest.TestCase):
 
             for method in (annotated, unannotated):
                 with self.subTest(deco=deco, method=method):
+                    with self.assertRaises(AttributeError):
+                        del unannotated.__annotations__
+
                     original_annotations = dict(method.__wrapped__.__annotations__)
                     self.assertNotIn('__annotations__', method.__dict__)
                     self.assertEqual(method.__annotations__, original_annotations)
@@ -1644,6 +1647,17 @@ class ClassPropertiesAndMethods(unittest.TestCase):
                     del method.__annotate__
                     self.assertIs(method.__annotate__, original_annotate)
 
+    def test_staticmethod_annotations_without_dict_access(self):
+        # gh-125017: this used to crash
+        class Spam:
+            def __new__(cls, x, y):
+                pass
+
+        self.assertEqual(Spam.__new__.__annotations__, {})
+        obj = Spam.__dict__['__new__']
+        self.assertIsInstance(obj, staticmethod)
+        self.assertEqual(obj.__annotations__, {})
+
     @support.refcount_test
     def test_refleaks_in_classmethod___init__(self):
         gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-16-23-06-06.gh-issue-125017.fcltj0.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-16-23-06-06.gh-issue-125017.fcltj0.rst
new file mode 100644 (file)
index 0000000..11c5266
--- /dev/null
@@ -0,0 +1,2 @@
+Fix crash on certain accesses to the ``__annotations__`` of
+:class:`staticmethod` and :class:`classmethod` objects.
index 6119a96b4aae76d50480f99dab6fda93bea77dff..f86ef32f1827bf3023b6ddfdd2325c7114f5b567 100644 (file)
@@ -1220,30 +1220,43 @@ functools_wraps(PyObject *wrapper, PyObject *wrapped)
 // Used for wrapping __annotations__ and __annotate__ on classmethod
 // and staticmethod objects.
 static PyObject *
-descriptor_get_wrapped_attribute(PyObject *wrapped, PyObject *dict, PyObject *name)
+descriptor_get_wrapped_attribute(PyObject *wrapped, PyObject *obj, PyObject *name)
 {
+    PyObject *dict = PyObject_GenericGetDict(obj, NULL);
+    if (dict == NULL) {
+        return NULL;
+    }
     PyObject *res;
     if (PyDict_GetItemRef(dict, name, &res) < 0) {
+        Py_DECREF(dict);
         return NULL;
     }
     if (res != NULL) {
+        Py_DECREF(dict);
         return res;
     }
     res = PyObject_GetAttr(wrapped, name);
     if (res == NULL) {
+        Py_DECREF(dict);
         return NULL;
     }
     if (PyDict_SetItem(dict, name, res) < 0) {
+        Py_DECREF(dict);
         Py_DECREF(res);
         return NULL;
     }
+    Py_DECREF(dict);
     return res;
 }
 
 static int
-descriptor_set_wrapped_attribute(PyObject *dict, PyObject *name, PyObject *value,
+descriptor_set_wrapped_attribute(PyObject *oobj, PyObject *name, PyObject *value,
                                  char *type_name)
 {
+    PyObject *dict = PyObject_GenericGetDict(oobj, NULL);
+    if (dict == NULL) {
+        return -1;
+    }
     if (value == NULL) {
         if (PyDict_DelItem(dict, name) < 0) {
             if (PyErr_ExceptionMatches(PyExc_KeyError)) {
@@ -1251,14 +1264,18 @@ descriptor_set_wrapped_attribute(PyObject *dict, PyObject *name, PyObject *value
                 PyErr_Format(PyExc_AttributeError,
                              "'%.200s' object has no attribute '%U'",
                              type_name, name);
+                return -1;
             }
             else {
+                Py_DECREF(dict);
                 return -1;
             }
         }
+        Py_DECREF(dict);
         return 0;
     }
     else {
+        Py_DECREF(dict);
         return PyDict_SetItem(dict, name, value);
     }
 }
@@ -1380,28 +1397,26 @@ static PyObject *
 cm_get___annotations__(PyObject *self, void *closure)
 {
     classmethod *cm = _PyClassMethod_CAST(self);
-    return descriptor_get_wrapped_attribute(cm->cm_callable, cm->cm_dict, &_Py_ID(__annotations__));
+    return descriptor_get_wrapped_attribute(cm->cm_callable, self, &_Py_ID(__annotations__));
 }
 
 static int
 cm_set___annotations__(PyObject *self, PyObject *value, void *closure)
 {
-    classmethod *cm = _PyClassMethod_CAST(self);
-    return descriptor_set_wrapped_attribute(cm->cm_dict, &_Py_ID(__annotations__), value, "classmethod");
+    return descriptor_set_wrapped_attribute(self, &_Py_ID(__annotations__), value, "classmethod");
 }
 
 static PyObject *
 cm_get___annotate__(PyObject *self, void *closure)
 {
     classmethod *cm = _PyClassMethod_CAST(self);
-    return descriptor_get_wrapped_attribute(cm->cm_callable, cm->cm_dict, &_Py_ID(__annotate__));
+    return descriptor_get_wrapped_attribute(cm->cm_callable, self, &_Py_ID(__annotate__));
 }
 
 static int
 cm_set___annotate__(PyObject *self, PyObject *value, void *closure)
 {
-    classmethod *cm = _PyClassMethod_CAST(self);
-    return descriptor_set_wrapped_attribute(cm->cm_dict, &_Py_ID(__annotate__), value, "classmethod");
+    return descriptor_set_wrapped_attribute(self, &_Py_ID(__annotate__), value, "classmethod");
 }
 
 
@@ -1615,28 +1630,26 @@ static PyObject *
 sm_get___annotations__(PyObject *self, void *closure)
 {
     staticmethod *sm = _PyStaticMethod_CAST(self);
-    return descriptor_get_wrapped_attribute(sm->sm_callable, sm->sm_dict, &_Py_ID(__annotations__));
+    return descriptor_get_wrapped_attribute(sm->sm_callable, self, &_Py_ID(__annotations__));
 }
 
 static int
 sm_set___annotations__(PyObject *self, PyObject *value, void *closure)
 {
-    staticmethod *sm = _PyStaticMethod_CAST(self);
-    return descriptor_set_wrapped_attribute(sm->sm_dict, &_Py_ID(__annotations__), value, "staticmethod");
+    return descriptor_set_wrapped_attribute(self, &_Py_ID(__annotations__), value, "staticmethod");
 }
 
 static PyObject *
 sm_get___annotate__(PyObject *self, void *closure)
 {
     staticmethod *sm = _PyStaticMethod_CAST(self);
-    return descriptor_get_wrapped_attribute(sm->sm_callable, sm->sm_dict, &_Py_ID(__annotate__));
+    return descriptor_get_wrapped_attribute(sm->sm_callable, self, &_Py_ID(__annotate__));
 }
 
 static int
 sm_set___annotate__(PyObject *self, PyObject *value, void *closure)
 {
-    staticmethod *sm = _PyStaticMethod_CAST(self);
-    return descriptor_set_wrapped_attribute(sm->sm_dict, &_Py_ID(__annotate__), value, "staticmethod");
+    return descriptor_set_wrapped_attribute(self, &_Py_ID(__annotate__), value, "staticmethod");
 }
 
 static PyGetSetDef sm_getsetlist[] = {