mirror of
https://github.com/python/cpython.git
synced 2025-08-29 21:25:01 +00:00
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
This commit is contained in:
parent
f3cb68f2e4
commit
0567786d26
10 changed files with 43 additions and 46 deletions
|
@ -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:
|
||||||
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.
|
||||||
|
|
||||||
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)
|
||||||
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?
|
||||||
>>> 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
|
||||||
>>> 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
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
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,
|
||||||
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)
|
||||||
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.
|
||||||
|
|
||||||
|
This also checks that all keyword names are strings. If not, a TypeError is
|
||||||
|
raised.
|
||||||
|
|
||||||
The newly allocated argument vector supports PY_VECTORCALL_ARGUMENTS_OFFSET.
|
The newly allocated argument vector supports PY_VECTORCALL_ARGUMENTS_OFFSET.
|
||||||
|
|
||||||
When done, you must call _PyStack_UnpackDict_Free(stack, nargs, kwnames)
|
When done, you must call _PyStack_UnpackDict_Free(stack, nargs, kwnames) */
|
||||||
|
|
||||||
The type of keyword keys is not checked, these checks should be done
|
|
||||||
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,
|
||||||
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,
|
||||||
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:
|
||||||
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)
|
||||||
_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,
|
|
||||||
"%.200s%.200s keywords must be strings",
|
|
||||||
PyEval_GetFuncName(func),
|
|
||||||
PyEval_GetFuncDesc(func));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_PyErr_Format(tstate, PyExc_TypeError,
|
_PyErr_Format(tstate, PyExc_TypeError,
|
||||||
"%.200s%.200s got multiple "
|
"%.200s%.200s got multiple "
|
||||||
"values for keyword argument '%U'",
|
"values for keyword argument '%S'",
|
||||||
PyEval_GetFuncName(func),
|
PyEval_GetFuncName(func),
|
||||||
PyEval_GetFuncDesc(func),
|
PyEval_GetFuncDesc(func),
|
||||||
key);
|
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)
|
||||||
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,
|
||||||
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,
|
||||||
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,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue