#!/usr/bin/env python3 """Check classmethod_get C89 local placement and validation order.""" import sys from pathlib import Path if len(sys.argv) != 3: raise SystemExit("usage: vc6-descrobject-classmethod-c89.test.py descrobject.c object.c") source = Path(sys.argv[1]).read_text() object_source = Path(sys.argv[2]).read_text() def body_for(signature, text=None): if text is None: text = source start = text.index(signature) brace = text.index("{", start) depth = 0 for pos in range(brace, len(text)): if text[pos] == "{": depth += 1 elif text[pos] == "}": depth -= 1 if depth == 0: return text[brace + 1:pos] raise SystemExit("unterminated function: " + signature) signature = "classmethod_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type)" body = body_for(signature) if "PyTypeObject *cls;" not in body or body.index("PyTypeObject *cls;") > body.index("if (type == NULL)"): raise SystemExit("classmethod_get cls must be declared before validation") if "PyTypeObject *cls =" in body: raise SystemExit("classmethod_get cls initialization must remain after subtype validation") order = ("if (type == NULL)", "if (!PyType_Check(type))", "if (!PyType_IsSubtype", "cls = NULL;", "if (descr->d_method->ml_flags & METH_METHOD)", "cls = descr->d_common.d_type;", "return PyCMethod_New(descr->d_method, type, NULL, cls);") positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("classmethod_get validation/class-selection/construction order changed") def require_entry_declarations(signature, declarations, first_statement): body = body_for(signature) first = body.index(first_statement) for declaration in declarations: if declaration not in body or body.index(declaration) > first: raise SystemExit(signature + " local missing or late: " + declaration) return body body = require_entry_declarations("method_check_args(PyObject *func,", ("PyObject *self;",), "assert(!PyErr_Occurred());") if body.index("if (nargs < 1)") > body.index("self = args[0];") or body.index("self = args[0];") > body.index("descr_check((PyDescrObject *)func, self)"): raise SystemExit("method_check_args validation/self-check order changed") wrappers = ( ("method_vectorcall_VARARGS(\n", ("PyObject *argstuple;", "PyCFunction meth;", "PyObject *result;"), "if (method_check_args(func, args, nargs, kwnames))", ("argstuple = _PyTuple_FromArray", "meth = (PyCFunction)method_enter_call", "result = _PyCFunction_TrampolineCall", "_Py_LeaveRecursiveCallTstate(tstate);", "return result;")), ("method_vectorcall_VARARGS_KEYWORDS(\n", ("PyObject *argstuple;", "PyObject *result;", "PyObject *kwdict;", "PyCFunctionWithKeywords meth;"), "if (method_check_args(func, args, nargs, NULL))", ("argstuple = _PyTuple_FromArray", "result = NULL;", "kwdict = NULL;", "if (kwnames != NULL", "meth = (PyCFunctionWithKeywords)", "result = _PyCFunctionWithKeywords_TrampolineCall", "_Py_LeaveRecursiveCallTstate(tstate);", "exit:", "Py_DECREF(argstuple);", "Py_XDECREF(kwdict);", "return result;")), ("method_vectorcall_FASTCALL_KEYWORDS_METHOD(\n", ("PyCMethod meth;", "PyObject *result;"), "if (method_check_args(func, args, nargs, NULL))", ("meth = (PyCMethod) method_enter_call", "result = meth(args[0],", "_Py_LeaveRecursiveCall();", "return result;")), ("method_vectorcall_FASTCALL(\n", ("_PyCFunctionFast meth;", "PyObject *result;"), "if (method_check_args(func, args, nargs, kwnames))", ("meth = (_PyCFunctionFast)", "result = meth(args[0],", "_Py_LeaveRecursiveCallTstate(tstate);", "return result;")), ("method_vectorcall_FASTCALL_KEYWORDS(\n", ("_PyCFunctionFastWithKeywords meth;", "PyObject *result;"), "if (method_check_args(func, args, nargs, NULL))", ("meth = (_PyCFunctionFastWithKeywords)", "result = meth(args[0],", "_Py_LeaveRecursiveCallTstate(tstate);", "return result;")), ("method_vectorcall_NOARGS(\n", ("PyObject *funcstr;", "PyCFunction meth;", "PyObject *result;"), "if (method_check_args(func, args, nargs, kwnames))", ("if (nargs != 1)", "funcstr = _PyObject_FunctionStr(func);", "meth = (PyCFunction)method_enter_call", "result = _PyCFunction_TrampolineCall", "_Py_LeaveRecursiveCallTstate(tstate);", "return result;")), ("method_vectorcall_O(\n", ("PyObject *funcstr;", "PyCFunction meth;", "PyObject *result;"), "if (method_check_args(func, args, nargs, kwnames))", ("if (nargs != 2)", "funcstr = _PyObject_FunctionStr(func);", "meth = (PyCFunction)method_enter_call", "result = _PyCFunction_TrampolineCall", "_Py_LeaveRecursiveCallTstate(tstate);", "return result;")), ) for signature, declarations, first_statement, order in wrappers: body = require_entry_declarations(signature, declarations, first_statement) positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit(signature + " dispatch/cleanup order changed") body = require_entry_declarations("classmethoddescr_call(PyMethodDescrObject *descr,", ("PyObject *self;", "PyObject *bound;", "PyObject *res;"), "if (argc < 1)") order = ("argc = PyTuple_GET_SIZE(args);", "if (argc < 1)", "self = PyTuple_GET_ITEM(args, 0);", "bound = classmethod_get(descr, NULL, self);", "if (bound == NULL)", "res = PyObject_VectorcallDict", "Py_DECREF(bound);", "return res;") if [body.index(item) for item in order] != sorted(body.index(item) for item in order): raise SystemExit("classmethoddescr_call bind/call/decref order changed") mapping_guard = "#if !defined(MS_WINDOWS) || !defined(_MSC_VER) || _MSC_VER >= 1300" if mapping_guard not in source or "static PyNumberMethods mappingproxy_as_number = {0};" not in source: raise SystemExit("mappingproxy number table must avoid designated initialization on VC6") number_init = body_for("_PyDesc_InitTypes(void)") if "mappingproxy_as_number.nb_or = mappingproxy_or;" not in number_init or "mappingproxy_as_number.nb_inplace_or = mappingproxy_ior;" not in number_init: raise SystemExit("VC6 mappingproxy slots must be assigned before type readiness") init_body = body_for("_PyTypes_InitTypes(PyInterpreterState *interp)", object_source) order = ("_PyClassObject_InitTypes();", "_PyDesc_InitTypes();", "for (i=0;", "PyType_Ready(type)") positions = [init_body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("legacy static type slots must be initialized before PyType_Ready") if "size_t i;" not in init_body or "for (size_t i=0;" in init_body: raise SystemExit("static type initialization loop must use its C89 entry declaration") body = body_for("PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method)") for declaration in ("PyMethodDescrObject *descr;", "vectorcallfunc vectorcall;"): if declaration not in body or body.index(declaration) > body.index("switch (method->ml_flags"): raise SystemExit("PyDescr_NewMethod local missing or late: " + declaration) if body.index("PyMethodDescrObject *descr;") > body.index("switch (method->ml_flags"): raise SystemExit("PyDescr_NewMethod descr must be declared before flag dispatch") order = ("switch (method->ml_flags", "vectorcall = method_vectorcall_VARARGS;", "default:", "descr = (PyMethodDescrObject *)descr_new", "descr->d_method = method;", "descr->vectorcall = vectorcall;", "return (PyObject *)descr;") positions = [body.index(item) for item in order] if positions != sorted(positions): raise SystemExit("PyDescr_NewMethod dispatch/allocation/initialization order changed") body = body_for("property_set_name(PyObject *self, PyObject *args)") for declaration in ("propertyobject *prop;", "PyObject *name;"): if declaration not in body or body.index(declaration) > body.index("if (PyTuple_GET_SIZE(args) != 2)"): raise SystemExit("property_set_name local missing or late: " + declaration) if "propertyobject *prop =" in body or "PyObject *name =" in body: raise SystemExit("property_set_name name/property assignments must remain after arity validation") order = ("if (PyTuple_GET_SIZE(args) != 2)", "prop = (propertyobject *)self;", "name = PyTuple_GET_ITEM(args, 1);", "Py_XINCREF(name);", "Py_XSETREF(prop->prop_name, name);", "Py_RETURN_NONE;") if [body.index(item) for item in order] != sorted(body.index(item) for item in order): raise SystemExit("property_set_name arity/reference-replacement order changed") body = body_for("property_descr_get(PyObject *self, PyObject *obj, PyObject *type)") if "propertyobject *gs;" not in body or body.index("propertyobject *gs;") > body.index("if (obj == NULL || obj == Py_None)"): raise SystemExit("property_descr_get gs must be declared before its early return") if "propertyobject *gs =" in body: raise SystemExit("property_descr_get property cast must remain after its early return") order = ("if (obj == NULL || obj == Py_None)", "return self;", "gs = (propertyobject *)self;", "if (gs->prop_get == NULL)", "PyObject *qualname = PyType_GetQualName", "return PyObject_CallOneArg(gs->prop_get, obj);") if [body.index(item) for item in order] != sorted(body.index(item) for item in order): raise SystemExit("property_descr_get early return/getter error/call order changed") print("descrobject C89 declarations, dispatch, and VC6 type init: PASS")