]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-110864: Fix _PyArg_UnpackKeywordsWithVararg overwriting vararg with NULL (#110868)
authorNikita Sobolev <mail@sobolevn.me>
Mon, 16 Oct 2023 11:42:44 +0000 (14:42 +0300)
committerGitHub <noreply@github.com>
Mon, 16 Oct 2023 11:42:44 +0000 (13:42 +0200)
Lib/test/test_clinic.py
Misc/NEWS.d/next/Core and Builtins/2023-10-14-12-19-34.gh-issue-110864.-baPDE.rst [new file with mode: 0644]
Modules/_testclinic.c
Modules/clinic/_testclinic.c.h
Python/getargs.c

index 627a329bb738a2292c3ca1dfd001de7096a04fff..0499828e4dedbc3f3f4e2cb90456b00babcbac34 100644 (file)
@@ -3154,6 +3154,10 @@ class ClinicFunctionalTest(unittest.TestCase):
         self.assertEqual(ac_tester.posonly_vararg(1, 2), (1, 2, ()))
         self.assertEqual(ac_tester.posonly_vararg(1, b=2), (1, 2, ()))
         self.assertEqual(ac_tester.posonly_vararg(1, 2, 3, 4), (1, 2, (3, 4)))
+        with self.assertRaises(TypeError):
+            ac_tester.posonly_vararg(b=4)
+        with self.assertRaises(TypeError):
+            ac_tester.posonly_vararg(1, 2, 3, b=4)
 
     def test_vararg_and_posonly(self):
         with self.assertRaises(TypeError):
@@ -3204,6 +3208,37 @@ class ClinicFunctionalTest(unittest.TestCase):
         with self.assertRaisesRegex(TypeError, err):
             ac_tester.gh_99240_double_free('a', '\0b')
 
+    def test_null_or_tuple_for_varargs(self):
+        # All of these should not crash:
+        valid_args_for_test = [
+            (('a',), {},
+             ('a', (), False)),
+            (('a', 1, 2, 3), {'covariant': True},
+             ('a', (1, 2, 3), True)),
+            ((), {'name': 'a'},
+             ('a', (), False)),
+            ((), {'name': 'a', 'covariant': True},
+             ('a', (), True)),
+            ((), {'covariant': True, 'name': 'a'},
+             ('a', (), True)),
+        ]
+        for args, kwargs, expected in valid_args_for_test:
+            with self.subTest(args=args, kwargs=kwargs):
+                self.assertEqual(
+                    ac_tester.null_or_tuple_for_varargs(*args, **kwargs),
+                    expected,
+                )
+
+    def test_null_or_tuple_for_varargs_error(self):
+        with self.assertRaises(TypeError):
+            ac_tester.null_or_tuple_for_varargs(covariant=True)
+        with self.assertRaises(TypeError):
+            ac_tester.null_or_tuple_for_varargs(1, name='a')
+        with self.assertRaises(TypeError):
+            ac_tester.null_or_tuple_for_varargs(1, 2, 3, name='a', covariant=True)
+        with self.assertRaises(TypeError):
+            ac_tester.null_or_tuple_for_varargs(1, 2, 3, covariant=True, name='a')
+
     def test_cloned_func_exception_message(self):
         incorrect_arg = -1  # f1() and f2() accept a single str
         with self.assertRaisesRegex(TypeError, "clone_f1"):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-10-14-12-19-34.gh-issue-110864.-baPDE.rst b/Misc/NEWS.d/next/Core and Builtins/2023-10-14-12-19-34.gh-issue-110864.-baPDE.rst
new file mode 100644 (file)
index 0000000..3d79a71
--- /dev/null
@@ -0,0 +1,2 @@
+Fix argument parsing by ``_PyArg_UnpackKeywordsWithVararg`` for functions
+defining pos-or-keyword, vararg, and kw-only parameters.
index 2e0535d52641e0df73d8097ce88d53f34583c94c..15e0093f15ba1e6146ee65ed8391ef805b933c36 100644 (file)
@@ -1128,6 +1128,26 @@ gh_99240_double_free_impl(PyObject *module, char *a, char *b)
     Py_RETURN_NONE;
 }
 
