port: expand VC6 C89 core frontier

This commit is contained in:
RayPals
2026-10-03 17:47:57 +00:00
parent 9221a2ee0e
commit 35caf1a558
15 changed files with 5972 additions and 11 deletions
+476 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Guard C89 local ordering in frame_getlocals."""
"""Guard C89 local ordering and frame operation order."""
import sys
from pathlib import Path
from typing import NoReturn
@@ -92,4 +92,479 @@ 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")
+163
View File
@@ -0,0 +1,163 @@
#!/usr/bin/env python3
"""Guard C89 declarations and cleanup order in function construction."""
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,
"PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname)",
)
declarations = (
"PyThreadState *tstate;",
"PyCodeObject *code_obj;",
"PyObject *name;",
"PyObject *consts;",
"PyObject *doc;",
"PyObject *module;",
"PyObject *builtins;",
"PyFunctionObject *op;",
)
remaining = body.lstrip()
for declaration in declarations:
if not remaining.startswith(declaration + "\n"):
fail("PyFunction_NewWithQualName entry declaration missing or out of order: " + declaration)
remaining = remaining[len(declaration):].lstrip()
for late_declaration in (
"PyThreadState *tstate =",
"PyCodeObject *code_obj =",
"PyObject *name =",
"PyObject *consts =",
"PyObject *doc;\n if",
"PyObject *module =",
"PyObject *builtins =",
"PyFunctionObject *op =",
):
if late_declaration in body:
fail("PyFunction_NewWithQualName contains a late declaration: " + late_declaration)
ordered = (
"assert(globals != NULL);",
"assert(PyDict_Check(globals));",
"Py_INCREF(globals);",
"tstate = _PyThreadState_GET();",
"code_obj = (PyCodeObject *)code;",
"Py_INCREF(code_obj);",
"name = code_obj->co_name;",
"assert(name != NULL);",
"Py_INCREF(name);",
"if (!qualname)",
"assert(qualname != NULL);",
"Py_INCREF(qualname);",
"consts = code_obj->co_consts;",
"assert(PyTuple_Check(consts));",
"if (PyTuple_Size(consts) >= 1)",
"doc = PyTuple_GetItem(consts, 0);",
"Py_INCREF(doc);",
"module = PyDict_GetItemWithError(globals, &_Py_ID(__name__));",
"builtins = NULL;",
"if (module == NULL && _PyErr_Occurred(tstate))",
"Py_XINCREF(module);",
"builtins = _PyEval_BuiltinsFromGlobals(tstate, globals);",
"Py_INCREF(builtins);",
"op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);",
"if (op == NULL)",
"op->func_globals = globals;",
"op->func_qualname = qualname;",
"op->func_doc = doc;",
"_PyObject_GC_TRACK(op);",
"return (PyObject *)op;",
"error:",
"Py_DECREF(globals);",
"Py_DECREF(code_obj);",
"Py_DECREF(doc);",
"Py_XDECREF(module);",
"Py_XDECREF(builtins);",
"return NULL;",
)
positions = [body.find(token) for token in ordered]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("PyFunction_NewWithQualName must preserve acquisition, failure cleanup, and ownership order")
version = extract_function(source, "_PyFunction_GetVersionForCurrentState(PyFunctionObject *func)")
if not version.lstrip().startswith("uint32_t v;") or "uint32_t v =" in version:
fail("_PyFunction_GetVersionForCurrentState local must be declared at function entry")
ordered_version = (
"if (func->func_version != 0)",
"if (next_func_version == 0)",
"v = next_func_version++;",
"func->func_version = v;",
"return v;",
)
positions = [version.find(token) for token in ordered_version]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("function-version allocation must preserve guard and update order")
annotation_dict = extract_function(source, "func_get_annotation_dict(PyFunctionObject *op)")
if "if (PyTuple_CheckExact(op->func_annotations)) {\n Py_ssize_t i;" not in annotation_dict:
fail("func_get_annotation_dict loop index must be declared at the owning block entry")
if "for (Py_ssize_t i =" in annotation_dict:
fail("func_get_annotation_dict must not declare the loop index in its for initializer")
if not annotation_dict.find("assert(PyTuple_GET_SIZE(ann_tuple) % 2 == 0);") < annotation_dict.find("for (i = 0;") < annotation_dict.find("Py_SETREF(op->func_annotations, ann_dict);"):
fail("func_get_annotation_dict must preserve tuple validation, iteration, and replacement order")
get_annotations = extract_function(source, "func_get_annotations(PyFunctionObject *op, void *Py_UNUSED(ignored))")
if not get_annotations.lstrip().startswith("PyObject *d;") or "PyObject *d =" in get_annotations:
fail("func_get_annotations result local must be declared at function entry")
ordered_get_annotations = (
"if (op->func_annotations == NULL)",
"op->func_annotations = PyDict_New();",
"d = func_get_annotation_dict(op);",
"if (d)",
"Py_INCREF(d);",
"return d;",
)
positions = [get_annotations.find(token) for token in ordered_get_annotations]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("func_get_annotations must preserve lazy allocation and incref order")
copy_attr = extract_function(source, "functools_copy_attr(PyObject *wrapper, PyObject *wrapped, PyObject *name)")
remaining = copy_attr.lstrip()
for declaration in ("PyObject *value;", "int res;"):
if not remaining.startswith(declaration + "\n"):
fail("functools_copy_attr entry declaration missing or out of order: " + declaration)
remaining = remaining[len(declaration):].lstrip()
if "PyObject *value =" in copy_attr or "int res =" in copy_attr:
fail("functools_copy_attr must not declare initialized locals after statements")
ordered_copy_attr = (
"value = PyObject_GetAttr(wrapped, name);",
"if (value == NULL)",
"res = PyObject_SetAttr(wrapper, name, value);",
"Py_DECREF(value);",
"return res;",
)
positions = [copy_attr.find(token) for token in ordered_copy_attr]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("functools_copy_attr must preserve attribute lookup, error handling, and decref order")
print("funcobject C89 declarations and function-construction ownership order: PASS")
+344
View File
@@ -0,0 +1,344 @@
#!/usr/bin/env python3
"""Guard C89 declarations and writer error order in GenericAlias repr."""
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 "static PyMappingMethods ga_as_mapping = {\n 0,\n ga_getitem,\n 0,\n};" not in source:
fail("ga_as_mapping must use VC6-compatible positional fields in PyMappingMethods order")
body = extract_function(source, "ga_repr(PyObject *self)")
if "Py_ssize_t i;" not in body or "for (Py_ssize_t i =" in body:
fail("ga_repr loop index must be declared before executable statements")
if "PyObject *p;" not in body or "PyObject *p =" in body:
fail("ga_repr tuple item must be declared at the loop-block entry")
ordered = (
"_PyUnicodeWriter_Init(&writer);",
"if (alias->starred)",
"if (ga_repr_item(&writer, alias->origin) < 0)",
"if (_PyUnicodeWriter_WriteASCIIString(&writer, \"[\", 1) < 0)",
"for (i = 0; i < len; i++)",
"if (i > 0)",
"p = PyTuple_GET_ITEM(alias->args, i);",
"if (ga_repr_item(&writer, p) < 0)",
"if (len == 0)",
"if (_PyUnicodeWriter_WriteASCIIString(&writer, \"]\", 1) < 0)",
"return _PyUnicodeWriter_Finish(&writer);",
"_PyUnicodeWriter_Dealloc(&writer);",
)
positions = [body.find(token) for token in ordered]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("ga_repr must preserve writer and tuple-iteration error order")
tuple_index = extract_function(source, "tuple_index(PyObject *self, Py_ssize_t len, PyObject *item)")
if not tuple_index.lstrip().startswith("Py_ssize_t i;") or "for (Py_ssize_t i =" in tuple_index:
fail("tuple_index loop variable must be declared at function entry")
if not tuple_index.find("for (i = 0; i < len; i++)") < tuple_index.find("if (PyTuple_GET_ITEM(self, i) == item)") < tuple_index.find("return -1;"):
fail("tuple_index must preserve scan and not-found behavior")
tuple_extend = extract_function(source, "tuple_extend(PyObject **dst, Py_ssize_t dstindex,\n PyObject **src, Py_ssize_t count)")
if not tuple_extend.lstrip().startswith("Py_ssize_t i;") or "for (Py_ssize_t i =" in tuple_extend:
fail("tuple_extend loop index must be declared at function entry")
if "PyObject *item;" not in tuple_extend or "PyObject *item =" in tuple_extend:
fail("tuple_extend item local must be declared at loop-block entry")
ordered_extend = (
"assert(count >= 0);",
"if (_PyTuple_Resize(dst, PyTuple_GET_SIZE(*dst) + count - 1) != 0)",
"assert(dstindex + count <= PyTuple_GET_SIZE(*dst));",
"for (i = 0; i < count; ++i)",
"item = src[i];",
"Py_INCREF(item);",
"PyTuple_SET_ITEM(*dst, dstindex + i, item);",
"return dstindex + count;",
)
positions = [tuple_extend.find(token) for token in ordered_extend]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("tuple_extend must preserve resize, retain, and write order")
make_parameters = extract_function(source, "_Py_make_parameters(PyObject *args)")
entry_declarations = (
"Py_ssize_t nargs;",
"Py_ssize_t len;",
"PyObject *parameters;",
"Py_ssize_t iparam;",
"Py_ssize_t iarg;",
)
remaining = make_parameters.lstrip()
for declaration in entry_declarations:
if not remaining.startswith(declaration + "\n"):
fail("_Py_make_parameters entry declaration missing or out of order: " + declaration)
remaining = remaining[len(declaration):].lstrip()
if "for (Py_ssize_t iarg =" in make_parameters or "Py_ssize_t iparam =" in make_parameters:
fail("_Py_make_parameters must not declare function-scope locals after statements")
if "Py_ssize_t j;" not in make_parameters or "for (Py_ssize_t j =" in make_parameters:
fail("_Py_make_parameters nested loop index must be declared at its block entry")
subs_tvars = extract_function(source, "subs_tvars(PyObject *obj, PyObject *params,\n PyObject **argitems, Py_ssize_t nargs)")
if "Py_ssize_t j;" not in subs_tvars or "Py_ssize_t i;" not in subs_tvars:
fail("subs_tvars loop indices must be declared at the tuple-processing block entry")
if "Py_ssize_t j =" in subs_tvars or "for (Py_ssize_t i =" in subs_tvars:
fail("subs_tvars must not initialize indices after statements or in for headers")
ordered_subs = (
"Py_ssize_t nparams = PyTuple_GET_SIZE(params);",
"Py_ssize_t nsubargs = PyTuple_GET_SIZE(subparams);",
"PyObject *subargs = PyTuple_New(nsubargs);",
"if (subargs == NULL)",
"j = 0;",
"for (i = 0; i < nsubargs; ++i)",
"PyObject *arg = PyTuple_GET_ITEM(subparams, i);",
"Py_ssize_t iparam = tuple_index(params, nparams, arg);",
"if (iparam >= 0)",
"Py_INCREF(arg);",
"PyTuple_SET_ITEM(subargs, j, arg);",
"Py_DECREF(subargs);",
"Py_XDECREF(subparams);",
"return obj;",
)
positions = [subs_tvars.find(token) for token in ordered_subs]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("subs_tvars must preserve tuple allocation, substitution, and cleanup order")
is_unpacked = extract_function(source, "_is_unpacked_typevartuple(PyObject *arg)")
if not is_unpacked.lstrip().startswith("PyObject *tmp;\n int res;") or "int res =" in is_unpacked:
fail("_is_unpacked_typevartuple locals must be declared at function entry")
ordered_is_unpacked = (
"if (PyType_Check(arg))",
"res = _PyObject_LookupAttr(arg, &_Py_ID(__typing_is_unpacked_typevartuple__), &tmp);",
"if (res > 0)",
"res = PyObject_IsTrue(tmp);",
"Py_DECREF(tmp);",
"return res;",
)
positions = [is_unpacked.find(token) for token in ordered_is_unpacked]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("_is_unpacked_typevartuple must preserve type-check, lookup, and cleanup order")
unpack_args = extract_function(source, "_unpack_args(PyObject *item)")
entry_unpacked = ("PyObject *newargs;", "int is_tuple;", "Py_ssize_t nitems;", "PyObject **argitems;", "Py_ssize_t i;")
remaining = unpack_args.lstrip()
for declaration in entry_unpacked:
if not remaining.startswith(declaration + "\n"):
fail("_unpack_args entry declaration missing or out of order: " + declaration)
remaining = remaining[len(declaration):].lstrip()
if "for (Py_ssize_t i =" in unpack_args:
fail("_unpack_args must not declare its index in the for initializer")
ordered_unpack = (
"newargs = PyList_New(0);",
"if (newargs == NULL)",
"is_tuple = PyTuple_Check(item);",
"nitems = is_tuple ? PyTuple_GET_SIZE(item) : 1;",
"argitems = is_tuple ? &PyTuple_GET_ITEM(item, 0) : &item;",
"for (i = 0; i < nitems; i++)",
"item = argitems[i];",
"if (!PyType_Check(item))",
"PyList_Append(newargs, item)",
"Py_SETREF(newargs, PySequence_Tuple(newargs));",
"return newargs;",
)
positions = [unpack_args.find(token) for token in ordered_unpack]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("_unpack_args must preserve list allocation, element processing, and tuple conversion order")
subs_parameters = extract_function(source, "_Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObject *item)")
entry_subs = ("Py_ssize_t nparams;", "Py_ssize_t i;", "int is_tuple;", "Py_ssize_t nitems;", "PyObject **argitems;", "Py_ssize_t nargs;", "PyObject *newargs;", "Py_ssize_t iarg;", "Py_ssize_t jarg;")
remaining = subs_parameters.lstrip()
for declaration in entry_subs:
if not remaining.startswith(declaration + "\n"):
fail("_Py_subs_parameters entry declaration missing or out of order: " + declaration)
remaining = remaining[len(declaration):].lstrip()
if "for (Py_ssize_t i =" in subs_parameters or "for (Py_ssize_t iarg =" in subs_parameters:
fail("_Py_subs_parameters must not declare loop indices in for initializers")
ordered_subs_parameters = (
"nparams = PyTuple_GET_SIZE(parameters);",
"if (nparams == 0)",
"item = _unpack_args(item);",
"for (i = 0; i < nparams; i++)",
"is_tuple = PyTuple_Check(item);",
"nitems = is_tuple ? PyTuple_GET_SIZE(item) : 1;",
"argitems = is_tuple ? &PyTuple_GET_ITEM(item, 0) : &item;",
"if (nitems != nparams)",
"nargs = PyTuple_GET_SIZE(args);",
"newargs = PyTuple_New(nargs);",
"for (iarg = 0, jarg = 0; iarg < nargs; iarg++)",
"Py_DECREF(item);",
"return newargs;",
)
positions = [subs_parameters.rfind(token) if token == "Py_DECREF(item);" else subs_parameters.find(token) for token in ordered_subs_parameters]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("_Py_subs_parameters must preserve validation, unpacking, substitution, and cleanup order")
ga_getitem = extract_function(source, "ga_getitem(PyObject *self, PyObject *item)")
if not ga_getitem.lstrip().startswith("gaobject *alias = (gaobject *)self;\n PyObject *newargs;\n PyObject *res;"):
fail("ga_getitem result locals must be declared before executable statements")
if "PyObject *newargs =" in ga_getitem or "PyObject *res =" in ga_getitem:
fail("ga_getitem must not declare initialized result locals late")
ordered_getitem = (
"if (alias->parameters == NULL)",
"alias->parameters = _Py_make_parameters(alias->args);",
"newargs = _Py_subs_parameters(self, alias->args, alias->parameters, item);",
"if (newargs == NULL)",
"res = Py_GenericAlias(alias->origin, newargs);",
"((gaobject *)res)->starred = alias->starred;",
"Py_DECREF(newargs);",
"return res;",
)
positions = [ga_getitem.find(token) for token in ordered_getitem]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("ga_getitem must preserve lazy parameters, allocation, starred flag, and decref order")
ga_hash = extract_function(source, "ga_hash(PyObject *self)")
if "Py_hash_t h1;" not in ga_hash or "Py_hash_t h1 =" in ga_hash:
fail("ga_hash second hash local must be declared at function entry")
ordered_hash = (
"Py_hash_t h0 = PyObject_Hash(alias->origin);",
"if (h0 == -1)",
"h1 = PyObject_Hash(alias->args);",
"if (h1 == -1)",
"return h0 ^ h1;",
)
positions = [ga_hash.find(token) for token in ordered_hash]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("ga_hash must preserve origin-first hashing and error checks")
ga_getattro = extract_function(source, "ga_getattro(PyObject *self, PyObject *name)")
if "const char * const *p;" not in ga_getattro or "for (const char * const *p =" in ga_getattro:
fail("ga_getattro attribute-table iterator must be declared before the loop")
ordered_getattro = (
"if (PyUnicode_Check(name))",
"for (p = attr_exceptions; ; p++)",
"if (*p == NULL)",
"return PyObject_GetAttr(alias->origin, name);",
"if (_PyUnicode_EqualToASCIIString(name, *p))",
"return PyObject_GenericGetAttr(self, name);",
)
positions = [ga_getattro.find(token) for token in ordered_getattro]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("ga_getattro must preserve exception-name scan and origin fallback order")
ga_richcompare = extract_function(source, "ga_richcompare(PyObject *a, PyObject *b, int op)")
if "gaobject *aa;" not in ga_richcompare or "gaobject *bb;" not in ga_richcompare or "int eq;" not in ga_richcompare:
fail("ga_richcompare locals must be declared before execution")
if "gaobject *aa =" in ga_richcompare or "gaobject *bb =" in ga_richcompare or "int eq =" in ga_richcompare:
fail("ga_richcompare must not declare locals after control flow")
ordered_richcompare = (
"if (!_PyGenericAlias_Check(b)",
"if (op == Py_NE)",
"aa = (gaobject *)a;",
"bb = (gaobject *)b;",
"if (aa->starred != bb->starred)",
"eq = PyObject_RichCompareBool(aa->origin, bb->origin, Py_EQ);",
"if (eq < 0)",
"return PyObject_RichCompare(aa->args, bb->args, Py_EQ);",
)
positions = [ga_richcompare.find(token) for token in ordered_richcompare]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("ga_richcompare must preserve comparison dispatch and error order")
ga_dir = extract_function(source, "ga_dir(PyObject *self, PyObject *Py_UNUSED(ignored))")
if "PyObject *dir_entry;" not in ga_dir or "const char * const *p;" not in ga_dir or "int contains;" not in ga_dir:
fail("ga_dir working locals must be declared before execution")
if "PyObject *dir_entry =" in ga_dir or "for (const char * const *p =" in ga_dir or "int contains =" in ga_dir:
fail("ga_dir must not declare locals after the origin lookup")
ordered_dir = (
"dir = PyObject_Dir(alias->origin);",
"if (dir == NULL)",
"dir_entry = NULL;",
"for (p = attr_exceptions; ; p++)",
"dir_entry = PyUnicode_FromString(*p);",
"contains = PySequence_Contains(dir, dir_entry);",
"Py_CLEAR(dir_entry);",
"return dir;",
"Py_DECREF(dir);",
"Py_XDECREF(dir_entry);",
)
positions = [ga_dir.find(token) for token in ordered_dir]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("ga_dir must preserve directory lookup, append, and cleanup order")
ga_new = extract_function(source, "ga_new(PyTypeObject *type, PyObject *args, PyObject *kwds)")
if "PyObject *origin;" not in ga_new or "PyObject *arguments;" not in ga_new or "gaobject *self;" not in ga_new:
fail("ga_new allocation locals must be declared before validation")
if "PyObject *origin =" in ga_new or "PyObject *arguments =" in ga_new or "gaobject *self =" in ga_new:
fail("ga_new must not declare initialized locals after argument checks")
ordered_new = (
"if (!_PyArg_NoKeywords(\"GenericAlias\", kwds))",
"if (!_PyArg_CheckPositional(\"GenericAlias\", PyTuple_GET_SIZE(args), 2, 2))",
"origin = PyTuple_GET_ITEM(args, 0);",
"arguments = PyTuple_GET_ITEM(args, 1);",
"self = (gaobject *)type->tp_alloc(type, 0);",
"if (self == NULL)",
"if (!setup_ga(self, origin, arguments))",
"Py_DECREF(self);",
"return (PyObject *)self;",
)
positions = [ga_new.find(token) for token in ordered_new]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("ga_new must preserve argument validation, allocation, and failure cleanup order")
ga_iternext = extract_function(source, "ga_iternext(gaiterobject *gi)")
if "gaobject *alias;" not in ga_iternext or "PyObject *starred_alias;" not in ga_iternext:
fail("ga_iternext result locals must be declared before the exhausted-iterator guard")
if "gaobject *alias =" in ga_iternext or "PyObject *starred_alias =" in ga_iternext:
fail("ga_iternext must not declare initialized locals after the guard")
ordered_next = (
"if (gi->obj == NULL)",
"alias = (gaobject *)gi->obj;",
"starred_alias = Py_GenericAlias(alias->origin, alias->args);",
"if (starred_alias == NULL)",
"((gaobject *)starred_alias)->starred = true;",
"Py_SETREF(gi->obj, NULL);",
"return starred_alias;",
)
positions = [ga_iternext.find(token) for token in ordered_next]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail("ga_iternext must preserve exhausted-state, allocation, and ownership order")
if "#if defined(MS_WINDOWS) && defined(_MSC_VER) && _MSC_VER < 1300\nstatic PyNumberMethods ga_as_number = {0};" not in source:
fail("VC6 ga_as_number initializer must use C89-compatible zero initialization")
if "PyTypeObject _Py_GenericAliasIterType = {\n PyVarObject_HEAD_INIT(&PyType_Type, 0)\n};" not in source:
fail("VC6 GenericAlias iterator type must defer designated slots to runtime setup")
if "PyTypeObject Py_GenericAliasType = {\n PyVarObject_HEAD_INIT(&PyType_Type, 0)\n};" not in source:
fail("VC6 GenericAlias type must defer designated slots to runtime setup")
if "_Py_GenericAlias_InitTypes(void)" not in source:
fail("VC6 GenericAlias type-slot initializer missing")
init_types = extract_function(source, "_Py_GenericAlias_InitTypes(void)")
for field in ("tp_name", "tp_doc", "tp_basicsize", "tp_dealloc", "tp_repr", "tp_as_number", "tp_as_mapping", "tp_hash", "tp_call", "tp_getattro", "tp_flags", "tp_traverse", "tp_richcompare", "tp_weaklistoffset", "tp_methods", "tp_members", "tp_alloc", "tp_new", "tp_free", "tp_getset", "tp_iter", "tp_vectorcall_offset"):
if "Py_GenericAliasType." + field + " =" not in init_types:
fail("VC6 GenericAlias runtime setup missing slot: " + field)
for field in ("tp_name", "tp_basicsize", "tp_iter", "tp_iternext", "tp_traverse", "tp_methods", "tp_dealloc", "tp_clear", "tp_flags"):
if "_Py_GenericAliasIterType." + field + " =" not in init_types:
fail("VC6 GenericAlias iterator runtime setup missing slot: " + field)
if "ga_as_number.nb_or = _Py_union_type_or;" not in init_types:
fail("VC6 GenericAlias number methods runtime setup missing nb_or")
if len(sys.argv) > 2:
object_source = Path(sys.argv[2]).read_text(encoding="utf-8")
if "_Py_GenericAlias_InitTypes();" not in object_source:
fail("object.c must initialize GenericAlias type slots before PyType_Ready")
type_loop = object_source.find("// All other static types (unless initialized elsewhere)")
setup_call = object_source.find("_Py_GenericAlias_InitTypes();")
if setup_call < 0 or type_loop < 0 or setup_call > type_loop:
fail("GenericAlias type-slot initialization must precede static type readiness")
print("genericaliasobject C89 declaration and writer order: PASS")
+277
View File
@@ -0,0 +1,277 @@
#!/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")
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""Guard C89 declarations and ownership/order invariants in interpreteridobject.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")
def ordered(body: str, tokens: tuple[str, ...], message: str) -> None:
positions = [body.find(token) for token in tokens]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail(message)
source = Path(sys.argv[1]).read_text(encoding="utf-8")
newinterpid = extract_function(source, "newinterpid(PyTypeObject *cls, int64_t id, int force)")
for declaration in ("PyInterpreterState *interp;", "interpid *self;"):
if declaration not in newinterpid:
fail("newinterpid local must be declared at function entry: " + declaration)
if "PyInterpreterState *interp =" in newinterpid or "interpid *self =" in newinterpid:
fail("newinterpid must not initialize locals in late declarations")
ordered(newinterpid, (
"interp = _PyInterpreterState_LookUpID(id);",
"if (interp == NULL)",
"if (interp != NULL)",
"_PyInterpreterState_IDIncref(interp)",
"self = PyObject_New(interpid, cls);",
"if (self == NULL)",
"_PyInterpreterState_IDDecref(interp);",
"self->id = id;",
"return self;",
), "newinterpid must preserve lookup, incref, allocation, rollback, and assignment order")
dealloc = extract_function(source, "interpid_dealloc(PyObject *v)")
for declaration in ("int64_t id;", "PyInterpreterState *interp;"):
if declaration not in dealloc:
fail("interpid_dealloc local must be declared at function entry: " + declaration)
ordered(dealloc, (
"id = ((interpid *)v)->id;",
"interp = _PyInterpreterState_LookUpID(id);",
"_PyInterpreterState_IDDecref(interp);",
"PyErr_Clear();",
"Py_TYPE(v)->tp_free(v);",
), "interpid_dealloc must preserve ID lookup, reference release, and free order")
hash_body = extract_function(source, "interpid_hash(PyObject *self)")
if "Py_hash_t hash;" not in hash_body or "Py_hash_t hash =" in hash_body:
fail("interpid_hash hash result must be declared before object hashing")
ordered(hash_body, (
"PyObject *obj = PyLong_FromLongLong(id->id);",
"if (obj == NULL)",
"hash = PyObject_Hash(obj);",
"Py_DECREF(obj);",
"return hash;",
), "interpid_hash must preserve allocation, hashing, decref, and return order")
richcompare = extract_function(source, "interpid_richcompare(PyObject *self, PyObject *other, int op)")
for declaration in ("interpid *id;", "int equal;"):
if declaration not in richcompare:
fail("interpid_richcompare local must be declared at function entry: " + declaration)
ordered(richcompare, (
"if (op != Py_EQ && op != Py_NE)",
"if (!PyObject_TypeCheck(self, &_PyInterpreterID_Type))",
"id = (interpid *)self;",
"if (PyObject_TypeCheck(other, &_PyInterpreterID_Type))",
"interpid *otherid = (interpid *)other;",
"equal = (id->id == otherid->id);",
"else if (PyLong_CheckExact(other))",
"int overflow;",
"PY_LONG_LONG otherid;",
"otherid = PyLong_AsLongLongAndOverflow(other, &overflow);",
"equal = !overflow && (otherid >= 0) && (id->id == otherid);",
"else if (PyNumber_Check(other))",
"pyid = PyLong_FromLongLong(id->id);",
"res = PyObject_RichCompare(pyid, other, op);",
"Py_DECREF(pyid);",
"return res;",
"if ((op == Py_EQ && equal) || (op == Py_NE && !equal))",
"Py_RETURN_TRUE;",
"Py_RETURN_FALSE;",
), "interpid_richcompare must preserve dispatch, overflow, and reference ordering")
get_id = extract_function(source, "_PyInterpreterState_GetIDObject(PyInterpreterState *interp)")
if "int64_t id;" not in get_id or "int64_t id =" in get_id:
fail("_PyInterpreterState_GetIDObject ID local must be declared at function entry")
ordered(get_id, (
"if (_PyInterpreterState_IDInitref(interp) != 0)",
"id = PyInterpreterState_GetID(interp);",
"if (id < 0)",
"return (PyObject *)newinterpid(&_PyInterpreterID_Type, id, 0);",
), "_PyInterpreterState_GetIDObject must preserve initref and ID validation order")
print("interpreteridobject C89 declarations and ownership order: PASS")
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Guard C89 declaration placement and reference order in iterobject.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")
def ordered(body: str, tokens: tuple[str, ...], message: str) -> None:
positions = [body.find(token) for token in tokens]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail(message)
source = Path(sys.argv[1]).read_text(encoding="utf-8")
getiter = extract_function(source, "anextawaitable_getiter(anextawaitableobject *obj)")
for declaration in ("PyObject *awaitable;", "unaryfunc getter;", "PyObject *new_awaitable;"):
if declaration not in getiter:
fail("anextawaitable_getiter local must be declared before use: " + declaration)
for late in ("PyObject *awaitable =", "unaryfunc getter =", "PyObject *new_awaitable ="):
if late in getiter:
fail("anextawaitable_getiter must not initialize a local in a late declaration: " + late)
branch = getiter.split("if (Py_TYPE(awaitable)->tp_iternext == NULL) {", 1)[1]
if branch.find("unaryfunc getter;") > branch.find("getter = Py_TYPE(awaitable)->tp_as_async->am_await;"):
fail("anextawaitable_getiter branch locals must be declared before statements")
if branch.find("PyObject *new_awaitable;") > branch.find("getter = Py_TYPE(awaitable)->tp_as_async->am_await;"):
fail("anextawaitable_getiter branch locals must be declared before statements")
ordered(getiter, (
"assert(obj->wrapped != NULL);",
"awaitable = _PyCoro_GetAwaitableIter(obj->wrapped);",
"if (awaitable == NULL)",
"if (Py_TYPE(awaitable)->tp_iternext == NULL)",
"getter = Py_TYPE(awaitable)->tp_as_async->am_await;",
"new_awaitable = getter(awaitable);",
"if (new_awaitable == NULL)",
"Py_SETREF(awaitable, new_awaitable);",
"if (!PyIter_Check(awaitable))",
"return awaitable;",
), "anextawaitable_getiter must preserve awaitable conversion and cleanup order")
iternext = extract_function(source, "anextawaitable_iternext(anextawaitableobject *obj)")
for declaration in ("PyObject *awaitable;", "PyObject *result;"):
if declaration not in iternext:
fail("anextawaitable_iternext local must be declared at function entry: " + declaration)
if "PyObject *result =" in iternext:
fail("anextawaitable_iternext result must not be declared after the NULL guard")
ordered(iternext, (
"awaitable = anextawaitable_getiter(obj);",
"if (awaitable == NULL)",
"result = (*Py_TYPE(awaitable)->tp_iternext)(awaitable);",
"Py_DECREF(awaitable);",
"if (result != NULL)",
"if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration))",
"_PyGen_SetStopIterationValue(obj->default_value);",
"\n return NULL;",
), "anextawaitable_iternext must preserve call, decref, and StopAsyncIteration handling order")
proxy = extract_function(source, "anextawaitable_proxy(anextawaitableobject *obj, char *meth, PyObject *arg)")
for declaration in ("PyObject *awaitable;", "PyObject *ret;"):
if declaration not in proxy:
fail("anextawaitable_proxy local must be declared at function entry: " + declaration)
if "PyObject *ret =" in proxy:
fail("anextawaitable_proxy result must not be declared after the NULL guard")
ordered(proxy, (
"awaitable = anextawaitable_getiter(obj);",
"if (awaitable == NULL)",
"ret = PyObject_CallMethod(awaitable, meth, \"O\", arg);",
"Py_DECREF(awaitable);",
"if (ret != NULL)",
"if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration))",
"_PyGen_SetStopIterationValue(obj->default_value);",
"\n return NULL;",
), "anextawaitable_proxy must preserve call, decref, and exception handling order")
print("iterobject anextawaitable C89 declarations and reference order: PASS")
+306
View File
@@ -0,0 +1,306 @@
#!/usr/bin/env python3
"""Guard C89 list-allocation declarations and ownership order in listobject.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")
def ordered(body: str, tokens: tuple[str, ...], message: str) -> None:
positions = [body.find(token) for token in tokens]
if any(position < 0 for position in positions) or positions != sorted(positions):
fail(message)
source = Path(sys.argv[1]).read_text(encoding="utf-8")
preallocate = extract_function(source, "list_preallocate_exact(PyListObject *self, Py_ssize_t size)")
if "PyObject **items;" not in preallocate or "PyObject **items =" in preallocate:
fail("list_preallocate_exact items local must be declared before size adjustment")
ordered(preallocate, (
"assert(self->ob_item == NULL);",
"assert(size > 0);",
"size = (size + 1) & ~(size_t)1;",
"items = PyMem_New(PyObject*, size);",
"if (items == NULL)",
"PyErr_NoMemory();",
"self->ob_item = items;",
"self->allocated = size;",
"return 0;",
), "list_preallocate_exact must preserve validation, rounding, allocation, and assignment order")
list_new = extract_function(source, "PyList_New(Py_ssize_t size)")
entry = "PyListObject *op;\n#if PyList_MAXFREELIST > 0\n struct _Py_list_state *state;\n#endif"
if entry not in list_new:
fail("PyList_New freelist state must be declared at function entry under its feature guard")
if "struct _Py_list_state *state =" in list_new:
fail("PyList_New must not initialize state in a late declaration")
ordered(list_new, (
"if (size < 0)",
"PyErr_BadInternalCall();",
"return NULL;",
"state = get_list_state();",
"assert(state->numfree != -1);",
"if (PyList_MAXFREELIST && state->numfree)",
"state->numfree--;",
"op = state->free_list[state->numfree];",
"_Py_NewReference((PyObject *)op);",
"op = PyObject_GC_New(PyListObject, &PyList_Type);",
"if (op == NULL)",
"if (size <= 0)",
"op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *));",
"Py_DECREF(op);",
"Py_SET_SIZE(op, size);",
"op->allocated = size;",
"_PyObject_GC_TRACK(op);",
"return (PyObject *) op;",
), "PyList_New must preserve validation, freelist, allocation, cleanup, and tracking order")
new_prealloc = extract_function(source, "list_new_prealloc(Py_ssize_t size)")
if "PyListObject *op;" not in new_prealloc or "PyListObject *op =" in new_prealloc:
fail("list_new_prealloc object local must be declared before validation")
ordered(new_prealloc, (
"assert(size > 0);",
"op = (PyListObject *) PyList_New(0);",
"if (op == NULL)",
"assert(op->ob_item == NULL);",
"op->ob_item = PyMem_New(PyObject *, size);",
"if (op->ob_item == NULL)",
"Py_DECREF(op);",
"op->allocated = size;",
"return (PyObject *) op;",
), "list_new_prealloc must preserve validation, allocation, cleanup, and assignment order")
list_dealloc = extract_function(source, "list_dealloc(PyListObject *op)")
entry = "Py_ssize_t i;\n#if PyList_MAXFREELIST > 0\n struct _Py_list_state *state;\n#endif"
if entry not in list_dealloc:
fail("list_dealloc freelist state must be declared before trashcan/deallocation operations")
if "struct _Py_list_state *state =" in list_dealloc:
fail("list_dealloc must not initialize state after item cleanup")
ordered(list_dealloc, (
"Py_ssize_t i;",
"PyObject_GC_UnTrack(op);",
"Py_TRASHCAN_BEGIN(op, list_dealloc)",
"i = Py_SIZE(op);",
"Py_XDECREF(op->ob_item[i]);",
"PyMem_Free(op->ob_item);",
"state = get_list_state();",
"assert(state->numfree != -1);",
"if (state->numfree < PyList_MAXFREELIST && PyList_CheckExact(op))",
"state->free_list[state->numfree++] = op;",
"Py_TYPE(op)->tp_free((PyObject *)op);",
"Py_TRASHCAN_END",
), "list_dealloc must preserve untrack, item cleanup, freelist, and free order")
list_repeat = extract_function(source, "list_repeat(PyListObject *a, Py_ssize_t n)")
for declaration in ("PyObject **dest;", "PyObject **dest_end;", "PyObject *elem;", "PyObject **src;", "PyObject **src_end;"):
if declaration not in list_repeat:
fail("list_repeat local must be declared at function entry: " + declaration)
for late in ("PyObject **dest =", "PyObject **dest_end =", "PyObject *elem =", "PyObject **src =", "PyObject **src_end ="):
if late in list_repeat:
fail("list_repeat must not initialize a local in a late declaration: " + late)
ordered(list_repeat, (
"if (n < 0)",
"if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n)",
"size = Py_SIZE(a) * n;",
"if (size == 0)",
"np = (PyListObject *) list_new_prealloc(size);",
"if (np == NULL)",
"dest = np->ob_item;",
"dest_end = dest + size;",
"if (Py_SIZE(a) == 1)",
"elem = a->ob_item[0];",
"Py_SET_REFCNT(elem, Py_REFCNT(elem) + n);",
"src = a->ob_item;",
"src_end = src + Py_SIZE(a);",
"Py_SET_REFCNT(*src, Py_REFCNT(*src) + n);",
"src = np->ob_item;",
"Py_SET_SIZE(np, size);",
"return (PyObject *) np;",
), "list_repeat must preserve size, pointer, and reference-count update order")
powerloop = extract_function(source, "powerloop(Py_ssize_t s1, Py_ssize_t n1, Py_ssize_t n2, Py_ssize_t n)")
for declaration in ("Py_ssize_t a;", "Py_ssize_t b;"):
if declaration not in powerloop:
fail("powerloop midpoint local must be declared at function entry: " + declaration)
if "Py_ssize_t a =" in powerloop or "Py_ssize_t b =" in powerloop:
fail("powerloop midpoint locals must not be declared after assertions")
ordered(powerloop, (
"int result = 0;",
"assert(s1 >= 0);",
"assert(n1 > 0 && n2 > 0);",
"assert(s1 + n1 + n2 <= n);",
"a = 2 * s1 + n1;",
"b = a + n1 + n2;",
"for (;;) {",
"++result;",
"return result;",
), "powerloop must preserve midpoint calculations and iteration order")
new_run = extract_function(source, "found_new_run(MergeState *ms, Py_ssize_t n2)")
branch = new_run.split("if (ms->n) {", 1)[1].split("\n }", 1)[0]
for declaration in ("struct s_slice *p;", "Py_ssize_t s1;", "Py_ssize_t n1;", "int power;"):
if declaration not in branch:
fail("found_new_run locals must be declared at the run branch entry: " + declaration)
for late in ("struct s_slice *p =", "Py_ssize_t s1 =", "Py_ssize_t n1 =", "int power ="):
if late in branch:
fail("found_new_run must not initialize a local after the branch assertion: " + late)
ordered(branch, (
"assert(ms->n > 0);",
"p = ms->pending;",
"s1 = p[ms->n - 1].base.keys - ms->basekeys;",
"n1 = p[ms->n - 1].len;",
"power = powerloop(s1, n1, n2, ms->listlen);",
"while (ms->n > 1 && p[ms->n - 2].power > power)",
"p[ms->n - 1].power = power;",
), "found_new_run must preserve assertion, run lookup, power computation, and merge order")
sort_impl = extract_function(source, "list_sort_impl(PyListObject *self, PyObject *keyfunc, int reverse)")
if "PyObject *key;" not in sort_impl or "PyObject *key =" in sort_impl:
fail("list_sort_impl key local must be declared before sort-time checks")
ordered(sort_impl, (
"if (keyfunc == Py_None)",
"saved_ob_size = Py_SIZE(self);",
"Py_SET_SIZE(self, 0);",
"if (keyfunc == NULL)",
"if (saved_ob_size > 1)",
"for (i=0; i < saved_ob_size; i++)",
"key = (keys_are_in_tuples ?",
"if (!Py_IS_TYPE(key, key_type))",
), "list_sort_impl must preserve key preparation and list mutation order")
for name, signature in (
("list_index_impl", "list_index_impl(PyListObject *self, PyObject *value, Py_ssize_t start,\n Py_ssize_t stop)"),
("list_count", "list_count(PyListObject *self, PyObject *value)"),
("list_remove", "list_remove(PyListObject *self, PyObject *value)"),
):
body = extract_function(source, signature)
loop = body.split("for (i =", 1)[1].split("\n }", 1)[0]
if "int cmp;" not in loop or "int cmp =" in loop:
fail(name + " comparison result must be declared before incref/comparison")
if "cmp = PyObject_RichCompareBool(obj, value, Py_EQ);" not in loop:
fail(name + " must assign comparison result after acquiring obj reference")
ordered(loop, (
"PyObject *obj = self->ob_item[i];",
"int cmp;",
"Py_INCREF(obj);",
"cmp = PyObject_RichCompareBool(obj, value, Py_EQ);",
"Py_DECREF(obj);",
), name + " must preserve item reference and comparison ordering")
richcompare = extract_function(source, "list_richcompare(PyObject *v, PyObject *w, int op)")
loop = richcompare.split("for (i =", 1)[1].split("\n }", 1)[0]
for declaration in ("PyObject *vitem;", "PyObject *witem;", "int k;"):
if declaration not in loop:
fail("list_richcompare loop local must be declared before item comparisons: " + declaration)
ordered(loop, (
"vitem = vl->ob_item[i];",
"witem = wl->ob_item[i];",
"if (vitem == witem)",
"Py_INCREF(vitem);",
"Py_INCREF(witem);",
"k = PyObject_RichCompareBool(vitem, witem, Py_EQ);",
"Py_DECREF(vitem);",
"Py_DECREF(witem);",
), "list_richcompare must preserve item comparison/reference ordering")
if "PyObject *vitem;" not in richcompare or "PyObject *result;" not in richcompare:
fail("list_richcompare final comparison locals must be declared in its final block")
vectorcall = extract_function(source, "list_vectorcall(PyObject *type, PyObject * const*args,\n size_t nargsf, PyObject *kwnames)")
for declaration in ("Py_ssize_t nargs;", "PyObject *list;"):
if declaration not in vectorcall:
fail("list_vectorcall local must be declared at function entry: " + declaration)
ordered(vectorcall, (
"if (!_PyArg_NoKwnames(\"list\", kwnames))",
"nargs = PyVectorcall_NARGS(nargsf);",
"if (!_PyArg_CheckPositional(\"list\", nargs, 0, 1))",
"list = PyType_GenericAlloc(_PyType_CAST(type), 0);",
"if (list == NULL)",
"if (nargs)",
"return list;",
), "list_vectorcall must preserve argument checks and allocation order")
ass_subscript = extract_function(source, "list_ass_subscript(PyListObject* self, PyObject* item, PyObject* value)")
assign_branch = ass_subscript.split("else {\n /* assign slice */", 1)[1]
if "Py_ssize_t slicelength;" not in assign_branch or "Py_ssize_t slicelength =" in assign_branch:
fail("extended-slice assignment length must be declared before sequence lookup")
ordered(assign_branch, (
"Py_ssize_t slicelength;",
"if (self == (PyListObject*)value)",
"seq = PySequence_Fast(value,",
"if (!seq)",
"slicelength = adjust_slice_indexes(self, &start, &stop,",
"if (step == 1)",
"if (PySequence_Fast_GET_SIZE(seq) != slicelength)",
), "extended-slice assignment must preserve input validation and sequence handling order")
type_start = source.find("PyTypeObject PyList_Type = {")
if type_start < 0:
fail("missing PyList_Type initializer")
type_end = source.find("\n};", type_start)
if type_end < 0:
fail("unterminated PyList_Type initializer")
type_init = source[type_start:type_end]
if ".tp_vectorcall" in type_init:
fail("PyList_Type vectorcall must use a VC6-compatible positional initializer")
ordered(type_init, (
"PyObject_GC_Del,",
"0, /* tp_is_gc */",
"0, /* tp_bases */",
"0, /* tp_mro */",
"0, /* tp_cache */",
"0, /* tp_subclasses */",
"0, /* tp_weaklist */",
"0, /* tp_del */",
"0, /* tp_version_tag */",
"0, /* tp_finalize */",
"list_vectorcall, /* tp_vectorcall */",
), "PyList_Type positional tail must match PyTypeObject field order")
reduce_signature = "listiter_reduce_general(void *_it, int forward)"
reduce_start = source.rfind(reduce_signature)
if reduce_start < 0:
fail("missing listiter_reduce_general definition")
reduce_general = extract_function(source[reduce_start:], reduce_signature)
for declaration in ("listiterobject *it;", "listreviterobject *rev_it;"):
if declaration not in reduce_general:
fail("listiter_reduce_general iterator local must be declared at function entry: " + declaration)
ordered(reduce_general, (
"if (forward)",
"iter = _PyEval_GetBuiltin(&_Py_ID(iter));",
"if (!iter)",
"it = (listiterobject *)_it;",
"} else {",
"iter = _PyEval_GetBuiltin(&_Py_ID(reversed));",
"rev_it = (listreviterobject *)_it;",
), "listiter_reduce_general must preserve builtin lookup before iterator access")
forward_guard = reduce_general.find("if (!iter)")
reverse_guard = reduce_general.rfind("if (!iter)")
if not (forward_guard < reduce_general.find("it = (listiterobject *)_it;") <
reduce_general.find("} else {") <
reduce_general.find("iter = _PyEval_GetBuiltin(&_Py_ID(reversed));") <
reverse_guard < reduce_general.find("rev_it = (listreviterobject *)_it;")):
fail("listiter_reduce_general must check both builtin lookups before touching iterators")
print("listobject C89 declarations and allocation order: PASS")
+339
View File
@@ -0,0 +1,339 @@
#!/usr/bin/env python3
"""Focused VC6 C89 regression checks for Objects/longobject.c."""
import pathlib
import re
import sys
source = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8")
match = re.search(r"static PyObject \*\s*get_small_int\(sdigit ival\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "get_small_int definition not found"
body = match.group("body")
assert re.search(r"\A\s*PyObject \*v;\s*assert\(IS_SMALL_INT\(ival\)\);\s*v = \(PyObject \*\)&_PyLong_SMALL_INTS\[_PY_NSMALLNEGINTS \+ ival\];\s*Py_INCREF\(v\);\s*return v;", body), "get_small_int must declare v at function entry and retain assertion, lookup, incref, and return order"
print("longobject get_small_int C89 declaration and evaluation order: PASS")
match = re.search(r"PyLongObject \*\s*_PyLong_New\(Py_ssize_t size\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "_PyLong_New definition not found"
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
expected = """
PyLongObject *result;
Py_ssize_t ndigits;
if (size > (Py_ssize_t)MAX_LONG_DIGITS) {
PyErr_SetString(PyExc_OverflowError,
"too many digits in integer");
return NULL;
}
ndigits = size ? size : 1;
result = PyObject_Malloc(offsetof(PyLongObject, ob_digit) +
ndigits*sizeof(digit));
if (!result) {
PyErr_NoMemory();
return NULL;
}
_PyObject_InitVar((PyVarObject*)result, &PyLong_Type, size);
return result;
"""
assert body.split() == expected.split(), "_PyLong_New must declare ndigits at function entry and retain overflow check, digit count, allocation, error, initialization, and return order"
print("longobject _PyLong_New C89 declaration and allocation/error order: PASS")
match = re.search(r"static PyObject \*\s*_PyLong_FromMedium\(sdigit x\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "_PyLong_FromMedium definition not found"
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
expected = """
PyLongObject *v;
Py_ssize_t sign;
digit abs_x;
assert(!IS_SMALL_INT(x));
assert(is_medium_int(x));
v = PyObject_Malloc(sizeof(PyLongObject));
if (v == NULL) {
PyErr_NoMemory();
return NULL;
}
sign = x < 0 ? -1: 1;
abs_x = x < 0 ? -x : x;
_PyObject_InitVar((PyVarObject*)v, &PyLong_Type, sign);
v->ob_digit[0] = abs_x;
return (PyObject*)v;
"""
assert body.split() == expected.split(), "_PyLong_FromMedium must declare v, sign, and abs_x at function entry and retain assertions, allocation, NULL/error/return, sign, abs_x, initialization, digit write, and return order"
print("longobject _PyLong_FromMedium C89 declarations and evaluation/error order: PASS")
match = re.search(r"static PyObject \*\s*_PyLong_FromLarge\(stwodigits ival\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "_PyLong_FromLarge definition not found"
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
expected = """
twodigits abs_ival;
twodigits t;
Py_ssize_t ndigits;
PyLongObject *v;
digit *p;
int sign;
assert(!is_medium_int(ival));
if (ival < 0) {
abs_ival = 0U-(twodigits)ival;
sign = -1;
}
else {
abs_ival = (twodigits)ival;
sign = 1;
}
assert(abs_ival >> PyLong_SHIFT != 0);
t = abs_ival >> (PyLong_SHIFT * 2);
ndigits = 2;
while (t) {
++ndigits;
t >>= PyLong_SHIFT;
}
v = _PyLong_New(ndigits);
if (v != NULL) {
p = v->ob_digit;
Py_SET_SIZE(v, ndigits * sign);
t = abs_ival;
while (t) {
*p++ = Py_SAFE_DOWNCAST(
t & PyLong_MASK, twodigits, digit);
t >>= PyLong_SHIFT;
}
}
return (PyObject *)v;
"""
assert body.split() == expected.split(), "_PyLong_FromLarge must declare abs_ival, t, ndigits, v, p, and sign at function entry and retain assertions, sign/absolute-value branches, digit count, allocation, initialization, digit writes, and return order"
print("longobject _PyLong_FromLarge C89 declarations and evaluation order: PASS")
match = re.search(r"^#define PYLONG_FROM_UINT\(INT_TYPE, ival\) (?P<body>.*?)^ \} while\(0\)$", source, re.S | re.M)
assert match, "PYLONG_FROM_UINT definition not found"
body = match.group("body") + " } while(0)"
body = re.sub(r"\\\n", "\n", body)
body = re.sub(r"/\*.*?\*/", "", body, flags=re.S)
assert re.search(r"\A\s*do\s*\{\s*Py_ssize_t ndigits;\s*INT_TYPE t;\s*PyLongObject \*v;\s*digit \*p;", body), "PYLONG_FROM_UINT must declare ndigits, t, v, and p at do-block entry"
expected = """
do {
Py_ssize_t ndigits;
INT_TYPE t;
PyLongObject *v;
digit *p;
if (IS_SMALL_UINT(ival)) {
return get_small_int((sdigit)(ival));
}
ndigits = 0;
t = (ival);
while (t) {
++ndigits;
t >>= PyLong_SHIFT;
}
v = _PyLong_New(ndigits);
if (v == NULL) {
return NULL;
}
p = v->ob_digit;
while ((ival)) {
*p++ = (digit)((ival) & PyLong_MASK);
(ival) >>= PyLong_SHIFT;
}
return (PyObject *)v;
} while(0)
"""
assert body.split() == expected.split(), "PYLONG_FROM_UINT must retain small-int early return, ndigits/t evaluation, digit count, allocation, NULL return, p initialization, ival evaluation/mutation, digit writes, and return order"
print("longobject PYLONG_FROM_UINT C89 declarations and evaluation/error order: PASS")
match = re.search(r"PyObject \*\s*PyLong_FromUnsignedLongLong\((?P<parameter>[^)]*)\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "PyLong_FromUnsignedLongLong definition not found"
assert match.group("parameter").split() == "unsigned PY_LONG_LONG ival".split(), "PyLong_FromUnsignedLongLong must use unsigned PY_LONG_LONG to match the public declaration and VC6 __int64 spelling"
assert match.group("body").split() == "PYLONG_FROM_UINT(unsigned PY_LONG_LONG, ival);".split(), "PyLong_FromUnsignedLongLong must pass unsigned PY_LONG_LONG to PYLONG_FROM_UINT, preserving the unsigned 64-bit temporary"
print("longobject PyLong_FromUnsignedLongLong VC6 parameter and macro type spelling: PASS")
match = re.search(r"PyObject \*\s*PyLong_FromDouble\(double dval\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "PyLong_FromDouble definition not found"
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
assert re.search(r"\A\s*PyLongObject \*v;\s*double frac;\s*int i, ndig, expo, neg;\s*const double int_max = \(unsigned long\)LONG_MAX \+ 1;", body), "PyLong_FromDouble locals must precede the fast-path statement"
ordered = (
"const double int_max = (unsigned long)LONG_MAX + 1;",
"if (-int_max < dval && dval < int_max)",
"return PyLong_FromLong((long)dval);",
"neg = 0;",
"if (Py_IS_INFINITY(dval))",
"if (Py_IS_NAN(dval))",
"if (dval < 0.0)",
"frac = frexp(dval, &expo);",
"ndig = (expo-1) / PyLong_SHIFT + 1;",
"v = _PyLong_New(ndig);",
"frac = ldexp(frac, (expo-1) % PyLong_SHIFT + 1);",
"for (i = ndig; --i >= 0; )",
"if (neg)",
"return (PyObject *)v;",
)
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "PyLong_FromDouble must preserve fast-path, validation, sign, conversion, allocation, digit-loop, and return order"
print("longobject PyLong_FromDouble C89 declarations and evaluation/error order: PASS")
code = re.sub(r"/\*.*?\*/|//[^\n]*|\"(?:\\.|[^\"\\])*\"", "", source, flags=re.S)
assert not re.search(r"\blong\s+long\b", code), "longobject must spell 64-bit integers with PY_LONG_LONG for VC6"
assert not re.search(r"\bLLONG_(?:MAX|MIN)\b", code), "longobject must use PY_LLONG_MAX/PY_LLONG_MIN instead of unavailable LLONG limits"
for signature in (
r"PyLong_FromLongLong\(PY_LONG_LONG ival\)",
r"PyLong_AsLongLong\(PyObject \*vv\)",
r"PyLong_AsUnsignedLongLong\(PyObject \*vv\)",
r"PyLong_AsUnsignedLongLongMask\(PyObject \*vv\)",
r"PyLong_AsLongLongAndOverflow\(PyObject \*vv, int \*overflow\)",
):
assert re.search(signature, source), "missing VC6-compatible long API declaration/definition: " + signature
print("longobject 64-bit API uses VC6 PY_LONG_LONG spellings: PASS")
match = re.search(r"static digit\s+inplace_divrem1\(digit \*pout, digit \*pin, Py_ssize_t size, digit n\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "inplace_divrem1 definition not found"
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
loop = re.search(r"while \(--size >= 0\)\s*\{(?P<body>.*?)\n \}", body, re.S)
assert loop, "inplace_divrem1 digit loop not found"
loop_body = loop.group("body")
assert re.search(r"\A\s*twodigits dividend;\s*digit quotient;\s*dividend = \(\(twodigits\)remainder << PyLong_SHIFT\) \| pin\[size\];\s*quotient = \(digit\)\(dividend / n\);\s*remainder = dividend % n;\s*pout\[size\] = quotient;", loop_body), "inplace_divrem1 loop temporaries must be declared before the executable dividend calculation, preserving divide/remainder/store order"
print("longobject inplace_divrem1 loop C89 declaration and division order: PASS")
late_ucs4 = "else {\n assert (kind == PyUnicode_4BYTE_KIND);\n Py_UCS4 *p;"
entry_ucs4 = "else {\n Py_UCS4 *p;\n assert (kind == PyUnicode_4BYTE_KIND);"
assert source.count(late_ucs4) == 0, "Py_UCS4 locals must be hoisted before their branch assertions"
assert source.count(entry_ucs4) == 2, "both long-formatting branches must declare Py_UCS4 before checking the kind"
print("longobject Unicode formatting Py_UCS4 C89 branch declarations: PASS")
match = re.search(r"PyObject \*\s*PyLong_FromString\(const char \*str, char \*\*pend, int base\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "PyLong_FromString definition not found"
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
assert re.search(r"\A\s*int sign = 1, error_if_nonzero = 0;\s*const char \*start, \*orig_str = str;\s*PyLongObject \*z = NULL;\s*PyObject \*strobj;\s*Py_ssize_t slen;\s*double fsize_z;", body), "PyLong_FromString must declare fsize_z in the function's opening declaration group"
ordered = (
"if (digits > _PY_LONG_MAX_STR_DIGITS_THRESHOLD)",
"fsize_z = (double)digits * log_base_BASE[base] + 1.0;",
"if (fsize_z > (double)MAX_LONG_DIGITS)",
"size_z = (Py_ssize_t)fsize_z;",
"z = _PyLong_New(size_z);",
)
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "PyLong_FromString must preserve digit-limit, size calculation, overflow, allocation, and conversion order"
print("longobject PyLong_FromString C89 size declaration and parsing order: PASS")
match = re.search(r"static int\s+long_divrem\(PyLongObject \*a, PyLongObject \*b,\s*PyLongObject \*\*pdiv, PyLongObject \*\*prem\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "long_divrem definition not found"
body = match.group("body")
short_case = re.search(r"if \(size_a < size_b.*?\) \{(?P<body>.*?)\n \}", body, re.S)
assert short_case, "long_divrem smaller-dividend branch not found"
branch = short_case.group("body")
tokens = (
"PyObject *zero;",
"*prem = (PyLongObject *)long_long((PyObject *)a);",
"if (*prem == NULL)",
"zero = _PyLong_GetZero();",
"Py_INCREF(zero);",
"*pdiv = (PyLongObject*)zero;",
)
positions = [branch.find(token) for token in tokens]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "long_divrem must declare zero at branch entry and preserve remainder, error, singleton, incref, and quotient ordering"
print("longobject long_divrem C89 local and reference order: PASS")
match = re.search(r"PyObject \*\s*_PyLong_Add\(PyLongObject \*a, PyLongObject \*b\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "_PyLong_Add definition not found"
body = match.group("body")
assert re.match(r"\s*PyLongObject \*z;", body), "_PyLong_Add must declare z at function entry before the small-value fast path"
assert " PyLongObject *z;" not in body[body.find("if (IS_MEDIUM_VALUE(a)"):], "_PyLong_Add must not declare z after an executable fast-path statement"
ordered = (
"PyLongObject *z;",
"if (IS_MEDIUM_VALUE(a) && IS_MEDIUM_VALUE(b))",
"return _PyLong_FromSTwoDigits(medium_value(a) + medium_value(b));",
"if (Py_SIZE(a) < 0)",
"z = x_add(a, b);",
"return (PyObject *)z;",
)
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "_PyLong_Add must preserve small-integer fast path and sign/operation order"
print("longobject _PyLong_Add C89 declaration and arithmetic order: PASS")
match = re.search(r"static PyObject \*\s*long_pow\(PyObject \*v, PyObject \*w, PyObject \*x\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "long_pow definition not found"
body = match.group("body")
assert 0 <= body.find("digit bi, bit;") < body.find("CHECK_BINOP(v, w);"), "long_pow must declare exponent digit temporaries with its opening locals"
assert "digit bi = i ?" not in body and "digit bit;" not in body[body.find("i = Py_SIZE(b);"):], "long_pow must not declare digit temporaries after executable statements"
ordered = ("i = Py_SIZE(b);", "bi = i ? b->ob_digit[i-1] : 0;", "if (i <= 1 && bi <= 3)")
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "long_pow must preserve exponent digit setup and branch ordering"
print("longobject long_pow C89 declarations and exponent setup order: PASS")
branch_start = body.find("else {\n int pending;\n int blen;")
branch_end = body.find("\n }\n\n if (negativeOutput", branch_start)
assert branch_start >= 0 and branch_end > branch_start, "long_pow k-ary branch boundaries not found"
branch = body[branch_start:branch_end]
assert branch.find("int pending;") < branch.find("Py_INCREF(a);") and branch.find("int blen;") < branch.find("Py_INCREF(a);"), "long_pow pending and bit-length locals must be declared at branch entry"
assert "int pending = 0, blen = 0;" not in branch, "long_pow pending locals must be declared separately from their original initialization point"
ordered = ("Py_CLEAR(a2);", "pending = 0;", "blen = 0;", "#define ABSORB_PENDING")
positions = [branch.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "long_pow must preserve pending initialization after table setup and before the sliding-window macro"
print("longobject long_pow C89 pending declarations and initialization order: PASS")
match = re.search(r"static int\s+divmod_shift\(PyObject \*shiftby, Py_ssize_t \*wordshift, digit \*remshift\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "divmod_shift definition not found"
body = match.group("body")
assert re.match(r"\s*Py_ssize_t lshiftby;\s*PyLongObject \*wordshift_obj;", body), "divmod_shift must declare both conversion temporaries at function entry"
assert "Py_ssize_t lshiftby =" not in body and "PyLongObject *wordshift_obj =" not in body, "divmod_shift must preserve original conversion points as assignments"
ordered = ("assert(PyLong_Check(shiftby));", "assert(Py_SIZE(shiftby) >= 0);", "lshiftby = PyLong_AsSsize_t((PyObject *)shiftby);", "wordshift_obj = divrem1((PyLongObject *)shiftby, PyLong_SHIFT, remshift);", "*wordshift = PyLong_AsSsize_t((PyObject *)wordshift_obj);")
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "divmod_shift must preserve validation, conversion, fallback division, and result extraction order"
print("longobject divmod_shift C89 locals and overflow-fallback order: PASS")
match = re.search(r"static PyObject \*\s*long_rshift1\(PyLongObject \*a, Py_ssize_t wordshift, digit remshift\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "long_rshift1 definition not found"
body = match.group("body")
assert "digit sticky = 0;" not in body and "for (Py_ssize_t j =" not in body and "for (Py_ssize_t i =" not in body, "long_rshift1 must not declare locals in executable branches or C99 for initializers"
negative_matches = list(re.finditer(r"if \(a_negative\)\s*\{(?P<body>.*?)\n \}", body, re.S))
assert negative_matches, "long_rshift1 negative-input branch not found"
negative_body = negative_matches[-1].group("body")
assert negative_body.find("digit sticky;") < negative_body.find("Py_SET_SIZE(z, -newsize);"), "long_rshift1 must declare sticky before branch statements"
assert negative_body.find("sticky = 0;") > negative_body.find("Py_SET_SIZE(z, -newsize);"), "long_rshift1 must initialize sticky after setting the result sign"
assert "{\n Py_ssize_t j;\n for (j = 0; j < wordshift; j++)" in negative_body, "long_rshift1 must keep the sticky-scan counter block-scoped with C89 declarations"
assert "{\n Py_ssize_t i, j;\n i = 0;\n j = wordshift + 1;\n for (; j < size_a; i++, j++)" in body, "long_rshift1 must keep copy-loop counters block-scoped and preserve initial values"
print("longobject long_rshift1 C89 locals and shifted-digit order: PASS")
for name, op in (("long_and", "&"), ("long_xor", "^"), ("long_or", "|")):
match = re.search(r"static PyObject \*\s*" + name + r"\(PyObject \*a, PyObject \*b\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, name + " definition not found"
body = match.group("body")
expected = """
PyLongObject *x;
PyLongObject *y;
CHECK_BINOP(a, b);
x = (PyLongObject*)a;
y = (PyLongObject*)b;
"""
assert body.split()[:len(expected.split())] == expected.split(), name + " must declare x/y before type checking, then cast and preserve the small-value fast path"
assert "PyLongObject *x =" not in body and "PyLongObject *y =" not in body, name + " must not declare cast locals after CHECK_BINOP"
assert "return long_bitwise(x, '" + op + "', y);" in body, name + " must retain the expected general bitwise operation"
print("longobject bitwise entry points C89 locals and dispatch order: PASS")
match = re.search(r"PyObject \*\s*_PyLong_DivmodNear\(PyObject \*a, PyObject \*b\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "_PyLong_DivmodNear definition not found"
body = match.group("body")
assert re.match(r"\s*PyLongObject \*quo = NULL, \*rem = NULL;\s*PyObject \*twice_rem, \*result, \*temp;\s*PyObject \*one;\s*int quo_is_odd, quo_is_neg;\s*Py_ssize_t cmp;", body), "_PyLong_DivmodNear must declare one with the function's opening locals"
assert "PyObject *one =" not in body, "_PyLong_DivmodNear must preserve _PyLong_GetOne lookup at its original execution point"
ordered = ("if (!PyLong_Check(a) || !PyLong_Check(b))", "quo_is_neg =", "long_divrem(", "one = _PyLong_GetOne();", "twice_rem = long_lshift((PyObject *)rem, one);")
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "_PyLong_DivmodNear must preserve validation, divrem, singleton lookup, and shift order"
print("longobject _PyLong_DivmodNear C89 local and singleton lookup order: PASS")
match = re.search(r"static PyObject \*\s*int_bit_count_impl\(PyObject \*self\)\s*/\*.*?\*/\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "int_bit_count_impl definition not found"
body = match.group("body")
expected = """
PyLongObject *z;
Py_ssize_t ndigits, bit_count, ndigits_fast, i;
PyObject *result;
assert(self != NULL);
assert(PyLong_Check(self));
z = (PyLongObject *)self;
ndigits = Py_ABS(Py_SIZE(z));
bit_count = 0;
"""
assert body.split()[:len(expected.split())] == expected.split(), "int_bit_count_impl must place declarations before assertions and preserve initialization order"
assert "Py_ssize_t ndigits =" not in body and "Py_ssize_t bit_count =" not in body and "for (Py_ssize_t i =" not in body, "int_bit_count_impl must not use late declarations or C99 loop variables"
assert "PyObject *result =" not in body and "PyObject *x =" not in body and "PyObject *y =" not in body, "int_bit_count_impl must initialize object locals at their original execution points"
assert "Py_ssize_t ndigits_fast =" not in body, "int_bit_count_impl must calculate the fast digit bound at the original point"
ordered = ("ndigits_fast = Py_MIN(ndigits, PY_SSIZE_T_MAX/PyLong_SHIFT);", "for (i = 0; i < ndigits_fast; i++)", "result = PyLong_FromSsize_t(bit_count);", "for (i = ndigits_fast; i < ndigits; i++)")
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "int_bit_count_impl must preserve fast and overflow-safe accumulation order"
loop = re.search(r"for \(i = ndigits_fast; i < ndigits; i\+\+\)\s*\{(?P<body>.*?)\n \}", body, re.S)
assert loop and re.match(r"\s*PyObject \*x;\s*PyObject \*y;\s*x = PyLong_FromLong\(popcount_digit\(z->ob_digit\[i\]\)\);", loop.group("body")), "int_bit_count_impl must declare per-digit temporaries before the loop body statements"
assert 0 <= loop.group("body").find("y = long_add(") < loop.group("body").find("Py_DECREF(x);"), "int_bit_count_impl must preserve accumulation before releasing the temporary digit count"
print("longobject int_bit_count_impl C89 declarations and accumulation order: PASS")
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""Focused Visual C++ 6.0 C89 regression checks for memoryobject.c."""
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
match = re.search(
r"static int\s+copy_single\(PyMemoryViewObject \*self, const Py_buffer \*dest, const Py_buffer \*src\)\s*\{(?P<body>.*?)\n\}",
source,
re.S,
)
assert match, "copy_single definition not found"
body = match.group("body")
expected = """
char *mem;
CHECK_RELEASED_INT_AGAIN(self);
mem = NULL;
"""
assert body.split()[:len(expected.split())] == expected.split(), (
"copy_single must declare mem before the release-check macro while keeping its initialization after the check"
)
assert "char *mem =" not in body, "copy_single must not initialize mem before the release check"
print("memoryobject copy_single C89 local and release-check order: PASS")
assert not re.search(r"\blong\s+long\b", source), "memoryobject must use PY_LONG_LONG spellings supported by VC6"
assert re.search(r"\bPY_LONG_LONG\b", source), "memoryobject must retain native 64-bit format support through PY_LONG_LONG"
assert not re.search(r"\b_Bool\b", source), "memoryobject must use the VC6-compatible one-byte bool representation"
assert "case '?': UNPACK_SINGLE(ld, ptr, unsigned char); goto convert_bool;" in source, "memoryobject bool unpacking must preserve nonzero-to-bool conversion"
assert "PACK_SINGLE(ptr, ld, unsigned char);" in source, "memoryobject bool packing must use a one-byte type after PyObject_IsTrue normalization"
print("memoryobject VC6 64-bit and bool format spellings: PASS")
match = re.search(r"static PyObject \*\s*memoryiter_next\(memoryiterobject \*it\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "memoryiter_next definition not found"
body = match.group("body")
assert "Py_buffer *view =" not in body and "char *ptr =" not in body, "memoryiter_next must not declare locals after CHECK_RELEASED"
assert "if (it->it_index < it->it_length) {\n Py_buffer *view;\n char *ptr;\n\n CHECK_RELEASED(seq);\n view = &(seq->view);\n ptr = (char *)seq->view.buf;" in body, "memoryiter_next must declare conditional locals before release checks and preserve pointer setup order"
print("memoryobject memoryiter_next C89 locals and release-check order: PASS")
match = re.search(r"static PyObject \*\s*memory_iter\(PyObject \*seq\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert match, "memory_iter definition not found"
body = match.group("body")
expected = """
PyMemoryViewObject *obj;
int ndims;
const char *fmt;
memoryiterobject *it;
if (!PyMemoryView_Check(seq))
"""
assert body.split()[:len(expected.split())] == expected.split(), "memory_iter must declare locals before validation"
ordered = ("if (!PyMemoryView_Check(seq))", "obj = (PyMemoryViewObject *)seq;", "ndims = obj->view.ndim;", "fmt = adjust_fmt(&obj->view);", "it = PyObject_GC_New(memoryiterobject, &_PyMemoryIter_Type);")
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "memory_iter must preserve validation, type/shape setup, format adjustment, and allocation order"
assert not re.search(r"\b(?:PyMemoryViewObject|int|const char|memoryiterobject)\s+\*?\w+\s*=", body), "memory_iter must not retain late initialized declarations"
print("memoryobject memory_iter C89 locals and iterator allocation order: PASS")
match = re.search(r"PyTypeObject _PyMemoryIter_Type = \{(?P<body>.*?)\n\};", source, re.S)
assert match, "_PyMemoryIter_Type initializer not found"
body = match.group("body")
assert ".tp_" not in body, "_PyMemoryIter_Type must use positional fields instead of C99 designated initializers"
expected = (
"PyVarObject_HEAD_INIT(&PyType_Type, 0)",
'"memory_iterator"',
"sizeof(memoryiterobject)",
"0,",
"(destructor)memoryiter_dealloc",
"0,",
"0,",
"0,",
"0,",
"0,",
"0,",
"0,",
"0,",
"0,",
"0,",
"0,",
"PyObject_GenericGetAttr",
"0,",
"0,",
"Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC",
"0,",
"(traverseproc)memoryiter_traverse",
"0,",
"0,",
"0,",
"PyObject_SelfIter",
"(iternextfunc)memoryiter_next",
)
positions = []
cursor = 0
for token in expected:
position = body.find(token, cursor)
positions.append(position)
if position >= 0:
cursor = position + len(token)
assert all(position >= 0 for position in positions), "_PyMemoryIter_Type positional initializer must preserve all configured field slots"
print("memoryobject _PyMemoryIter_Type C89 positional initializer: PASS")
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""Focused Visual C++ 6.0 C89 regression checks for methodobject.c."""
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
match = re.search(
r"PyObject \*\s*PyCMethod_New\(PyMethodDef \*ml, PyObject \*self, PyObject \*module, PyTypeObject \*cls\)\s*\{(?P<body>.*?)\n\}",
source,
re.S,
)
assert match, "PyCMethod_New definition not found"
body = match.group("body")
declarations = ("vectorcallfunc vectorcall;", "PyCFunctionObject *op;", "PyCMethodObject *om;", "switch (ml->ml_flags")
declaration_positions = [body.find(token) for token in declarations]
assert all(position >= 0 for position in declaration_positions) and declaration_positions == sorted(declaration_positions), (
"PyCMethod_New must declare op and om at function entry before its dispatch switch"
)
ordered = (
"switch (ml->ml_flags",
"op = NULL;",
"if (ml->ml_flags & METH_METHOD)",
"om = PyObject_GC_New(PyCMethodObject, &PyCMethod_Type);",
"op = PyObject_GC_New(PyCFunctionObject, &PyCFunction_Type);",
"op->m_weakreflist = NULL;",
)
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), (
"PyCMethod_New must preserve flag dispatch, null initialization, allocation, and field setup order"
)
assert "PyCFunctionObject *op =" not in body and "PyCMethodObject *om =" not in body, (
"PyCMethod_New must not introduce late C89 declarations"
)
print("methodobject PyCMethod_New C89 declarations and allocation order: PASS")
match = re.search(r"PyTypeObject PyCMethod_Type = \{(?P<body>.*?)\n\};", source, re.S)
assert match, "PyCMethod_Type initializer not found"
body = match.group("body")
assert ".tp_" not in body, "PyCMethod_Type must use positional fields instead of C99 designated initializers"
expected = (
"PyVarObject_HEAD_INIT(&PyType_Type, 0)",
'"builtin_method"',
"sizeof(PyCMethodObject)",
) + ("0,",) * 27 + ("&PyCFunction_Type",)
positions = []
cursor = 0
for token in expected:
position = body.find(token, cursor)
positions.append(position)
if position >= 0:
cursor = position + len(token)
assert all(position >= 0 for position in positions), "PyCMethod_Type positional initializer must preserve the base-type slot"
print("methodobject PyCMethod_Type C89 positional initializer: PASS")
def function_body(name):
found = re.search(
r"static PyObject \*\s*" + re.escape(name) + r"\s*\([^;]*\)\s*\{(?P<body>.*?)\n\}",
source,
re.S,
)
assert found, name + " definition not found"
return found.group("body")
checks = {
"cfunction_vectorcall_FASTCALL": (
"PyThreadState *tstate;",
"Py_ssize_t nargs;",
"_PyCFunctionFast meth;",
"PyObject *result;",
"tstate = _PyThreadState_GET();",
"nargs = PyVectorcall_NARGS(nargsf);",
"meth = (_PyCFunctionFast)",
"result = meth(",
),
"cfunction_vectorcall_FASTCALL_KEYWORDS": (
"PyThreadState *tstate;",
"Py_ssize_t nargs;",
"_PyCFunctionFastWithKeywords meth;",
"PyObject *result;",
"tstate = _PyThreadState_GET();",
"nargs = PyVectorcall_NARGS(nargsf);",
"meth = (_PyCFunctionFastWithKeywords)",
"result = meth(",
),
"cfunction_vectorcall_NOARGS": (
"Py_ssize_t nargs;",
"PyCFunction meth;",
"PyObject *result;",
"PyThreadState *tstate = _PyThreadState_GET();",
"if (cfunction_check_kwargs(",
"nargs = PyVectorcall_NARGS(nargsf);",
"meth = (PyCFunction)cfunction_enter_call(",
"result = _PyCFunction_TrampolineCall(",
),
"cfunction_vectorcall_O": (
"Py_ssize_t nargs;",
"PyCFunction meth;",
"PyObject *result;",
"PyThreadState *tstate = _PyThreadState_GET();",
"if (cfunction_check_kwargs(",
"nargs = PyVectorcall_NARGS(nargsf);",
"meth = (PyCFunction)cfunction_enter_call(",
"result = _PyCFunction_TrampolineCall(",
),
"cfunction_call": (
"PyThreadState *tstate;",
"int flags;",
"PyCFunction meth;",
"PyObject *self;",
"PyObject *result;",
"assert(kwargs == NULL || PyDict_Check(kwargs));",
"tstate = _PyThreadState_GET();",
"flags = PyCFunction_GET_FLAGS(func);",
"meth = PyCFunction_GET_FUNCTION(func);",
"self = PyCFunction_GET_SELF(func);",
),
}
for name, tokens in checks.items():
body = function_body(name)
positions = [body.find(token) for token in tokens]
assert all(position >= 0 for position in positions) and positions == sorted(positions), (
name + " must keep declarations before statements and preserve evaluation order"
)
for declaration in tokens[:5]:
if " = " not in declaration and declaration.endswith(";"):
assert declaration in body, name + " is missing an opening local declaration"
print("methodobject vectorcall and call paths C89 declarations and evaluation order: PASS")
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Focused Visual C++ 6.0 C89 regression checks for moduleobject.c."""
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def function_body(name):
match = re.search(
re.escape(name) + r"\s*\([^;]*\)\s*\{(?P<body>.*?)\n\}",
source,
re.S,
)
assert match, name + " definition not found"
return match.group("body")
def ordered(body, tokens, message):
positions = [body.find(token) for token in tokens]
assert all(position >= 0 for position in positions) and positions == sorted(positions), message
body = function_body("_PyModule_IsExtension")
ordered(body, (
"PyModuleObject *module;",
"PyModuleDef *def;",
"if (!PyModule_Check(obj))",
"module = (PyModuleObject*)obj;",
"def = module->md_def;",
), "_PyModule_IsExtension must declare its locals before validation and preserve lookup order")
assert "PyModuleObject *module =" not in body and "PyModuleDef *def =" not in body
print("moduleobject _PyModule_IsExtension C89 locals and lookup order: PASS")
body = function_body("_PyModuleSpec_IsUninitializedSubmodule")
ordered(body, (
"PyObject *value;",
"int is_uninitialized;",
"if (spec == NULL)",
"value = PyObject_GetAttr(spec, &_Py_ID(_uninitialized_submodules));",
"is_uninitialized = PySequence_Contains(value, name);",
"Py_DECREF(value);",
), "_PyModuleSpec_IsUninitializedSubmodule must hoist declarations while preserving reference order")
assert "PyObject *value =" not in body and "int is_uninitialized =" not in body
print("moduleobject uninitialized-submodule C89 locals and reference order: PASS")
body = function_body("module_getattro")
ordered(body, (
"PyObject *attr, *mod_name, *getattr, *spec;",
"attr = PyObject_GenericGetAttr((PyObject *)m, name);",
"Py_INCREF(mod_name);",
"spec = PyDict_GetItemWithError(m->md_dict, &_Py_ID(__spec__));",
), "module_getattro must hoist spec without changing lookup timing")
assert "PyObject *spec =" not in body
print("moduleobject module_getattro C89 local and lookup order: PASS")
body = function_body("module_get_annotations")
ordered(body, (
"PyObject *dict;",
"PyObject *annotations;",
"int result;",
"dict = PyObject_GetAttr((PyObject *)m, &_Py_ID(__dict__));",
"annotations = PyDict_GetItemWithError(dict, &_Py_ID(__annotations__));",
"result = PyDict_SetItem(",
), "module_get_annotations must hoist locals and preserve dictionary/error order")
assert "PyObject *annotations =" not in body and "int result =" not in body
print("moduleobject module_get_annotations C89 locals and dictionary order: PASS")
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""Focused Visual C++ 6.0 C89 regression checks for object.c."""
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body(name):
found = re.search(re.escape(name) + r"\s*\([^;]*\)\s*\{(?P<body>.*?)\n\}", source, re.S)
assert found, name + " definition not found"
return found.group("body")
def check(name, tokens, message):
text = body(name)
positions = [text.find(token) for token in tokens]
assert all(position >= 0 for position in positions) and positions == sorted(positions), message
return text
text = check("PyObject_CallFinalizerFromDealloc", (
"Py_ssize_t refcnt;",
"if (Py_REFCNT(self) != 0)",
"refcnt = Py_REFCNT(self);",
"_Py_NewReference(self);",
"Py_SET_REFCNT(self, refcnt);",
), "PyObject_CallFinalizerFromDealloc must hoist refcnt while preserving resurrection order")
assert "Py_ssize_t refcnt =" not in text
print("object PyObject_CallFinalizerFromDealloc C89 local and refcount order: PASS")
text = check("_PyObject_Dump", (
"PyTypeObject *type;",
"PyGILState_STATE gil;",
"PyObject *error_type, *error_value, *error_traceback;",
"if (_PyObject_IsFreed(op))",
"type = Py_TYPE(op);",
"gil = PyGILState_Ensure();",
"PyErr_Fetch(&error_type, &error_value, &error_traceback);",
), "_PyObject_Dump must hoist locals without changing diagnostics or exception preservation")
assert "PyTypeObject *type =" not in text and "PyGILState_STATE gil =" not in text
print("object _PyObject_Dump C89 locals and exception preservation: PASS")
text = check("PyObject_Repr", (
"PyObject *res;",
"PyThreadState *tstate;",
"if (PyErr_CheckSignals())",
"tstate = _PyThreadState_GET();",
), "PyObject_Repr must hoist tstate while preserving signal-check order")
assert "PyThreadState *tstate =" not in text
print("object PyObject_Repr C89 local and signal-check order: PASS")
@@ -9945,6 +9945,66 @@ else
bad "frameobject frame_getlocals C89 declaration and ownership order"
fi
if python3 "$REPO_ROOT/scripts/vc6-funcobject-c89.test.py" "$REPO_ROOT/cpython/Objects/funcobject.c"; then
pass "funcobject C89 declarations preserve function-construction ownership order"
else
bad "funcobject C89 declarations preserve function-construction ownership order"
fi
if python3 "$REPO_ROOT/scripts/vc6-genericaliasobject-c89.test.py" "$REPO_ROOT/cpython/Objects/genericaliasobject.c" "$REPO_ROOT/cpython/Objects/object.c"; then
pass "genericaliasobject C89 declarations preserve writer and ownership order"
else
bad "genericaliasobject C89 declarations preserve writer and ownership order"
fi
if python3 "$REPO_ROOT/scripts/vc6-genobject-c89.test.py" "$REPO_ROOT/cpython/Objects/genobject.c"; then
pass "genobject generator traversal C89 declaration and error order"
else
bad "genobject generator traversal C89 declaration and error order"
fi
if python3 "$REPO_ROOT/scripts/vc6-interpreteridobject-c89.test.py" "$REPO_ROOT/cpython/Objects/interpreteridobject.c"; then
pass "interpreteridobject C89 declarations and ownership order"
else
bad "interpreteridobject C89 declarations and ownership order"
fi
if python3 "$REPO_ROOT/scripts/vc6-iterobject-c89.test.py" "$REPO_ROOT/cpython/Objects/iterobject.c"; then
pass "iterobject anextawaitable C89 declarations and reference order"
else
bad "iterobject anextawaitable C89 declarations and reference order"
fi
if python3 "$REPO_ROOT/scripts/vc6-listobject-c89.test.py" "$REPO_ROOT/cpython/Objects/listobject.c"; then
pass "listobject C89 declarations preserve allocation and ownership order"
else
bad "listobject C89 declarations preserve allocation and ownership order"
fi
if python3 "$REPO_ROOT/scripts/vc6-longobject-c89.test.py" "$REPO_ROOT/cpython/Objects/longobject.c"; then
pass "longobject C89 declarations preserve allocation and evaluation order"
else
bad "longobject C89 declarations preserve allocation and evaluation order"
fi
if python3 "$REPO_ROOT/scripts/vc6-memoryobject-c89.test.py" "$REPO_ROOT/cpython/Objects/memoryobject.c"; then
pass "memoryobject VC6 C89 declarations and native-format type spellings"
else
bad "memoryobject VC6 C89 declarations and native-format type spellings"
fi
if python3 "$REPO_ROOT/scripts/vc6-methodobject-c89.test.py" "$REPO_ROOT/cpython/Objects/methodobject.c"; then
pass "methodobject VC6 C89 declarations and type initialization"
else
bad "methodobject VC6 C89 declarations and type initialization"
fi
if python3 "$REPO_ROOT/scripts/vc6-moduleobject-c89.test.py" "$REPO_ROOT/cpython/Objects/moduleobject.c"; then
pass "moduleobject VC6 C89 declarations and evaluation order"
else
bad "moduleobject VC6 C89 declarations and evaluation order"
fi
if [ "$fail" -ne 0 ]; then
echo "FAILED"
exit 1