#!/usr/bin/env python3 """Focused Visual C++ 6.0 C89 regression checks for methodobject.c.""" import re import sys from pathlib import Path source = Path(sys.argv[1]).read_text() match = re.search( r"PyObject \*\s*PyCMethod_New\(PyMethodDef \*ml, PyObject \*self, PyObject \*module, PyTypeObject \*cls\)\s*\{(?P.*?)\n\}", source, re.S, ) assert match, "PyCMethod_New definition not found" body = match.group("body") declarations = ("vectorcallfunc vectorcall;", "PyCFunctionObject *op;", "PyCMethodObject *om;", "switch (ml->ml_flags") declaration_positions = [body.find(token) for token in declarations] assert all(position >= 0 for position in declaration_positions) and declaration_positions == sorted(declaration_positions), ( "PyCMethod_New must declare op and om at function entry before its dispatch switch" ) ordered = ( "switch (ml->ml_flags", "op = NULL;", "if (ml->ml_flags & METH_METHOD)", "om = PyObject_GC_New(PyCMethodObject, &PyCMethod_Type);", "op = PyObject_GC_New(PyCFunctionObject, &PyCFunction_Type);", "op->m_weakreflist = NULL;", ) positions = [body.find(token) for token in ordered] assert all(position >= 0 for position in positions) and positions == sorted(positions), ( "PyCMethod_New must preserve flag dispatch, null initialization, allocation, and field setup order" ) assert "PyCFunctionObject *op =" not in body and "PyCMethodObject *om =" not in body, ( "PyCMethod_New must not introduce late C89 declarations" ) print("methodobject PyCMethod_New C89 declarations and allocation order: PASS") match = re.search(r"PyTypeObject PyCMethod_Type = \{(?P.*?)\n\};", source, re.S) assert match, "PyCMethod_Type initializer not found" body = match.group("body") assert ".tp_" not in body, "PyCMethod_Type must use positional fields instead of C99 designated initializers" expected = ( "PyVarObject_HEAD_INIT(&PyType_Type, 0)", '"builtin_method"', "sizeof(PyCMethodObject)", ) + ("0,",) * 27 + ("&PyCFunction_Type",) positions = [] cursor = 0 for token in expected: position = body.find(token, cursor) positions.append(position) if position >= 0: cursor = position + len(token) assert all(position >= 0 for position in positions), "PyCMethod_Type positional initializer must preserve the base-type slot" print("methodobject PyCMethod_Type C89 positional initializer: PASS") def function_body(name): found = re.search( r"static PyObject \*\s*" + re.escape(name) + r"\s*\([^;]*\)\s*\{(?P.*?)\n\}", source, re.S, ) assert found, name + " definition not found" return found.group("body") checks = { "cfunction_vectorcall_FASTCALL": ( "PyThreadState *tstate;", "Py_ssize_t nargs;", "_PyCFunctionFast meth;", "PyObject *result;", "tstate = _PyThreadState_GET();", "nargs = PyVectorcall_NARGS(nargsf);", "meth = (_PyCFunctionFast)", "result = meth(", ), "cfunction_vectorcall_FASTCALL_KEYWORDS": ( "PyThreadState *tstate;", "Py_ssize_t nargs;", "_PyCFunctionFastWithKeywords meth;", "PyObject *result;", "tstate = _PyThreadState_GET();", "nargs = PyVectorcall_NARGS(nargsf);", "meth = (_PyCFunctionFastWithKeywords)", "result = meth(", ), "cfunction_vectorcall_NOARGS": ( "Py_ssize_t nargs;", "PyCFunction meth;", "PyObject *result;", "PyThreadState *tstate = _PyThreadState_GET();", "if (cfunction_check_kwargs(", "nargs = PyVectorcall_NARGS(nargsf);", "meth = (PyCFunction)cfunction_enter_call(", "result = _PyCFunction_TrampolineCall(", ), "cfunction_vectorcall_O": ( "Py_ssize_t nargs;", "PyCFunction meth;", "PyObject *result;", "PyThreadState *tstate = _PyThreadState_GET();", "if (cfunction_check_kwargs(", "nargs = PyVectorcall_NARGS(nargsf);", "meth = (PyCFunction)cfunction_enter_call(", "result = _PyCFunction_TrampolineCall(", ), "cfunction_call": ( "PyThreadState *tstate;", "int flags;", "PyCFunction meth;", "PyObject *self;", "PyObject *result;", "assert(kwargs == NULL || PyDict_Check(kwargs));", "tstate = _PyThreadState_GET();", "flags = PyCFunction_GET_FLAGS(func);", "meth = PyCFunction_GET_FUNCTION(func);", "self = PyCFunction_GET_SELF(func);", ), } for name, tokens in checks.items(): body = function_body(name) positions = [body.find(token) for token in tokens] assert all(position >= 0 for position in positions) and positions == sorted(positions), ( name + " must keep declarations before statements and preserve evaluation order" ) for declaration in tokens[:5]: if " = " not in declaration and declaration.endswith(";"): assert declaration in body, name + " is missing an opening local declaration" print("methodobject vectorcall and call paths C89 declarations and evaluation order: PASS")