+/*[clinic input]
+null_or_tuple_for_varargs
+
+    name: object
+    *constraints: object
+    covariant: bool = False
+
+See https://github.com/python/cpython/issues/110864
+[clinic start generated code]*/
+
+static PyObject *
+null_or_tuple_for_varargs_impl(PyObject *module, PyObject *name,
+                               PyObject *constraints, int covariant)
+/*[clinic end generated code: output=a785b35421358983 input=c9bce186637956b3]*/
+{
+    assert(name != NULL);
+    assert(constraints != NULL);
+    PyObject *c = covariant ? Py_True : Py_False;
+    return pack_arguments_newref(3, name, constraints, c);
+}
 
 /*[clinic input]
 _testclinic.clone_f1 as clone_f1
@@ -1843,6 +1863,7 @@ static PyMethodDef tester_methods[] = {
     GH_32092_KW_PASS_METHODDEF
     GH_99233_REFCOUNT_METHODDEF
     GH_99240_DOUBLE_FREE_METHODDEF
+    NULL_OR_TUPLE_FOR_VARARGS_METHODDEF
     CLONE_F1_METHODDEF
     CLONE_F2_METHODDEF
     CLONE_WITH_CONV_F1_METHODDEF
index ba3aeca49e26eb45785f18a41b4472dc781edaad..3655e82150d4011932e20342f74df055749691ee 100644 (file)
@@ -2813,6 +2813,76 @@ exit:
     return return_value;
 }
 
+PyDoc_STRVAR(null_or_tuple_for_varargs__doc__,
+"null_or_tuple_for_varargs($module, /, name, *constraints,\n"
+"                          covariant=False)\n"
+"--\n"
+"\n"
+"See https://github.com/python/cpython/issues/110864");
+
+#define NULL_OR_TUPLE_FOR_VARARGS_METHODDEF    \
+    {"null_or_tuple_for_varargs", _PyCFunction_CAST(null_or_tuple_for_varargs), METH_FASTCALL|METH_KEYWORDS, null_or_tuple_for_varargs__doc__},
+
+static PyObject *
+null_or_tuple_for_varargs_impl(PyObject *module, PyObject *name,
+                               PyObject *constraints, int covariant);
+
+static PyObject *
+null_or_tuple_for_varargs(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+    PyObject *return_value = NULL;
+    #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+    #define NUM_KEYWORDS 2
+    static struct {
+        PyGC_Head _this_is_not_used;
+        PyObject_VAR_HEAD
+        PyObject *ob_item[NUM_KEYWORDS];
+    } _kwtuple = {
+        .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+        .ob_item = { &_Py_ID(name), &_Py_ID(covariant), },
+    };
+    #undef NUM_KEYWORDS
+    #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+    #else  // !Py_BUILD_CORE
+    #  define KWTUPLE NULL
+    #endif  // !Py_BUILD_CORE
+
+    static const char * const _keywords[] = {"name", "covariant", NULL};
+    static _PyArg_Parser _parser = {
+        .keywords = _keywords,
+        .fname = "null_or_tuple_for_varargs",
+        .kwtuple = KWTUPLE,
+    };
+    #undef KWTUPLE
+    PyObject *argsbuf[3];
+    Py_ssize_t noptargs = Py_MIN(nargs, 1) + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
+    PyObject *name;
+    PyObject *constraints = NULL;
+    int covariant = 0;
+
+    args = _PyArg_UnpackKeywordsWithVararg(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, 1, argsbuf);
+    if (!args) {
+        goto exit;
+    }
+    name = args[0];
+    constraints = args[1];
+    if (!noptargs) {
+        goto skip_optional_kwonly;
+    }
+    covariant = PyObject_IsTrue(args[2]);
+    if (covariant < 0) {
+        goto exit;
+    }
+skip_optional_kwonly:
+    return_value = null_or_tuple_for_varargs_impl(module, name, constraints, covariant);
+
+exit:
+    Py_XDECREF(constraints);
+    return return_value;
+}
+
 PyDoc_STRVAR(clone_f1__doc__,
 "clone_f1($module, /, path)\n"
 "--\n"
@@ -3070,4 +3140,4 @@ skip_optional_pos:
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=c2a69a08ffdfc466 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=aea5f282f24761aa input=a9049054013a1b77]*/
index a0eef2ccee5c3786916515fbde1227171255bb5c..e21698619468d8c6f15ce7be14bef4cf9449a170 100644 (file)
@@ -2522,7 +2522,7 @@ _PyArg_UnpackKeywordsWithVararg(PyObject *const *args, Py_ssize_t nargs,
          *
          * Otherwise, we leave a place at `buf[vararg]` for vararg tuple
          * so the index is `i + 1`. */
-        if (nargs < vararg) {
+        if (nargs < vararg && i != vararg) {
             buf[i] = current_arg;
         }
         else {