gh-125017: Fix crash on premature access to classmethod/staticmethod annotations (#125636)

This commit is contained in:
Jelle Zijlstra 2024-10-17 09:45:25 -07:00 committed by GitHub
parent 04d6dd23e2
commit f203d1cb52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 43 additions and 14 deletions

View File

@ -1618,6 +1618,9 @@ class ClassPropertiesAndMethods(unittest.TestCase):
for method in (annotated, unannotated): for method in (annotated, unannotated):
with self.subTest(deco=deco, method=method): with self.subTest(deco=deco, method=method):
with self.assertRaises(AttributeError):
del unannotated.__annotations__
original_annotations = dict(method.__wrapped__.__annotations__) original_annotations = dict(method.__wrapped__.__annotations__)
self.assertNotIn('__annotations__', method.__dict__) self.assertNotIn('__annotations__', method.__dict__)
self.assertEqual(method.__annotations__, original_annotations) self.assertEqual(method.__annotations__, original_annotations)
@ -1644,6 +1647,17 @@ class ClassPropertiesAndMethods(unittest.TestCase):
del method.__annotate__ del method.__annotate__
self.assertIs(method.__annotate__, original_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 @support.refcount_test
def test_refleaks_in_classmethod___init__(self): def test_refleaks_in_classmethod___init__(self):
gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount') gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')

View File

@ -0,0 +1,2 @@
Fix crash on certain accesses to the ``__annotations__`` of
:class:`staticmethod` and :class:`classmethod` objects.

View File

@ -1220,30 +1220,43 @@ functools_wraps(PyObject *wrapper, PyObject *wrapped)
// Used for wrapping __annotations__ and __annotate__ on classmethod // Used for wrapping __annotations__ and __annotate__ on classmethod
// and staticmethod objects. // and staticmethod objects.
static PyObject * 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; PyObject *res;
if (PyDict_GetItemRef(dict, name, &res) < 0) { if (PyDict_GetItemRef(dict, name, &res) < 0) {
Py_DECREF(dict);
return NULL; return NULL;
} }
if (res != NULL) { if (res != NULL) {
Py_DECREF(dict);
return res; return res;
} }
res = PyObject_GetAttr(wrapped, name); res = PyObject_GetAttr(wrapped, name);
if (res == NULL) { if (res == NULL) {
Py_DECREF(dict);
return NULL; return NULL;
} }
if (PyDict_SetItem(dict, name, res) < 0) { if (PyDict_SetItem(dict, name, res) < 0) {
Py_DECREF(dict);
Py_DECREF(res); Py_DECREF(res);
return NULL; return NULL;
} }
Py_DECREF(dict);
return res; return res;
} }
static int 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) char *type_name)
{ {
PyObject *dict = PyObject_GenericGetDict(oobj, NULL);
if (dict == NULL) {
return -1;
}
if (value == NULL) { if (value == NULL) {
if (PyDict_DelItem(dict, name) < 0) { if (PyDict_DelItem(dict, name) < 0) {
if (PyErr_ExceptionMatches(PyExc_KeyError)) { if (PyErr_ExceptionMatches(PyExc_KeyError)) {
@ -1251,14 +1264,18 @@ descriptor_set_wrapped_attribute(PyObject *dict, PyObject *name, PyObject *value
PyErr_Format(PyExc_AttributeError, PyErr_Format(PyExc_AttributeError,
"'%.200s' object has no attribute '%U'", "'%.200s' object has no attribute '%U'",
type_name, name); type_name, name);
return -1;
} }
else { else {
Py_DECREF(dict);
return -1; return -1;
} }
} }
Py_DECREF(dict);
return 0; return 0;
} }
else { else {
Py_DECREF(dict);
return PyDict_SetItem(dict, name, value); return PyDict_SetItem(dict, name, value);
} }
} }
@ -1380,28 +1397,26 @@ static PyObject *
cm_get___annotations__(PyObject *self, void *closure) cm_get___annotations__(PyObject *self, void *closure)
{ {
classmethod *cm = _PyClassMethod_CAST(self); 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 static int
cm_set___annotations__(PyObject *self, PyObject *value, void *closure) cm_set___annotations__(PyObject *self, PyObject *value, void *closure)
{ {
classmethod *cm = _PyClassMethod_CAST(self); return descriptor_set_wrapped_attribute(self, &_Py_ID(__annotations__), value, "classmethod");
return descriptor_set_wrapped_attribute(cm->cm_dict, &_Py_ID(__annotations__), value, "classmethod");
} }
static PyObject * static PyObject *
cm_get___annotate__(PyObject *self, void *closure) cm_get___annotate__(PyObject *self, void *closure)
{ {
classmethod *cm = _PyClassMethod_CAST(self); 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 static int
cm_set___annotate__(PyObject *self, PyObject *value, void *closure) cm_set___annotate__(PyObject *self, PyObject *value, void *closure)
{ {
classmethod *cm = _PyClassMethod_CAST(self); return descriptor_set_wrapped_attribute(self, &_Py_ID(__annotate__), value, "classmethod");
return descriptor_set_wrapped_attribute(cm->cm_dict, &_Py_ID(__annotate__), value, "classmethod");
} }
@ -1615,28 +1630,26 @@ static PyObject *
sm_get___annotations__(PyObject *self, void *closure) sm_get___annotations__(PyObject *self, void *closure)
{ {
staticmethod *sm = _PyStaticMethod_CAST(self); 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 static int
sm_set___annotations__(PyObject *self, PyObject *value, void *closure) sm_set___annotations__(PyObject *self, PyObject *value, void *closure)
{ {
staticmethod *sm = _PyStaticMethod_CAST(self); return descriptor_set_wrapped_attribute(self, &_Py_ID(__annotations__), value, "staticmethod");
return descriptor_set_wrapped_attribute(sm->sm_dict, &_Py_ID(__annotations__), value, "staticmethod");
} }
static PyObject * static PyObject *
sm_get___annotate__(PyObject *self, void *closure) sm_get___annotate__(PyObject *self, void *closure)
{ {
staticmethod *sm = _PyStaticMethod_CAST(self); 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 static int
sm_set___annotate__(PyObject *self, PyObject *value, void *closure) sm_set___annotate__(PyObject *self, PyObject *value, void *closure)
{ {
staticmethod *sm = _PyStaticMethod_CAST(self); return descriptor_set_wrapped_attribute(self, &_Py_ID(__annotate__), value, "staticmethod");
return descriptor_set_wrapped_attribute(sm->sm_dict, &_Py_ID(__annotate__), value, "staticmethod");
} }
static PyGetSetDef sm_getsetlist[] = { static PyGetSetDef sm_getsetlist[] = {