port: advance VC6 typeobject frontier and preserve regression coverage
This commit is contained in:
@@ -0,0 +1,361 @@
|
||||
#!/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")
|
||||
Reference in New Issue
Block a user