Skip to content

[3.11] gh-94938: Fix errror detection of unexpected keyword arguments… #95353

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Lib/test/test_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@
import contextlib


class BadStr(str):
def __eq__(self, other):
return True
def __hash__(self):
# Guaranteed different hash
return str.__hash__(self) ^ 3

def __eq__(self, other):
return False
def __hash__(self):
return str.__hash__(self)


class FunctionCalls(unittest.TestCase):

def test_kwargs_order(self):
Expand Down Expand Up @@ -145,6 +158,18 @@ def test_varargs17_kw(self):
self.assertRaisesRegex(TypeError, msg,
print, 0, sep=1, end=2, file=3, flush=4, foo=5)

def test_varargs18_kw(self):
# _PyArg_UnpackKeywordsWithVararg()
msg = r"invalid keyword argument for print\(\)$"
with self.assertRaisesRegex(TypeError, msg):
print(0, 1, **{BadStr('foo'): ','})

def test_varargs19_kw(self):
# _PyArg_UnpackKeywords()
msg = r"invalid keyword argument for round\(\)$"
with self.assertRaisesRegex(TypeError, msg):
round(1.75, **{BadStr('foo'): 1})

def test_oldargs0_1(self):
msg = r"keys\(\) takes no arguments \(1 given\)"
self.assertRaisesRegex(TypeError, msg, {}.keys, 0)
Expand Down
27 changes: 27 additions & 0 deletions Lib/test/test_getargs2.py
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,33 @@ def test_surrogate_keyword(self):
"'\udc80' is an invalid keyword argument for this function"):
getargs_keyword_only(1, 2, **{'\uDC80': 10})

def test_weird_str_subclass(self):
class BadStr(str):
def __eq__(self, other):
return True
def __hash__(self):
# Guaranteed different hash
return str.__hash__(self) ^ 3
with self.assertRaisesRegex(TypeError,
"invalid keyword argument for this function"):
getargs_keyword_only(1, 2, **{BadStr("keyword_only"): 3})
with self.assertRaisesRegex(TypeError,
"invalid keyword argument for this function"):
getargs_keyword_only(1, 2, **{BadStr("monster"): 666})

def test_weird_str_subclass2(self):
class BadStr(str):
def __eq__(self, other):
return False
def __hash__(self):
return str.__hash__(self)
with self.assertRaisesRegex(TypeError,
"invalid keyword argument for this function"):
getargs_keyword_only(1, 2, **{BadStr("keyword_only"): 3})
with self.assertRaisesRegex(TypeError,
"invalid keyword argument for this function"):
getargs_keyword_only(1, 2, **{BadStr("monster"): 666})


class PositionalOnlyAndKeywords_TestCase(unittest.TestCase):
from _testcapi import getargs_positional_only_and_keywords as getargs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix error detection in some builtin functions when keyword argument name is
an instance of a str subclass with overloaded ``__eq__`` and ``__hash__``.
Previously it could cause SystemError or other undesired behavior.
142 changes: 57 additions & 85 deletions Python/getargs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1554,6 +1554,50 @@ _PyArg_VaParseTupleAndKeywordsFast_SizeT(PyObject *args, PyObject *keywords,
return retval;
}

static void
error_unexpected_keyword_arg(PyObject *kwargs, PyObject *kwnames, PyObject *kwtuple, const char *fname)
{
/* make sure there are no extraneous keyword arguments */
Py_ssize_t j = 0;
while (1) {
PyObject *keyword;
if (kwargs != NULL) {
if (!PyDict_Next(kwargs, &j, &keyword, NULL))
break;
}
else {
if (j >= PyTuple_GET_SIZE(kwnames))
break;
keyword = PyTuple_GET_ITEM(kwnames, j);
j++;
}
if (!PyUnicode_Check(keyword)) {
PyErr_SetString(PyExc_TypeError,
"keywords must be strings");
return;
}

int match = PySequence_Contains(kwtuple, keyword);
if (match <= 0) {
if (!match) {
PyErr_Format(PyExc_TypeError,
"'%S' is an invalid keyword "
"argument for %.200s%s",
keyword,
(fname == NULL) ? "this function" : fname,
(fname == NULL) ? "" : "()");
}
return;
}
}
/* Something wrong happened. There are extraneous keyword arguments,
* but we don't know what. And we don't bother. */
PyErr_Format(PyExc_TypeError,
"invalid keyword argument for %.200s%s",
(fname == NULL) ? "this function" : fname,
(fname == NULL) ? "" : "()");
}

