#!/usr/bin/env python3 """Check the first VC6 C89 frontier in Objects/call.c.""" import sys from pathlib import Path if len(sys.argv) != 2: raise SystemExit("usage: vc6-call-first-c89.test.py call.c") source = Path(sys.argv[1]).read_text() signature = "_PyObject_FastCallDictTstate(PyThreadState *tstate, PyObject *callable," start = source.index(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] first_statement = body.index("assert(callable != NULL);") for declaration in ("Py_ssize_t nargs;", "vectorcallfunc func;", "PyObject *res;"): if body.count(declaration) != 1 or body.index(declaration) > first_statement: raise SystemExit("_PyObject_FastCallDictTstate locals must be declared before the first statement") if "Py_ssize_t nargs =" in body or "vectorcallfunc func =" in body: raise SystemExit("_PyObject_FastCallDictTstate declarations must be separate from assignments") order = ( "assert(callable != NULL);", "assert(!_PyErr_Occurred(tstate));", "nargs = PyVectorcall_NARGS(nargsf);", "func = _PyVectorcall_Function(callable);", "if (func == NULL)", "if (kwargs == NULL || PyDict_GET_SIZE(kwargs) == 0)", "return _Py_CheckFunctionResult(tstate, callable, res, NULL);", ) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("vectorcall validation/dispatch/result-check order changed") signature = "_PyObject_MakeTpCall(PyThreadState *tstate, PyObject *callable," start = source.index(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] first_statement = body.index("assert(nargs >= 0);") for declaration in ("ternaryfunc call;", "PyObject *argstuple;", "PyObject *kwdict;", "PyObject *result;"): if body.count(declaration) != 1 or body.index(declaration) > first_statement: raise SystemExit("_PyObject_MakeTpCall locals must be declared before the first statement") if any(declaration in body for declaration in ("ternaryfunc call =", "PyObject *argstuple =", "PyObject *result =")): raise SystemExit("_PyObject_MakeTpCall locals must be declared separately from assignment") order = ( "assert(nargs >= 0);", "assert(keywords == NULL || PyTuple_Check(keywords) || PyDict_Check(keywords));", "call = Py_TYPE(callable)->tp_call;", "if (call == NULL)", "argstuple = _PyTuple_FromArray(args, nargs);", "if (keywords == NULL || PyDict_Check(keywords))", "result = NULL;", "if (_Py_EnterRecursiveCallTstate(tstate,", "_Py_LeaveRecursiveCallTstate(tstate);", "\n Py_DECREF(argstuple);\n if (kwdict != keywords)", "if (kwdict != keywords)", "return _Py_CheckFunctionResult(tstate, callable, result, NULL);", ) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("tp_call argument construction/reference cleanup order changed") signature = "_PyVectorcall_Call(PyThreadState *tstate, vectorcallfunc func," start = source.index(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] first_statement = body.index("assert(func != NULL);") for declaration in ("Py_ssize_t nargs;", "PyObject *const *args;", "PyObject *kwnames;", "PyObject *result;"): if body.count(declaration) != 1 or body.index(declaration) > first_statement: raise SystemExit("_PyVectorcall_Call locals must be declared before the first statement") if "Py_ssize_t nargs =" in body or "PyObject *result =" in body: raise SystemExit("_PyVectorcall_Call declarations must be separate from assignments") order = ( "assert(func != NULL);", "nargs = PyTuple_GET_SIZE(tuple);", "if (kwargs == NULL || PyDict_GET_SIZE(kwargs) == 0)", "args = _PyStack_UnpackDict(tstate,", "if (args == NULL)", "result = func(callable, args,", "_PyStack_UnpackDict_Free(args, nargs, kwnames);", "return _Py_CheckFunctionResult(tstate, callable, result, NULL);", ) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("vectorcall argument unpack/call/free/check order changed") signature = "PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *kwargs)" start = source.index(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] for declaration in ("PyThreadState *tstate;", "Py_ssize_t offset;", "vectorcallfunc func;"): if body.count(declaration) != 1 or body.index(declaration) > body.index("tstate = _PyThreadState_GET();"): raise SystemExit("PyVectorcall_Call locals must be declared before initialization") if "PyThreadState *tstate =" in body or "Py_ssize_t offset =" in body: raise SystemExit("PyVectorcall_Call declarations must be separate from assignments") order = ( "tstate = _PyThreadState_GET();", "offset = Py_TYPE(callable)->tp_vectorcall_offset;", "if (offset <= 0)", "assert(PyCallable_Check(callable));", "memcpy(&func, (char *) callable + offset, sizeof(func));", "if (func == NULL)", "return _PyVectorcall_Call(tstate, func, callable, tuple, kwargs);", ) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("PyVectorcall_Call validation/copy/dispatch order changed") signature = "_PyObject_Call(PyThreadState *tstate, PyObject *callable," start = source.index(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] first_statement = body.index("assert(!_PyErr_Occurred(tstate));") if body.count("vectorcallfunc vector_func;") != 1 or body.index("vectorcallfunc vector_func;") > first_statement: raise SystemExit("_PyObject_Call vectorcall local must be declared before validation") if "vectorcallfunc vector_func =" in body: raise SystemExit("_PyObject_Call vectorcall declaration must be separate from lookup") order = ( "assert(!_PyErr_Occurred(tstate));", "assert(PyTuple_Check(args));", "vector_func = _PyVectorcall_Function(callable);", "if (vector_func != NULL)", "call = Py_TYPE(callable)->tp_call;", "if (call == NULL)", "if (_Py_EnterRecursiveCallTstate(tstate,", "result = (*call)(callable, args, kwargs);", "_Py_LeaveRecursiveCallTstate(tstate);", "return _Py_CheckFunctionResult(tstate, callable, result, NULL);", ) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("_PyObject_Call validation/dispatch/recursion order changed") signature = "PyObject_CallOneArg(PyObject *func, PyObject *arg)" start = source.index(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] first_statement = body.index("assert(arg != NULL);") for declaration in ("PyObject *_args[2];", "PyObject **args;", "PyThreadState *tstate;", "size_t nargsf;"): if body.count(declaration) != 1 or body.index(declaration) > first_statement: raise SystemExit("PyObject_CallOneArg locals must be declared before the assertion") if any(text in body for text in ("PyObject **args =", "PyThreadState *tstate =", "size_t nargsf =")): raise SystemExit("PyObject_CallOneArg declarations must be separate from assignments") order = ( "assert(arg != NULL);", "args = _args + 1;", "args[0] = arg;", "tstate = _PyThreadState_GET();", "nargsf = 1 | PY_VECTORCALL_ARGUMENTS_OFFSET;", "return _PyObject_VectorcallTstate(tstate, func, args, nargsf, NULL);", ) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("PyObject_CallOneArg vectorcall argument setup order changed") signature = "_PyFunction_Vectorcall(PyObject *func, PyObject* const* stack," start = source.index(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] first_statement = body.index("assert(PyFunction_Check(func));") for declaration in ("PyFunctionObject *f;", "Py_ssize_t nargs;", "PyThreadState *tstate;"): if body.count(declaration) != 1 or body.index(declaration) > first_statement: raise SystemExit("_PyFunction_Vectorcall locals must be declared before the assertion") if any(text in body for text in ("PyFunctionObject *f =", "Py_ssize_t nargs =", "PyThreadState *tstate =")): raise SystemExit("_PyFunction_Vectorcall declarations must be separate from assignments") order = ( "assert(PyFunction_Check(func));", "f = (PyFunctionObject *)func;", "nargs = PyVectorcall_NARGS(nargsf);", "assert(nargs >= 0);", "tstate = _PyThreadState_GET();", "assert(nargs == 0 || stack != NULL);", "if (((PyCodeObject *)f->func_code)->co_flags & CO_OPTIMIZED)", "return _PyEval_Vector(tstate, f, NULL, stack, nargs, kwnames);", "return _PyEval_Vector(tstate, f, f->func_globals, stack, nargs, kwnames);", ) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("function vectorcall validation/state/dispatch order changed") signature = "_PyObject_Call_Prepend(PyThreadState *tstate, PyObject *callable," start = source.index(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] first_statement = body.index("assert(PyTuple_Check(args));") for declaration in ( "PyObject *small_stack[_PY_FASTCALL_SMALL_STACK];", "PyObject **stack;", "Py_ssize_t argcount;", "PyObject *result;", ): if body.count(declaration) != 1 or body.index(declaration) > first_statement: raise SystemExit("_PyObject_Call_Prepend locals must be declared before the assertion") if "Py_ssize_t argcount =" in body or "PyObject *result =" in body: raise SystemExit("_PyObject_Call_Prepend declarations must be separate from assignments") order = ( "assert(PyTuple_Check(args));", "argcount = PyTuple_GET_SIZE(args);", "if (argcount + 1 <= (Py_ssize_t)Py_ARRAY_LENGTH(small_stack))", "stack = PyMem_Malloc((argcount + 1) * sizeof(PyObject *));", "stack[0] = obj;", "memcpy(&stack[1],", "result = _PyObject_FastCallDictTstate(tstate, callable,", "if (stack != small_stack)", "return result;", ) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("_PyObject_Call_Prepend stack/call/free order changed") signature = "_PyObject_CallFunction_SizeT(PyObject *callable, const char *format, ...)" start = source.index(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] first_call = body.index("va_start(va, format);") for declaration in ("va_list va;", "PyObject *result;"): if body.count(declaration) != 1 or body.index(declaration) > first_call: raise SystemExit("_PyObject_CallFunction_SizeT locals must be declared before va_start") if "PyObject *result =" in body: raise SystemExit("_PyObject_CallFunction_SizeT result must be assigned after va_start") order = ( "tstate = _PyThreadState_GET();", "va_start(va, format);", "result = _PyObject_CallFunctionVa(tstate, callable, format, va, 1);", "va_end(va);", "return result;", ) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("SizeT wrapper thread/va/result lifecycle order changed") method_wrappers = ( ("PyObject_CallMethod(PyObject *obj, const char *name, const char *format, ...)", "PyObject_GetAttrString(obj, name)", 0), ("PyEval_CallMethod(PyObject *obj, const char *name, const char *format, ...)", "PyObject_GetAttrString(obj, name)", 0), ("_PyObject_CallMethod(PyObject *obj, PyObject *name,", "PyObject_GetAttr(obj, name)", 1), ("_PyObject_CallMethodId(PyObject *obj, _Py_Identifier *name,", "_PyObject_GetAttrId(obj, name)", 0), ("_PyObject_CallMethodFormat(PyThreadState *tstate, PyObject *callable,", None, 0), ("_PyObject_CallMethod_SizeT(PyObject *obj, const char *name,", "PyObject_GetAttrString(obj, name)", 1), ("_PyObject_CallMethodId_SizeT(PyObject *obj, _Py_Identifier *name,", "_PyObject_GetAttrId(obj, name)", 1), ) for signature, lookup, size_t in method_wrappers: start = source.index(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] first_statement = body.index("if (obj == NULL || name == NULL)") if lookup else body.index("va_start(va, format);") required = ["va_list va;", "PyObject *retval;"] if lookup: required.append("PyObject *callable;") for declaration in required: if body.count(declaration) != 1 or body.index(declaration) > first_statement: raise SystemExit(signature.split("(", 1)[0] + " locals must be declared before execution") if "PyObject *retval =" in body or (lookup and "PyObject *callable =" in body): raise SystemExit(signature.split("(", 1)[0] + " locals must be declared separately from assignments") if lookup: order = ( "if (obj == NULL || name == NULL)", lookup + ";", "if (callable == NULL)", "va_start(va, format);", "retval = callmethod(tstate, callable, format, va, " + str(size_t) + ");", "va_end(va);", "Py_DECREF(callable);", "return retval;", ) else: order = ( "va_start(va, format);", "retval = callmethod(tstate, callable, format, va, " + str(size_t) + ");", "va_end(va);", "return retval;", ) missing = [item for item in order if item not in body] if missing: raise SystemExit(signature.split("(", 1)[0] + " missing expected order text: " + repr(missing)) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit(signature.split("(", 1)[0] + " lookup/va/cleanup order changed") signature = "PyObject_VectorcallMethod(PyObject *name, PyObject *const *args," start = source.index(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] first_statement = body.index("assert(name != NULL);") for declaration in ("PyThreadState *tstate;", "PyObject *callable;", "int unbound;", "PyObject *result;"): if body.count(declaration) != 1 or body.index(declaration) > first_statement: raise SystemExit("PyObject_VectorcallMethod locals must be declared before validation") if "PyObject *callable =" in body or "int unbound =" in body or "PyObject *result =" in body: raise SystemExit("PyObject_VectorcallMethod declarations must be separate from initialization") order = ( "assert(name != NULL);", "assert(args != NULL);", "assert(PyVectorcall_NARGS(nargsf) >= 1);", "tstate = _PyThreadState_GET();", "callable = NULL;", "unbound = _PyObject_GetMethod(args[0], name, &callable);", "if (callable == NULL)", "if (unbound)", "else {", "result = _PyObject_VectorcallTstate(tstate, callable,", "Py_DECREF(callable);", "return result;", ) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("vectorcall method lookup/argument adjustment/cleanup order changed") method_objargs = ( ("PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...)", False), ("_PyObject_CallMethodIdObjArgs(PyObject *obj, _Py_Identifier *name, ...)", True), ) for signature, has_oname in method_objargs: start = source.index(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] first_statement = body.index("if (obj == NULL || name == NULL)") required = ["PyObject *callable;", "int is_method;", "va_list vargs;", "PyObject *result;"] if has_oname: required.append("PyObject *oname;") for declaration in required: if body.count(declaration) != 1 or body.index(declaration) > first_statement: raise SystemExit(signature.split("(", 1)[0] + " locals must be declared before execution") if "PyObject *callable =" in body or "int is_method =" in body or "PyObject *result =" in body: raise SystemExit(signature.split("(", 1)[0] + " declarations must be separate from initialization") order = ["if (obj == NULL || name == NULL)"] if has_oname: order.extend(("oname = _PyUnicode_FromId(name);", "if (!oname)")) order.extend(( "callable = NULL;", "is_method = _PyObject_GetMethod(obj, " + ("oname" if has_oname else "name") + ", &callable);", "if (callable == NULL)", "obj = is_method ? obj : NULL;", "va_start(vargs, name);", "result = object_vacall(tstate, obj, callable, vargs);", "va_end(vargs);", "Py_DECREF(callable);", "return result;", )) missing = [item for item in order if item not in body] if missing: raise SystemExit(signature.split("(", 1)[0] + " missing expected order text: " + repr(missing)) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit(signature.split("(", 1)[0] + " lookup/va/cleanup order changed") signature = "_PyStack_UnpackDict(PyThreadState *tstate," start = source.rindex(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] first_statement = body.index("assert(nargs >= 0);") declarations = ( "Py_ssize_t nkwargs;", "Py_ssize_t maxnargs;", "PyObject **stack;", "PyObject *kwnames;", "PyObject **kwstack;", "Py_ssize_t pos;", "Py_ssize_t i;", "PyObject *key;", "PyObject *value;", "unsigned long keys_are_strings;", ) for declaration in declarations: if body.count(declaration) != 1 or body.index(declaration) > first_statement: raise SystemExit("_PyStack_UnpackDict locals must be declared before the first assertion") if "for (Py_ssize_t i =" in body or "Py_ssize_t nkwargs =" in body or "Py_ssize_t maxnargs =" in body: raise SystemExit("_PyStack_UnpackDict declaration/initialization separation failed") order = ( "assert(nargs >= 0);", "assert(kwargs != NULL);", "nkwargs = PyDict_GET_SIZE(kwargs);", "maxnargs = PY_SSIZE_T_MAX / sizeof(args[0]) - 1;", "if (nargs > maxnargs - nkwargs)", "stack = PyMem_Malloc((1 + nargs + nkwargs) * sizeof(args[0]));", "kwnames = PyTuple_New(nkwargs);", "stack++; /* For PY_VECTORCALL_ARGUMENTS_OFFSET */", "for (i = 0; i < nargs; i++)", "kwstack = stack + nargs;", "pos = 0;", "\n i = 0;\n", "keys_are_strings = Py_TPFLAGS_UNICODE_SUBCLASS;", "while (PyDict_Next(kwargs, &pos, &key, &value))", "if (!keys_are_strings)", ) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("_PyStack_UnpackDict validation/allocation/iteration order changed: " + repr(list(zip(order, positions)))) signature = "_PyStack_UnpackDict_Free(PyObject *const *stack, Py_ssize_t nargs," start = source.rindex(signature) body_start = source.index("\n{\n", start) + 2 body_end = source.index("\n}\n", body_start) body = source[body_start:body_end] loop = body.index("for (i = 0;") if body.count("Py_ssize_t i;") != 1 or body.index("Py_ssize_t i;") > loop: raise SystemExit("_PyStack_UnpackDict_Free loop index must be declared before execution") if "for (Py_ssize_t i =" in body: raise SystemExit("_PyStack_UnpackDict_Free loop must use its entry-declared index") order = ( "Py_ssize_t n = PyTuple_GET_SIZE(kwnames) + nargs;", "for (i = 0; i < n; i++)", "Py_DECREF(stack[i]);", "PyMem_Free((PyObject **)stack - 1);", "Py_DECREF(kwnames);", ) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("_PyStack_UnpackDict_Free release order changed") print("call.c C89 declarations and stack cleanup order: PASS")