Files
Python3-Win9x/scripts/vc6-classobject-c89.test.py

63 lines
3.7 KiB
Python

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