Skip to content

gh-118814: Fix crash in _PyArg_UnpackKeywordsWithVararg #122558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
95 changes: 95 additions & 0 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Licensed to the PSF under a contributor agreement.

from functools import partial
from itertools import permutations
from test import support, test_tools
from test.support import os_helper
from test.support.os_helper import TESTFN, unlink, rmtree
Expand Down Expand Up @@ -3333,12 +3334,106 @@ def test_vararg(self):
ac_tester.vararg(1, b=2)
self.assertEqual(ac_tester.vararg(1, 2, 3, 4), (1, (2, 3, 4)))

def test_vararg_with_multiple_pos(self):
# vararg_with_multiple_pos(a, b, *args)
func = ac_tester.vararg_with_multiple_pos
with self.assertRaises(TypeError):
func()

self.assertTupleEqual(func(1, 2), (1, 2, ()))
self.assertTupleEqual(func(1, b=2), (1, 2, ()))
self.assertTupleEqual(func(a=1, b=2), (1, 2, ()))
self.assertTupleEqual(func(1, 2, 'c'), (1, 2, ('c',)))
self.assertTupleEqual(func(1, 2, 'c', 'd'), (1, 2, ('c', 'd')))

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))
self.assertEqual(ac_tester.vararg_with_default(a=1, b=False), (1, (), False))
self.assertEqual(ac_tester.vararg_with_default(b=False, a=1), (1, (), False))

def test_vararg_with_more_defaults(self):
# vararg_with_more_defaults(a, *args, kw1=False, kw2=False)
with self.assertRaises(TypeError):
ac_tester.vararg_with_more_defaults()

check = self.assertTupleEqual
fn = ac_tester.vararg_with_more_defaults

check(fn(1), (1, (), False, False))

check(fn(1, 'x'), (1, ('x',), False, False))
check(fn(1, 'x', kw1=True), (1, ('x',), True, False))
check(fn(1, 'x', kw2=True), (1, ('x',), False, True))
check(fn(1, 'x', kw1=True, kw2=True), (1, ('x',), True, True))
check(fn(1, 'x', kw2=True, kw1=True), (1, ('x',), True, True))

check(fn(1, 'x', 'y'), (1, ('x', 'y'), False, False))
check(fn(1, 'x', 'y', kw1=True), (1, ('x', 'y'), True, False))
check(fn(1, 'x', 'y', kw2=True), (1, ('x', 'y'), False, True))
check(fn(1, 'x', 'y', kw1=True, kw2=True), (1, ('x', 'y'), True, True))
check(fn(1, 'x', 'y', kw2=True, kw1=True), (1, ('x', 'y'), True, True))

check(fn(1, kw1=True), (1, (), True, False))
check(fn(1, kw2=True), (1, (), False, True))
check(fn(1, kw1=True, kw2=True), (1, (), True, True))
check(fn(1, kw2=True, kw1=True), (1, (), True, True))

check(fn(a=1), (1, (), False, False))
check(fn(a=1, kw1=True), (1, (), True, False))
check(fn(kw1=True, a=1), (1, (), True, False))
check(fn(a=1, kw2=True), (1, (), False, True))
check(fn(kw2=True, a=1), (1, (), False, True))

for kwds in permutations(dict(a=1, kw1=True, kw2=True).items()):
check(fn(**dict(kwds)), (1, (), True, True))

def test_vararg_with_more_defaults_and_pos(self):
# vararg_with_more_defaults_and_pos(a, b, *args, kw1=False, kw2=False)
with self.assertRaises(TypeError):
ac_tester.vararg_with_more_defaults_and_pos()

check = self.assertTupleEqual
fn = ac_tester.vararg_with_more_defaults_and_pos

check(fn(1, 2), (1, 2, (), False, False))

check(fn(1, 2, kw1=True), (1, 2, (), True, False))
check(fn(1, 2, kw2=True), (1, 2, (), False, True))
check(fn(1, 2, kw1=True, kw2=True), (1, 2, (), True, True))
check(fn(1, 2, kw2=True, kw1=True), (1, 2, (), True, True))

check(fn(1, b=1), (1, 1, (), False, False))
check(fn(1, b=1, kw1=True), (1, 1, (), True, False))
check(fn(1, kw1=True, b=1), (1, 1, (), True, False))
check(fn(1, b=1, kw2=True), (1, 1, (), False, True))
check(fn(1, kw2=True, b=1), (1, 1, (), False, True))

