#!/usr/bin/env python3 """Check C89 locals and mutation/cleanup order in method_vectorcall.""" import sys from pathlib import Path if len(sys.argv) != 2: raise SystemExit("usage: vc6-classobject-c89.test.py classobject.c") source = Path(sys.argv[1]).read_text() signature = "method_vectorcall(PyObject *method, 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(Py_IS_TYPE(method, &PyMethod_Type));") for declaration in ("PyThreadState *tstate;", "PyObject *self;", "PyObject *func;", "Py_ssize_t nargs;", "PyObject *result;"): if body.count(declaration) != 1 or body.index(declaration) > first_statement: raise SystemExit("method_vectorcall locals must be declared before the assertion") if any(text in body for text in ("PyThreadState *tstate =", "PyObject *self =", "PyObject *func =", "Py_ssize_t nargs =")): raise SystemExit("method_vectorcall declarations must be separate from assignments") branch_start = body.index("if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) {") branch_end = body.index("\n else {", branch_start) branch = body[branch_start:branch_end] if "PyObject **newargs;\n PyObject *tmp;" not in branch: raise SystemExit("offset branch locals must be declared at block entry") if not (branch.index("newargs = (PyObject**)args - 1;") < branch.index("nargs += 1;") < branch.index("tmp = newargs[0];") < branch.index("newargs[0] = self;") < branch.index("result = _PyObject_VectorcallTstate") < branch.index("newargs[0] = tmp;")): raise SystemExit("offset argument mutation and restoration order changed") else_start = body.index("\n else {", branch_start) else_body = body[else_start:] first_exec = else_body.index("nkwargs =") for declaration in ("Py_ssize_t nkwargs;", "Py_ssize_t totalargs;", "PyObject *newargs_stack[_PY_FASTCALL_SMALL_STACK];", "PyObject **newargs;"): if else_body.count(declaration) != 1 or else_body.index(declaration) > first_exec: raise SystemExit("non-offset branch locals must be declared before initialization") if not (else_body.index("nkwargs =") < else_body.index("totalargs =") < else_body.index("if (totalargs == 0)") < else_body.index("if (totalargs <=") < else_body.index("newargs = newargs_stack;") < else_body.index("newargs[0] = self;") < else_body.index("result = _PyObject_VectorcallTstate") < else_body.index("if (newargs != newargs_stack)")): raise SystemExit("non-offset allocation/call/free order changed") signature = "PyMethod_New(PyObject *func, PyObject *self)" 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 (self == NULL)") if body.count("PyMethodObject *im;") != 1 or body.index("PyMethodObject *im;") > first_statement: raise SystemExit("PyMethod_New object local must be declared before validation") if "PyMethodObject *im =" in body: raise SystemExit("PyMethod_New allocation must remain at its original execution point") order = ( "if (self == NULL)", "im = PyObject_GC_New(PyMethodObject, &PyMethod_Type);", "if (im == NULL)", "im->im_weakreflist = NULL;", "Py_INCREF(func);", "im->im_func = func;", "Py_INCREF(self);", "im->im_self = self;", "im->vectorcall = method_vectorcall;", "_PyObject_GC_TRACK(im);", "return (PyObject *)im;", ) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("PyMethod_New allocation/reference/GC order changed") print("classobject C89 declarations and method initialization order: PASS")