diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 9d15ab3a96b..b7e0f4d6d64 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -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 index 00000000000..11c526643c3 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-16-23-06-06.gh-issue-125017.fcltj0.rst @@ -0,0 +1,2 @@ +Fix crash on certain accesses to the ``__annotations__`` of +:class:`staticmethod` and :class:`classmethod` objects. diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 6119a96b4aa..f86ef32f182 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -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[] = {