Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 14 additions & 18 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1727,6 +1727,18 @@ def annotated(cls) -> int: pass
del method.__annotate__
self.assertIs(method.__annotate__, original_annotate)

def test_classmethod_without_dict_access(self):
class Spam:
@classmethod
def method(cls, x, y):
pass

obj = Spam.__dict__['method']
self.assertIsInstance(obj, classmethod)
self.assertEqual(obj.__annotations__, {})
self.assertEqual(obj.__name__, 'method')
self.assertEqual(obj.__module__, __name__)

def test_staticmethod_annotations_without_dict_access(self):
# gh-125017: this used to crash
class Spam:
Expand All @@ -1737,15 +1749,8 @@ def __new__(cls, x, y):
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')
cm = classmethod(None)
refs_before = gettotalrefcount()
for i in range(100):
cm.__init__(None)
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
self.assertEqual(obj.__name__, '__new__')
self.assertEqual(obj.__module__, __name__)

@support.impl_detail("the module 'xxsubtype' is internal")
@unittest.skipIf(xxsubtype is None, "requires xxsubtype module")
Expand Down Expand Up @@ -1822,15 +1827,6 @@ class D(C):
del sm.x
self.assertNotHasAttr(sm, "x")

@support.refcount_test
def test_refleaks_in_staticmethod___init__(self):
gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
sm = staticmethod(None)
refs_before = gettotalrefcount()
for i in range(100):
sm.__init__(None)
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)

@support.impl_detail("the module 'xxsubtype' is internal")
@unittest.skipIf(xxsubtype is None, "requires xxsubtype module")
def test_staticmethods_in_c(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Move ``classmethod`` and ``staticmethod`` initialization from ``__init__()``
to ``__new__()``. Patch by Victor Stinner.
130 changes: 95 additions & 35 deletions Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1466,33 +1466,59 @@ static PyObject *
cm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
classmethod *cm = (classmethod *)self;

if (cm->cm_callable == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"uninitialized classmethod object");
return NULL;
}
if (type == NULL)
type = (PyObject *)(Py_TYPE(obj));
return PyMethod_New(cm->cm_callable, type);
}

static int
cm_init(PyObject *self, PyObject *args, PyObject *kwds)
cm_set_callable(classmethod *cm, PyObject *callable)
{
classmethod *cm = (classmethod *)self;
PyObject *callable;
assert(callable != NULL);
if (cm->cm_callable == callable) {
// cm_init() sets the same callable than cm_new()
return 0;
}

if (!_PyArg_NoKeywords("classmethod", kwds))
return -1;
if (!PyArg_UnpackTuple(args, "classmethod", 1, 1, &callable))
return -1;
Py_XSETREF(cm->cm_callable, Py_NewRef(callable));
return functools_wraps((PyObject *)cm, cm->cm_callable);
}

static PyObject *
cm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
if (!_PyArg_NoKeywords("classmethod", kwds)) {
return NULL;
}
PyObject *callable; // borrowed ref
if (!PyArg_UnpackTuple(args, "classmethod", 1, 1, &callable)) {
return NULL;
}

if (functools_wraps((PyObject *)cm, cm->cm_callable) < 0) {
classmethod *cm = (classmethod *)PyType_GenericAlloc(type, 0);
if (cm == NULL) {
return NULL;
}
if (cm_set_callable(cm, callable) < 0) {
Py_DECREF(cm);
return NULL;
}
return (PyObject *)cm;
}

static int
cm_init(PyObject *self, PyObject *args, PyObject *kwds)
{
if (!_PyArg_NoKeywords("classmethod", kwds)) {
return -1;
}
return 0;
PyObject *callable; // borrowed ref
if (!PyArg_UnpackTuple(args, "classmethod", 1, 1, &callable)) {
return -1;
}

classmethod *cm = (classmethod *)self;
return cm_set_callable(cm, callable);
}

