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