#!/usr/bin/env python3 """Guard C89 declarations and ownership/order invariants in interpreteridobject.c.""" 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") def ordered(body: str, tokens: tuple[str, ...], message: str) -> None: positions = [body.find(token) for token in tokens] if any(position < 0 for position in positions) or positions != sorted(positions): fail(message) source = Path(sys.argv[1]).read_text(encoding="utf-8") newinterpid = extract_function(source, "newinterpid(PyTypeObject *cls, int64_t id, int force)") for declaration in ("PyInterpreterState *interp;", "interpid *self;"): if declaration not in newinterpid: fail("newinterpid local must be declared at function entry: " + declaration) if "PyInterpreterState *interp =" in newinterpid or "interpid *self =" in newinterpid: fail("newinterpid must not initialize locals in late declarations") ordered(newinterpid, ( "interp = _PyInterpreterState_LookUpID(id);", "if (interp == NULL)", "if (interp != NULL)", "_PyInterpreterState_IDIncref(interp)", "self = PyObject_New(interpid, cls);", "if (self == NULL)", "_PyInterpreterState_IDDecref(interp);", "self->id = id;", "return self;", ), "newinterpid must preserve lookup, incref, allocation, rollback, and assignment order") dealloc = extract_function(source, "interpid_dealloc(PyObject *v)") for declaration in ("int64_t id;", "PyInterpreterState *interp;"): if declaration not in dealloc: fail("interpid_dealloc local must be declared at function entry: " + declaration) ordered(dealloc, ( "id = ((interpid *)v)->id;", "interp = _PyInterpreterState_LookUpID(id);", "_PyInterpreterState_IDDecref(interp);", "PyErr_Clear();", "Py_TYPE(v)->tp_free(v);", ), "interpid_dealloc must preserve ID lookup, reference release, and free order") hash_body = extract_function(source, "interpid_hash(PyObject *self)") if "Py_hash_t hash;" not in hash_body or "Py_hash_t hash =" in hash_body: fail("interpid_hash hash result must be declared before object hashing") ordered(hash_body, ( "PyObject *obj = PyLong_FromLongLong(id->id);", "if (obj == NULL)", "hash = PyObject_Hash(obj);", "Py_DECREF(obj);", "return hash;", ), "interpid_hash must preserve allocation, hashing, decref, and return order") richcompare = extract_function(source, "interpid_richcompare(PyObject *self, PyObject *other, int op)") for declaration in ("interpid *id;", "int equal;"): if declaration not in richcompare: fail("interpid_richcompare local must be declared at function entry: " + declaration) ordered(richcompare, ( "if (op != Py_EQ && op != Py_NE)", "if (!PyObject_TypeCheck(self, &_PyInterpreterID_Type))", "id = (interpid *)self;", "if (PyObject_TypeCheck(other, &_PyInterpreterID_Type))", "interpid *otherid = (interpid *)other;", "equal = (id->id == otherid->id);", "else if (PyLong_CheckExact(other))", "int overflow;", "PY_LONG_LONG otherid;", "otherid = PyLong_AsLongLongAndOverflow(other, &overflow);", "equal = !overflow && (otherid >= 0) && (id->id == otherid);", "else if (PyNumber_Check(other))", "pyid = PyLong_FromLongLong(id->id);", "res = PyObject_RichCompare(pyid, other, op);", "Py_DECREF(pyid);", "return res;", "if ((op == Py_EQ && equal) || (op == Py_NE && !equal))", "Py_RETURN_TRUE;", "Py_RETURN_FALSE;", ), "interpid_richcompare must preserve dispatch, overflow, and reference ordering") get_id = extract_function(source, "_PyInterpreterState_GetIDObject(PyInterpreterState *interp)") if "int64_t id;" not in get_id or "int64_t id =" in get_id: fail("_PyInterpreterState_GetIDObject ID local must be declared at function entry") ordered(get_id, ( "if (_PyInterpreterState_IDInitref(interp) != 0)", "id = PyInterpreterState_GetID(interp);", "if (id < 0)", "return (PyObject *)newinterpid(&_PyInterpreterID_Type, id, 0);", ), "_PyInterpreterState_GetIDObject must preserve initref and ID validation order") print("interpreteridobject C89 declarations and ownership order: PASS")