#!/usr/bin/env python3 """Guard C89 local ordering and frame operation order.""" 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") setlineno = extract_function(source, 'frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignored))') entry_declarations = ( 'PyFrameState state;', 'int i;', 'int new_lineno;', 'int overflow;', 'long l_new_lineno;', 'int len;', 'int *lines;', 'int64_t *stacks;', 'int64_t best_stack;', 'int best_addr;', 'int64_t start_stack;', 'int err;', 'const char *msg;', ) remaining = setlineno.lstrip() for declaration in entry_declarations: if not remaining.startswith(declaration + "\n"): fail("frame_setlineno function-entry declaration missing or out of order: " + declaration) remaining = remaining[len(declaration):].lstrip() # Freeze the execution body, including validation, cleanup, error selection, # loop order, frame stack operations, and unchanged nested block declarations. # Only blank lines and indentation are ignored; no executable text is omitted. expected_execution = r""" if (p_new_lineno == NULL) { PyErr_SetString(PyExc_AttributeError, "cannot delete attribute"); return -1; } /* f_lineno must be an integer. */ if (!PyLong_CheckExact(p_new_lineno)) { PyErr_SetString(PyExc_ValueError, "lineno must be an integer"); return -1; } state = _PyFrame_GetState(f); /* * This code preserves the historical restrictions on * setting the line number of a frame. * Jumps are forbidden on a 'return' trace event (except after a yield). * Jumps from 'call' trace events are also forbidden. * In addition, jumps are forbidden when not tracing, * as this is a debugging feature. */ switch(PyThreadState_GET()->tracing_what) { case PyTrace_EXCEPTION: PyErr_SetString(PyExc_ValueError, "can only jump from a 'line' trace event"); return -1; case PyTrace_CALL: PyErr_Format(PyExc_ValueError, "can't jump from the 'call' trace event of a new frame"); return -1; case PyTrace_LINE: break; case PyTrace_RETURN: if (state == FRAME_SUSPENDED) { break; } /* fall through */ default: PyErr_SetString(PyExc_ValueError, "can only jump from a 'line' trace event"); return -1; } if (!f->f_trace) { PyErr_Format(PyExc_ValueError, "f_lineno can only be set by a trace function"); return -1; } /* Fail if the line falls outside the code block and select first line with actual code. */ l_new_lineno = PyLong_AsLongAndOverflow(p_new_lineno, &overflow); if (overflow #if SIZEOF_LONG > SIZEOF_INT || l_new_lineno > INT_MAX || l_new_lineno < INT_MIN #endif ) { PyErr_SetString(PyExc_ValueError, "lineno out of range"); return -1; } new_lineno = (int)l_new_lineno; if (new_lineno < f->f_frame->f_code->co_firstlineno) { PyErr_Format(PyExc_ValueError, "line %d comes before the current code block", new_lineno); return -1; } /* PyCode_NewWithPosOnlyArgs limits co_code to be under INT_MAX so this * should never overflow. */ len = (int)Py_SIZE(f->f_frame->f_code); lines = marklines(f->f_frame->f_code, len); if (lines == NULL) { return -1; } new_lineno = first_line_not_before(lines, len, new_lineno); if (new_lineno < 0) { PyErr_Format(PyExc_ValueError, "line %d comes after the current code block", (int)l_new_lineno); PyMem_Free(lines); return -1; } stacks = mark_stacks(f->f_frame->f_code, len); if (stacks == NULL) { PyMem_Free(lines); return -1; } best_stack = OVERFLOWED; best_addr = -1; start_stack = stacks[_PyInterpreterFrame_LASTI(f->f_frame)]; err = -1; msg = "cannot find bytecode for specified line"; for (i = 0; i < len; i++) { if (lines[i] == new_lineno) { int64_t target_stack = stacks[i]; if (compatible_stack(start_stack, target_stack)) { err = 0; if (target_stack > best_stack) { best_stack = target_stack; best_addr = i; } } else if (err < 0) { if (start_stack == OVERFLOWED) { msg = "stack to deep to analyze"; } else if (start_stack == UNINITIALIZED) { msg = "can't jump from unreachable code"; } else { msg = explain_incompatible_stack(target_stack); err = 1; } } } } PyMem_Free(stacks); PyMem_Free(lines); if (err) { PyErr_SetString(PyExc_ValueError, msg); return -1; } if (state == FRAME_SUSPENDED) { /* Account for value popped by yield */ start_stack = pop_value(start_stack); } while (start_stack > best_stack) { if (top_of_stack(start_stack) == Except) { /* Pop exception stack as well as the evaluation stack */ PyThreadState *tstate = _PyThreadState_GET(); _PyErr_StackItem *exc_info = tstate->exc_info; PyObject *value = exc_info->exc_value; PyObject *exc = _PyFrame_StackPop(f->f_frame); assert(PyExceptionInstance_Check(exc) || exc == Py_None); exc_info->exc_value = exc; Py_XDECREF(value); } else { PyObject *v = _PyFrame_StackPop(f->f_frame); Py_XDECREF(v); } start_stack = pop_value(start_stack); } /* Finally set the new lasti and return OK. */ f->f_lineno = 0; f->f_frame->prev_instr = _PyCode_CODE(f->f_frame->f_code) + best_addr; return 0; """ def execution_lines(text: str) -> list: return [line.strip() for line in text.splitlines() if line.strip()] if execution_lines(remaining) != execution_lines(expected_execution): fail("frame_setlineno execution body changed or contains a late function-scope declaration") frame_dealloc = extract_function(source, "frame_dealloc(PyFrameObject *f)") expected_dealloc_declarations = ( "PyCodeObject *co;", "_PyInterpreterFrame *frame;", "PyObject **locals;", "int i;", ) remaining = frame_dealloc.lstrip() for declaration in expected_dealloc_declarations: if not remaining.startswith(declaration + "\n"): fail("frame_dealloc entry declaration missing or out of order: " + declaration) remaining = remaining[len(declaration):].lstrip() if "PyCodeObject *co =" in frame_dealloc or "_PyInterpreterFrame *frame =" in frame_dealloc or "PyObject **locals =" in frame_dealloc or "for (int i =" in frame_dealloc: fail("frame_dealloc must not declare locals after statements") ordered_dealloc = ( "Py_TRASHCAN_BEGIN(f, frame_dealloc);", "co = NULL;", "frame = (_PyInterpreterFrame *)f->_f_frame_data;", "if (f->f_frame == frame && frame->owner == FRAME_OWNED_BY_FRAME_OBJECT)", "co = frame->f_code;", "frame->f_code = NULL;", "Py_CLEAR(frame->f_func);", "Py_CLEAR(frame->f_locals);", "locals = _PyFrame_GetLocalsArray(frame);", "for (i = 0; i < frame->stacktop; i++)", "Py_CLEAR(f->f_back);", "Py_CLEAR(f->f_trace);", "PyObject_GC_Del(f);", "Py_XDECREF(co);", "Py_TRASHCAN_END;", ) positions = [frame_dealloc.find(token) for token in ordered_dealloc] if any(position < 0 for position in positions) or positions != sorted(positions): fail("frame_dealloc must preserve trashcan, owned-frame cleanup, and decref order") frame_clear_locals = extract_function(source, "frame_tp_clear(PyFrameObject *f)") remaining = frame_clear_locals.lstrip() for declaration in ("PyObject **locals;", "int i;"): if not remaining.startswith(declaration + "\n"): fail("frame_tp_clear entry declaration missing or out of order: " + declaration) remaining = remaining[len(declaration):].lstrip() if "PyObject **locals =" in frame_clear_locals or "for (int i =" in frame_clear_locals: fail("frame_tp_clear must not declare locals after statements") ordered_clear = ( "Py_CLEAR(f->f_trace);", "locals = _PyFrame_GetLocalsArray(f->f_frame);", "assert(f->f_frame->stacktop >= 0);", "for (i = 0; i < f->f_frame->stacktop; i++)", "Py_CLEAR(locals[i]);", "f->f_frame->stacktop = 0;", ) positions = [frame_clear_locals.find(token) for token in ordered_clear] if any(position < 0 for position in positions) or positions != sorted(positions): fail("frame_tp_clear must preserve trace/local/stack cleanup order") frame_sizeof = extract_function(source, "frame_sizeof(PyFrameObject *f, PyObject *Py_UNUSED(ignored))") remaining = frame_sizeof.lstrip() for declaration in ("Py_ssize_t res;", "PyCodeObject *code;"): if not remaining.startswith(declaration + "\n"): fail("frame_sizeof entry declaration missing or out of order: " + declaration) remaining = remaining[len(declaration):].lstrip() if "PyCodeObject *code =" in frame_sizeof: fail("frame_sizeof code pointer must not be declared after initialization") ordered_sizeof = ( "res = offsetof(PyFrameObject, _f_frame_data) + offsetof(_PyInterpreterFrame, localsplus);", "code = f->f_frame->f_code;", "res += (code->co_nlocalsplus+code->co_stacksize) * sizeof(PyObject *);", "return PyLong_FromSsize_t(res);", ) positions = [frame_sizeof.find(token) for token in ordered_sizeof] if any(position < 0 for position in positions) or positions != sorted(positions): fail("frame_sizeof must preserve size calculation and return order") init_frame = extract_function(source, "init_frame(_PyInterpreterFrame *frame, PyFunctionObject *func, PyObject *locals)") remaining = init_frame.lstrip() for declaration in ("PyCodeObject *code;", "Py_ssize_t i;"): if not remaining.startswith(declaration + "\n"): fail("init_frame entry declaration missing or out of order: " + declaration) remaining = remaining[len(declaration):].lstrip() if "PyCodeObject *code =" in init_frame or "for (Py_ssize_t i =" in init_frame: fail("init_frame must not declare locals after statements") ordered_init_frame = ( "Py_INCREF(func);", "code = (PyCodeObject *)func->func_code;", "_PyFrame_InitializeSpecials(frame, func, locals, code->co_nlocalsplus);", "frame->previous = NULL;", "for (i = 0; i < code->co_nlocalsplus; i++)", "frame->localsplus[i] = NULL;", ) positions = [init_frame.find(token) for token in ordered_init_frame] if any(position < 0 for position in positions) or positions != sorted(positions): fail("init_frame must preserve reference and frame initialization order") new_no_track = extract_function(source, "_PyFrame_New_NoTrack(PyCodeObject *code)") remaining = new_no_track.lstrip() for declaration in ("int slots;", "PyFrameObject *f;"): if not remaining.startswith(declaration + "\n"): fail("_PyFrame_New_NoTrack entry declaration missing or out of order: " + declaration) remaining = remaining[len(declaration):].lstrip() if "int slots =" in new_no_track or "PyFrameObject *f =" in new_no_track: fail("_PyFrame_New_NoTrack locals must not be initialized after statements") ordered_new_no_track = ( "CALL_STAT_INC(frame_objects_created);", "slots = code->co_nlocalsplus + code->co_stacksize;", "f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, slots);", "if (f == NULL)", "f->f_back = NULL;", "f->f_lineno = 0;", "return f;", ) positions = [new_no_track.find(token) for token in ordered_new_no_track] if any(position < 0 for position in positions) or positions != sorted(positions): fail("_PyFrame_New_NoTrack must preserve stat, allocation, and initialization order") frame_new = extract_function(source, "PyFrame_New(PyThreadState *tstate, PyCodeObject *code,") remaining = frame_new.lstrip() for declaration in ( "PyObject *builtins;", "PyFrameConstructor desc;", "PyFunctionObject *func;", "PyFrameObject *f;", ): if not remaining.startswith(declaration + "\n"): fail("PyFrame_New entry declaration missing or out of order: " + declaration) remaining = remaining[len(declaration):].lstrip() if "PyFrameConstructor desc =" in frame_new or "PyFunctionObject *func =" in frame_new or "PyFrameObject *f =" in frame_new: fail("PyFrame_New must not use initialized late declarations") if "\n .fc_globals =" in frame_new or "\n .fc_builtins =" in frame_new: fail("PyFrame_New constructor must not use designated initializers") ordered_frame_new = ( "builtins = _PyEval_BuiltinsFromGlobals(tstate, globals);", "if (builtins == NULL)", "desc.fc_globals = globals;", "desc.fc_builtins = builtins;", "desc.fc_name = code->co_name;", "desc.fc_qualname = code->co_name;", "desc.fc_code = (PyObject *)code;", "desc.fc_defaults = NULL;", "desc.fc_kwdefaults = NULL;", "desc.fc_closure = NULL;", "func = _PyFunction_FromConstructor(&desc);", "if (func == NULL)", "f = _PyFrame_New_NoTrack(code);", "if (f == NULL)", "init_frame((_PyInterpreterFrame *)f->_f_frame_data, func, locals);", "Py_DECREF(func);", "return f;", ) positions = [frame_new.rfind(token) if token == "Py_DECREF(func);" else frame_new.find(token) for token in ordered_frame_new] if any(position < 0 for position in positions) or positions != sorted(positions): fail("PyFrame_New must preserve validation, construction, and cleanup order") op_already_ran = extract_function(source, "_PyFrame_OpAlreadyRan(_PyInterpreterFrame *frame, int opcode, int oparg)") remaining = op_already_ran.lstrip() for declaration in ("int check_oparg;", "_Py_CODEUNIT *instruction;"): if not remaining.startswith(declaration + "\n"): fail("_PyFrame_OpAlreadyRan entry declaration missing or out of order: " + declaration) remaining = remaining[len(declaration):].lstrip() if "int check_oparg =" in op_already_ran or "for (_Py_CODEUNIT *instruction =" in op_already_ran: fail("_PyFrame_OpAlreadyRan must not declare locals after statements") ordered_op_check = ( "assert(_PyOpcode_Deopt[opcode] == opcode);", "check_oparg = 0;", "for (instruction = _PyCode_CODE(frame->f_code);", "instruction < frame->prev_instr; instruction++)", "int check_opcode = _PyOpcode_Deopt[_Py_OPCODE(*instruction)];", "check_oparg |= _Py_OPARG(*instruction);", "instruction += _PyOpcode_Caches[check_opcode];", "return 0;", ) positions = [op_already_ran.find(token) for token in ordered_op_check] if any(position < 0 for position in positions) or positions != sorted(positions): fail("_PyFrame_OpAlreadyRan must preserve assert, opcode scan, and return order") fast_to_locals = extract_function(source, "_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame)") entry_statements = fast_to_locals.find("locals = frame->f_locals;") for declaration in ("int lasti;", "int i;"): if fast_to_locals.find(declaration) < 0 or fast_to_locals.find(declaration) > entry_statements: fail("_PyFrame_FastToLocalsWithError loop/state locals must be declared at entry: " + declaration) if "int lasti =" in fast_to_locals or "for (int i =" in fast_to_locals: fail("_PyFrame_FastToLocalsWithError must not use late or loop declarations") if not fast_to_locals.find("lasti = _PyInterpreterFrame_LASTI(frame);") < fast_to_locals.find("if (lasti < 0"): fail("_PyFrame_FastToLocalsWithError must preserve instruction-state lookup order") if "PyObject *name;" not in fast_to_locals or "PyObject *value;" not in fast_to_locals: fail("_PyFrame_FastToLocalsWithError loop item locals must be declared before loop statements") if not fast_to_locals.find("if (kind & CO_FAST_FREE && !(co->co_flags & CO_OPTIMIZED))") < fast_to_locals.find("name = PyTuple_GET_ITEM(co->co_localsplusnames, i);") < fast_to_locals.find("value = fast[i];"): fail("_PyFrame_FastToLocalsWithError must preserve skip and local lookup order") public_fast_to_locals = extract_function(source, "PyFrame_FastToLocalsWithError(PyFrameObject *f)") if not public_fast_to_locals.lstrip().startswith("int err;") or "int err =" in public_fast_to_locals: fail("PyFrame_FastToLocalsWithError result must be declared at function entry") ordered_public_fast = ( "assert(!_PyFrame_IsIncomplete(f->f_frame));", "if (f == NULL)", "err = _PyFrame_FastToLocalsWithError(f->f_frame);", "if (err == 0)", "f->f_fast_as_locals = 1;", "return err;", ) positions = [public_fast_to_locals.find(token) for token in ordered_public_fast] if any(position < 0 for position in positions) or positions != sorted(positions): fail("PyFrame_FastToLocalsWithError must preserve guard and status-update order") locals_to_fast = extract_function(source, "_PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear)") entry_statement = locals_to_fast.find("locals = frame->f_locals;") if "int i;" not in locals_to_fast or locals_to_fast.find("int i;") > entry_statement or "for (int i =" in locals_to_fast: fail("_PyFrame_LocalsToFast index must be declared before execution") if "PyObject *name;" not in locals_to_fast or "PyObject *value;" not in locals_to_fast or "PyObject *oldvalue;" not in locals_to_fast or "PyObject *cell;" not in locals_to_fast: fail("_PyFrame_LocalsToFast loop temporaries must be declared at loop entry") if "PyObject *name =" in locals_to_fast or "PyObject *value =" in locals_to_fast or "PyObject *oldvalue =" in locals_to_fast or "PyObject *cell =" in locals_to_fast: fail("_PyFrame_LocalsToFast must not use late initialized declarations") ordered_locals_to_fast = ( "PyErr_Fetch(&error_type, &error_value, &error_traceback);", "for (i = 0; i < co->co_nlocalsplus; i++)", "kind = _PyLocals_GetKind(co->co_localspluskinds, i);", "if (kind & CO_FAST_FREE && !(co->co_flags & CO_OPTIMIZED))", "name = PyTuple_GET_ITEM(co->co_localsplusnames, i);", "value = PyObject_GetItem(locals, name);", "if (value == NULL)", "oldvalue = fast[i];", "cell = NULL;", "Py_XDECREF(value);", "PyErr_Restore(error_type, error_value, error_traceback);", ) positions = [locals_to_fast.find(token) for token in ordered_locals_to_fast] if any(position < 0 for position in positions) or positions != sorted(positions): fail("_PyFrame_LocalsToFast must preserve error, lookup, and refcount order") get_code = extract_function(source, "PyFrame_GetCode(PyFrameObject *frame)") if not get_code.lstrip().startswith("PyCodeObject *code;") or "PyCodeObject *code =" in get_code: fail("PyFrame_GetCode local must be declared at function entry") ordered_get_code = ( "assert(frame != NULL);", "assert(!_PyFrame_IsIncomplete(frame->f_frame));", "code = frame->f_frame->f_code;", "assert(code != NULL);", "Py_INCREF(code);", "return code;", ) positions = [get_code.find(token) for token in ordered_get_code] if any(position < 0 for position in positions) or positions != sorted(positions): fail("PyFrame_GetCode must preserve validation and reference ownership order") get_back = extract_function(source, "PyFrame_GetBack(PyFrameObject *frame)") if not get_back.lstrip().startswith("PyFrameObject *back;") or "PyFrameObject *back =" in get_back: fail("PyFrame_GetBack local must be declared at function entry") ordered_get_back = ( "assert(frame != NULL);", "assert(!_PyFrame_IsIncomplete(frame->f_frame));", "back = frame->f_back;", "if (back == NULL)", "_PyInterpreterFrame *prev = frame->f_frame->previous;", "back = _PyFrame_GetFrameObject(prev);", "Py_XINCREF(back);", "return back;", ) positions = [get_back.find(token) for token in ordered_get_back] if any(position < 0 for position in positions) or positions != sorted(positions): fail("PyFrame_GetBack must preserve frame-chain lookup and incref order") get_lasti = extract_function(source, "PyFrame_GetLasti(PyFrameObject *frame)") if not get_lasti.lstrip().startswith("int lasti;") or "int lasti =" in get_lasti: fail("PyFrame_GetLasti local must be declared at function entry") ordered_get_lasti = ( "assert(!_PyFrame_IsIncomplete(frame->f_frame));", "lasti = _PyInterpreterFrame_LASTI(frame->f_frame);", "if (lasti < 0)", "return lasti * sizeof(_Py_CODEUNIT);", ) positions = [get_lasti.find(token) for token in ordered_get_lasti] if any(position < 0 for position in positions) or positions != sorted(positions): fail("PyFrame_GetLasti must preserve assertion and bytecode-offset order") get_generator = extract_function(source, "PyFrame_GetGenerator(PyFrameObject *frame)") if not get_generator.lstrip().startswith("PyGenObject *gen;") or "PyGenObject *gen =" in get_generator: fail("PyFrame_GetGenerator local must be declared at function entry") ordered_get_generator = ( "assert(!_PyFrame_IsIncomplete(frame->f_frame));", "if (frame->f_frame->owner != FRAME_OWNED_BY_GENERATOR)", "return NULL;", "gen = _PyFrame_GetGenerator(frame->f_frame);", "return Py_NewRef(gen);", ) positions = [get_generator.find(token) for token in ordered_get_generator] if any(position < 0 for position in positions) or positions != sorted(positions): fail("PyFrame_GetGenerator must preserve owner guard and new-reference order") print("frameobject C89 declarations and frame locals ownership order: PASS")