| /* getargs implementation copied from Python 3.8 and stripped down to only include |
| * the functions we need. |
| * We also add support for required kwonly args and accepting *args / **kwargs. |
| * A good idea would be to also vendor in the Fast versions and get our stuff |
| * working with *that*. |
| * Another probably good idea is to strip out all the formatting stuff we don't need |
| * and then add in custom stuff that we do need. |
| * |
| * DOCUMENTATION OF THE EXTENSIONS: |
| * - Arguments given after a @ format specify are 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 **kwargs. On seeing a %, 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. |
| * - All arguments must use the 'O' format. |
| * - There's minimal error checking of format strings. They are generated |
| * programmatically and can be assumed valid. |
| */ |
| |
| // These macro definitions are copied from pyport.h in Python 3.9 and later |
| // https://bugs.python.org/issue19569 |
| #if defined(__clang__) |
| #define _Py_COMP_DIAG_PUSH _Pragma("clang diagnostic push") |
| #define _Py_COMP_DIAG_IGNORE_DEPR_DECLS \ |
| _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") |
| #define _Py_COMP_DIAG_POP _Pragma("clang diagnostic pop") |
| #elif defined(__GNUC__) \ |
| && ((__GNUC__ >= 5) || (__GNUC__ == 4) && (__GNUC_MINOR__ >= 6)) |
| #define _Py_COMP_DIAG_PUSH _Pragma("GCC diagnostic push") |
| #define _Py_COMP_DIAG_IGNORE_DEPR_DECLS \ |
| _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") |
| #define _Py_COMP_DIAG_POP _Pragma("GCC diagnostic pop") |
| #elif defined(_MSC_VER) |
| #define _Py_COMP_DIAG_PUSH __pragma(warning(push)) |
| #define _Py_COMP_DIAG_IGNORE_DEPR_DECLS __pragma(warning(disable: 4996)) |
| #define _Py_COMP_DIAG_POP __pragma(warning(pop)) |
| #else |
| #define _Py_COMP_DIAG_PUSH |
| #define _Py_COMP_DIAG_IGNORE_DEPR_DECLS |
| #define _Py_COMP_DIAG_POP |
| #endif |
| |
| #include "Python.h" |
| #include "pythonsupport.h" |
| |
| #include <ctype.h> |
| #include <float.h> |
| |
| #ifndef PyDict_GET_SIZE |
| #define PyDict_GET_SIZE(d) PyDict_Size(d) |
| #endif |
| |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| int CPyArg_ParseTupleAndKeywords(PyObject *, PyObject *, |
| const char *, const char *, const char * const *, ...); |
| |
| /* Forward */ |
| static int vgetargskeywords(PyObject *, PyObject *, |
| const char *, const char *, const char * const *, va_list *); |
| static void skipitem(const char **, va_list *); |
| |
| /* Support for keyword arguments donated by |
| Geoff Philbrick <philbric@delphi.hks.com> */ |
| |
| /* Return false (0) for error, else true. */ |
| int |
| CPyArg_ParseTupleAndKeywords(PyObject *args, |
| PyObject *keywords, |
| const char *format, |
| const char *fname, |
| const char * const *kwlist, ...) |
| { |
| int retval; |
| va_list va; |
| |
| va_start(va, kwlist); |
| retval = vgetargskeywords(args, keywords, format, fname, kwlist, &va); |
| va_end(va); |
| return retval; |
| } |
| |
| #define IS_END_OF_FORMAT(c) (c == '\0' || c == ';' || c == ':') |
| |
| static int |
| vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format, |
| const char *fname, const char * const *kwlist, va_list *p_va) |
| { |
| int min = INT_MAX; |
| int max = INT_MAX; |
| int required_kwonly_start = INT_MAX; |
| int has_required_kws = 0; |
| int i, pos, len; |
| int skip = 0; |
| Py_ssize_t nargs, nkwargs; |
| PyObject *current_arg; |
| int bound_pos_args; |
| |
| PyObject **p_args = NULL, **p_kwargs = NULL; |
| |
| assert(args != NULL && PyTuple_Check(args)); |
| assert(kwargs == NULL || PyDict_Check(kwargs)); |
| assert(format != NULL); |
| assert(kwlist != NULL); |
| assert(p_va != NULL); |
| |
| /* scan kwlist and count the number of positional-only parameters */ |
| for (pos = 0; kwlist[pos] && !*kwlist[pos]; pos++) { |
| } |
| /* scan kwlist and get greatest possible nbr of args */ |
| for (len = pos; kwlist[len]; len++) { |
| #ifdef DEBUG |
| if (!*kwlist[len]) { |
| PyErr_SetString(PyExc_SystemError, |
| "Empty keyword parameter name"); |
| return 0; |
| } |
| #endif |
| } |
| |
| if (*format == '%') { |
| p_args = va_arg(*p_va, PyObject **); |
| p_kwargs = va_arg(*p_va, PyObject **); |
| format++; |
| } |
| |
| nargs = PyTuple_GET_SIZE(args); |
| nkwargs = (kwargs == NULL) ? 0 : PyDict_GET_SIZE(kwargs); |
| if (unlikely(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)", |
| (fname == NULL) ? "function" : fname, |
| (fname == NULL) ? "" : "()", |
| len, |
| (nargs == 0) ? "keyword " : "", |
| (len == 1) ? "" : "s", |
| nargs + nkwargs); |
| return 0; |
| } |
| |
| /* convert tuple args and keyword args in same loop, using kwlist to drive process */ |
| for (i = 0; i < len; i++) { |
| if (*format == '|') { |
| #ifdef DEBUG |
| if (min != INT_MAX) { |
| PyErr_SetString(PyExc_SystemError, |
| "Invalid format string (| specified twice)"); |
| return 0; |
| } |
| #endif |
| |
| min = i; |
| format++; |
| |
| #ifdef DEBUG |
| if (max != INT_MAX) { |
| PyErr_SetString(PyExc_SystemError, |
| "Invalid format string ($ before |)"); |
| return 0; |
| } |
| #endif |
| |
| /* If there are optional args, figure out whether we have |
| * required keyword arguments so that we don't bail without |
| * enforcing them. */ |
| has_required_kws = strchr(format, '@') != NULL; |
| } |
| if (*format == '$') { |
| #ifdef DEBUG |
| if (max != INT_MAX) { |
| PyErr_SetString(PyExc_SystemError, |
| "Invalid format string ($ specified twice)"); |
| return 0; |
| } |
| #endif |
| |
| max = i; |
| format++; |
| |
| #ifdef DEBUG |
| if (max < pos) { |
| PyErr_SetString(PyExc_SystemError, |
| "Empty parameter name after $"); |
| return 0; |
| } |
| #endif |
| if (skip) { |
| /* Now we know the minimal and the maximal numbers of |
| * positional arguments and can raise an exception with |
| * informative message (see below). */ |
| break; |
| } |
| if (unlikely(max < nargs && !p_args)) { |
| if (max == 0) { |
| PyErr_Format(PyExc_TypeError, |
| "%.200s%s takes no positional arguments", |
| (fname == NULL) ? "function" : fname, |
| (fname == NULL) ? "" : "()"); |
| } |
| else { |
| PyErr_Format(PyExc_TypeError, |
| "%.200s%s takes %s %d positional argument%s" |
| " (%zd given)", |
| (fname == NULL) ? "function" : fname, |
| (fname == NULL) ? "" : "()", |
| (min < max) ? "at most" : "exactly", |
| max, |
| max == 1 ? "" : "s", |
| nargs); |
| } |
| return 0; |
| } |
| } |
| if (*format == '@') { |
| #ifdef DEBUG |
| if (min == INT_MAX && max == INT_MAX) { |
| PyErr_SetString(PyExc_SystemError, |
| "Invalid format string " |
| "(@ without preceding | and $)"); |
| return 0; |
| } |
| if (required_kwonly_start != INT_MAX) { |
| PyErr_SetString(PyExc_SystemError, |
| "Invalid format string (@ specified twice)"); |
| return 0; |
| } |
| #endif |
| |
| required_kwonly_start = i; |
| format++; |
| } |
| #ifdef DEBUG |
| if (IS_END_OF_FORMAT(*format)) { |
| PyErr_Format(PyExc_SystemError, |
| "More keyword list entries (%d) than " |
| "format specifiers (%d)", len, i); |
| return 0; |
| } |
| #endif |
| if (!skip) { |
| if (i < nargs && i < max) { |
| current_arg = PyTuple_GET_ITEM(args, i); |
| } |
| else if (nkwargs && i >= pos) { |
| current_arg = _PyDict_GetItemStringWithError(kwargs, kwlist[i]); |
| if (current_arg) { |
| --nkwargs; |
| } |
| else if (PyErr_Occurred()) { |
| return 0; |
| } |
| } |
| else { |
| current_arg = NULL; |
| } |
| |
| if (current_arg) { |
| PyObject **p = va_arg(*p_va, PyObject **); |
| *p = current_arg; |
| format++; |
| continue; |
| } |
| |
| if (i < min || i >= required_kwonly_start) { |
| if (likely(i < pos)) { |
| assert (min == INT_MAX); |
| assert (max == INT_MAX); |
| skip = 1; |
| /* At that moment we still don't know the minimal and |
| * the maximal numbers of positional arguments. Raising |
| * an exception is deferred until we encounter | and $ |
| * or the end of the format. */ |
| } |
| else { |
| if (i >= max) { |
| PyErr_Format(PyExc_TypeError, |
| "%.200s%s missing required " |
| "keyword-only argument '%s'", |
| (fname == NULL) ? "function" : fname, |
| (fname == NULL) ? "" : "()", |
| kwlist[i]); |
| } |
| else { |
| PyErr_Format(PyExc_TypeError, |
| "%.200s%s missing required " |
| "argument '%s' (pos %d)", |
| (fname == NULL) ? "function" : fname, |
| (fname == NULL) ? "" : "()", |
| kwlist[i], 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 && !skip && !has_required_kws && |
| !p_args && !p_kwargs) |
| { |
| return 1; |
| } |
| } |
| |
| /* We are into optional args, skip through to any remaining |
| * keyword args */ |
| skipitem(&format, p_va); |
| } |
| |
| if (unlikely(skip)) { |
| PyErr_Format(PyExc_TypeError, |
| "%.200s%s takes %s %d positional argument%s" |
| " (%zd given)", |
| (fname == NULL) ? "function" : fname, |
| (fname == NULL) ? "" : "()", |
| (Py_MIN(pos, min) < i) ? "at least" : "exactly", |
| Py_MIN(pos, min), |
| Py_MIN(pos, min) == 1 ? "" : "s", |
| nargs); |
| return 0; |
| } |
| |
| #ifdef DEBUG |
| if (!IS_END_OF_FORMAT(*format) && |
| (*format != '|') && (*format != '$') && (*format != '@')) |
| { |
| PyErr_Format(PyExc_SystemError, |
| "more argument specifiers than keyword list entries " |
| "(remaining format:'%s')", format); |
| return 0; |
| } |
| #endif |
| |
| bound_pos_args = Py_MIN(nargs, Py_MIN(max, len)); |
| if (p_args) { |
| *p_args = PyTuple_GetSlice(args, bound_pos_args, nargs); |
| if (!*p_args) { |
| return 0; |
| } |
| } |
| |
| if (p_kwargs) { |
| /* This unfortunately needs to be special cased because if len is 0 then we |
| * never go through the main loop. */ |
| if (unlikely(nargs > 0 && len == 0 && !p_args)) { |
| PyErr_Format(PyExc_TypeError, |
| "%.200s%s takes no positional arguments", |
| (fname == NULL) ? "function" : fname, |
| (fname == NULL) ? "" : "()"); |
| |
| return 0; |
| } |
| |
| *p_kwargs = PyDict_New(); |
| if (!*p_kwargs) { |
| goto latefail; |
| } |
| } |
| |
| if (nkwargs > 0) { |
| PyObject *key, *value; |
| Py_ssize_t j; |
| /* make sure there are no arguments given by name and position */ |
| for (i = pos; i < bound_pos_args && i < len; i++) { |
| current_arg = _PyDict_GetItemStringWithError(kwargs, kwlist[i]); |
| if (unlikely(current_arg != NULL)) { |
| /* arg present in tuple and in dict */ |
| PyErr_Format(PyExc_TypeError, |
| "argument for %.200s%s given by name ('%s') " |
| "and position (%d)", |
| (fname == NULL) ? "function" : fname, |
| (fname == NULL) ? "" : "()", |
| kwlist[i], i+1); |
| goto latefail; |
| } |
| else if (unlikely(PyErr_Occurred() != NULL)) { |
| goto latefail; |
| } |
| } |
| /* make sure there are no extraneous keyword arguments */ |
| j = 0; |
| while (PyDict_Next(kwargs, &j, &key, &value)) { |
| int match = 0; |
| if (unlikely(!PyUnicode_Check(key))) { |
| PyErr_SetString(PyExc_TypeError, |
| "keywords must be strings"); |
| goto latefail; |
| } |
| for (i = pos; i < len; i++) { |
| if (CPyUnicode_EqualToASCIIString(key, kwlist[i])) { |
| match = 1; |
| break; |
| } |
| } |
| if (!match) { |
| if (unlikely(!p_kwargs)) { |
| PyErr_Format(PyExc_TypeError, |
| "'%U' is an invalid keyword " |
| "argument for %.200s%s", |
| key, |
| (fname == NULL) ? "this function" : fname, |
| (fname == NULL) ? "" : "()"); |
| goto latefail; |
| } else { |
| if (PyDict_SetItem(*p_kwargs, key, value) < 0) { |
| 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(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; |
| } |
| |
| #ifdef __cplusplus |
| }; |
| #endif |