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