static PyMemberDef cm_memberlist[] = {
Expand Down Expand Up @@ -1623,7 +1649,7 @@ PyTypeObject PyClassMethod_Type = {
offsetof(classmethod, cm_dict), /* tp_dictoffset */
cm_init, /* tp_init */
PyType_GenericAlloc, /* tp_alloc */
PyType_GenericNew, /* tp_new */
cm_new, /* tp_new */
PyObject_GC_Del, /* tp_free */
};

Expand All @@ -1632,8 +1658,12 @@ PyClassMethod_New(PyObject *callable)
{
classmethod *cm = (classmethod *)
PyType_GenericAlloc(&PyClassMethod_Type, 0);
if (cm != NULL) {
cm->cm_callable = Py_NewRef(callable);
if (cm == NULL) {
return NULL;
}
if (cm_set_callable(cm, callable) < 0) {
Py_DECREF(cm);
return NULL;
}
return (PyObject *)cm;
}
Expand Down Expand Up @@ -1699,31 +1729,57 @@ static PyObject *
sm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
staticmethod *sm = (staticmethod *)self;
return Py_NewRef(sm->sm_callable);
}

if (sm->sm_callable == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"uninitialized staticmethod object");
static int
sm_set_callable(staticmethod *sm, PyObject *callable)
{
assert(callable != NULL);
if (sm->sm_callable == callable) {
// sm_init() sets the same callable than sm_new()
return 0;
}

Py_XSETREF(sm->sm_callable, Py_NewRef(callable));
return functools_wraps((PyObject *)sm, sm->sm_callable);
}

static PyObject *
sm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
if (!_PyArg_NoKeywords("staticmethod", kwds)) {
return NULL;
}
return Py_NewRef(sm->sm_callable);
PyObject *callable; // borrowed ref
if (!PyArg_UnpackTuple(args, "staticmethod", 1, 1, &callable)) {
return NULL;
}

staticmethod *sm = (staticmethod *)PyType_GenericAlloc(type, 0);
if (sm == NULL) {
return NULL;
}
if (sm_set_callable(sm, callable) < 0) {
Py_DECREF(sm);
return NULL;
}
return (PyObject *)sm;
}

static int
sm_init(PyObject *self, PyObject *args, PyObject *kwds)
{
staticmethod *sm = (staticmethod *)self;
PyObject *callable;

if (!_PyArg_NoKeywords("staticmethod", kwds))
if (!_PyArg_NoKeywords("staticmethod", kwds)) {
return -1;
if (!PyArg_UnpackTuple(args, "staticmethod", 1, 1, &callable))
return -1;
Py_XSETREF(sm->sm_callable, Py_NewRef(callable));

if (functools_wraps((PyObject *)sm, sm->sm_callable) < 0) {
}
PyObject *callable; // borrowed ref
if (!PyArg_UnpackTuple(args, "staticmethod", 1, 1, &callable)) {
return -1;
}
return 0;

staticmethod *sm = (staticmethod *)self;
return sm_set_callable(sm, callable);
}

static PyObject*
Expand Down Expand Up @@ -1858,7 +1914,7 @@ PyTypeObject PyStaticMethod_Type = {
offsetof(staticmethod, sm_dict), /* tp_dictoffset */
sm_init, /* tp_init */
PyType_GenericAlloc, /* tp_alloc */
PyType_GenericNew, /* tp_new */
sm_new, /* tp_new */
PyObject_GC_Del, /* tp_free */
};

Expand All @@ -1867,8 +1923,12 @@ PyStaticMethod_New(PyObject *callable)
{
staticmethod *sm = (staticmethod *)
PyType_GenericAlloc(&PyStaticMethod_Type, 0);
if (sm != NULL) {
sm->sm_callable = Py_NewRef(callable);
if (sm == NULL) {
return NULL;
}
if (sm_set_callable(sm, callable) < 0) {
Py_DECREF(sm);
return NULL;
}
return (PyObject *)sm;
}
7 changes: 5 additions & 2 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2446,13 +2446,17 @@ static PyTypeObject* static_types[] = {
&PyBaseObject_Type,
&PyType_Type,

// PyStaticMethod_Type and PyCFunction_Type are used by PyType_Ready()
// on other types and so must be initialized first.
&PyStaticMethod_Type,
&PyCFunction_Type,

// Static types with base=&PyBaseObject_Type
&PyAsyncGen_Type,
&PyByteArrayIter_Type,
&PyByteArray_Type,
&PyBytesIter_Type,
&PyBytes_Type,
&PyCFunction_Type,
&PyCallIter_Type,
&PyCapsule_Type,
&PyCell_Type,
Expand Down Expand Up @@ -2509,7 +2513,6 @@ static PyTypeObject* static_types[] = {
&PySetIter_Type,
&PySet_Type,
&PySlice_Type,
&PyStaticMethod_Type,
&PyStdPrinter_Type,
&PySuper_Type,
&PyTraceBack_Type,
Expand Down
Loading