check(fn(1, 2, 'x'), (1, 2, ('x',), False, False))
check(fn(1, 2, 'x', 'y'), (1, 2, ('x', 'y'), False, False))
check(fn(1, 2, 'x', kw1=True), (1, 2, ('x',), True, False))
check(fn(1, 2, 'x', 'y', kw1=True), (1, 2, ('x', 'y'), True, False))
check(fn(1, 2, 'x', kw2=True), (1, 2, ('x',), False, True))
check(fn(1, 2, 'x', 'y', kw2=True), (1, 2, ('x', 'y'), False, True))
check(fn(1, 2, 'x', kw1=True, kw2=True), (1, 2, ('x',), True, True))
check(fn(1, 2, 'x', 'y', kw1=True, kw2=True), (1, 2, ('x', 'y'), True, True))
check(fn(1, 2, 'x', kw2=True, kw1=True), (1, 2, ('x',), True, True))
check(fn(1, 2, 'x', 'y', kw2=True, kw1=True), (1, 2, ('x', 'y'), True, True))

check(fn(a=1, b=2), (1, 2, (), False, False))
check(fn(b=2, a=1), (1, 2, (), False, False))

for kwds in permutations(dict(a=1, b=2, kw1=True).items()):
check(fn(**dict(kwds)), (1, 2, (), True, False))
for kwds in permutations(dict(a=1, b=2, kw2=True).items()):
check(fn(**dict(kwds)), (1, 2, (), False, True))
for kwds in permutations(dict(b=2, kw1=True, kw2=True).items()):
check(fn(1, **dict(kwds)), (1, 2, (), True, True))
for kwds in permutations(dict(a=1, b=2, kw1=True, kw2=True).items()):
check(fn(**dict(kwds)), (1, 2, (), True, True))

def test_vararg_with_only_defaults(self):
self.assertEqual(ac_tester.vararg_with_only_defaults(), ((), None))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix argument parsing by ``_PyArg_UnpackKeywordsWithVararg`` when passing
positional as keyword arguments before variadic arguments. Patch by Bénédikt
Tran.
66 changes: 66 additions & 0 deletions Modules/_testclinic.c
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,24 @@ vararg_impl(PyObject *module, PyObject *a, PyObject *args)
}


/*[clinic input]
vararg_with_multiple_pos

a: object
b: object
*args: object

[clinic start generated code]*/

static PyObject *
vararg_with_multiple_pos_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *args)
/*[clinic end generated code: output=7fe8cbc4165d8592 input=49b49a877d24f459]*/
{
return pack_arguments_newref(3, a, b, args);
}


/*[clinic input]
vararg_with_default

Expand All @@ -1033,6 +1051,51 @@ vararg_with_default_impl(PyObject *module, PyObject *a, PyObject *args,
}


/*[clinic input]
vararg_with_more_defaults

a: object
*args: object
kw1: bool = False
kw2: bool = False

[clinic start generated code]*/

static PyObject *
vararg_with_more_defaults_impl(PyObject *module, PyObject *a, PyObject *args,
int kw1, int kw2)
/*[clinic end generated code: output=efb60dafc084a301 input=d84a0e8641b30838]*/
{
PyObject *obj_kw1 = kw1 ? Py_True : Py_False;
PyObject *obj_kw2 = kw2 ? Py_True : Py_False;
return pack_arguments_newref(4, a, args, obj_kw1, obj_kw2);
}


/*[clinic input]
vararg_with_more_defaults_and_pos

a: object
b: object
*args: object
kw1: bool = False
kw2: bool = False

[clinic start generated code]*/

static PyObject *
vararg_with_more_defaults_and_pos_impl(PyObject *module, PyObject *a,
PyObject *b, PyObject *args, int kw1,
int kw2)
/*[clinic end generated code: output=9828348aee06bd21 input=447044ec4b59bb45]*/

{
PyObject *obj_kw1 = kw1 ? Py_True : Py_False;
PyObject *obj_kw2 = kw2 ? Py_True : Py_False;
return pack_arguments_newref(5, a, b, args, obj_kw1, obj_kw2);
}


/*[clinic input]
vararg_with_only_defaults

Expand Down Expand Up @@ -1906,7 +1969,10 @@ static PyMethodDef tester_methods[] = {
POSONLY_VARARG_METHODDEF
VARARG_AND_POSONLY_METHODDEF
VARARG_METHODDEF
VARARG_WITH_MULTIPLE_POS_METHODDEF
VARARG_WITH_DEFAULT_METHODDEF
VARARG_WITH_MORE_DEFAULTS_METHODDEF
VARARG_WITH_MORE_DEFAULTS_AND_POS_METHODDEF
VARARG_WITH_ONLY_DEFAULTS_METHODDEF
GH_32092_OOB_METHODDEF
GH_32092_KW_PASS_METHODDEF
Expand Down
Loading
Loading