63 lines
3.7 KiB
Python
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")
|