99 lines
4.3 KiB
Python
99 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Guard C89 declaration placement and reference order in iterobject.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")
|
|
|
|
getiter = extract_function(source, "anextawaitable_getiter(anextawaitableobject *obj)")
|
|
for declaration in ("PyObject *awaitable;", "unaryfunc getter;", "PyObject *new_awaitable;"):
|
|
if declaration not in getiter:
|
|
fail("anextawaitable_getiter local must be declared before use: " + declaration)
|
|
for late in ("PyObject *awaitable =", "unaryfunc getter =", "PyObject *new_awaitable ="):
|
|
if late in getiter:
|
|
fail("anextawaitable_getiter must not initialize a local in a late declaration: " + late)
|
|
branch = getiter.split("if (Py_TYPE(awaitable)->tp_iternext == NULL) {", 1)[1]
|
|
if branch.find("unaryfunc getter;") > branch.find("getter = Py_TYPE(awaitable)->tp_as_async->am_await;"):
|
|
fail("anextawaitable_getiter branch locals must be declared before statements")
|
|
if branch.find("PyObject *new_awaitable;") > branch.find("getter = Py_TYPE(awaitable)->tp_as_async->am_await;"):
|
|
fail("anextawaitable_getiter branch locals must be declared before statements")
|
|
ordered(getiter, (
|
|
"assert(obj->wrapped != NULL);",
|
|
"awaitable = _PyCoro_GetAwaitableIter(obj->wrapped);",
|
|
"if (awaitable == NULL)",
|
|
"if (Py_TYPE(awaitable)->tp_iternext == NULL)",
|
|
"getter = Py_TYPE(awaitable)->tp_as_async->am_await;",
|
|
"new_awaitable = getter(awaitable);",
|
|
"if (new_awaitable == NULL)",
|
|
"Py_SETREF(awaitable, new_awaitable);",
|
|
"if (!PyIter_Check(awaitable))",
|
|
"return awaitable;",
|
|
), "anextawaitable_getiter must preserve awaitable conversion and cleanup order")
|
|
|
|
iternext = extract_function(source, "anextawaitable_iternext(anextawaitableobject *obj)")
|
|
for declaration in ("PyObject *awaitable;", "PyObject *result;"):
|
|
if declaration not in iternext:
|
|
fail("anextawaitable_iternext local must be declared at function entry: " + declaration)
|
|
if "PyObject *result =" in iternext:
|
|
fail("anextawaitable_iternext result must not be declared after the NULL guard")
|
|
ordered(iternext, (
|
|
"awaitable = anextawaitable_getiter(obj);",
|
|
"if (awaitable == NULL)",
|
|
"result = (*Py_TYPE(awaitable)->tp_iternext)(awaitable);",
|
|
"Py_DECREF(awaitable);",
|
|
"if (result != NULL)",
|
|
"if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration))",
|
|
"_PyGen_SetStopIterationValue(obj->default_value);",
|
|
"\n return NULL;",
|
|
), "anextawaitable_iternext must preserve call, decref, and StopAsyncIteration handling order")
|
|
|
|
proxy = extract_function(source, "anextawaitable_proxy(anextawaitableobject *obj, char *meth, PyObject *arg)")
|
|
for declaration in ("PyObject *awaitable;", "PyObject *ret;"):
|
|
if declaration not in proxy:
|
|
fail("anextawaitable_proxy local must be declared at function entry: " + declaration)
|
|
if "PyObject *ret =" in proxy:
|
|
fail("anextawaitable_proxy result must not be declared after the NULL guard")
|
|
ordered(proxy, (
|
|
"awaitable = anextawaitable_getiter(obj);",
|
|
"if (awaitable == NULL)",
|
|
"ret = PyObject_CallMethod(awaitable, meth, \"O\", arg);",
|
|
"Py_DECREF(awaitable);",
|
|
"if (ret != NULL)",
|
|
"if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration))",
|
|
"_PyGen_SetStopIterationValue(obj->default_value);",
|
|
"\n return NULL;",
|
|
), "anextawaitable_proxy must preserve call, decref, and exception handling order")
|
|
|
|
print("iterobject anextawaitable C89 declarations and reference order: PASS")
|