| /* getargskeywordsfast implementation copied from Python 3.9 and stripped down to |
| * only include the functionality we need. |
| * |
| * We also add support for required kwonly args and accepting *args / **kwargs. |
| * |
| * DOCUMENTATION OF THE EXTENSIONS: |
| * - Arguments given after a @ format specify required keyword-only arguments. |
| * The | and $ specifiers must both appear before @. |
| * - If the first character of a format string is %, then the function can support |
| * *args and/or **kwargs. In this case the parser will consume two arguments, |
| * which should be pointers to variables to store the *args and **kwargs, respectively. |
| * Either pointer can be NULL, in which case the function doesn't take that |
| * variety of vararg. |
| * Unlike most format specifiers, the caller takes ownership of these objects |
| * and is responsible for decrefing them. |
| */ |
| |
| #include <Python.h> |
| #include "CPy.h" |
| |
| /* None of this is supported on Python 3.6 or earlier */ |
| #if PY_VERSION_HEX >= 0x03070000 |
| |
| #define PARSER_INITED(parser) ((parser)->kwtuple != NULL) |
| |
| /* Forward */ |
| static int |
| vgetargskeywordsfast_impl(PyObject *const *args, Py_ssize_t nargs, |
| PyObject *kwargs, PyObject *kwnames, |
| CPyArg_Parser *parser, |
| va_list *p_va); |
| static void skipitem_fast(const char **, va_list *); |
| |
| /* Parse args for an arbitrary signature */ |
| int |
| CPyArg_ParseStackAndKeywords(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames, |
| CPyArg_Parser *parser, ...) |
| { |
| int retval; |
| va_list va; |
| |
| va_start(va, parser); |
| retval = vgetargskeywordsfast_impl(args, nargs, NULL, kwnames, parser, &va); |
| va_end(va); |
| return retval; |
| } |
| |
| /* Parse args for a function that takes no args */ |
| int |
| CPyArg_ParseStackAndKeywordsNoArgs(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames, |
| CPyArg_Parser *parser, ...) |
| { |
| int retval; |
| va_list va; |
| |
| va_start(va, parser); |
| if (nargs == 0 && kwnames == NULL) { |
| // Fast path: no arguments |
| retval = 1; |
| } else { |
| retval = vgetargskeywordsfast_impl(args, nargs, NULL, kwnames, parser, &va); |
| } |
| va_end(va); |
| return retval; |
| } |
| |
| /* Parse args for a function that takes one arg */ |
| int |
| CPyArg_ParseStackAndKeywordsOneArg(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames, |
| CPyArg_Parser *parser, ...) |
| { |
| int retval; |
| va_list va; |
| |
| va_start(va, parser); |
| if (kwnames == NULL && nargs == 1) { |
| // Fast path: one positional argument |
| PyObject **p; |
| p = va_arg(va, PyObject **); |
| *p = args[0]; |
| retval = 1; |
| } else { |
| retval = vgetargskeywordsfast_impl(args, nargs, NULL, kwnames, parser, &va); |
| } |
| va_end(va); |
| return retval; |
| } |
| |
| /* Parse args for a function that takes no keyword-only args, *args or **kwargs */ |
| int |
| CPyArg_ParseStackAndKeywordsSimple(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames, |
| CPyArg_Parser *parser, ...) |
| { |
| int retval; |
| va_list va; |
| |
| va_start(va, parser); |
| if (kwnames == NULL && PARSER_INITED(parser) && |
| nargs >= parser->min && nargs <= parser->max) { |
| // Fast path: correct number of positional arguments only |
| PyObject **p; |
| Py_ssize_t i; |
| for (i = 0; i < nargs; i++) { |
| p = va_arg(va, PyObject **); |
| *p = args[i]; |
| } |
| retval = 1; |
| } else { |
| retval = vgetargskeywordsfast_impl(args, nargs, NULL, kwnames, parser, &va); |
| } |
| va_end(va); |
| return retval; |
| } |
| |
| #define IS_END_OF_FORMAT(c) (c == '\0' || c == ';' || c == ':') |
| |
| |
| /* List of static parsers. */ |
| static struct CPyArg_Parser *static_arg_parsers = NULL; |
| |
| static int |
| parser_init(CPyArg_Parser *parser) |
| { |
| const char * const *keywords; |
| const char *format, *msg; |
| int i, len, min, max, nkw; |
| PyObject *kwtuple; |
| |
| assert(parser->keywords != NULL); |
| if (PARSER_INITED(parser)) { |
| return 1; |
| } |
| |
| keywords = parser->keywords; |
| /* scan keywords and count the number of positional-only parameters */ |
| for (i = 0; keywords[i] && !*keywords[i]; i++) { |
| } |
| parser->pos = i; |
| /* scan keywords and get greatest possible nbr of args */ |
| for (; keywords[i]; i++) { |
| if (!*keywords[i]) { |
| PyErr_SetString(PyExc_SystemError, |
| "Empty keyword parameter name"); |
| return 0; |
| } |
| } |
| len = i; |
| |
| parser->required_kwonly_start = INT_MAX; |
| if (*parser->format == '%') { |
| parser->format++; |
| parser->varargs = 1; |
| } |
| |
| format = parser->format; |
| if (format) { |
| /* grab the function name or custom error msg first (mutually exclusive) */ |
| parser->fname = strchr(parser->format, ':'); |
| if (parser->fname) { |
| parser->fname++; |
| parser->custom_msg = NULL; |
| } |
| else { |
| parser->custom_msg = strchr(parser->format,';'); |
| if (parser->custom_msg) |
| parser->custom_msg++; |
| } |
| |
| min = max = INT_MAX; |
| for (i = 0; i < len; i++) { |
| if (*format == '|') { |
| if (min != INT_MAX) { |
| PyErr_SetString(PyExc_SystemError, |
| "Invalid format string (| specified twice)"); |
| return 0; |
| } |
| if (max != INT_MAX) { |
| PyErr_SetString(PyExc_SystemError, |
| "Invalid format string ($ before |)"); |
| return 0; |
| } |
| min = i; |
| format++; |
| } |
| if (*format == '$') { |
| if (max != INT_MAX) { |
| PyErr_SetString(PyExc_SystemError, |
| "Invalid format string ($ specified twice)"); |
| return 0; |
| } |
| if (i < parser->pos) { |
| PyErr_SetString(PyExc_SystemError, |
| "Empty parameter name after $"); |
| return 0; |
| } |
| max = i; |
| format++; |
| } |
| if (*format == '@') { |
| if (parser->required_kwonly_start != INT_MAX) { |
| PyErr_SetString(PyExc_SystemError, |
| "Invalid format string (@ specified twice)"); |
| return 0; |
| } |
| if (min == INT_MAX && max == INT_MAX) { |
| PyErr_SetString(PyExc_SystemError, |
| "Invalid format string " |
| "(@ without preceding | and $)"); |
| return 0; |
| } |
| format++; |
| parser->has_required_kws = 1; |
| parser->required_kwonly_start = i; |
| } |
| if (IS_END_OF_FORMAT(*format)) { |
| PyErr_Format(PyExc_SystemError, |
| "More keyword list entries (%d) than " |
| "format specifiers (%d)", len, i); |
| return 0; |
| } |
| |
| skipitem_fast(&format, NULL); |
| } |
| parser->min = Py_MIN(min, len); |
| parser->max = Py_MIN(max, len); |
| |
| if (!IS_END_OF_FORMAT(*format) && (*format != '|') && (*format != '$')) { |
| PyErr_Format(PyExc_SystemError, |
| "more argument specifiers than keyword list entries " |
| "(remaining format:'%s')", format); |
| return 0; |
| } |
| } |
| |
| nkw = len - parser->pos; |
| kwtuple = PyTuple_New(nkw); |
| if (kwtuple == NULL) { |
| return 0; |
| } |
| keywords = parser->keywords + parser->pos; |
| for (i = 0; i < nkw; i++) { |
| PyObject *str = PyUnicode_FromString(keywords[i]); |
| if (str == NULL) { |
| Py_DECREF(kwtuple); |
| return 0; |
| } |
| PyUnicode_InternInPlace(&str); |
| PyTuple_SET_ITEM(kwtuple, i, str); |
| } |
| parser->kwtuple = kwtuple; |
| |
| assert(parser->next == NULL); |
| parser->next = static_arg_parsers; |
| static_arg_parsers = parser; |
| return 1; |
| } |
| |
| static PyObject* |
| find_keyword(PyObject *kwnames, PyObject *const *kwstack, PyObject *key) |
| { |
| Py_ssize_t i, nkwargs; |
| |
| nkwargs = PyTuple_GET_SIZE(kwnames); |
| for (i = 0; i < nkwargs; i++) { |
| PyObject *kwname = PyTuple_GET_ITEM(kwnames, i); |
| |
| /* kwname == key will normally find a match in since keyword keys |
| should be interned strings; if not retry below in a new loop. */ |
| if (kwname == key) { |
| return kwstack[i]; |
| } |
| } |
| |
| for (i = 0; i < nkwargs; i++) { |
| PyObject *kwname = PyTuple_GET_ITEM(kwnames, i); |
| assert(PyUnicode_Check(kwname)); |
| if (_PyUnicode_EQ(kwname, key)) { |
| return kwstack[i]; |
| } |
| } |
| return NULL; |
| } |
| |
| static int |
| vgetargskeywordsfast_impl(PyObject *const *args, Py_ssize_t nargs, |
| PyObject *kwargs, PyObject *kwnames, |
| CPyArg_Parser *parser, |
| va_list *p_va) |
| { |
| PyObject *kwtuple; |
| const char *format; |
| PyObject *keyword; |
| int i, pos, len; |
| Py_ssize_t nkwargs; |
| PyObject *current_arg; |
| PyObject *const *kwstack = NULL; |
| int bound_pos_args; |
| PyObject **p_args = NULL, **p_kwargs = NULL; |
| |
| assert(kwargs == NULL || PyDict_Check(kwargs)); |
| assert(kwargs == NULL || kwnames == NULL); |
| assert(p_va != NULL); |
| |
| if (!parser_init(parser)) { |
| return 0; |
| } |
| |
| kwtuple = parser->kwtuple; |
| pos = parser->pos; |
| len = pos + (int)PyTuple_GET_SIZE(kwtuple); |
| |
| if (parser->varargs) { |
| p_args = va_arg(*p_va, PyObject **); |
| p_kwargs = va_arg(*p_va, PyObject **); |
| } |
| |
| if (kwargs != NULL) { |
| nkwargs = PyDict_GET_SIZE(kwargs); |
| } |
| else if (kwnames != NULL) { |
| nkwargs = PyTuple_GET_SIZE(kwnames); |
| kwstack = args + nargs; |
| } |
| else { |
| nkwargs = 0; |
| } |
| if (nargs + nkwargs > len && !p_args && !p_kwargs) { |
| /* Adding "keyword" (when nargs == 0) prevents producing wrong error |
| messages in some special cases (see bpo-31229). */ |
| PyErr_Format(PyExc_TypeError, |
| "%.200s%s takes at most %d %sargument%s (%zd given)", |
| (parser->fname == NULL) ? "function" : parser->fname, |
| (parser->fname == NULL) ? "" : "()", |
| len, |
| (nargs == 0) ? "keyword " : "", |
| (len == 1) ? "" : "s", |
| nargs + nkwargs); |
| return 0; |
| } |
| if (parser->max < nargs && !p_args) { |
| if (parser->max == 0) { |
| PyErr_Format(PyExc_TypeError, |
| "%.200s%s takes no positional arguments", |
| (parser->fname == NULL) ? "function" : parser->fname, |
| (parser->fname == NULL) ? "" : "()"); |
| } |
| else { |
| PyErr_Format(PyExc_TypeError, |
| "%.200s%s takes %s %d positional argument%s (%zd given)", |
| (parser->fname == NULL) ? "function" : parser->fname, |
| (parser->fname == NULL) ? "" : "()", |
| (parser->min < parser->max) ? "at most" : "exactly", |
| parser->max, |
| parser->max == 1 ? "" : "s", |
| nargs); |
| } |
| return 0; |
| } |
| |
| format = parser->format; |
| |
| /* convert tuple args and keyword args in same loop, using kwtuple to drive process */ |
| for (i = 0; i < len; i++) { |
| if (*format == '|') { |
| format++; |
| } |
| if (*format == '$') { |
| format++; |
| } |
| if (*format == '@') { |
| format++; |
| } |
| assert(!IS_END_OF_FORMAT(*format)); |
| |
| if (i < nargs && i < parser->max) { |
| current_arg = args[i]; |
| } |
| else if (nkwargs && i >= pos) { |
| keyword = PyTuple_GET_ITEM(kwtuple, i - pos); |
| if (kwargs != NULL) { |
| current_arg = PyDict_GetItemWithError(kwargs, keyword); |
| if (!current_arg && PyErr_Occurred()) { |
| return 0; |
| } |
| } |
| else { |
| current_arg = find_keyword(kwnames, kwstack, keyword); |
| } |
| if (current_arg) { |
| --nkwargs; |
| } |
| } |
| else { |
| current_arg = NULL; |
| } |
| |
| if (current_arg) { |
| PyObject **p = va_arg(*p_va, PyObject **); |
| *p = current_arg; |
| format++; |
| continue; |
| } |
| |
| if (i < parser->min || i >= parser->required_kwonly_start) { |
| /* Less arguments than required */ |
| if (i < pos) { |
| Py_ssize_t min = Py_MIN(pos, parser->min); |
| PyErr_Format(PyExc_TypeError, |
| "%.200s%s takes %s %d positional argument%s" |
| " (%zd given)", |
| (parser->fname == NULL) ? "function" : parser->fname, |
| (parser->fname == NULL) ? "" : "()", |
| min < parser->max ? "at least" : "exactly", |
| min, |
| min == 1 ? "" : "s", |
| nargs); |
| } |
| else { |
| keyword = PyTuple_GET_ITEM(kwtuple, i - pos); |
| if (i >= parser->max) { |
| PyErr_Format(PyExc_TypeError, "%.200s%s missing required " |
| "keyword-only argument '%U'", |
| (parser->fname == NULL) ? "function" : parser->fname, |
| (parser->fname == NULL) ? "" : "()", |
| keyword); |
| } |
| else { |
| PyErr_Format(PyExc_TypeError, "%.200s%s missing required " |
| "argument '%U' (pos %d)", |
| (parser->fname == NULL) ? "function" : parser->fname, |
| (parser->fname == NULL) ? "" : "()", |
| keyword, i+1); |
| } |
| } |
| return 0; |
| } |
| /* current code reports success when all required args |
| * fulfilled and no keyword args left, with no further |
| * validation. XXX Maybe skip this in debug build ? |
| */ |
| if (!nkwargs && !parser->has_required_kws && !p_args && !p_kwargs) { |
| return 1; |
| } |
| |
| /* We are into optional args, skip through to any remaining |
| * keyword args */ |
| skipitem_fast(&format, p_va); |
| } |
| |
| assert(IS_END_OF_FORMAT(*format) || (*format == '|') || (*format == '$')); |
| |
| bound_pos_args = Py_MIN(nargs, Py_MIN(parser->max, len)); |
| if (p_args) { |
| *p_args = PyTuple_New(nargs - bound_pos_args); |
| if (!*p_args) { |
| return 0; |
| } |
| for (i = bound_pos_args; i < nargs; i++) { |
| PyObject *arg = args[i]; |
| Py_INCREF(arg); |
| PyTuple_SET_ITEM(*p_args, i - bound_pos_args, arg); |
| } |
| } |
| |
| if (p_kwargs) { |
| /* This unfortunately needs to be special cased because if len is 0 then we |
| * never go through the main loop. */ |
| if (nargs > 0 && len == 0 && !p_args) { |
| PyErr_Format(PyExc_TypeError, |
| "%.200s%s takes no positional arguments", |
| (parser->fname == NULL) ? "function" : parser->fname, |
| (parser->fname == NULL) ? "" : "()"); |
| |
| return 0; |
| } |
| |
| *p_kwargs = PyDict_New(); |
| if (!*p_kwargs) { |
| goto latefail; |
| } |
| } |
| |
| if (nkwargs > 0) { |
| Py_ssize_t j; |
| PyObject *value; |
| /* make sure there are no arguments given by name and position */ |
| for (i = pos; i < bound_pos_args; i++) { |
| keyword = PyTuple_GET_ITEM(kwtuple, i - pos); |
| if (kwargs != NULL) { |
| current_arg = PyDict_GetItemWithError(kwargs, keyword); |
| if (!current_arg && PyErr_Occurred()) { |
| goto latefail; |
| } |
| } |
| else { |
| current_arg = find_keyword(kwnames, kwstack, keyword); |
| } |
| if (current_arg) { |
| /* arg present in tuple and in dict */ |
| PyErr_Format(PyExc_TypeError, |
| "argument for %.200s%s given by name ('%U') " |
| "and position (%d)", |
| (parser->fname == NULL) ? "function" : parser->fname, |
| (parser->fname == NULL) ? "" : "()", |
| keyword, i+1); |
| goto latefail; |
| } |
| } |
| /* make sure there are no extraneous keyword arguments */ |
| j = 0; |
| while (1) { |
| int match; |
| if (kwargs != NULL) { |
| if (!PyDict_Next(kwargs, &j, &keyword, &value)) |
| break; |
| } |
| else { |
| if (j >= PyTuple_GET_SIZE(kwnames)) |
| break; |
| keyword = PyTuple_GET_ITEM(kwnames, j); |
| value = kwstack[j]; |
| j++; |
| } |
| |
| match = PySequence_Contains(kwtuple, keyword); |
| if (match <= 0) { |
| if (!match) { |
| if (!p_kwargs) { |
| 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 latefail; |
| } else { |
| if (PyDict_SetItem(*p_kwargs, keyword, value) < 0) { |
| goto latefail; |
| } |
| } |
| } else { |
| goto latefail; |
| } |
| } |
| } |
| } |
| |
| return 1; |
| /* Handle failures that have happened after we have tried to |
| * create *args and **kwargs, if they exist. */ |
| latefail: |
| if (p_args) { |
| Py_XDECREF(*p_args); |
| } |
| if (p_kwargs) { |
| Py_XDECREF(*p_kwargs); |
| } |
| return 0; |
| } |
| |
| static void |
| skipitem_fast(const char **p_format, va_list *p_va) |
| { |
| const char *format = *p_format; |
| char c = *format++; |
| |
| if (p_va != NULL) { |
| (void) va_arg(*p_va, PyObject **); |
| } |
| |
| *p_format = format; |
| } |
| |
| #endif |