Files
Python3-Win9x/scripts/vc6-object-richcompare-c89.test.py

362 lines
12 KiB
Python

#!/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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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")