Commit 0567786d authored by Jeroen Demeyer's avatar Jeroen Demeyer Committed by Miss Islington (bot)

bpo-37540: vectorcall: keyword names must be strings (GH-14682)



The fact that keyword names are strings is now part of the vectorcall and `METH_FASTCALL` protocols. The biggest concrete change is that `_PyStack_UnpackDict` now checks that and raises `TypeError` if not.

CC @markshannon @vstinner 


https://bugs.python.org/issue37540
parent f3cb68f2
...@@ -400,8 +400,8 @@ Object Protocol ...@@ -400,8 +400,8 @@ Object Protocol
:c:func:`PyVectorcall_NARGS(nargsf) <PyVectorcall_NARGS>`. :c:func:`PyVectorcall_NARGS(nargsf) <PyVectorcall_NARGS>`.
*kwnames* can be either NULL (no keyword arguments) or a tuple of keyword *kwnames* can be either NULL (no keyword arguments) or a tuple of keyword
names. In the latter case, the values of the keyword arguments are stored names, which must be strings. In the latter case, the values of the keyword
in *args* after the positional arguments. arguments are stored in *args* after the positional arguments.
The number of keyword arguments does not influence *nargsf*. The number of keyword arguments does not influence *nargsf*.
*kwnames* must contain only objects of type ``str`` (not a subclass), *kwnames* must contain only objects of type ``str`` (not a subclass),
......
...@@ -204,6 +204,7 @@ also keyword arguments. So there are a total of 6 calling conventions: ...@@ -204,6 +204,7 @@ also keyword arguments. So there are a total of 6 calling conventions:
Keyword arguments are passed the same way as in the vectorcall protocol: Keyword arguments are passed the same way as in the vectorcall protocol:
there is an additional fourth :c:type:`PyObject\*` parameter there is an additional fourth :c:type:`PyObject\*` parameter
which is a tuple representing the names of the keyword arguments which is a tuple representing the names of the keyword arguments
(which are guaranteed to be strings)
or possibly *NULL* if there are no keywords. The values of the keyword or possibly *NULL* if there are no keywords. The values of the keyword
arguments are stored in the *args* array, after the positional arguments. arguments are stored in the *args* array, after the positional arguments.
......
...@@ -1142,8 +1142,10 @@ All of the following opcodes use their arguments. ...@@ -1142,8 +1142,10 @@ All of the following opcodes use their arguments.
Calls a callable object with positional (if any) and keyword arguments. Calls a callable object with positional (if any) and keyword arguments.
*argc* indicates the total number of positional and keyword arguments. *argc* indicates the total number of positional and keyword arguments.
The top element on the stack contains a tuple of keyword argument names. The top element on the stack contains a tuple with the names of the
Below that are keyword arguments in the order corresponding to the tuple. keyword arguments, which must be strings.
Below that are the values for the keyword arguments,
in the order corresponding to the tuple.
Below that are positional arguments, with the right-most parameter on Below that are positional arguments, with the right-most parameter on
top. Below the arguments is a callable object to call. top. Below the arguments is a callable object to call.
``CALL_FUNCTION_KW`` pops all arguments and the callable object off the stack, ``CALL_FUNCTION_KW`` pops all arguments and the callable object off the stack,
......
...@@ -88,8 +88,7 @@ _PyVectorcall_Function(PyObject *callable) ...@@ -88,8 +88,7 @@ _PyVectorcall_Function(PyObject *callable)
of keyword arguments does not change nargsf). kwnames can also be NULL if of keyword arguments does not change nargsf). kwnames can also be NULL if
there are no keyword arguments. there are no keyword arguments.
keywords must only contains str strings (no subclass), and all keys must keywords must only contain strings and all keys must be unique.
be unique.
Return the result on success. Raise an exception and return NULL on Return the result on success. Raise an exception and return NULL on
error. */ error. */
......
...@@ -237,7 +237,7 @@ What about willful misconduct? ...@@ -237,7 +237,7 @@ What about willful misconduct?
>>> f(**{1:2}) >>> f(**{1:2})
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: f() keywords must be strings TypeError: keywords must be strings
>>> h(**{'e': 2}) >>> h(**{'e': 2})
Traceback (most recent call last): Traceback (most recent call last):
......
...@@ -256,7 +256,7 @@ Overridden parameters ...@@ -256,7 +256,7 @@ Overridden parameters
>>> f(**{1: 3}, **{1: 5}) >>> f(**{1: 3}, **{1: 5})
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: f() keywords must be strings TypeError: f() got multiple values for keyword argument '1'
Unpacking non-sequence Unpacking non-sequence
......
The vectorcall protocol now requires that the caller passes only strings as
keyword names.
...@@ -322,8 +322,7 @@ _PyFunction_Vectorcall(PyObject *func, PyObject* const* stack, ...@@ -322,8 +322,7 @@ _PyFunction_Vectorcall(PyObject *func, PyObject* const* stack,
assert(nargs >= 0); assert(nargs >= 0);
assert(kwnames == NULL || PyTuple_CheckExact(kwnames)); assert(kwnames == NULL || PyTuple_CheckExact(kwnames));
assert((nargs == 0 && nkwargs == 0) || stack != NULL); assert((nargs == 0 && nkwargs == 0) || stack != NULL);
/* kwnames must only contains str strings, no subclass, and all keys must /* kwnames must only contain strings and all keys must be unique */
be unique */
if (co->co_kwonlyargcount == 0 && nkwargs == 0 && if (co->co_kwonlyargcount == 0 && nkwargs == 0 &&
(co->co_flags & ~PyCF_MASK) == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) (co->co_flags & ~PyCF_MASK) == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE))
...@@ -943,12 +942,12 @@ _PyStack_AsDict(PyObject *const *values, PyObject *kwnames) ...@@ -943,12 +942,12 @@ _PyStack_AsDict(PyObject *const *values, PyObject *kwnames)
vector; return NULL with exception set on error. Return the keyword names vector; return NULL with exception set on error. Return the keyword names
tuple in *p_kwnames. tuple in *p_kwnames.
The newly allocated argument vector supports PY_VECTORCALL_ARGUMENTS_OFFSET. This also checks that all keyword names are strings. If not, a TypeError is
raised.
When done, you must call _PyStack_UnpackDict_Free(stack, nargs, kwnames) The newly allocated argument vector supports PY_VECTORCALL_ARGUMENTS_OFFSET.
The type of keyword keys is not checked, these checks should be done When done, you must call _PyStack_UnpackDict_Free(stack, nargs, kwnames) */
later (ex: _PyArg_ParseStackAndKeywords). */
static PyObject *const * static PyObject *const *
_PyStack_UnpackDict(PyObject *const *args, Py_ssize_t nargs, PyObject *kwargs, _PyStack_UnpackDict(PyObject *const *args, Py_ssize_t nargs, PyObject *kwargs,
PyObject **p_kwnames) PyObject **p_kwnames)
...@@ -994,7 +993,9 @@ _PyStack_UnpackDict(PyObject *const *args, Py_ssize_t nargs, PyObject *kwargs, ...@@ -994,7 +993,9 @@ _PyStack_UnpackDict(PyObject *const *args, Py_ssize_t nargs, PyObject *kwargs,
called in the performance critical hot code. */ called in the performance critical hot code. */
Py_ssize_t pos = 0, i = 0; Py_ssize_t pos = 0, i = 0;
PyObject *key, *value; PyObject *key, *value;
unsigned long keys_are_strings = Py_TPFLAGS_UNICODE_SUBCLASS;
while (PyDict_Next(kwargs, &pos, &key, &value)) { while (PyDict_Next(kwargs, &pos, &key, &value)) {
keys_are_strings &= Py_TYPE(key)->tp_flags;
Py_INCREF(key); Py_INCREF(key);
Py_INCREF(value); Py_INCREF(value);
PyTuple_SET_ITEM(kwnames, i, key); PyTuple_SET_ITEM(kwnames, i, key);
...@@ -1002,6 +1003,18 @@ _PyStack_UnpackDict(PyObject *const *args, Py_ssize_t nargs, PyObject *kwargs, ...@@ -1002,6 +1003,18 @@ _PyStack_UnpackDict(PyObject *const *args, Py_ssize_t nargs, PyObject *kwargs,
i++; i++;
} }
/* keys_are_strings has the value Py_TPFLAGS_UNICODE_SUBCLASS if that
* flag is set for all keys. Otherwise, keys_are_strings equals 0.
* We do this check once at the end instead of inside the loop above
* because it simplifies the deallocation in the failing case.
* It happens to also make the loop above slightly more efficient. */
if (!keys_are_strings) {
PyErr_SetString(PyExc_TypeError,
"keywords must be strings");
_PyStack_UnpackDict_Free(stack, nargs, kwnames);
return NULL;
}
*p_kwnames = kwnames; *p_kwnames = kwnames;
return stack; return stack;
} }
......
...@@ -3504,7 +3504,9 @@ main_loop: ...@@ -3504,7 +3504,9 @@ main_loop:
PyObject **sp, *res, *names; PyObject **sp, *res, *names;
names = POP(); names = POP();
assert(PyTuple_CheckExact(names) && PyTuple_GET_SIZE(names) <= oparg); assert(PyTuple_Check(names));
assert(PyTuple_GET_SIZE(names) <= oparg);
/* We assume without checking that names contains only strings */
sp = stack_pointer; sp = stack_pointer;
res = call_function(tstate, &sp, oparg, names); res = call_function(tstate, &sp, oparg, names);
stack_pointer = sp; stack_pointer = sp;
...@@ -5372,20 +5374,12 @@ format_kwargs_error(PyThreadState *tstate, PyObject *func, PyObject *kwargs) ...@@ -5372,20 +5374,12 @@ format_kwargs_error(PyThreadState *tstate, PyObject *func, PyObject *kwargs)
_PyErr_Fetch(tstate, &exc, &val, &tb); _PyErr_Fetch(tstate, &exc, &val, &tb);
if (val && PyTuple_Check(val) && PyTuple_GET_SIZE(val) == 1) { if (val && PyTuple_Check(val) && PyTuple_GET_SIZE(val) == 1) {
PyObject *key = PyTuple_GET_ITEM(val, 0); PyObject *key = PyTuple_GET_ITEM(val, 0);
if (!PyUnicode_Check(key)) { _PyErr_Format(tstate, PyExc_TypeError,
_PyErr_Format(tstate, PyExc_TypeError, "%.200s%.200s got multiple "
"%.200s%.200s keywords must be strings", "values for keyword argument '%S'",
PyEval_GetFuncName(func), PyEval_GetFuncName(func),
PyEval_GetFuncDesc(func)); PyEval_GetFuncDesc(func),
} key);
else {
_PyErr_Format(tstate, PyExc_TypeError,
"%.200s%.200s got multiple "
"values for keyword argument '%U'",
PyEval_GetFuncName(func),
PyEval_GetFuncDesc(func),
key);
}
Py_XDECREF(exc); Py_XDECREF(exc);
Py_XDECREF(val); Py_XDECREF(val);
Py_XDECREF(tb); Py_XDECREF(tb);
......
...@@ -2043,11 +2043,7 @@ find_keyword(PyObject *kwnames, PyObject *const *kwstack, PyObject *key) ...@@ -2043,11 +2043,7 @@ find_keyword(PyObject *kwnames, PyObject *const *kwstack, PyObject *key)
if (kwname == key) { if (kwname == key) {
return kwstack[i]; return kwstack[i];
} }
if (!PyUnicode_Check(kwname)) { assert(PyUnicode_Check(kwname));
/* ignore non-string keyword keys:
an error will be raised below */
continue;
}
if (_PyUnicode_EQ(kwname, key)) { if (_PyUnicode_EQ(kwname, key)) {
return kwstack[i]; return kwstack[i];
} }
...@@ -2275,16 +2271,11 @@ vgetargskeywordsfast_impl(PyObject *const *args, Py_ssize_t nargs, ...@@ -2275,16 +2271,11 @@ vgetargskeywordsfast_impl(PyObject *const *args, Py_ssize_t nargs,
j++; j++;
} }
if (!PyUnicode_Check(keyword)) {
PyErr_SetString(PyExc_TypeError,
"keywords must be strings");
return cleanreturn(0, &freelist);
}
match = PySequence_Contains(kwtuple, keyword); match = PySequence_Contains(kwtuple, keyword);
if (match <= 0) { if (match <= 0) {
if (!match) { if (!match) {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
"'%U' is an invalid keyword " "'%S' is an invalid keyword "
"argument for %.200s%s", "argument for %.200s%s",
keyword, keyword,
(parser->fname == NULL) ? "this function" : parser->fname, (parser->fname == NULL) ? "this function" : parser->fname,
...@@ -2505,16 +2496,11 @@ _PyArg_UnpackKeywords(PyObject *const *args, Py_ssize_t nargs, ...@@ -2505,16 +2496,11 @@ _PyArg_UnpackKeywords(PyObject *const *args, Py_ssize_t nargs,
j++; j++;
} }
if (!PyUnicode_Check(keyword)) {
PyErr_SetString(PyExc_TypeError,
"keywords must be strings");
return NULL;
}
match = PySequence_Contains(kwtuple, keyword); match = PySequence_Contains(kwtuple, keyword);
if (match <= 0) { if (match <= 0) {
if (!match) { if (!match) {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
"'%U' is an invalid keyword " "'%S' is an invalid keyword "
"argument for %.200s%s", "argument for %.200s%s",
keyword, keyword,
(parser->fname == NULL) ? "this function" : parser->fname, (parser->fname == NULL) ? "this function" : parser->fname,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment