Skip to content

Commit

Permalink
gh-64490: Fix bugs in argument clinic varargs processing (#32092)
Browse files Browse the repository at this point in the history
  • Loading branch information
colorfulappl authored Nov 24, 2022
1 parent 351842b commit 0da7283
Show file tree
Hide file tree
Showing 11 changed files with 612 additions and 11 deletions.
5 changes: 5 additions & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,9 @@ struct _Py_global_strings {
STRUCT_FOR_ID(keyfile)
STRUCT_FOR_ID(keys)
STRUCT_FOR_ID(kind)
STRUCT_FOR_ID(kw)
STRUCT_FOR_ID(kw1)
STRUCT_FOR_ID(kw2)
STRUCT_FOR_ID(lambda)
STRUCT_FOR_ID(last)
STRUCT_FOR_ID(last_node)
Expand Down Expand Up @@ -570,6 +573,8 @@ struct _Py_global_strings {
STRUCT_FOR_ID(pid)
STRUCT_FOR_ID(policy)
STRUCT_FOR_ID(pos)
STRUCT_FOR_ID(pos1)
STRUCT_FOR_ID(pos2)
STRUCT_FOR_ID(print_file_and_line)
STRUCT_FOR_ID(priority)
STRUCT_FOR_ID(progress)
Expand Down
5 changes: 5 additions & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions Lib/test/clinic.test
Original file line number Diff line number Diff line change
Expand Up @@ -3845,7 +3845,6 @@ test_vararg(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
};
#undef KWTUPLE
PyObject *argsbuf[2];
Py_ssize_t noptargs = 0 + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
PyObject *a;
PyObject *__clinic_args = NULL;

Expand All @@ -3864,7 +3863,7 @@ exit:

static PyObject *
test_vararg_impl(PyObject *module, PyObject *a, PyObject *args)
/*[clinic end generated code: output=6661f3ca97d85e8c input=81d33815ad1bae6e]*/
/*[clinic end generated code: output=880365c61ae205d7 input=81d33815ad1bae6e]*/

/*[clinic input]
test_vararg_with_default
Expand Down Expand Up @@ -3918,7 +3917,7 @@ test_vararg_with_default(PyObject *module, PyObject *const *args, Py_ssize_t nar
};
#undef KWTUPLE
PyObject *argsbuf[3];
Py_ssize_t noptargs = 0 + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
Py_ssize_t noptargs = Py_MIN(nargs, 1) + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
PyObject *a;
PyObject *__clinic_args = NULL;
int b = 0;
Expand Down Expand Up @@ -3947,7 +3946,7 @@ exit:
static PyObject *
test_vararg_with_default_impl(PyObject *module, PyObject *a, PyObject *args,
int b)
/*[clinic end generated code: output=5fe3cfccb1bef781 input=6e110b54acd9b22d]*/
/*[clinic end generated code: output=291e9a5a09831128 input=6e110b54acd9b22d]*/

/*[clinic input]
test_vararg_with_only_defaults
Expand Down
44 changes: 44 additions & 0 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,15 @@ def test_parameters_not_permitted_after_slash_for_now(self):
x: int
""")

def test_parameters_no_more_than_one_vararg(self):
s = self.parse_function_should_fail("""
module foo
foo.bar
*vararg1: object
*vararg2: object
""")
self.assertEqual(s, "Error on line 0:\nToo many var args\n")

def test_function_not_at_column_0(self):
function = self.parse_function("""
module foo
Expand Down Expand Up @@ -1222,13 +1231,47 @@ def test_keyword_only_parameter(self):
ac_tester.keyword_only_parameter(1)
self.assertEqual(ac_tester.keyword_only_parameter(a=1), (1,))

def test_posonly_vararg(self):
with self.assertRaises(TypeError):
ac_tester.posonly_vararg()
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)))

def test_vararg_and_posonly(self):
with self.assertRaises(TypeError):
ac_tester.vararg_and_posonly()
with self.assertRaises(TypeError):
ac_tester.vararg_and_posonly(1, b=2)
self.assertEqual(ac_tester.vararg_and_posonly(1, 2, 3, 4), (1, (2, 3, 4)))

def test_vararg(self):
with self.assertRaises(TypeError):
ac_tester.vararg()
with self.assertRaises(TypeError):
ac_tester.vararg(1, b=2)
self.assertEqual(ac_tester.vararg(1, 2, 3, 4), (1, (2, 3, 4)))

def test_vararg_with_default(self):
with self.assertRaises(TypeError):
ac_tester.vararg_with_default()
self.assertEqual(ac_tester.vararg_with_default(1, b=False), (1, (), False))
self.assertEqual(ac_tester.vararg_with_default(1, 2, 3, 4), (1, (2, 3, 4), False))
self.assertEqual(ac_tester.vararg_with_default(1, 2, 3, 4, b=True), (1, (2, 3, 4), True))

def test_vararg_with_only_defaults(self):
self.assertEqual(ac_tester.vararg_with_only_defaults(), ((), None))
self.assertEqual(ac_tester.vararg_with_only_defaults(b=2), ((), 2))
self.assertEqual(ac_tester.vararg_with_only_defaults(1, b=2), ((1, ), 2))
self.assertEqual(ac_tester.vararg_with_only_defaults(1, 2, 3, 4), ((1, 2, 3, 4), None))
self.assertEqual(ac_tester.vararg_with_only_defaults(1, 2, 3, 4, b=5), ((1, 2, 3, 4), 5))

def test_gh_32092_oob(self):
ac_tester.gh_32092_oob(1, 2, 3, 4, kw1=5, kw2=6)

def test_gh_32092_kw_pass(self):
ac_tester.gh_32092_kw_pass(1, 2, 3)

def test_gh_99233_refcount(self):
arg = '*A unique string is not referenced by anywhere else.*'
arg_refcount_origin = sys.getrefcount(arg)
Expand All @@ -1241,5 +1284,6 @@ def test_gh_99240_double_free(self):
with self.assertRaisesRegex(TypeError, expected_error):
ac_tester.gh_99240_double_free('a', '\0b')


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Argument Clinic varargs bugfixes

* Fix out-of-bounds error in :c:func:`!_PyArg_UnpackKeywordsWithVararg`.
* Fix incorrect check which allowed more than one varargs in clinic.py.
* Fix miscalculation of ``noptargs`` in generated code.
* Do not generate ``noptargs`` when there is a vararg argument and no optional argument.

119 changes: 119 additions & 0 deletions Modules/_testclinic.c
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,25 @@ keyword_only_parameter_impl(PyObject *module, PyObject *a)
}


/*[clinic input]
posonly_vararg
a: object
/
b: object
*args: object
[clinic start generated code]*/

static PyObject *
posonly_vararg_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *args)
/*[clinic end generated code: output=ee6713acda6b954e input=783427fe7ec2b67a]*/
{
return pack_arguments_newref(3, a, b, args);
}


/*[clinic input]
vararg_and_posonly
Expand All @@ -967,6 +986,100 @@ vararg_and_posonly_impl(PyObject *module, PyObject *a, PyObject *args)
}


/*[clinic input]
vararg
a: object
*args: object
[clinic start generated code]*/

static PyObject *
vararg_impl(PyObject *module, PyObject *a, PyObject *args)
/*[clinic end generated code: output=91ab7a0efc52dd5e input=02c0f772d05f591e]*/
{
return pack_arguments_newref(2, a, args);
}


/*[clinic input]
vararg_with_default
a: object
*args: object
b: bool = False
[clinic start generated code]*/

static PyObject *
vararg_with_default_impl(PyObject *module, PyObject *a, PyObject *args,
int b)
/*[clinic end generated code: output=182c01035958ce92 input=68cafa6a79f89e36]*/
{
PyObject *obj_b = b ? Py_True : Py_False;
return pack_arguments_newref(3, a, args, obj_b);
}


/*[clinic input]
vararg_with_only_defaults
*args: object
b: object = None
[clinic start generated code]*/

static PyObject *
vararg_with_only_defaults_impl(PyObject *module, PyObject *args, PyObject *b)
/*[clinic end generated code: output=c06b1826d91f2f7b input=678c069bc67550e1]*/
{
return pack_arguments_newref(2, args, b);
}



/*[clinic input]
gh_32092_oob
pos1: object
pos2: object
*varargs: object
kw1: object = None
kw2: object = None
Proof-of-concept of GH-32092 OOB bug.
[clinic start generated code]*/

static PyObject *
gh_32092_oob_impl(PyObject *module, PyObject *pos1, PyObject *pos2,
PyObject *varargs, PyObject *kw1, PyObject *kw2)
/*[clinic end generated code: output=ee259c130054653f input=46d15c881608f8ff]*/
{
Py_RETURN_NONE;
}


/*[clinic input]
gh_32092_kw_pass
pos: object
*args: object
kw: object = None
Proof-of-concept of GH-32092 keyword args passing bug.
[clinic start generated code]*/

static PyObject *
gh_32092_kw_pass_impl(PyObject *module, PyObject *pos, PyObject *args,
PyObject *kw)
/*[clinic end generated code: output=4a2bbe4f7c8604e9 input=5c0bd5b9079a0cce]*/
{
Py_RETURN_NONE;
}


/*[clinic input]
gh_99233_refcount
Expand Down Expand Up @@ -1046,7 +1159,13 @@ static PyMethodDef tester_methods[] = {
POSONLY_KEYWORDS_OPT_KWONLY_OPT_METHODDEF
POSONLY_OPT_KEYWORDS_OPT_KWONLY_OPT_METHODDEF
KEYWORD_ONLY_PARAMETER_METHODDEF
POSONLY_VARARG_METHODDEF
VARARG_AND_POSONLY_METHODDEF
VARARG_METHODDEF
VARARG_WITH_DEFAULT_METHODDEF
VARARG_WITH_ONLY_DEFAULTS_METHODDEF
GH_32092_OOB_METHODDEF
GH_32092_KW_PASS_METHODDEF
GH_99233_REFCOUNT_METHODDEF
GH_99240_DOUBLE_FREE_METHODDEF
{NULL, NULL}
Expand Down
Loading

0 comments on commit 0da7283

Please sign in to comment.