Files
Python3-Win9x/scripts/vc6-genobject-c89.test.py
T

278 lines
14 KiB
Python

#!/usr/bin/env python3
"""Guard C89 declarations and frame traversal order in genobject.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")
source = Path(sys.argv[1]).read_text(encoding="utf-8")
body = extract_function(source, "gen_traverse(PyGenObject *gen, visitproc visit, void *arg)")
if "int err;" not in body or "int err =" in body:
fail("gen_traverse frame result local must be declared before traversal assertions")
ordered = (
"if (gen->gi_frame_state < FRAME_CLEARED)",
"_PyInterpreterFrame *frame = (_PyInterpreterFrame *)(gen->gi_iframe);",
"assert(frame->frame_obj == NULL ||",
"err = _PyFrame_Traverse(frame, visit, arg);",
"if (err)",
"return exc_state_traverse(&gen->gi_exc_state, visit, arg);",
)
positions = [body.find(token) for token in ordered]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("gen_traverse must preserve frame validation, visit, and error return order")
gen_yf = extract_function(source, "_PyGen_yf(PyGenObject *gen)")
if "_Py_CODEUNIT next;" not in gen_yf or "_Py_CODEUNIT next =" in gen_yf:
fail("_PyGen_yf instruction local must be declared at function entry")
ordered_yf = (
"PyObject *yf = NULL;",
"if (gen->gi_frame_state < FRAME_CLEARED)",
"if (gen->gi_frame_state == FRAME_CREATED)",
"return NULL;",
"next = frame->prev_instr[1];",
"if (_PyOpcode_Deopt[_Py_OPCODE(next)] != RESUME",
"yf = _PyFrame_StackPeek(frame);",
"Py_INCREF(yf);",
"return yf;",
)
positions = [gen_yf.find(token) for token in ordered_yf]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("_PyGen_yf must preserve created-frame guard and yield-from inspection order")
gen_throw = extract_function(source, "_gen_throw(PyGenObject *gen, int close_on_genexit,\n PyObject *typ, PyObject *val, PyObject *tb)")
if "PyFrameState state;" not in gen_throw:
fail("_gen_throw nested generator-state locals must be declared at branch entry")
recursive_branch = gen_throw.split("if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {", 1)[1].split("} else {", 1)[0]
if "PyFrameState state;" not in recursive_branch or "PyFrameState state =" in recursive_branch:
fail("_gen_throw recursive branch state local must be declared before current-frame updates")
ordered_recursive = (
"PyThreadState *tstate = _PyThreadState_GET();",
"_PyInterpreterFrame *prev = tstate->cframe->current_frame;",
"frame->previous = prev;",
"tstate->cframe->current_frame = frame;",
"state = gen->gi_frame_state;",
"gen->gi_frame_state = FRAME_EXECUTING;",
"ret = _gen_throw((PyGenObject *)yf, close_on_genexit,",
"gen->gi_frame_state = state;",
"tstate->cframe->current_frame = prev;",
"frame->previous = NULL;",
)
positions = [recursive_branch.find(token) for token in ordered_recursive]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("_gen_throw must preserve recursive frame/state restoration order")
iterator_branch = gen_throw.split("} else {", 1)[1]
if "PyFrameState state;" not in iterator_branch or "PyFrameState state =" in iterator_branch:
fail("_gen_throw iterator branch state local must be declared at branch entry")
ordered_iterator = (
"PyObject *meth;",
"if (_PyObject_LookupAttr(yf, &_Py_ID(throw), &meth) < 0)",
"if (meth == NULL)",
"state = gen->gi_frame_state;",
"gen->gi_frame_state = FRAME_EXECUTING;",
"ret = PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);",
"gen->gi_frame_state = state;",
)
positions = [iterator_branch.find(token) for token in ordered_iterator]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("_gen_throw must preserve iterator lookup and state restoration order")
throw_failure = gen_throw.split("if (!ret) {", 1)[1]
if "int jump;" not in throw_failure or "int jump =" in throw_failure:
fail("_gen_throw SEND jump local must be declared at its failure-branch entry")
ordered_jump = (
"PyObject *val;",
"assert(gen->gi_frame_state < FRAME_CLEARED);",
"ret = _PyFrame_StackPop((_PyInterpreterFrame *)gen->gi_iframe);",
"assert(_PyInterpreterFrame_LASTI(frame) >= 0);",
"assert(_Py_OPCODE(frame->prev_instr[-1]) == SEND);",
"jump = _Py_OPARG(frame->prev_instr[-1]);",
"frame->prev_instr += jump - 1;",
"if (_PyGen_FetchStopIterationValue(&val) == 0)",
)
positions = [throw_failure.find(token) for token in ordered_jump]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("_gen_throw must preserve SEND validation and jump/stop-value handling order")
gen_sizeof = extract_function(source, "gen_sizeof(PyGenObject *gen, PyObject *Py_UNUSED(ignored))")
if "PyCodeObject *code;" not in gen_sizeof or "PyCodeObject *code =" in gen_sizeof:
fail("gen_sizeof code local must be declared before size calculation")
ordered_sizeof = (
"res = offsetof(PyGenObject, gi_iframe) + offsetof(_PyInterpreterFrame, localsplus);",
"code = gen->gi_code;",
"res += (code->co_nlocalsplus+code->co_stacksize) * sizeof(PyObject *);",
"return PyLong_FromSsize_t(res);",
)
positions = [gen_sizeof.find(token) for token in ordered_sizeof]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("gen_sizeof must preserve base-size, code-size, and result order")
make_coro = extract_function(source, "_Py_MakeCoro(PyFunctionObject *func)")
entry_coro = ("int coro_flags;", "PyObject *coro;", "PyThreadState *tstate;", "int origin_depth;")
remaining = make_coro.lstrip()
for declaration in entry_coro:
if not remaining.startswith(declaration + "\n"):
fail("_Py_MakeCoro entry declaration missing or out of order: " + declaration)
remaining = remaining[len(declaration):].lstrip()
if "int coro_flags =" in make_coro or "PyObject *coro =" in make_coro or "PyThreadState *tstate =" in make_coro or "int origin_depth =" in make_coro:
fail("_Py_MakeCoro must not declare initialized locals after control flow")
if "PyObject *cr_origin;" not in make_coro or "PyObject *cr_origin =" in make_coro:
fail("_Py_MakeCoro origin local must be declared at the fallback branch entry")
ordered_coro = (
"coro_flags = ((PyCodeObject *)func->func_code)->co_flags &",
"if (coro_flags == CO_GENERATOR)",
"if (coro_flags == CO_ASYNC_GENERATOR)",
"assert (coro_flags == CO_COROUTINE);",
"coro = make_gen(&PyCoro_Type, func);",
"tstate = _PyThreadState_GET();",
"origin_depth = tstate->coroutine_origin_tracking_depth;",
"if (origin_depth == 0)",
"cr_origin = compute_cr_origin(origin_depth, _PyEval_GetFrame()->previous);",
"return coro;",
)
positions = [make_coro.find(token) for token in ordered_coro]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("_Py_MakeCoro must preserve coroutine selection, creation, and origin tracking order")
compute_origin_start = source.rfind("static PyObject *\ncompute_cr_origin(int origin_depth, _PyInterpreterFrame *current_frame)")
if compute_origin_start < 0:
fail("missing compute_cr_origin definition")
compute_origin = extract_function(source[compute_origin_start:], "compute_cr_origin(int origin_depth, _PyInterpreterFrame *current_frame)")
for declaration in ("_PyInterpreterFrame *frame;", "int frame_count;", "PyObject *cr_origin;", "int i;"):
if declaration not in compute_origin:
fail("compute_cr_origin local must be declared before frame traversal: " + declaration)
if "int frame_count =" in compute_origin or "PyObject *cr_origin =" in compute_origin or "for (int i =" in compute_origin:
fail("compute_cr_origin must not declare initialized locals after traversal starts")
ordered_origin = (
"frame = current_frame;",
"frame_count = 0;",
"for (; frame && frame_count < origin_depth; ++frame_count)",
"cr_origin = PyTuple_New(frame_count);",
"if (cr_origin == NULL)",
"frame = current_frame;\n for (i = 0; i < frame_count; ++i)",
"PyTuple_SET_ITEM(cr_origin, i, frameinfo);",
"return cr_origin;",
)
positions = [compute_origin.find(token) for token in ordered_origin]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("compute_cr_origin must preserve frame counting, tuple creation, and collection order")
pycoro_new = extract_function(source, "PyCoro_New(PyFrameObject *f, PyObject *name, PyObject *qualname)")
for declaration in ("PyObject *coro;", "PyThreadState *tstate;", "int origin_depth;"):
if declaration not in pycoro_new:
fail("PyCoro_New local must be declared at function entry: " + declaration)
if "PyObject *coro =" in pycoro_new or "PyThreadState *tstate =" in pycoro_new or "int origin_depth =" in pycoro_new:
fail("PyCoro_New must not declare initialized locals after allocation")
if "PyObject *cr_origin;" not in pycoro_new or "PyObject *cr_origin =" in pycoro_new:
fail("PyCoro_New origin local must be declared at the fallback branch entry")
ordered_pycoro = (
"coro = gen_new_with_qualname(&PyCoro_Type, f, name, qualname);",
"if (!coro)",
"tstate = _PyThreadState_GET();",
"origin_depth = tstate->coroutine_origin_tracking_depth;",
"if (origin_depth == 0)",
"cr_origin = compute_cr_origin(origin_depth, _PyEval_GetFrame());",
"return coro;",
)
positions = [pycoro_new.find(token) for token in ordered_pycoro]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("PyCoro_New must preserve allocation and origin-tracking order")
gen_new_qualname = extract_function(source, "gen_new_with_qualname(PyTypeObject *type, PyFrameObject *f,\n PyObject *name, PyObject *qualname)")
if "_PyInterpreterFrame *frame;" not in gen_new_qualname or "_PyInterpreterFrame *frame =" in gen_new_qualname:
fail("gen_new_with_qualname frame local must be declared before allocation checks")
ordered_gen_new = (
"PyCodeObject *code = f->f_frame->f_code;",
"int size = code->co_nlocalsplus + code->co_stacksize;",
"PyGenObject *gen = PyObject_GC_NewVar(PyGenObject, type, size);",
"if (gen == NULL)",
"assert(f->f_frame->frame_obj == NULL);",
"assert(f->f_frame->owner == FRAME_OWNED_BY_FRAME_OBJECT);",
"frame = (_PyInterpreterFrame *)gen->gi_iframe;",
"_PyFrame_Copy((_PyInterpreterFrame *)f->_f_frame_data, frame);",
"gen->gi_frame_state = FRAME_CREATED;",
"return (PyObject *)gen;",
)
positions = [gen_new_qualname.find(token) for token in ordered_gen_new]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("gen_new_with_qualname must preserve frame validation, copy, and ownership order")
asend_dealloc = extract_function(source, "async_gen_asend_dealloc(PyAsyncGenASend *o)")
if not asend_dealloc.lstrip().startswith("#if _PyAsyncGen_MAXFREELIST > 0\n struct _Py_async_gen_state *state;\n#endif"):
fail("async_gen_asend_dealloc state local must be declared at function entry under its feature guard")
if "struct _Py_async_gen_state *state =" in asend_dealloc:
fail("async_gen_asend_dealloc must not declare state after object cleanup")
ordered_asend_dealloc = (
"_PyObject_GC_UNTRACK((PyObject *)o);",
"Py_CLEAR(o->ags_gen);",
"Py_CLEAR(o->ags_sendval);",
"state = get_async_gen_state();",
"assert(state->asend_numfree != -1);",
"if (state->asend_numfree < _PyAsyncGen_MAXFREELIST)",
"state->asend_freelist[state->asend_numfree++] = o;",
"PyObject_GC_Del(o);",
)
positions = [asend_dealloc.find(token) for token in ordered_asend_dealloc]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("async_gen_asend_dealloc must preserve untracking, clearing, freelist, and free order")
wrapped_dealloc = extract_function(source, "async_gen_wrapped_val_dealloc(_PyAsyncGenWrappedValue *o)")
if not wrapped_dealloc.lstrip().startswith("#if _PyAsyncGen_MAXFREELIST > 0\n struct _Py_async_gen_state *state;\n#endif"):
fail("wrapped-value freelist state must be declared at function entry under its feature guard")
ordered_wrapped = (
"_PyObject_GC_UNTRACK((PyObject *)o);",
"Py_CLEAR(o->agw_val);",
"state = get_async_gen_state();",
"assert(state->value_numfree != -1);",
"if (state->value_numfree < _PyAsyncGen_MAXFREELIST)",
"state->value_freelist[state->value_numfree++] = o;",
"PyObject_GC_Del(o);",
)
positions = [wrapped_dealloc.find(token) for token in ordered_wrapped]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("wrapped-value deallocator must preserve untrack, clear, freelist, and free order")
wrapped_new = extract_function(source, "_PyAsyncGenValueWrapperNew(PyObject *val)")
state_decl = "#if _PyAsyncGen_MAXFREELIST > 0\n struct _Py_async_gen_state *state;\n#endif"
if state_decl not in wrapped_new or wrapped_new.find(state_decl) > wrapped_new.find("assert(val);"):
fail("_PyAsyncGenValueWrapperNew freelist state must be declared before executable statements")
if "struct _Py_async_gen_state *state =" in wrapped_new:
fail("_PyAsyncGenValueWrapperNew must not initialize state in a late declaration")
ordered_wrapped_new = (
"assert(val);",
"state = get_async_gen_state();",
"assert(state->value_numfree != -1);",
"if (state->value_numfree)",
"o = PyObject_GC_New(_PyAsyncGenWrappedValue,",
"o->agw_val = val;",
"Py_INCREF(val);",
"_PyObject_GC_TRACK((PyObject*)o);",
"return (PyObject*)o;",
)
positions = [wrapped_new.find(token) for token in ordered_wrapped_new]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("_PyAsyncGenValueWrapperNew must preserve freelist, allocation, and value ownership order")
print("genobject C89 declaration and generator ownership order: PASS")