307 lines
14 KiB
Python
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")
|