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