diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 0bff7ded4670f1..fa37d3ae494785 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -321,6 +321,9 @@ def static_method(): @cpython_only class FastCallTests(unittest.TestCase): + from _testcapi import CallTest + calltest = CallTest() + # Test calls with positional arguments CALLS_POSARGS = ( # (func, args: tuple, result) @@ -341,20 +344,24 @@ class FastCallTests(unittest.TestCase): (PYTHON_INSTANCE.class_method, (), "classmethod"), (PYTHON_INSTANCE.static_method, (), "staticmethod"), - # C function: METH_NOARGS - (globals, (), IGNORE_RESULT), - - # C function: METH_O - (id, ("hello",), IGNORE_RESULT), - - # C function: METH_VARARGS - (dir, (1,), IGNORE_RESULT), - - # C function: METH_VARARGS | METH_KEYWORDS - (min, (5, 9), 5), - - # C function: METH_FASTCALL - (divmod, (1000, 33), (30, 10)), + # C methods + (calltest.varargs, (1, 2), [(1, 2), {}]), + (calltest.varargs_keywords, (1, 2), [(1, 2), {}]), + (calltest.fastcall, (1, 2), [(1, 2), {}]), + (calltest.fastcall_keywords, (1, 2), [(1, 2), {}]), + (calltest.noargs, (), [(), {}]), + (calltest.onearg, (123, ), [(123, ), {}]), + (calltest.staticmeth, (1, 2), [(1, 2), {}]), + (calltest.classmeth, (1, 2), [(1, 2), {}]), + + (CallTest.varargs, (calltest, 1, 2), [(1, 2), {}]), + (CallTest.varargs_keywords, (calltest, 1, 2), [(1, 2), {}]), + (CallTest.fastcall, (calltest, 1, 2), [(1, 2), {}]), + (CallTest.fastcall_keywords, (calltest, 1, 2), [(1, 2), {}]), + (CallTest.noargs, (calltest, ), [(), {}]), + (CallTest.onearg, (calltest, 123), [(123, ), {}]), + (CallTest.staticmeth, (1, 2), [(1, 2), {}]), + (CallTest.classmeth, (1, 2), [(1, 2), {}]), # C type static method: METH_FASTCALL | METH_CLASS (int.from_bytes, (b'\x01\x00', 'little'), 1), @@ -376,8 +383,18 @@ class FastCallTests(unittest.TestCase): (PYTHON_INSTANCE.method, (1,), {'arg2': 2}, [1, 2]), (PYTHON_INSTANCE.method, (), {'arg1': 1, 'arg2': 2}, [1, 2]), - # C function: METH_VARARGS | METH_KEYWORDS - (max, ([],), {'default': 9}, 9), + # C methods + (calltest.varargs_keywords, (1, 2), {'x': 'y'}, [(1, 2), {'x': 'y'}]), + (calltest.fastcall_keywords, (1, 2), {'x': 'y'}, [(1, 2), {'x': 'y'}]), + (calltest.staticmeth, (1, 2), {'x': 'y'}, [(1, 2), {'x': 'y'}]), + (calltest.classmeth, (1, 2), {'x': 'y'}, [(1, 2), {'x': 'y'}]), + + (CallTest.varargs_keywords, + (calltest, 1, 2), {'x': 'y'}, [(1, 2), {'x': 'y'}]), + (CallTest.fastcall_keywords, + (calltest, 1, 2), {'x': 'y'}, [(1, 2), {'x': 'y'}]), + (CallTest.staticmeth, (1, 2), {'x': 'y'}, [(1, 2), {'x': 'y'}]), + (CallTest.classmeth, (1, 2), {'x': 'y'}, [(1, 2), {'x': 'y'}]), # C type static method: METH_FASTCALL | METH_CLASS (int.from_bytes, (b'\x01\x00',), {'byteorder': 'little'}, 1), diff --git a/Lib/test/test_gdb.py b/Lib/test/test_gdb.py index e07d3273a4552d..2a44a2db1bd318 100644 --- a/Lib/test/test_gdb.py +++ b/Lib/test/test_gdb.py @@ -849,19 +849,24 @@ def test_pycfunction(self): # Various optimizations multiply the code paths by which these are # called, so test a variety of calling conventions. for py_name, py_args, c_name, expected_frame_number in ( - ('gmtime', '', 'time_gmtime', 1), # METH_VARARGS - ('len', '[]', 'builtin_len', 1), # METH_O - ('locals', '', 'builtin_locals', 1), # METH_NOARGS - ('iter', '[]', 'builtin_iter', 1), # METH_FASTCALL - ('sorted', '[]', 'builtin_sorted', 1), # METH_FASTCALL|METH_KEYWORDS + ('varargs', '', 'CallTest_varargs', 1), + ('varargs_keywords', '', 'CallTest_varargs_keywords', 1), + ('fastcall', '', 'CallTest_fastcall', 1), + ('fastcall_keywords', '', 'CallTest_fastcall_keywords', 1), + ('noargs', '', 'CallTest_noargs', 1), + ('onearg', 'None', 'CallTest_onearg', 1), + ('staticmeth', '', 'CallTest_call', 1), + ('classmeth', '', 'CallTest_call', 1), ): with self.subTest(c_name): - cmd = ('from time import gmtime\n' # (not always needed) - 'def foo():\n' - f' {py_name}({py_args})\n' - 'def bar():\n' - ' foo()\n' - 'bar()\n') + cmd = textwrap.dedent(f''' + from _testcapi import CallTest + def foo(): + f = CallTest().{py_name} + f({py_args}) + def bar(): + foo() + bar()''') # Verify with "py-bt": gdb_output = self.get_stack_trace( cmd, diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 8f34e935353ba1..9073e66815dcb5 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -5856,6 +5856,105 @@ static PyTypeObject Generic_Type = { }; +/* Test various calling conventions. These functions always return a + two-element list [args, kwargs] */ + +static PyObject * +CallTest_call(PyObject *Py_UNUSED(self), PyObject *args, PyObject *kwargs) +{ + PyObject *res = PyList_New(2); + if (res == NULL) { + return NULL; + } + Py_INCREF(args); + PyList_SET_ITEM(res, 0, args); + if (kwargs == NULL) { + kwargs = PyDict_New(); + if (kwargs == NULL) { + Py_DECREF(res); + return NULL; + } + PyList_SET_ITEM(res, 1, kwargs); + } + else { + Py_INCREF(kwargs); + PyList_SET_ITEM(res, 1, kwargs); + } + return res; +} + +static PyObject * +CallTest_varargs(PyObject *self, PyObject *args) +{ + return PyObject_Call(self, args, NULL); +} + +static PyObject * +CallTest_varargs_keywords(PyObject *self, PyObject *args, PyObject *kwargs) +{ + return PyObject_Call(self, args, kwargs); +} + +static PyObject * +CallTest_noargs(PyObject *self, PyObject *unused) +{ + if (unused != NULL) { + PyErr_BadInternalCall(); + return NULL; + } + return _PyObject_CallNoArg(self); +} + +static PyObject * +CallTest_onearg(PyObject *self, PyObject *arg) +{ + return _PyObject_CallOneArg(self, arg); +} + +static PyObject * +CallTest_fastcall(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + return _PyObject_Vectorcall(self, args, nargs, NULL); +} + +static PyObject * +CallTest_fastcall_keywords(PyObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) +{ + return _PyObject_Vectorcall(self, args, nargs, kwnames); +} + +static PyMethodDef CallTest_methods[] = { + {"varargs", (PyCFunction)(void(*)(void))CallTest_varargs, + METH_VARARGS, NULL}, + {"varargs_keywords", (PyCFunction)(void(*)(void))CallTest_varargs_keywords, + METH_VARARGS|METH_KEYWORDS, NULL}, + {"noargs", (PyCFunction)(void(*)(void))CallTest_noargs, METH_NOARGS, NULL}, + {"onearg", (PyCFunction)(void(*)(void))CallTest_onearg, METH_O, NULL}, + {"fastcall", (PyCFunction)(void(*)(void))CallTest_fastcall, + METH_FASTCALL, NULL}, + {"fastcall_keywords", + (PyCFunction)(void(*)(void))CallTest_fastcall_keywords, + METH_FASTCALL|METH_KEYWORDS, NULL}, + {"staticmeth", (PyCFunction)(void(*)(void))CallTest_call, + METH_STATIC|METH_VARARGS|METH_KEYWORDS, NULL}, + {"classmeth", (PyCFunction)(void(*)(void))CallTest_call, + METH_CLASS|METH_VARARGS|METH_KEYWORDS, NULL}, + {NULL} /* sentinel */ +}; + +static PyTypeObject CallTest_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "CallTest", + sizeof(PyObject), + .tp_new = PyType_GenericNew, + .tp_dealloc = (destructor)PyObject_Del, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_call = CallTest_call, + .tp_methods = CallTest_methods, +}; + + /* Test PEP 590 */ typedef struct { @@ -6008,6 +6107,11 @@ PyInit__testcapi(void) Py_INCREF(&MyList_Type); PyModule_AddObject(m, "MyList", (PyObject *)&MyList_Type); + if (PyType_Ready(&CallTest_Type) < 0) + return NULL; + Py_INCREF(&CallTest_Type); + PyModule_AddObject(m, "CallTest", (PyObject *)&CallTest_Type); + if (PyType_Ready(&MethodDescriptorBase_Type) < 0) return NULL; Py_INCREF(&MethodDescriptorBase_Type);