96 lines
5.7 KiB
Python
96 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Guard C89 local ordering in frame_getlocals."""
|
|
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")
|
|
if "#define WILL_OVERFLOW (PY_ULL(1)<<((MAX_STACK_ENTRIES-1)*BITS_PER_BLOCK))" not in source:
|
|
fail("frame stack overflow threshold must use Python's portable unsigned-long-long macro")
|
|
if "#define WILL_OVERFLOW (1ULL<<" in source:
|
|
fail("frame stack overflow threshold must not use a compiler-specific ULL literal")
|
|
body = extract_function(source, "frame_getlocals(PyFrameObject *f, void *closure)")
|
|
declaration = " PyObject *locals;"
|
|
if declaration not in body or "PyObject *locals =" in body:
|
|
fail("frame_getlocals locals must be declared without initializer at entry")
|
|
if not body.find("if (PyFrame_FastToLocalsWithError(f) < 0)") < body.find("locals = f->f_frame->f_locals;") < body.find("Py_INCREF(locals);") < body.find("return locals;"):
|
|
fail("frame_getlocals must preserve conversion, lookup, incref, and return order")
|
|
|
|
swap = extract_function(source, "stack_swap(int64_t stack, int n)")
|
|
for declaration in (" Kind to_swap, top;", " int shift;", " int64_t replaced_low, replaced_top;"):
|
|
if declaration not in swap:
|
|
fail("stack_swap locals must be declared before its assertion")
|
|
if "Kind to_swap =" in swap or "Kind top =" in swap or "int shift =" in swap or "int64_t replaced_low =" in swap or "int64_t replaced_top =" in swap:
|
|
fail("stack_swap temporaries must not be initialized after statements")
|
|
if not swap.find("assert(n >= 1);") < swap.find("to_swap = peek(stack, n);") < swap.find("top = top_of_stack(stack);") < swap.find("shift = BITS_PER_BLOCK*(n-1);") < swap.find("replaced_low =") < swap.find("replaced_top ="):
|
|
fail("stack_swap must preserve peek, top, shift, and replacement order")
|
|
|
|
pop_level = extract_function(source, "pop_to_level(int64_t stack, int level)")
|
|
if " int64_t max_item, level_max_stack;" not in pop_level:
|
|
fail("pop_to_level locals must be declared at function entry")
|
|
if "int64_t max_item =" in pop_level or "int64_t level_max_stack =" in pop_level:
|
|
fail("pop_to_level locals must not be initialized after the early return")
|
|
if not pop_level.find("if (level == 0)") < pop_level.find("max_item = (1<<BITS_PER_BLOCK) - 1;") < pop_level.find("level_max_stack = max_item <<") < pop_level.find("while (stack > level_max_stack)"):
|
|
fail("pop_to_level must preserve level guard and stack reduction order")
|
|
|
|
mark = extract_function(source, "mark_stacks(PyCodeObject *code_obj, int len)")
|
|
for declaration in (
|
|
" PyObject *co_code;", " _Py_CODEUNIT *code;", " int64_t *stacks;",
|
|
" int i, j, opcode, todo;", " int64_t next_stack, target_stack;",
|
|
" int n, delta;", " unsigned char *start, *end, *scan;",
|
|
" int start_offset, size, handler, depth_and_lasti, level, lasti;",
|
|
" uint64_t exception_target_stack;",
|
|
):
|
|
if declaration not in mark:
|
|
fail("mark_stacks locals must be declared at function entry")
|
|
if "for (int " in mark or "int64_t target_stack =" in mark or "uint64_t target_stack =" in mark or "int64_t next_stack =" in mark:
|
|
fail("mark_stacks must not introduce declarations after statements")
|
|
if not mark.find("co_code = _PyCode_GetCode(code_obj);") < mark.find("code = (_Py_CODEUNIT *)PyBytes_AS_STRING(co_code);") < mark.find("stacks = PyMem_New(int64_t, len+1);"):
|
|
fail("mark_stacks must preserve code lookup and stack allocation order")
|
|
|
|
explain = extract_function(source, "explain_incompatible_stack(int64_t to_stack)")
|
|
if " Kind target_kind;" not in explain or "Kind target_kind =" in explain:
|
|
fail("explain_incompatible_stack target kind must be declared at function entry")
|
|
if not explain.find("assert(to_stack != 0);") < explain.find("target_kind = top_of_stack(to_stack);") < explain.find("switch(target_kind)"):
|
|
fail("explain_incompatible_stack must preserve assertion and dispatch order")
|
|
|
|
marklines = extract_function(source, "marklines(PyCodeObject *code, int len)")
|
|
for declaration in (" PyCodeAddressRange bounds;", " int last_line;", " int *linestarts;", " int i;"):
|
|
if declaration not in marklines:
|
|
fail("marklines locals must be declared at function entry")
|
|
if "int last_line =" in marklines or "int *linestarts =" in marklines or "for (int i =" in marklines:
|
|
fail("marklines must not declare locals after statements")
|
|
if not marklines.find("_PyCode_InitAddressRange(code, &bounds);") < marklines.find("last_line = -1;") < marklines.find("linestarts = PyMem_New(int, len);") < marklines.find("for (i = 0; i < len; i++)"):
|
|
fail("marklines must preserve range-init/allocation/initialization order")
|
|
|
|
firstline = extract_function(source, "first_line_not_before(int *lines, int len, int line)")
|
|
if " int result, i;" not in firstline or "for (int i =" in firstline:
|
|
fail("first_line_not_before loop index must be declared at function entry")
|
|
if not firstline.find("result = INT_MAX;") < firstline.find("for (i = 0; i < len; i++)"):
|
|
fail("first_line_not_before must initialize its minimum before scanning")
|
|
print("frameobject C89 declarations and frame locals ownership order: PASS")
|