#!/usr/bin/env python3 """Guard C89 locals and statement order in rich comparison and attribute access.""" import re import sys from pathlib import Path def fail(message): print(message, file=sys.stderr) raise SystemExit(1) source = Path(sys.argv[1]).read_text(encoding="utf-8") found = re.search( r"\nPyObject_RichCompare\(PyObject \*v, PyObject \*w, int op\)\s*\{(?P.*?)\n\}", source, re.S, ) if not found: fail("PyObject_RichCompare definition not found") body = found.group("body") if len(re.findall(r"\bPyObject\s*\*\s*res\b", body)) != 1 or not re.match( r"\s*PyThreadState \*tstate = _PyThreadState_GET\(\);\s*PyObject \*res;", body, ): fail("PyObject_RichCompare must declare res exactly once without initializer at entry") expected_statements = ''' assert(Py_LT <= op && op <= Py_GE); if (v == NULL || w == NULL) { if (!_PyErr_Occurred(tstate)) { PyErr_BadInternalCall(); } return NULL; } if (_Py_EnterRecursiveCallTstate(tstate, " in comparison")) { return NULL; } res = do_richcompare(tstate, v, w, op); _Py_LeaveRecursiveCallTstate(tstate); return res; ''' statements = body[body.index("PyObject *res;") + len("PyObject *res;"):] if statements.split() != expected_statements.split(): fail("PyObject_RichCompare must preserve validation, recursive-call, leave, and result order") print("object PyObject_RichCompare C89 res declaration and recursive-call/result order: PASS") found = re.search( r"\nset_attribute_error_context\(PyObject\* v, PyObject\* name\)\s*\{(?P.*?)\n\}", source, re.S, ) if not found: fail("set_attribute_error_context definition not found") body = found.group("body") declarations = re.match( r"\s*PyObject \*type, \*value, \*traceback;\s*" r"PyAttributeErrorObject\s*\*\s*the_exc;", body, ) if not declarations: fail("set_attribute_error_context must declare type/value/traceback and the_exc without initializers at entry") expected_statements = ''' assert(PyErr_Occurred()); if (!PyErr_ExceptionMatches(PyExc_AttributeError)){ return 0; } PyErr_Fetch(&type, &value, &traceback); PyErr_NormalizeException(&type, &value, &traceback); if (!PyErr_GivenExceptionMatches(value, PyExc_AttributeError)) { goto restore; } the_exc = (PyAttributeErrorObject*) value; if (the_exc->name || the_exc->obj) { goto restore; } if (PyObject_SetAttr(value, &_Py_ID(name), name) || PyObject_SetAttr(value, &_Py_ID(obj), v)) { return 1; } restore: PyErr_Restore(type, value, traceback); return 0; ''' statements = re.sub(r"//[^\n]*", "", body[declarations.end():]) if statements.split() != expected_statements.split(): fail("set_attribute_error_context must preserve exception check/fetch/normalize/augment/restore order with no late declarations") print("object set_attribute_error_context C89 locals and exception check/fetch/normalize/augment/restore order: PASS") found = re.search( r"\nPyObject_GetAttr\(PyObject \*v, PyObject \*name\)\s*\{(?P.*?)\n\}", source, re.S, ) if not found: fail("PyObject_GetAttr definition not found") body = found.group("body") declarations = re.match( r"\s*PyTypeObject \*tp = Py_TYPE\(v\);\s*PyObject \*result;", body, ) if not declarations or len(re.findall(r"\bPyObject\s*\*\s*result\b", body)) != 1: fail("PyObject_GetAttr must declare result exactly once without initializer at entry") expected_statements = ''' if (!PyUnicode_Check(name)) { PyErr_Format(PyExc_TypeError, "attribute name must be string, not '%.200s'", Py_TYPE(name)->tp_name); return NULL; } result = NULL; if (tp->tp_getattro != NULL) { result = (*tp->tp_getattro)(v, name); } else if (tp->tp_getattr != NULL) { const char *name_str = PyUnicode_AsUTF8(name); if (name_str == NULL) { return NULL; } result = (*tp->tp_getattr)(v, (char *)name_str); } else { PyErr_Format(PyExc_AttributeError, "'%.50s' object has no attribute '%U'", tp->tp_name, name); } if (result == NULL) { set_attribute_error_context(v, name); } return result; ''' if body[declarations.end():].split() != expected_statements.split(): fail("PyObject_GetAttr must preserve name validation, result initialization, dispatch, and error-context order") print("object PyObject_GetAttr C89 result declaration and validation/dispatch/error-context order: PASS") found = re.search( r"\n_PyObject_GetDictPtr\(PyObject \*obj\)\s*\{(?P.*?)\n\}", source, re.S, ) if not found: fail("_PyObject_GetDictPtr definition not found") body = found.group("body") declarations = re.match( r"\s*PyObject \*\*dict_ptr;\s*PyDictValues \*\*values_ptr;\s*PyObject \*dict;", body, ) if not declarations: fail("_PyObject_GetDictPtr must declare dict_ptr, values_ptr, and dict without initializers at entry") expected_statements = ''' if ((Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) { return _PyObject_DictPointer(obj); } dict_ptr = _PyObject_ManagedDictPointer(obj); values_ptr = _PyObject_ValuesPointer(obj); if (*values_ptr == NULL) { return dict_ptr; } assert(*dict_ptr == NULL); dict = _PyObject_MakeDictFromInstanceAttributes(obj, *values_ptr); if (dict == NULL) { PyErr_Clear(); return NULL; } *values_ptr = NULL; *dict_ptr = dict; return dict_ptr; ''' if body[declarations.end():].split() != expected_statements.split(): fail("_PyObject_GetDictPtr must preserve branch, pointer initialization, conversion, error-clear, and values_ptr/dict_ptr store order with no late declarations") print("object _PyObject_GetDictPtr C89 locals and branch/initialization/conversion/error-clear/store order: PASS") found = re.search( r"\n_PyObject_GetMethod\(PyObject \*obj, PyObject \*name, PyObject \*\*method\)\s*\{(?P.*?)\n\}", source, re.S, ) if not found: fail("_PyObject_GetMethod definition not found") body = found.group("body") declarations = re.match( r"\s*int meth_found = 0;\s*PyTypeObject \*tp;\s*PyObject \*descr;\s*descrgetfunc f;\s*PyDictValues \*values;(?:\s*PyObject \*attr;)?", body, ) if not declarations or len(re.findall(r"\bPyTypeObject\s*\*\s*tp\b", body)) != 1: fail("_PyObject_GetMethod must declare tp exactly once without initializer at entry") expected_statements = ''' assert(*method == NULL); tp = Py_TYPE(obj); if (!_PyType_IsReady(tp)) { if (PyType_Ready(tp) < 0) { return 0; } } ''' statements = body[declarations.end():] if not statements.startswith(expected_statements): fail("_PyObject_GetMethod must preserve assertion, type assignment, readiness check, and early-return order") print("object _PyObject_GetMethod C89 tp declaration and readiness order: PASS") found = re.search( r"\n_PyObject_GetMethod\(PyObject \*obj, PyObject \*name, PyObject \*\*method\)\s*\{(?P.*?)\n\}", source, re.S, ) if not found: fail("_PyObject_GetMethod definition not found") body = found.group("body") declarations = re.match( r"\s*int meth_found = 0;\s*PyTypeObject \*tp;\s*PyObject \*descr;\s*descrgetfunc f;\s*PyDictValues \*values;(?:\s*PyObject \*attr;)?", body, ) if not declarations or len(re.findall(r"\bPyObject\s*\*\s*descr\b", body)) != 1 or len(re.findall(r"\bdescrgetfunc\s+f\b", body)) != 1: fail("_PyObject_GetMethod must declare descr and f exactly once without initializers at entry") expected_statements = ''' assert(*method == NULL); tp = Py_TYPE(obj); if (!_PyType_IsReady(tp)) { if (PyType_Ready(tp) < 0) { return 0; } } if (tp->tp_getattro != PyObject_GenericGetAttr || !PyUnicode_CheckExact(name)) { *method = PyObject_GetAttr(obj, name); return 0; } descr = _PyType_Lookup(tp, name); f = NULL; ''' if not body[declarations.end():].startswith(expected_statements): fail("_PyObject_GetMethod must preserve lookup and descrgetfunc initialization order") print("object _PyObject_GetMethod C89 descr/f declarations and lookup order: PASS") found = re.search( r"\n_PyObject_GetMethod\(PyObject \*obj, PyObject \*name, PyObject \*\*method\)\s*\{(?P.*?)\n\}", source, re.S, ) if not found: fail("_PyObject_GetMethod definition not found") body = found.group("body") if len(re.findall(r"\bPyDictValues\s*\*\s*values\b", body)) != 1 or not re.match( r"\s*int meth_found = 0;\s*PyTypeObject \*tp;\s*PyObject \*descr;\s*descrgetfunc f;\s*PyDictValues \*values;(?:\s*PyObject \*attr;)?", body, ): fail("_PyObject_GetMethod must declare values exactly once at function entry") if not re.search(r"descr = _PyType_Lookup\(tp, name\);\s*f = NULL;", body) or "values = *_PyObject_ValuesPointer(obj)" not in body: fail("_PyObject_GetMethod must preserve values lookup at its original managed-dict check point") print("object _PyObject_GetMethod C89 values declaration and initialization order: PASS") # Both mutually exclusive dictionary branches reuse the entry attr local. declarations = re.match( r"\s*int meth_found = 0;\s*PyTypeObject \*tp;\s*PyObject \*descr;" r"\s*descrgetfunc f;\s*PyDictValues \*values;\s*PyObject \*attr;", body, ) if not declarations or len(re.findall(r"\bPyObject\s*\*\s*attr\s*;", body)) != 1: fail("_PyObject_GetMethod must declare attr exactly once without initializer at function entry") if len(re.findall(r"\bPyObject\s*\*\s*attr\b", body)) != 1: fail("_PyObject_GetMethod ordinary-dict attr must reuse the entry local, with assignment after Py_INCREF(dict)") expected_statements = ''' assert(*method == NULL); tp = Py_TYPE(obj); if (!_PyType_IsReady(tp)) { if (PyType_Ready(tp) < 0) { return 0; } } if (tp->tp_getattro != PyObject_GenericGetAttr || !PyUnicode_CheckExact(name)) { *method = PyObject_GetAttr(obj, name); return 0; } descr = _PyType_Lookup(tp, name); f = NULL; if (descr != NULL) { Py_INCREF(descr); if (_PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_METHOD_DESCRIPTOR)) { meth_found = 1; } else { f = Py_TYPE(descr)->tp_descr_get; if (f != NULL && PyDescr_IsData(descr)) { *method = f(descr, obj, (PyObject *)Py_TYPE(obj)); Py_DECREF(descr); return 0; } } } if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) && (values = *_PyObject_ValuesPointer(obj))) { assert(*_PyObject_DictPointer(obj) == NULL); attr = _PyObject_GetInstanceAttribute(obj, values, name); if (attr != NULL) { *method = attr; Py_XDECREF(descr); return 0; } } else { PyObject **dictptr = _PyObject_DictPointer(obj); PyObject *dict; if (dictptr != NULL && (dict = *dictptr) != NULL) { Py_INCREF(dict); attr = PyDict_GetItemWithError(dict, name); if (attr != NULL) { *method = Py_NewRef(attr); Py_DECREF(dict); Py_XDECREF(descr); return 0; } Py_DECREF(dict); if (PyErr_Occurred()) { Py_XDECREF(descr); return 0; } } } if (meth_found) { *method = descr; return 1; } if (f != NULL) { *method = f(descr, obj, (PyObject *)Py_TYPE(obj)); Py_DECREF(descr); return 0; } if (descr != NULL) { *method = descr; return 0; } PyErr_Format(PyExc_AttributeError, "'%.50s' object has no attribute '%U'", tp->tp_name, name); set_attribute_error_context(obj, name); return 0;''' if body[declarations.end():].split() != expected_statements.split(): fail("_PyObject_GetMethod must reuse attr in managed/ordinary-dict branches and preserve lookup/branch/assignment/error/cleanup order") print("object _PyObject_GetMethod C89 managed/ordinary-dict attr declaration and lookup/branch/assignment/error/cleanup order: PASS")