164 lines
6.4 KiB
Python
164 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Guard C89 declarations and cleanup order in function construction."""
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import NoReturn
|
|
|
|
|
|
def fail(message: str) -> NoReturn:
|
|
print(message, file=sys.stderr)
|
|
raise SystemExit(1)
|
|
|
|
|
|
def extract_function(source: str, signature: str) -> str:
|
|
start = source.find(signature)
|
|
if start < 0:
|
|
fail(f"missing function signature: {signature}")
|
|
brace = source.find("{", start + len(signature))
|
|
if brace < 0:
|
|
fail("missing function body")
|
|
depth = 0
|
|
for end in range(brace, len(source)):
|
|
if source[end] == "{":
|
|
depth += 1
|
|
elif source[end] == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return source[brace + 1:end]
|
|
fail("unterminated function body")
|
|
|
|
|
|
source = Path(sys.argv[1]).read_text(encoding="utf-8")
|
|
body = extract_function(
|
|
source,
|
|
"PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname)",
|
|
)
|
|
declarations = (
|
|
"PyThreadState *tstate;",
|
|
"PyCodeObject *code_obj;",
|
|
"PyObject *name;",
|
|
"PyObject *consts;",
|
|
"PyObject *doc;",
|
|
"PyObject *module;",
|
|
"PyObject *builtins;",
|
|
"PyFunctionObject *op;",
|
|
)
|
|
remaining = body.lstrip()
|
|
for declaration in declarations:
|
|
if not remaining.startswith(declaration + "\n"):
|
|
fail("PyFunction_NewWithQualName entry declaration missing or out of order: " + declaration)
|
|
remaining = remaining[len(declaration):].lstrip()
|
|
|
|
for late_declaration in (
|
|
"PyThreadState *tstate =",
|
|
"PyCodeObject *code_obj =",
|
|
"PyObject *name =",
|
|
"PyObject *consts =",
|
|
"PyObject *doc;\n if",
|
|
"PyObject *module =",
|
|
"PyObject *builtins =",
|
|
"PyFunctionObject *op =",
|
|
):
|
|
if late_declaration in body:
|
|
fail("PyFunction_NewWithQualName contains a late declaration: " + late_declaration)
|
|
|
|
ordered = (
|
|
"assert(globals != NULL);",
|
|
"assert(PyDict_Check(globals));",
|
|
"Py_INCREF(globals);",
|
|
"tstate = _PyThreadState_GET();",
|
|
"code_obj = (PyCodeObject *)code;",
|
|
"Py_INCREF(code_obj);",
|
|
"name = code_obj->co_name;",
|
|
"assert(name != NULL);",
|
|
"Py_INCREF(name);",
|
|
"if (!qualname)",
|
|
"assert(qualname != NULL);",
|
|
"Py_INCREF(qualname);",
|
|
"consts = code_obj->co_consts;",
|
|
"assert(PyTuple_Check(consts));",
|
|
"if (PyTuple_Size(consts) >= 1)",
|
|
"doc = PyTuple_GetItem(consts, 0);",
|
|
"Py_INCREF(doc);",
|
|
"module = PyDict_GetItemWithError(globals, &_Py_ID(__name__));",
|
|
"builtins = NULL;",
|
|
"if (module == NULL && _PyErr_Occurred(tstate))",
|
|
"Py_XINCREF(module);",
|
|
"builtins = _PyEval_BuiltinsFromGlobals(tstate, globals);",
|
|
"Py_INCREF(builtins);",
|
|
"op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);",
|
|
"if (op == NULL)",
|
|
"op->func_globals = globals;",
|
|
"op->func_qualname = qualname;",
|
|
"op->func_doc = doc;",
|
|
"_PyObject_GC_TRACK(op);",
|
|
"return (PyObject *)op;",
|
|
"error:",
|
|
"Py_DECREF(globals);",
|
|
"Py_DECREF(code_obj);",
|
|
"Py_DECREF(doc);",
|
|
"Py_XDECREF(module);",
|
|
"Py_XDECREF(builtins);",
|
|
"return NULL;",
|
|
)
|
|
positions = [body.find(token) for token in ordered]
|
|
if any(position < 0 for position in positions) or positions != sorted(positions):
|
|
fail("PyFunction_NewWithQualName must preserve acquisition, failure cleanup, and ownership order")
|
|
|
|
version = extract_function(source, "_PyFunction_GetVersionForCurrentState(PyFunctionObject *func)")
|
|
if not version.lstrip().startswith("uint32_t v;") or "uint32_t v =" in version:
|
|
fail("_PyFunction_GetVersionForCurrentState local must be declared at function entry")
|
|
ordered_version = (
|
|
"if (func->func_version != 0)",
|
|
"if (next_func_version == 0)",
|
|
"v = next_func_version++;",
|
|
"func->func_version = v;",
|
|
"return v;",
|
|
)
|
|
positions = [version.find(token) for token in ordered_version]
|
|
if any(position < 0 for position in positions) or positions != sorted(positions):
|
|
fail("function-version allocation must preserve guard and update order")
|
|
|
|
annotation_dict = extract_function(source, "func_get_annotation_dict(PyFunctionObject *op)")
|
|
if "if (PyTuple_CheckExact(op->func_annotations)) {\n Py_ssize_t i;" not in annotation_dict:
|
|
fail("func_get_annotation_dict loop index must be declared at the owning block entry")
|
|
if "for (Py_ssize_t i =" in annotation_dict:
|
|
fail("func_get_annotation_dict must not declare the loop index in its for initializer")
|
|
if not annotation_dict.find("assert(PyTuple_GET_SIZE(ann_tuple) % 2 == 0);") < annotation_dict.find("for (i = 0;") < annotation_dict.find("Py_SETREF(op->func_annotations, ann_dict);"):
|
|
fail("func_get_annotation_dict must preserve tuple validation, iteration, and replacement order")
|
|
|
|
get_annotations = extract_function(source, "func_get_annotations(PyFunctionObject *op, void *Py_UNUSED(ignored))")
|
|
if not get_annotations.lstrip().startswith("PyObject *d;") or "PyObject *d =" in get_annotations:
|
|
fail("func_get_annotations result local must be declared at function entry")
|
|
ordered_get_annotations = (
|
|
"if (op->func_annotations == NULL)",
|
|
"op->func_annotations = PyDict_New();",
|
|
"d = func_get_annotation_dict(op);",
|
|
"if (d)",
|
|
"Py_INCREF(d);",
|
|
"return d;",
|
|
)
|
|
positions = [get_annotations.find(token) for token in ordered_get_annotations]
|
|
if any(position < 0 for position in positions) or positions != sorted(positions):
|
|
fail("func_get_annotations must preserve lazy allocation and incref order")
|
|
|
|
copy_attr = extract_function(source, "functools_copy_attr(PyObject *wrapper, PyObject *wrapped, PyObject *name)")
|
|
remaining = copy_attr.lstrip()
|
|
for declaration in ("PyObject *value;", "int res;"):
|
|
if not remaining.startswith(declaration + "\n"):
|
|
fail("functools_copy_attr entry declaration missing or out of order: " + declaration)
|
|
remaining = remaining[len(declaration):].lstrip()
|
|
if "PyObject *value =" in copy_attr or "int res =" in copy_attr:
|
|
fail("functools_copy_attr must not declare initialized locals after statements")
|
|
ordered_copy_attr = (
|
|
"value = PyObject_GetAttr(wrapped, name);",
|
|
"if (value == NULL)",
|
|
"res = PyObject_SetAttr(wrapper, name, value);",
|
|
"Py_DECREF(value);",
|
|
"return res;",
|
|
)
|
|
positions = [copy_attr.find(token) for token in ordered_copy_attr]
|
|
if any(position < 0 for position in positions) or positions != sorted(positions):
|
|
fail("functools_copy_attr must preserve attribute lookup, error handling, and decref order")
|
|
print("funcobject C89 declarations and function-construction ownership order: PASS")
|