in ext/Python/getargs.cpp [1604:1842]
static int vgetargskeywords(PyObject* args, PyObject* keywords,
const char* format, char** kwlist, va_list* p_va,
int flags) {
char msgbuf[512];
int levels[32];
const char *fname, *msg, *custom_msg, *keyword;
int min = INT_MAX;
int max = INT_MAX;
int i, pos, len;
int skip = 0;
Py_ssize_t nargs, nkeywords;
PyObject* current_arg;
freelistentry_t static_entries[STATIC_FREELIST_ENTRIES];
freelist_t freelist;
freelist.entries = static_entries;
freelist.first_available = 0;
freelist.entries_malloced = 0;
assert(args != nullptr && PyTuple_Check(args));
assert(keywords == nullptr || PyDict_Check(keywords));
assert(format != nullptr);
assert(kwlist != nullptr);
assert(p_va != nullptr);
// grab the function name or custom error msg first (mutually exclusive)
fname = strchr(format, ':');
if (fname) {
fname++;
custom_msg = nullptr;
} else {
custom_msg = strchr(format, ';');
if (custom_msg) custom_msg++;
}
// 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++) {
if (!*kwlist[len]) {
Thread::current()->raiseWithFmt(LayoutId::kSystemError,
"Empty keyword parameter name");
return cleanreturn(0, &freelist);
}
}
if (len > STATIC_FREELIST_ENTRIES) {
freelist.entries = PyMem_NEW(freelistentry_t, len);
if (freelist.entries == nullptr) {
PyErr_NoMemory();
return 0;
}
freelist.entries_malloced = 1;
}
nargs = PyTuple_GET_SIZE(args);
nkeywords = (keywords == nullptr) ? 0 : PyDict_Size(keywords);
if (nargs + nkeywords > len) {
PyErr_Format(
PyExc_TypeError, "%s%s takes at most %d argument%s (%zd given)",
(fname == nullptr) ? "function" : fname, (fname == nullptr) ? "" : "()",
len, (len == 1) ? "" : "s", nargs + nkeywords);
return cleanreturn(0, &freelist);
}
// convert tuple args and keyword args in same loop, using kwlist to drive
// process
for (i = 0; i < len; i++) {
keyword = kwlist[i];
if (*format == '|') {
if (min != INT_MAX) {
Thread::current()->raiseWithFmt(
LayoutId::kSystemError,
"Invalid format string (| specified twice)");
return cleanreturn(0, &freelist);
}
min = i;
format++;
if (max != INT_MAX) {
Thread::current()->raiseWithFmt(LayoutId::kSystemError,
"Invalid format string ($ before |)");
return cleanreturn(0, &freelist);
}
}
if (*format == '$') {
if (max != INT_MAX) {
Thread::current()->raiseWithFmt(
LayoutId::kSystemError,
"Invalid format string ($ specified twice)");
return cleanreturn(0, &freelist);
}
max = i;
format++;
if (max < pos) {
Thread::current()->raiseWithFmt(LayoutId::kSystemError,
"Empty parameter name after $");
return cleanreturn(0, &freelist);
}
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 (max < nargs) {
PyErr_Format(PyExc_TypeError,
"Function takes %s %d positional arguments"
" (%d given)",
(min != INT_MAX) ? "at most" : "exactly", max, nargs);
return cleanreturn(0, &freelist);
}
}
if (IS_END_OF_FORMAT(*format)) {
PyErr_Format(PyExc_SystemError,
"More keyword list entries (%d) than "
"format specifiers (%d)",
len, i);
return cleanreturn(0, &freelist);
}
if (!skip) {
current_arg = nullptr;
if (nkeywords && i >= pos) {
current_arg = PyDict_GetItemString(keywords, keyword);
if (!current_arg && PyErr_Occurred()) {
return cleanreturn(0, &freelist);
}
}
if (current_arg) {
--nkeywords;
if (i < nargs) {
// arg present in tuple and in dict
PyErr_Format(PyExc_TypeError,
"Argument given by name ('%s') "
"and position (%d)",
keyword, i + 1);
return cleanreturn(0, &freelist);
}
} else if (i < nargs) {
// Facebook: Use PyTuple_GetItem instead of &PyTuple_GET_ITEM
// (D12953145)
current_arg = PyTuple_GetItem(args, i);
}
if (current_arg) {
msg = convertitem(current_arg, &format, p_va, flags, levels, msgbuf,
sizeof(msgbuf), &freelist);
if (msg) {
seterror(i + 1, msg, levels, fname, custom_msg);
return cleanreturn(0, &freelist);
}
continue;
}
if (i < min) {
if (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 {
PyErr_Format(PyExc_TypeError,
"Required argument "
"'%s' (pos %d) not found",
keyword, i + 1);
return cleanreturn(0, &freelist);
}
}
// 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 (!nkeywords && !skip) {
return cleanreturn(1, &freelist);
}
}
// We are into optional args, skip thru to any remaining
// keyword args
msg = skipitem(&format, p_va, flags);
if (msg) {
PyErr_Format(PyExc_SystemError, "%s: '%s'", msg, format);
return cleanreturn(0, &freelist);
}
}
if (skip) {
PyErr_Format(PyExc_TypeError,
"Function takes %s %d positional arguments"
" (%d given)",
(Py_MIN(pos, min) < i) ? "at least" : "exactly",
Py_MIN(pos, min), nargs);
return cleanreturn(0, &freelist);
}
if (!IS_END_OF_FORMAT(*format) && (*format != '|') && (*format != '$')) {
PyErr_Format(PyExc_SystemError,
"more argument specifiers than keyword list entries "
"(remaining format:'%s')",
format);
return cleanreturn(0, &freelist);
}
// make sure there are no extraneous keyword arguments
if (nkeywords > 0) {
PyObject *key, *value;
Py_ssize_t iter_pos = 0;
while (PyDict_Next(keywords, &iter_pos, &key, &value)) {
int match = 0;
if (!PyUnicode_Check(key)) {
Thread::current()->raiseWithFmt(LayoutId::kTypeError,
"keywords must be strings");
return cleanreturn(0, &freelist);
}
for (i = 0; i < len; i++) {
if (*kwlist[i] && _PyUnicode_EqualToASCIIString(key, kwlist[i])) {
match = 1;
break;
}
}
if (!match) {
PyErr_Format(PyExc_TypeError,
"'%U' is an invalid keyword "
"argument for this function",
key);
return cleanreturn(0, &freelist);
}
}
}
return cleanreturn(1, &freelist);
}