Commit f958377b authored by Petr Viktorin's avatar Petr Viktorin Committed by Miss Islington (bot)

bpo-37499: Test various C calling conventions (GH-15776)



Add functions with various calling conventions to `_testcapi`, expose them as module-level functions, bound methods, class methods, and static methods, and test calling them and introspecting them through GDB.


https://bugs.python.org/issue37499Co-authored-by: default avatarJeroen Demeyer <J.Demeyer@UGent.be>
Automerge-Triggered-By: @pganssle
parent f1a297ac
This diff is collapsed.
......@@ -838,47 +838,66 @@ id(42)
)
self.assertIn('Garbage-collecting', gdb_output)
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
# Some older versions of gdb will fail with
# "Cannot find new threads: generic error"
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
#
# gdb will also generate many erroneous errors such as:
# Function "meth_varargs" not defined.
# This is because we are calling functions from an "external" module
# (_testcapimodule) rather than compiled-in functions. It seems difficult
# to suppress these. See also the comment in DebuggerTests.get_stack_trace
def test_pycfunction(self):
'Verify that "py-bt" displays invocations of PyCFunction instances'
# 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
for func_name, args, expected_frame in (
('meth_varargs', '', 1),
('meth_varargs_keywords', '', 1),
('meth_o', '[]', 1),
('meth_noargs', '', 1),
('meth_fastcall', '', 1),
('meth_fastcall_keywords', '', 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')
# Verify with "py-bt":
gdb_output = self.get_stack_trace(
cmd,
breakpoint=c_name,
cmds_after_breakpoint=['bt', 'py-bt'],
)
self.assertIn(f'<built-in method {py_name}', gdb_output)
# Verify with "py-bt-full":
gdb_output = self.get_stack_trace(
cmd,
breakpoint=c_name,
cmds_after_breakpoint=['py-bt-full'],
)
self.assertIn(
f'#{expected_frame_number} <built-in method {py_name}',
gdb_output,
)
for obj in (
'_testcapi',
'_testcapi.MethClass',
'_testcapi.MethClass()',
'_testcapi.MethStatic()',
# XXX: bound methods don't yet give nice tracebacks
# '_testcapi.MethInstance()',
):
with self.subTest(f'{obj}.{func_name}'):
cmd = textwrap.dedent(f'''
import _testcapi
def foo():
{obj}.{func_name}({args})
def bar():
foo()
bar()
''')
# Verify with "py-bt":
gdb_output = self.get_stack_trace(
cmd,
breakpoint=func_name,
cmds_after_breakpoint=['bt', 'py-bt'],
)
self.assertIn(f'<built-in method {func_name}', gdb_output)
# Verify with "py-bt-full":
gdb_output = self.get_stack_trace(
cmd,
breakpoint=func_name,
cmds_after_breakpoint=['py-bt-full'],
)
self.assertIn(
f'#{expected_frame} <built-in method {func_name}',
gdb_output,
)
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
......
......@@ -5176,6 +5176,83 @@ sequence_getitem(PyObject *self, PyObject *args)
}
/* Functions for testing C calling conventions (METH_*) are named meth_*,
* e.g. "meth_varargs" for METH_VARARGS.
*
* They all return a tuple of their C-level arguments, with None instead
* of NULL and Python tuples instead of C arrays.
*/
static PyObject*
_null_to_none(PyObject* obj)
{
if (obj == NULL) {
Py_RETURN_NONE;
}
Py_INCREF(obj);
return obj;
}
static PyObject*
meth_varargs(PyObject* self, PyObject* args)
{
return Py_BuildValue("NO", _null_to_none(self), args);
}
static PyObject*
meth_varargs_keywords(PyObject* self, PyObject* args, PyObject* kwargs)
{
return Py_BuildValue("NON", _null_to_none(self), args, _null_to_none(kwargs));
}
static PyObject*
meth_o(PyObject* self, PyObject* obj)
{
return Py_BuildValue("NO", _null_to_none(self), obj);
}
static PyObject*
meth_noargs(PyObject* self, PyObject* ignored)
{
return _null_to_none(self);
}
static PyObject*
_fastcall_to_tuple(PyObject* const* args, Py_ssize_t nargs)
{
PyObject *tuple = PyTuple_New(nargs);
if (tuple == NULL) {
return NULL;
}
for (Py_ssize_t i=0; i < nargs; i++) {
Py_INCREF(args[i]);
PyTuple_SET_ITEM(tuple, i, args[i]);
}
return tuple;
}
static PyObject*
meth_fastcall(PyObject* self, PyObject* const* args, Py_ssize_t nargs)
{
return Py_BuildValue(
"NN", _null_to_none(self), _fastcall_to_tuple(args, nargs)
);
}
static PyObject*
meth_fastcall_keywords(PyObject* self, PyObject* const* args,
Py_ssize_t nargs, PyObject* kwargs)
{
PyObject *pyargs = _fastcall_to_tuple(args, nargs);
if (pyargs == NULL) {
return NULL;
}
PyObject *pykwargs = _PyObject_Vectorcall((PyObject*)&PyDict_Type,
args + nargs, 0, kwargs);
return Py_BuildValue("NNN", _null_to_none(self), pyargs, pykwargs);
}
static PyMethodDef TestMethods[] = {
{"raise_exception", raise_exception, METH_VARARGS},
{"raise_memoryerror", raise_memoryerror, METH_NOARGS},
......@@ -5426,6 +5503,12 @@ static PyMethodDef TestMethods[] = {
#endif
{"write_unraisable_exc", test_write_unraisable_exc, METH_VARARGS},
{"sequence_getitem", sequence_getitem, METH_VARARGS},
{"meth_varargs", meth_varargs, METH_VARARGS},
{"meth_varargs_keywords", (PyCFunction)(void(*)(void))meth_varargs_keywords, METH_VARARGS|METH_KEYWORDS},
{"meth_o", meth_o, METH_O},
{"meth_noargs", meth_noargs, METH_NOARGS},
{"meth_fastcall", (PyCFunction)(void(*)(void))meth_fastcall, METH_FASTCALL},
{"meth_fastcall_keywords", (PyCFunction)(void(*)(void))meth_fastcall_keywords, METH_FASTCALL|METH_KEYWORDS},
{NULL, NULL} /* sentinel */
};
......@@ -6086,6 +6169,72 @@ static PyTypeObject MethodDescriptor2_Type = {
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | _Py_TPFLAGS_HAVE_VECTORCALL,
};
static PyMethodDef meth_instance_methods[] = {
{"meth_varargs", meth_varargs, METH_VARARGS},
{"meth_varargs_keywords", (PyCFunction)(void(*)(void))meth_varargs_keywords, METH_VARARGS|METH_KEYWORDS},
{"meth_o", meth_o, METH_O},
{"meth_noargs", meth_noargs, METH_NOARGS},
{"meth_fastcall", (PyCFunction)(void(*)(void))meth_fastcall, METH_FASTCALL},
{"meth_fastcall_keywords", (PyCFunction)(void(*)(void))meth_fastcall_keywords, METH_FASTCALL|METH_KEYWORDS},
{NULL, NULL} /* sentinel */
};
static PyTypeObject MethInstance_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"MethInstance",
sizeof(PyObject),
.tp_new = PyType_GenericNew,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_methods = meth_instance_methods,
.tp_doc = PyDoc_STR(
"Class with normal (instance) methods to test calling conventions"),
};
static PyMethodDef meth_class_methods[] = {
{"meth_varargs", meth_varargs, METH_VARARGS|METH_CLASS},
{"meth_varargs_keywords", (PyCFunction)(void(*)(void))meth_varargs_keywords, METH_VARARGS|METH_KEYWORDS|METH_CLASS},
{"meth_o", meth_o, METH_O|METH_CLASS},
{"meth_noargs", meth_noargs, METH_NOARGS|METH_CLASS},
{"meth_fastcall", (PyCFunction)(void(*)(void))meth_fastcall, METH_FASTCALL|METH_CLASS},
{"meth_fastcall_keywords", (PyCFunction)(void(*)(void))meth_fastcall_keywords, METH_FASTCALL|METH_KEYWORDS|METH_CLASS},
{NULL, NULL} /* sentinel */
};
static PyTypeObject MethClass_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"MethClass",
sizeof(PyObject),
.tp_new = PyType_GenericNew,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_methods = meth_class_methods,
.tp_doc = PyDoc_STR(
"Class with class methods to test calling conventions"),
};
static PyMethodDef meth_static_methods[] = {
{"meth_varargs", meth_varargs, METH_VARARGS|METH_STATIC},
{"meth_varargs_keywords", (PyCFunction)(void(*)(void))meth_varargs_keywords, METH_VARARGS|METH_KEYWORDS|METH_STATIC},
{"meth_o", meth_o, METH_O|METH_STATIC},
{"meth_noargs", meth_noargs, METH_NOARGS|METH_STATIC},
{"meth_fastcall", (PyCFunction)(void(*)(void))meth_fastcall, METH_FASTCALL|METH_STATIC},
{"meth_fastcall_keywords", (PyCFunction)(void(*)(void))meth_fastcall_keywords, METH_FASTCALL|METH_KEYWORDS|METH_STATIC},
{NULL, NULL} /* sentinel */
};
static PyTypeObject MethStatic_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"MethStatic",
sizeof(PyObject),
.tp_new = PyType_GenericNew,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_methods = meth_static_methods,
.tp_doc = PyDoc_STR(
"Class with static methods to test calling conventions"),
};
static struct PyModuleDef _testcapimodule = {
PyModuleDef_HEAD_INIT,
......@@ -6172,6 +6321,21 @@ PyInit__testcapi(void)
Py_INCREF(&Generic_Type);
PyModule_AddObject(m, "Generic", (PyObject *)&Generic_Type);
if (PyType_Ready(&MethInstance_Type) < 0)
return NULL;
Py_INCREF(&MethInstance_Type);
PyModule_AddObject(m, "MethInstance", (PyObject *)&MethInstance_Type);
if (PyType_Ready(&MethClass_Type) < 0)
return NULL;
Py_INCREF(&MethClass_Type);
PyModule_AddObject(m, "MethClass", (PyObject *)&MethClass_Type);
if (PyType_Ready(&MethStatic_Type) < 0)
return NULL;
Py_INCREF(&MethStatic_Type);
PyModule_AddObject(m, "MethStatic", (PyObject *)&MethStatic_Type);
PyRecursingInfinitelyError_Type.tp_base = (PyTypeObject *)PyExc_Exception;
if (PyType_Ready(&PyRecursingInfinitelyError_Type) < 0) {
return NULL;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment