Files
Python3-Win9x/scripts/vc6-frameobject-c89.test.py

571 lines
27 KiB
Python

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