usse/funda-scraper/venv/lib/python3.10/site-packages/mypyc/lib-rt/getargsfast.c

575 lines
18 KiB
C
Raw Normal View History

2023-02-20 22:38:24 +00:00
/* 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