port: expand VC6 C89 core frontier
This commit is contained in:
@@ -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")
|
||||
Reference in New Issue
Block a user