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

307 lines
14 KiB
Python

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