From de98b3fba0ad6257f3f49ba2905ed9d6c9dc3700 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 26 Aug 2023 17:10:01 +0200 Subject: [PATCH] gh-108494: Fix AC limited C API for kwargs Fix the limited C API code path in Argument Clinic to parse "positional or keywords" arguments. Add an unit test in _testclinic_limited. --- Lib/test/test_clinic.py | 14 +++++++++++ Modules/_testclinic_limited.c | 17 +++++++++++++ Modules/clinic/_testclinic_limited.c.h | 30 ++++++++++++++++++++++- Tools/clinic/clinic.py | 34 ++++++++++++++------------ 4 files changed, 79 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index e7039d590705971..a2e566f7ca76eaa 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3534,6 +3534,7 @@ class LimitedCAPIFunctionalTest(unittest.TestCase): for name in dir(_testclinic_limited) if name.startswith('test_')) def test_my_int_func(self): + # METH_O with self.assertRaises(TypeError): _testclinic_limited.my_int_func() self.assertEqual(_testclinic_limited.my_int_func(3), 3) @@ -3543,6 +3544,7 @@ def test_my_int_func(self): _testclinic_limited.my_int_func("xyz") def test_my_int_sum(self): + # PyArg_ParseTuple() with "ii:my_int_sum" format with self.assertRaises(TypeError): _testclinic_limited.my_int_sum() with self.assertRaises(TypeError): @@ -3553,6 +3555,18 @@ def test_my_int_sum(self): with self.assertRaises(TypeError): _testclinic_limited.my_int_sum(1, "str") + def test_my_obj_func(self): + # PyArg_ParseTupleAndKeywords() with "OO:my_obj_func" format + arg1 = object() + arg2 = object() + with self.assertRaises(TypeError): + _testclinic_limited.my_obj_func() + with self.assertRaises(TypeError): + _testclinic_limited.my_obj_func(arg1) + self.assertIs(_testclinic_limited.my_obj_func(arg1, arg2), arg1) + with self.assertRaises(TypeError): + _testclinic_limited.my_obj_func(arg1, arg2, "arg3") + class PermutationTests(unittest.TestCase): diff --git a/Modules/_testclinic_limited.c b/Modules/_testclinic_limited.c index 0b606c9857fc408..7c70a7cafe38b3b 100644 --- a/Modules/_testclinic_limited.c +++ b/Modules/_testclinic_limited.c @@ -45,6 +45,22 @@ my_int_func_impl(PyObject *module, int arg) } +/*[clinic input] +my_obj_func + + arg1: object + arg2: object + +[clinic start generated code]*/ + +static PyObject * +my_obj_func_impl(PyObject *module, PyObject *arg1, PyObject *arg2) +/*[clinic end generated code: output=7f07d359d2e50436 input=c6f78c836ecab1d6]*/ +{ + return Py_NewRef(arg1); +} + + /*[clinic input] my_int_sum -> int @@ -66,6 +82,7 @@ static PyMethodDef tester_methods[] = { TEST_EMPTY_FUNCTION_METHODDEF MY_INT_FUNC_METHODDEF MY_INT_SUM_METHODDEF + MY_OBJ_FUNC_METHODDEF {NULL, NULL} }; diff --git a/Modules/clinic/_testclinic_limited.c.h b/Modules/clinic/_testclinic_limited.c.h index 9b0032528ff746c..bf89483f9202a38 100644 --- a/Modules/clinic/_testclinic_limited.c.h +++ b/Modules/clinic/_testclinic_limited.c.h @@ -51,6 +51,34 @@ my_int_func(PyObject *module, PyObject *arg_) return return_value; } +PyDoc_STRVAR(my_obj_func__doc__, +"my_obj_func($module, /, arg1, arg2)\n" +"--\n" +"\n"); + +#define MY_OBJ_FUNC_METHODDEF \ + {"my_obj_func", _PyCFunction_CAST(my_obj_func), METH_VARARGS|METH_KEYWORDS, my_obj_func__doc__}, + +static PyObject * +my_obj_func_impl(PyObject *module, PyObject *arg1, PyObject *arg2); + +static PyObject * +my_obj_func(PyObject *module, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + char* _keywords[] = {"arg1", "arg2", NULL}; + PyObject *arg1; + PyObject *arg2; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO:my_obj_func", _keywords, + &arg1, &arg2)) + goto exit; + return_value = my_obj_func_impl(module, arg1, arg2); + +exit: + return return_value; +} + PyDoc_STRVAR(my_int_sum__doc__, "my_int_sum($module, x, y, /)\n" "--\n" @@ -82,4 +110,4 @@ my_int_sum(PyObject *module, PyObject *args) exit: return return_value; } -/*[clinic end generated code: output=f9f7209255bb969e input=a9049054013a1b77]*/ +/*[clinic end generated code: output=7e5151a2029efc21 input=a9049054013a1b77]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 70ec18f726e3f4f..c0bd413b221c91e 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1251,7 +1251,8 @@ def parser_body( parser_definition = parser_body(parser_prototype, ' {option_group_parsing}') elif not requires_defining_class and pos_only == len(parameters) - pseudo_args and clinic.limited_capi: - # positional-only for the limited C API + # positional-only for the limited C API: + # PyArg_ParseTuple() flags = "METH_VARARGS" parser_prototype = self.PARSER_PROTOTYPE_VARARGS @@ -1266,6 +1267,22 @@ def parser_body( parser_definition = parser_body(parser_prototype, *parser_code, declarations=declarations) + elif clinic.limited_capi: + # positional-or-keyword arguments for the limited C API: + # PyArg_ParseTupleAndKeywords() + flags = "METH_VARARGS|METH_KEYWORDS" + + parser_prototype = self.PARSER_PROTOTYPE_KEYWORD + parser_code = [normalize_snippet(""" + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords, + {parse_arguments})) + goto exit; + """, indent=4)] + declarations = "char* _keywords[] = {{{keywords_c} NULL}};" + + parser_definition = parser_body(parser_prototype, *parser_code, + declarations=declarations) + elif not requires_defining_class and pos_only == len(parameters) - pseudo_args: if not new_or_init: # positional-only, but no option groups @@ -1384,20 +1401,7 @@ def parser_body( ) nargs = f"Py_MIN(nargs, {max_pos})" if max_pos else "0" - if clinic.limited_capi: - # positional-or-keyword arguments - flags = "METH_VARARGS|METH_KEYWORDS" - - parser_prototype = self.PARSER_PROTOTYPE_KEYWORD - parser_code = [normalize_snippet(""" - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords, - {parse_arguments})) - goto exit; - """, indent=4)] - argname_fmt = 'args[%d]' - declarations = "" - - elif not new_or_init: + if not new_or_init: flags = "METH_FASTCALL|METH_KEYWORDS" parser_prototype = self.PARSER_PROTOTYPE_FASTCALL_KEYWORDS argname_fmt = 'args[%d]'