int
PyArg_ValidateKeywordArguments(PyObject *kwargs)
{
Expand Down Expand Up @@ -1842,6 +1886,13 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
return cleanreturn(0, &freelist);
}
}
/* Something wrong happened. There are extraneous keyword arguments,
* but we don't know what. And we don't bother. */
PyErr_Format(PyExc_TypeError,
"invalid keyword argument for %.200s%s",
(fname == NULL) ? "this function" : fname,
(fname == NULL) ? "" : "()");
return cleanreturn(0, &freelist);
}

return cleanreturn(1, &freelist);
Expand Down Expand Up @@ -2184,7 +2235,6 @@ vgetargskeywordsfast_impl(PyObject *const *args, Py_ssize_t nargs,
assert(IS_END_OF_FORMAT(*format) || (*format == '|') || (*format == '$'));

if (nkwargs > 0) {
Py_ssize_t j;
/* make sure there are no arguments given by name and position */
for (i = pos; i < nargs; i++) {
keyword = PyTuple_GET_ITEM(kwtuple, i - pos);
Expand All @@ -2208,34 +2258,9 @@ vgetargskeywordsfast_impl(PyObject *const *args, Py_ssize_t nargs,
return cleanreturn(0, &freelist);
}
}
/* make sure there are no extraneous keyword arguments */
j = 0;
while (1) {
int match;
if (kwargs != NULL) {
if (!PyDict_Next(kwargs, &j, &keyword, NULL))
break;
}
else {
if (j >= PyTuple_GET_SIZE(kwnames))
break;
keyword = PyTuple_GET_ITEM(kwnames, j);
j++;
}

match = PySequence_Contains(kwtuple, keyword);
if (match <= 0) {
if (!match) {
PyErr_Format(PyExc_TypeError,
"'%S' is an invalid keyword "
"argument for %.200s%s",
keyword,
(parser->fname == NULL) ? "this function" : parser->fname,
(parser->fname == NULL) ? "" : "()");
}
return cleanreturn(0, &freelist);
}
}
error_unexpected_keyword_arg(kwargs, kwnames, kwtuple, parser->fname);
return cleanreturn(0, &freelist);
}

return cleanreturn(1, &freelist);
Expand Down Expand Up @@ -2409,7 +2434,6 @@ _PyArg_UnpackKeywords(PyObject *const *args, Py_ssize_t nargs,
}

if (nkwargs > 0) {
Py_ssize_t j;
/* make sure there are no arguments given by name and position */
for (i = posonly; i < nargs; i++) {
keyword = PyTuple_GET_ITEM(kwtuple, i - posonly);
Expand All @@ -2433,34 +2457,9 @@ _PyArg_UnpackKeywords(PyObject *const *args, Py_ssize_t nargs,
return NULL;
}
}
/* make sure there are no extraneous keyword arguments */
j = 0;
while (1) {
int match;
if (kwargs != NULL) {
if (!PyDict_Next(kwargs, &j, &keyword, NULL))
break;
}
else {
if (j >= PyTuple_GET_SIZE(kwnames))
break;
keyword = PyTuple_GET_ITEM(kwnames, j);
j++;
}

match = PySequence_Contains(kwtuple, keyword);
if (match <= 0) {
if (!match) {
PyErr_Format(PyExc_TypeError,
"'%S' is an invalid keyword "
"argument for %.200s%s",
keyword,
(parser->fname == NULL) ? "this function" : parser->fname,
(parser->fname == NULL) ? "" : "()");
}
return NULL;
}
}
error_unexpected_keyword_arg(kwargs, kwnames, kwtuple, parser->fname);
return NULL;
}

return buf;
Expand Down Expand Up @@ -2589,35 +2588,8 @@ _PyArg_UnpackKeywordsWithVararg(PyObject *const *args, Py_ssize_t nargs,
}

if (nkwargs > 0) {
Py_ssize_t j;
/* make sure there are no extraneous keyword arguments */
j = 0;
while (1) {
int match;
if (kwargs != NULL) {
if (!PyDict_Next(kwargs, &j, &keyword, NULL))
break;
}
else {
if (j >= PyTuple_GET_SIZE(kwnames))
break;
keyword = PyTuple_GET_ITEM(kwnames, j);
j++;
}

match = PySequence_Contains(kwtuple, keyword);
if (match <= 0) {
if (!match) {
PyErr_Format(PyExc_TypeError,
"'%S' is an invalid keyword "
"argument for %.200s%s",
keyword,
(parser->fname == NULL) ? "this function" : parser->fname,
(parser->fname == NULL) ? "" : "()");
}
goto exit;
}
}
error_unexpected_keyword_arg(kwargs, kwnames, kwtuple, parser->fname);
goto exit;
}

return buf;
Expand Down