static int vgetargskeywords()

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);
}