Files
Python3-Win9x/scripts/vc6-call-first-c89.test.py

413 lines
20 KiB
Python

#!/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")