Files
Python3-Win9x/scripts/vc6-genericaliasobject-c89.test.py
T

345 lines
18 KiB
Python

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