port: advance VC6 typeobject frontier and preserve regression coverage
This commit is contained in:
+4210
-43
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -73,10 +73,12 @@ 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 (size_t i=0;", "PyType_Ready(type)")
|
||||
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;"):
|
||||
|
||||
@@ -51,3 +51,35 @@ text = check("PyObject_Repr", (
|
||||
), "PyObject_Repr must hoist tstate while preserving signal-check order")
|
||||
assert "PyThreadState *tstate =" not in text
|
||||
print("object PyObject_Repr C89 local and signal-check order: PASS")
|
||||
|
||||
text = body("_PyObject_DictPointer")
|
||||
expected = '''
|
||||
Py_ssize_t dictoffset;
|
||||
PyTypeObject *tp = Py_TYPE(obj);
|
||||
|
||||
if (tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
|
||||
return _PyObject_ManagedDictPointer(obj);
|
||||
}
|
||||
dictoffset = tp->tp_dictoffset;
|
||||
if (dictoffset == 0)
|
||||
return NULL;
|
||||
if (dictoffset < 0) {
|
||||
Py_ssize_t tsize = Py_SIZE(obj);
|
||||
size_t size;
|
||||
if (tsize < 0) {
|
||||
tsize = -tsize;
|
||||
}
|
||||
size = _PyObject_VAR_SIZE(tp, tsize);
|
||||
assert(size <= (size_t)PY_SSIZE_T_MAX);
|
||||
dictoffset += (Py_ssize_t)size;
|
||||
|
||||
_PyObject_ASSERT(obj, dictoffset > 0);
|
||||
_PyObject_ASSERT(obj, dictoffset % SIZEOF_VOID_P == 0);
|
||||
}
|
||||
return (PyObject **) ((char *)obj + dictoffset);
|
||||
'''
|
||||
assert text.split() == expected.split(), (
|
||||
"_PyObject_DictPointer must hoist only size to the block's opening declarations "
|
||||
"and preserve the size expression and statement order"
|
||||
)
|
||||
print("object _PyObject_DictPointer C89 size declaration and offset calculation order: PASS")
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard the entry locals and evaluation order in _PyObject_FunctionStr."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
found = re.search(
|
||||
r"\n_PyObject_FunctionStr\(PyObject \*x\)\s*\{(?P<body>.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert found, "_PyObject_FunctionStr definition not found"
|
||||
body = found.group("body")
|
||||
for name in ("module", "result"):
|
||||
declarations = re.findall(r"\bPyObject\s*\*\s*" + name + r"\b", body)
|
||||
assert len(declarations) == 1, (
|
||||
f"_PyObject_FunctionStr must declare {name} exactly once at entry; "
|
||||
f"found {len(declarations)} declarations"
|
||||
)
|
||||
|
||||
tokens = (
|
||||
"PyObject *module;",
|
||||
"PyObject *result;",
|
||||
"int ret;",
|
||||
"assert(!PyErr_Occurred());",
|
||||
"ret = _PyObject_LookupAttr(x, &_Py_ID(__qualname__), &qualname);",
|
||||
"if (qualname == NULL)",
|
||||
"return PyObject_Str(x);",
|
||||
"result = NULL;",
|
||||
"ret = _PyObject_LookupAttr(x, &_Py_ID(__module__), &module);",
|
||||
"ret = PyObject_RichCompareBool(module, &_Py_ID(builtins), Py_NE);",
|
||||
'result = PyUnicode_FromFormat("%S.%S()", module, qualname);',
|
||||
'result = PyUnicode_FromFormat("%S()", qualname);',
|
||||
"Py_DECREF(qualname);",
|
||||
"Py_XDECREF(module);",
|
||||
"return result;",
|
||||
)
|
||||
positions = [body.find(token) for token in tokens]
|
||||
assert all(position >= 0 for position in positions) and positions == sorted(positions), (
|
||||
"_PyObject_FunctionStr must keep locals at entry and preserve evaluation/statement order"
|
||||
)
|
||||
print("object _PyObject_FunctionStr unique C89 locals and evaluation order: PASS")
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard _PyTypes_InitState's C89 declaration and initialization order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
found = re.search(
|
||||
r"\n_PyTypes_InitState\(PyInterpreterState \*interp\)\s*\{(?P<body>.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert found, "_PyTypes_InitState definition not found"
|
||||
body = found.group("body")
|
||||
declaration = re.match(r"\s*PyStatus status;", body)
|
||||
assert declaration and len(re.findall(r"\bPyStatus\s+status\b", body)) == 1, (
|
||||
"_PyTypes_InitState must declare status exactly once without initializer at entry"
|
||||
)
|
||||
expected_statements = '''
|
||||
if (!_Py_IsMainInterpreter(interp)) {
|
||||
return _PyStatus_OK();
|
||||
}
|
||||
|
||||
status = _PyTypes_InitSlotDefs();
|
||||
if (_PyStatus_EXCEPTION(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
return _PyStatus_OK();
|
||||
'''
|
||||
assert body[declaration.end():].split() == expected_statements.split(), (
|
||||
"_PyTypes_InitState must preserve the interpreter guard, slot initialization, "
|
||||
"exception propagation, and success return order"
|
||||
)
|
||||
print("object _PyTypes_InitState C89 status declaration and initialization/return order: PASS")
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check NotImplemented's positional number slots against the CPython header."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
header = Path(sys.argv[2]).read_text()
|
||||
layout = re.search(r"typedef struct \{([^{}]*)\} PyNumberMethods;", header, re.S)
|
||||
assert layout, "PyNumberMethods definition not found"
|
||||
fields = re.findall(r"\b(nb_\w+)\s*;", layout.group(1))
|
||||
assert len(fields) == 36 and fields.index("nb_bool") == 9, (
|
||||
"expected 36 PyNumberMethods slots with nb_bool in slot 10"
|
||||
)
|
||||
initializer = re.search(
|
||||
r"static PyNumberMethods notimplemented_as_number\s*=\s*\{([^{}]*)\};",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert initializer, "notimplemented_as_number initializer not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", initializer.group(1), flags=re.S)
|
||||
assert not re.search(r"\.\s*\w+\s*=", body), (
|
||||
"notimplemented_as_number must use a VC6-compatible positional initializer"
|
||||
)
|
||||
slots = [value.strip() for value in body.strip().rstrip(",").split(",")]
|
||||
assert len(slots) == len(fields), "every PyNumberMethods slot must be explicit"
|
||||
for field, value in zip(fields, slots):
|
||||
expected = "notimplemented_bool" if field == "nb_bool" else "0"
|
||||
assert value == expected, f"{field}: expected {expected}, got {value}"
|
||||
print("object NotImplemented C89 positional initializer: PASS (36 slots; nb_bool slot 10)")
|
||||
|
||||
start = source.index("_PyTypes_InitTypes(PyInterpreterState *interp)\n{")
|
||||
opening = source.index("{", start)
|
||||
end = source.index("\n}\n", opening)
|
||||
init_types = source[opening:end]
|
||||
assert re.match(r"\{\s*size_t i;\s*if \(!_Py_IsMainInterpreter\(interp\)\)", init_types), (
|
||||
"_PyTypes_InitTypes must declare size_t i before the interpreter guard"
|
||||
)
|
||||
assert len(re.findall(r"\bsize_t\s+i\b", init_types)) == 1, (
|
||||
"_PyTypes_InitTypes must declare its loop counter exactly once"
|
||||
)
|
||||
assert "for (i=0; i < Py_ARRAY_LENGTH(static_types); i++) {" in init_types, (
|
||||
"_PyTypes_InitTypes must use the C89 counter with unchanged loop bounds/traversal"
|
||||
)
|
||||
assert init_types.index("_Py_GenericAlias_InitTypes();") < init_types.index("for (i=0;"), (
|
||||
"_PyTypes_InitTypes loop initialization must remain after VC6 type initialization"
|
||||
)
|
||||
print("object _PyTypes_InitTypes C89 loop counter declaration and initialization order: PASS")
|
||||
|
||||
start = source.index("_PyTypes_FiniTypes(PyInterpreterState *interp)\n{")
|
||||
opening = source.index("{", start)
|
||||
end = source.index("\n}\n", opening)
|
||||
fini_types = source[opening:end]
|
||||
assert re.match(r"\{\s*Py_ssize_t i;\s*if \(!_Py_IsMainInterpreter\(interp\)\)", fini_types), (
|
||||
"_PyTypes_FiniTypes must declare Py_ssize_t i before the interpreter guard"
|
||||
)
|
||||
assert len(re.findall(r"\bPy_ssize_t\s+i\b", fini_types)) == 1, (
|
||||
"_PyTypes_FiniTypes must declare its signed loop counter exactly once"
|
||||
)
|
||||
expected = '''{
|
||||
Py_ssize_t i;
|
||||
if (!_Py_IsMainInterpreter(interp)) {
|
||||
return;
|
||||
}
|
||||
for (i=Py_ARRAY_LENGTH(static_types)-1; i>=0; i--) {
|
||||
PyTypeObject *type = static_types[i];
|
||||
_PyStaticType_Dealloc(type);
|
||||
}
|
||||
'''
|
||||
assert re.sub(r"//[^\n]*", "", fini_types).split() == expected.split(), (
|
||||
"_PyTypes_FiniTypes must preserve the interpreter guard, reverse loop bounds/traversal, "
|
||||
"and block-opening type lookup before each deallocation"
|
||||
)
|
||||
print("object _PyTypes_FiniTypes C89 loop counter and reverse per-item deallocation order: PASS")
|
||||
@@ -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")
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard _PyMem_GetCurrentAllocatorName C89 local placement and comparisons."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"_PyMem_GetCurrentAllocatorName\s*\([^)]*\)\s*\{(?P<body>.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "_PyMem_GetCurrentAllocatorName definition not found"
|
||||
body = match.group("body")
|
||||
|
||||
expected_declarations = [
|
||||
"PyMemAllocatorEx malloc_alloc = MALLOC_ALLOC;",
|
||||
"PyMemAllocatorEx pymalloc = PYMALLOC_ALLOC;",
|
||||
"PyMemAllocatorEx dbg_raw = PYDBGRAW_ALLOC;",
|
||||
"PyMemAllocatorEx dbg_mem = PYDBGMEM_ALLOC;",
|
||||
"PyMemAllocatorEx dbg_obj = PYDBGOBJ_ALLOC;",
|
||||
]
|
||||
positions = [body.find(declaration) for declaration in expected_declarations]
|
||||
assert all(position >= 0 for position in positions), "allocator snapshot declarations must remain present"
|
||||
first_check = body.find("if (pymemallocator_eq")
|
||||
assert first_check >= 0 and positions == sorted(positions) and positions[-1] < first_check, (
|
||||
"all allocator snapshots must be initialized before the first statement for C89"
|
||||
)
|
||||
assert body.count("if (pymemallocator_eq(&_PyMem_Raw, &malloc_alloc)") == 2, (
|
||||
"preserve the malloc and pymalloc base-allocator comparisons"
|
||||
)
|
||||
assert body.count("if (pymemallocator_eq(&_PyMem_Debug.raw.alloc, &malloc_alloc) &&") == 2, (
|
||||
"preserve malloc_debug and pymalloc_debug allocator comparisons"
|
||||
)
|
||||
assert "pymemallocator_eq(&_PyMem_Raw, &dbg_raw)" in body
|
||||
assert "pymemallocator_eq(&_PyMem, &dbg_mem)" in body
|
||||
assert "pymemallocator_eq(&_PyObject, &dbg_obj)" in body
|
||||
assert body.index("/* Debug hooks installed */") < body.index('return "malloc_debug";')
|
||||
assert body.index('return "malloc_debug";') < body.index('return "pymalloc_debug";')
|
||||
assert body.index('return "pymalloc_debug";') < body.index("return NULL;")
|
||||
print("obmalloc allocator-name C89 declarations and allocator classification order: PASS")
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard arena_map_is_used's C89 locals and original evaluation order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(r"\narena_map_is_used\(block \*p\)\s*\{(.*?)\n\}", source, re.S)
|
||||
assert match, "arena_map_is_used definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
declarations = """
|
||||
arena_map_bot_t *n = arena_map_get(p, 0);
|
||||
int i3;
|
||||
int32_t hi;
|
||||
int32_t lo;
|
||||
int32_t tail;
|
||||
"""
|
||||
assert body.split()[:len(declarations.split())] == declarations.split(), (
|
||||
"arena_map_is_used must declare i3, hi, lo, tail before the NULL guard"
|
||||
)
|
||||
|
||||
# Failed lookup must return before index evaluation or node dereferences.
|
||||
# Preserve lookup count, assignment order, and the short-circuit predicate.
|
||||
expected = declarations + """
|
||||
if (n == NULL) {
|
||||
return 0;
|
||||
}
|
||||
i3 = MAP_BOT_INDEX(p);
|
||||
hi = n->arenas[i3].tail_hi;
|
||||
lo = n->arenas[i3].tail_lo;
|
||||
tail = (int32_t)(AS_UINT(p) & ARENA_SIZE_MASK);
|
||||
return (tail < lo) || (tail >= hi && hi != 0);
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve arena_map_is_used lookup, NULL return, evaluation order and predicate"
|
||||
)
|
||||
print("obmalloc arena_map_is_used C89 declarations and original evaluation order: PASS")
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard only arena_map_mark_used's entry locals and original operation order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"\narena_map_mark_used\(uintptr_t arena_base, int is_used\)\s*\{(.*?)\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "arena_map_mark_used definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
declarations = """
|
||||
arena_map_bot_t *n_hi;
|
||||
int i3;
|
||||
int32_t tail;
|
||||
uintptr_t arena_base_next;
|
||||
arena_map_bot_t *n_lo;
|
||||
int i3_next;
|
||||
"""
|
||||
assert body.split()[:len(declarations.split())] == declarations.split(), (
|
||||
"n_hi, i3, tail, arena_base_next, n_lo, i3_next must be declared without initializers at function entry"
|
||||
)
|
||||
|
||||
# Keep evaluations at their original positions: the sanity assertion precedes
|
||||
# lookup; failed lookup returns before index/tail evaluation. Lock both branches,
|
||||
# including rollback and i3_next evaluation after the n_lo NULL return.
|
||||
expected = declarations + """
|
||||
assert(HIGH_BITS(arena_base) == HIGH_BITS(&arena_map_root));
|
||||
n_hi = arena_map_get((block *)arena_base, is_used);
|
||||
if (n_hi == NULL) {
|
||||
assert(is_used);
|
||||
return 0;
|
||||
}
|
||||
i3 = MAP_BOT_INDEX((block *)arena_base);
|
||||
tail = (int32_t)(arena_base & ARENA_SIZE_MASK);
|
||||
if (tail == 0) {
|
||||
n_hi->arenas[i3].tail_hi = is_used ? -1 : 0;
|
||||
}
|
||||
else {
|
||||
n_hi->arenas[i3].tail_hi = is_used ? tail : 0;
|
||||
arena_base_next = arena_base + ARENA_SIZE;
|
||||
assert(arena_base < arena_base_next);
|
||||
n_lo = arena_map_get((block *)arena_base_next, is_used);
|
||||
if (n_lo == NULL) {
|
||||
assert(is_used);
|
||||
n_hi->arenas[i3].tail_hi = 0;
|
||||
return 0;
|
||||
}
|
||||
i3_next = MAP_BOT_INDEX(arena_base_next);
|
||||
n_lo->arenas[i3_next].tail_lo = is_used ? tail : 0;
|
||||
}
|
||||
return 1;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve original initialization/evaluation/control-flow order"
|
||||
)
|
||||
print("obmalloc arena_map_mark_used entry declarations and original operation order: PASS")
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard _PyObject_Calloc's C89 declarations and original operation order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"\n_PyObject_Calloc\(void \*ctx, size_t nelem, size_t elsize\)\s*\{(.*?)\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "_PyObject_Calloc definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
assert body.split()[:4] == ["size_t", "nbytes;", "void*", "ptr;"], (
|
||||
"_PyObject_Calloc must declare nbytes and ptr without initializers at function entry"
|
||||
)
|
||||
|
||||
# Keep multiplication after the overflow assertion, allocation after the size
|
||||
# calculation, and zeroing, fallback allocation, and accounting in their order.
|
||||
expected = """
|
||||
size_t nbytes;
|
||||
void* ptr;
|
||||
assert(elsize == 0 || nelem <= (size_t)PY_SSIZE_T_MAX / elsize);
|
||||
nbytes = nelem * elsize;
|
||||
ptr = pymalloc_alloc(ctx, nbytes);
|
||||
if (LIKELY(ptr != NULL)) {
|
||||
memset(ptr, 0, nbytes);
|
||||
return ptr;
|
||||
}
|
||||
ptr = PyMem_RawCalloc(nelem, elsize);
|
||||
if (ptr != NULL) {
|
||||
raw_allocated_blocks++;
|
||||
}
|
||||
return ptr;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve _PyObject_Calloc assertion, evaluation, zeroing, fallback, and accounting order"
|
||||
)
|
||||
print("obmalloc _PyObject_Calloc nbytes/ptr entry declarations and original operation order: PASS")
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard _PyMem_DebugCheckAddress's C89 locals and validation order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"\n_PyMem_DebugCheckAddress\(const char \*func, char api, const void \*p\)\s*\{(.*?)\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "_PyMem_DebugCheckAddress definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
declarations = """
|
||||
const uint8_t *q;
|
||||
size_t nbytes;
|
||||
const uint8_t *tail;
|
||||
int i;
|
||||
char id;
|
||||
"""
|
||||
assert body.split()[:len(declarations.split())] == declarations.split(), (
|
||||
"_PyMem_DebugCheckAddress must declare all locals without initializers at function entry"
|
||||
)
|
||||
|
||||
# Keep the pointer cast after the assertion, and validate the leading pad
|
||||
# before reading the size or checking the tail of a possibly corrupt block.
|
||||
expected = declarations + """
|
||||
assert(p != NULL);
|
||||
q = (const uint8_t *)p;
|
||||
id = (char)q[-SST];
|
||||
if (id != api) {
|
||||
_PyObject_DebugDumpAddress(p);
|
||||
_Py_FatalErrorFormat(func,
|
||||
"bad ID: Allocated using API '%c', "
|
||||
"verified using API '%c'",
|
||||
id, api);
|
||||
}
|
||||
for (i = SST-1; i >= 1; --i) {
|
||||
if (*(q-i) != PYMEM_FORBIDDENBYTE) {
|
||||
_PyObject_DebugDumpAddress(p);
|
||||
_Py_FatalErrorFunc(func, "bad leading pad byte");
|
||||
}
|
||||
}
|
||||
nbytes = read_size_t(q - 2*SST);
|
||||
tail = q + nbytes;
|
||||
for (i = 0; i < SST; ++i) {
|
||||
if (tail[i] != PYMEM_FORBIDDENBYTE) {
|
||||
_PyObject_DebugDumpAddress(p);
|
||||
_Py_FatalErrorFunc(func, "bad trailing pad byte");
|
||||
}
|
||||
}
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve _PyMem_DebugCheckAddress assertion, cast evaluation point, "
|
||||
"and complete validation/diagnostic order"
|
||||
)
|
||||
print("obmalloc _PyMem_DebugCheckAddress C89 declarations and original validation order: PASS")
|
||||
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard _PyObject_DebugMallocStats C89 declarations and evaluation order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"\n_PyObject_DebugMallocStats\(FILE \*out\)\s*\{(.*?)\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "_PyObject_DebugMallocStats definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
declarations = r"""
|
||||
uint i;
|
||||
uint numclasses;
|
||||
size_t numpools[SMALL_REQUEST_THRESHOLD >> ALIGNMENT_SHIFT];
|
||||
size_t numblocks[SMALL_REQUEST_THRESHOLD >> ALIGNMENT_SHIFT];
|
||||
size_t numfreeblocks[SMALL_REQUEST_THRESHOLD >> ALIGNMENT_SHIFT];
|
||||
size_t allocated_bytes;
|
||||
size_t available_bytes;
|
||||
uint numfreepools;
|
||||
size_t arena_alignment;
|
||||
size_t pool_header_bytes;
|
||||
size_t quantization;
|
||||
size_t narenas;
|
||||
size_t total;
|
||||
char buf[128];
|
||||
"""
|
||||
assert body.split()[:len(declarations.split())] == declarations.split(), (
|
||||
"_PyObject_DebugMallocStats must hoist only uninitialized declarations "
|
||||
"before the pymalloc guard, preserving array extents"
|
||||
)
|
||||
|
||||
# Freeze the guard before all scalar assignments, their original order, and
|
||||
# the entire remaining body, including array zeroing and conditional paths.
|
||||
expected = declarations + r"""
|
||||
if (!_PyMem_PymallocEnabled()) {
|
||||
return 0;
|
||||
}
|
||||
numclasses = SMALL_REQUEST_THRESHOLD >> ALIGNMENT_SHIFT;
|
||||
allocated_bytes = 0;
|
||||
available_bytes = 0;
|
||||
numfreepools = 0;
|
||||
arena_alignment = 0;
|
||||
pool_header_bytes = 0;
|
||||
quantization = 0;
|
||||
narenas = 0;
|
||||
fprintf(out, "Small block threshold = %d, in %u size classes.\n",
|
||||
SMALL_REQUEST_THRESHOLD, numclasses);
|
||||
|
||||
for (i = 0; i < numclasses; ++i)
|
||||
numpools[i] = numblocks[i] = numfreeblocks[i] = 0;
|
||||
|
||||
|
||||
for (i = 0; i < maxarenas; ++i) {
|
||||
uintptr_t base = arenas[i].address;
|
||||
|
||||
|
||||
if (arenas[i].address == (uintptr_t)NULL)
|
||||
continue;
|
||||
narenas += 1;
|
||||
|
||||
numfreepools += arenas[i].nfreepools;
|
||||
|
||||
|
||||
if (base & (uintptr_t)POOL_SIZE_MASK) {
|
||||
arena_alignment += POOL_SIZE;
|
||||
base &= ~(uintptr_t)POOL_SIZE_MASK;
|
||||
base += POOL_SIZE;
|
||||
}
|
||||
|
||||
|
||||
assert(base <= (uintptr_t) arenas[i].pool_address);
|
||||
for (; base < (uintptr_t) arenas[i].pool_address; base += POOL_SIZE) {
|
||||
poolp p = (poolp)base;
|
||||
const uint sz = p->szidx;
|
||||
uint freeblocks;
|
||||
|
||||
if (p->ref.count == 0) {
|
||||
|
||||
#ifdef Py_DEBUG
|
||||
assert(pool_is_in_list(p, arenas[i].freepools));
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
++numpools[sz];
|
||||
numblocks[sz] += p->ref.count;
|
||||
freeblocks = NUMBLOCKS(sz) - p->ref.count;
|
||||
numfreeblocks[sz] += freeblocks;
|
||||
#ifdef Py_DEBUG
|
||||
if (freeblocks > 0)
|
||||
assert(pool_is_in_list(p, usedpools[sz + sz]));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
assert(narenas == narenas_currently_allocated);
|
||||
|
||||
fputc('\n', out);
|
||||
fputs("class size num pools blocks in use avail blocks\n"
|
||||
"----- ---- --------- ------------- ------------\n",
|
||||
out);
|
||||
|
||||
for (i = 0; i < numclasses; ++i) {
|
||||
size_t p = numpools[i];
|
||||
size_t b = numblocks[i];
|
||||
size_t f = numfreeblocks[i];
|
||||
uint size = INDEX2SIZE(i);
|
||||
if (p == 0) {
|
||||
assert(b == 0 && f == 0);
|
||||
continue;
|
||||
}
|
||||
fprintf(out, "%5u %6u %11zu %15zu %13zu\n",
|
||||
i, size, p, b, f);
|
||||
allocated_bytes += b * size;
|
||||
available_bytes += f * size;
|
||||
pool_header_bytes += p * POOL_OVERHEAD;
|
||||
quantization += p * ((POOL_SIZE - POOL_OVERHEAD) % size);
|
||||
}
|
||||
fputc('\n', out);
|
||||
#ifdef PYMEM_DEBUG_SERIALNO
|
||||
if (_PyMem_DebugEnabled()) {
|
||||
(void)printone(out, "# times object malloc called", serialno);
|
||||
}
|
||||
#endif
|
||||
(void)printone(out, "# arenas allocated total", ntimes_arena_allocated);
|
||||
(void)printone(out, "# arenas reclaimed", ntimes_arena_allocated - narenas);
|
||||
(void)printone(out, "# arenas highwater mark", narenas_highwater);
|
||||
(void)printone(out, "# arenas allocated current", narenas);
|
||||
|
||||
PyOS_snprintf(buf, sizeof(buf),
|
||||
"%zu arenas * %d bytes/arena",
|
||||
narenas, ARENA_SIZE);
|
||||
(void)printone(out, buf, narenas * ARENA_SIZE);
|
||||
|
||||
fputc('\n', out);
|
||||
|
||||
|
||||
total = printone(out, "# bytes in allocated blocks", allocated_bytes);
|
||||
total += printone(out, "# bytes in available blocks", available_bytes);
|
||||
|
||||
PyOS_snprintf(buf, sizeof(buf),
|
||||
"%u unused pools * %d bytes", numfreepools, POOL_SIZE);
|
||||
total += printone(out, buf, (size_t)numfreepools * POOL_SIZE);
|
||||
|
||||
total += printone(out, "# bytes lost to pool headers", pool_header_bytes);
|
||||
total += printone(out, "# bytes lost to quantization", quantization);
|
||||
total += printone(out, "# bytes lost to arena alignment", arena_alignment);
|
||||
(void)printone(out, "Total", total);
|
||||
assert(narenas * ARENA_SIZE == total);
|
||||
|
||||
#if WITH_PYMALLOC_RADIX_TREE
|
||||
fputs("\narena map counts\n", out);
|
||||
#ifdef USE_INTERIOR_NODES
|
||||
(void)printone(out, "# arena map mid nodes", arena_map_mid_count);
|
||||
(void)printone(out, "# arena map bot nodes", arena_map_bot_count);
|
||||
fputc('\n', out);
|
||||
#endif
|
||||
total = printone(out, "# bytes lost to arena map root", sizeof(arena_map_root));
|
||||
#ifdef USE_INTERIOR_NODES
|
||||
total += printone(out, "# bytes lost to arena map mid",
|
||||
sizeof(arena_map_mid_t) * arena_map_mid_count);
|
||||
total += printone(out, "# bytes lost to arena map bot",
|
||||
sizeof(arena_map_bot_t) * arena_map_bot_count);
|
||||
(void)printone(out, "Total", total);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return 1;"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve _PyObject_DebugMallocStats early return, initialization/evaluation "
|
||||
"points, array contents, and complete operation order"
|
||||
)
|
||||
print("obmalloc _PyObject_DebugMallocStats C89 declarations and operation order: PASS")
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard _PyMem_DebugRawFree's C89 locals and NULL-safe evaluation order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"\n_PyMem_DebugRawFree\(void \*ctx, void \*p\)\s*\{(.*?)\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "_PyMem_DebugRawFree definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
declarations = """
|
||||
debug_alloc_api_t *api;
|
||||
uint8_t *q;
|
||||
size_t nbytes;
|
||||
"""
|
||||
assert body.split()[:len(declarations.split())] == declarations.split(), (
|
||||
"_PyMem_DebugRawFree must declare api, q, and nbytes without initializers at function entry"
|
||||
)
|
||||
|
||||
# Preserve the NULL return before either initializer is evaluated, especially
|
||||
# the pointer subtraction, and retain all subsequent operations in order.
|
||||
expected = declarations + """
|
||||
if (p == NULL) {
|
||||
return;
|
||||
}
|
||||
api = (debug_alloc_api_t *)ctx;
|
||||
q = (uint8_t *)p - 2*SST;
|
||||
_PyMem_DebugCheckAddress(__func__, api->api_id, p);
|
||||
nbytes = read_size_t(q);
|
||||
nbytes += PYMEM_DEBUG_EXTRA_BYTES;
|
||||
memset(q, PYMEM_DEADBYTE, nbytes);
|
||||
api->alloc.free(api->alloc.ctx, q);
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve _PyMem_DebugRawFree assignments after the NULL return and original operation order"
|
||||
)
|
||||
print("obmalloc _PyMem_DebugRawFree entry declarations and NULL-safe operation order: PASS")
|
||||
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard _PyMem_DebugRawRealloc's C89 locals and complete operation order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"\n_PyMem_DebugRawRealloc\(void \*ctx, void \*p, size_t nbytes\)\s*\{(.*?)\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "_PyMem_DebugRawRealloc definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
declarations = """
|
||||
debug_alloc_api_t *api;
|
||||
uint8_t *head;
|
||||
uint8_t *data;
|
||||
uint8_t *r;
|
||||
uint8_t *tail;
|
||||
size_t total;
|
||||
size_t original_nbytes;
|
||||
#define ERASED_SIZE 64
|
||||
uint8_t save[2*ERASED_SIZE];
|
||||
#ifdef PYMEM_DEBUG_SERIALNO
|
||||
size_t block_serialno;
|
||||
#endif
|
||||
"""
|
||||
assert body.split()[:len(declarations.split())] == declarations.split(), (
|
||||
"_PyMem_DebugRawRealloc must declare locals without initializers at function entry, "
|
||||
"including block_serialno under PYMEM_DEBUG_SERIALNO"
|
||||
)
|
||||
|
||||
# Keep the ctx cast after the NULL return and the serial-number read after
|
||||
# the overflow guard and tail calculation. Freeze both preprocessor paths,
|
||||
# allocation failure recovery, saved-byte restoration, and growth filling.
|
||||
expected = declarations + """
|
||||
|
||||
if (p == NULL) {
|
||||
return _PyMem_DebugRawAlloc(0, ctx, nbytes);
|
||||
}
|
||||
|
||||
api = (debug_alloc_api_t *)ctx;
|
||||
_PyMem_DebugCheckAddress(__func__, api->api_id, p);
|
||||
|
||||
data = (uint8_t *)p;
|
||||
head = data - 2*SST;
|
||||
original_nbytes = read_size_t(head);
|
||||
if (nbytes > (size_t)PY_SSIZE_T_MAX - PYMEM_DEBUG_EXTRA_BYTES) {
|
||||
|
||||
return NULL;
|
||||
}
|
||||
total = nbytes + PYMEM_DEBUG_EXTRA_BYTES;
|
||||
|
||||
tail = data + original_nbytes;
|
||||
#ifdef PYMEM_DEBUG_SERIALNO
|
||||
block_serialno = read_size_t(tail + SST);
|
||||
#endif
|
||||
|
||||
if (original_nbytes <= sizeof(save)) {
|
||||
memcpy(save, data, original_nbytes);
|
||||
memset(data - 2 * SST, PYMEM_DEADBYTE,
|
||||
original_nbytes + PYMEM_DEBUG_EXTRA_BYTES);
|
||||
}
|
||||
else {
|
||||
memcpy(save, data, ERASED_SIZE);
|
||||
memset(head, PYMEM_DEADBYTE, ERASED_SIZE + 2 * SST);
|
||||
memcpy(&save[ERASED_SIZE], tail - ERASED_SIZE, ERASED_SIZE);
|
||||
memset(tail - ERASED_SIZE, PYMEM_DEADBYTE,
|
||||
ERASED_SIZE + PYMEM_DEBUG_EXTRA_BYTES - 2 * SST);
|
||||
}
|
||||
|
||||
|
||||
r = (uint8_t *)api->alloc.realloc(api->alloc.ctx, head, total);
|
||||
if (r == NULL) {
|
||||
|
||||
nbytes = original_nbytes;
|
||||
}
|
||||
else {
|
||||
head = r;
|
||||
#ifdef PYMEM_DEBUG_SERIALNO
|
||||
bumpserialno();
|
||||
block_serialno = serialno;
|
||||
#endif
|
||||
}
|
||||
data = head + 2*SST;
|
||||
|
||||
write_size_t(head, nbytes);
|
||||
head[SST] = (uint8_t)api->api_id;
|
||||
memset(head + SST + 1, PYMEM_FORBIDDENBYTE, SST-1);
|
||||
|
||||
tail = data + nbytes;
|
||||
memset(tail, PYMEM_FORBIDDENBYTE, SST);
|
||||
#ifdef PYMEM_DEBUG_SERIALNO
|
||||
write_size_t(tail + SST, block_serialno);
|
||||
#endif
|
||||
|
||||
|
||||
if (original_nbytes <= sizeof(save)) {
|
||||
memcpy(data, save, Py_MIN(nbytes, original_nbytes));
|
||||
}
|
||||
else {
|
||||
size_t i = original_nbytes - ERASED_SIZE;
|
||||
memcpy(data, save, Py_MIN(nbytes, ERASED_SIZE));
|
||||
if (nbytes > i) {
|
||||
memcpy(data + i, &save[ERASED_SIZE],
|
||||
Py_MIN(nbytes - i, ERASED_SIZE));
|
||||
}
|
||||
}
|
||||
|
||||
if (r == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (nbytes > original_nbytes) {
|
||||
|
||||
memset(data + original_nbytes, PYMEM_CLEANBYTE,
|
||||
nbytes - original_nbytes);
|
||||
}
|
||||
|
||||
return data;"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve _PyMem_DebugRawRealloc initialization evaluation points, guards, "
|
||||
"and complete operation/control-flow order"
|
||||
)
|
||||
print("obmalloc _PyMem_DebugRawRealloc C89 declarations and original operation order: PASS")
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard VC6-compatible default allocator copies, fields, and call order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
# Preserve ctx/malloc/calloc/realloc/free and WITH_PYMALLOC selection.
|
||||
macros = source[source.index("#define MALLOC_ALLOC"):source.index("typedef struct {", source.index("#define MALLOC_ALLOC"))]
|
||||
expected_macros = """
|
||||
#define MALLOC_ALLOC {NULL, _PyMem_RawMalloc, _PyMem_RawCalloc, _PyMem_RawRealloc, _PyMem_RawFree}
|
||||
#ifdef WITH_PYMALLOC
|
||||
# define PYMALLOC_ALLOC {NULL, _PyObject_Malloc, _PyObject_Calloc, _PyObject_Realloc, _PyObject_Free}
|
||||
#endif
|
||||
#define PYRAW_ALLOC MALLOC_ALLOC
|
||||
#ifdef WITH_PYMALLOC
|
||||
# define PYOBJ_ALLOC PYMALLOC_ALLOC
|
||||
#else
|
||||
# define PYOBJ_ALLOC MALLOC_ALLOC
|
||||
#endif
|
||||
#define PYMEM_ALLOC PYOBJ_ALLOC
|
||||
"""
|
||||
assert macros.split() == expected_macros.split(), (
|
||||
"default allocator fields or WITH_PYMALLOC selection changed"
|
||||
)
|
||||
|
||||
match = re.search(
|
||||
r"\npymem_set_default_allocator\([^)]*\)\s*\{(.*?)\n\}", source, re.S
|
||||
)
|
||||
assert match, "pymem_set_default_allocator definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
declaration = "PyMemAllocatorEx new_alloc;"
|
||||
if body.count(declaration) != 1 or not body.lstrip().startswith(declaration):
|
||||
sys.exit("new_alloc must be declared once without initializer at function entry")
|
||||
|
||||
expected = """
|
||||
if (old_alloc != NULL) {
|
||||
PyMem_GetAllocator(domain, old_alloc);
|
||||
}
|
||||
switch(domain)
|
||||
{
|
||||
case PYMEM_DOMAIN_RAW:
|
||||
{
|
||||
PyMemAllocatorEx alloc = PYRAW_ALLOC;
|
||||
new_alloc = alloc;
|
||||
break;
|
||||
}
|
||||
case PYMEM_DOMAIN_MEM:
|
||||
{
|
||||
PyMemAllocatorEx alloc = PYMEM_ALLOC;
|
||||
new_alloc = alloc;
|
||||
break;
|
||||
}
|
||||
case PYMEM_DOMAIN_OBJ:
|
||||
{
|
||||
PyMemAllocatorEx alloc = PYOBJ_ALLOC;
|
||||
new_alloc = alloc;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
PyMem_SetAllocator(domain, &new_alloc);
|
||||
if (debug) {
|
||||
_PyMem_SetupDebugHooksDomain(domain);
|
||||
}
|
||||
return 0;
|
||||
"""
|
||||
if body.replace(declaration, "").split() != expected.split():
|
||||
sys.exit("default allocator switch, assignments, or call order changed")
|
||||
print("obmalloc default allocator C89 assignments, allocator semantics, and execution order: PASS")
|
||||
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard insert_to_freepool's C89 locals and complete operation order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"\ninsert_to_freepool\(poolp pool\)\s*\{(.*?)\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "insert_to_freepool definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
declarations = """
|
||||
poolp next;
|
||||
poolp prev;
|
||||
struct arena_object *ao;
|
||||
uint nf;
|
||||
struct arena_object* lastnf;
|
||||
struct arena_object* p;
|
||||
"""
|
||||
assert body.split()[:len(declarations.split())] == declarations.split(), (
|
||||
"insert_to_freepool must declare all six locals without initializers at function entry"
|
||||
)
|
||||
|
||||
# Assignments stay at the original evaluation points, including p inside its
|
||||
# branch. Preserve every assertion, branch, return, and pointer update, as well
|
||||
# as the radix-tree conditional, throughout all four arena-management cases.
|
||||
expected = declarations + """
|
||||
next = pool->nextpool;
|
||||
prev = pool->prevpool;
|
||||
next->prevpool = prev;
|
||||
prev->nextpool = next;
|
||||
ao = &arenas[pool->arenaindex];
|
||||
pool->nextpool = ao->freepools;
|
||||
ao->freepools = pool;
|
||||
nf = ao->nfreepools;
|
||||
lastnf = nfp2lasta[nf];
|
||||
assert((nf == 0 && lastnf == NULL) ||
|
||||
(nf > 0 &&
|
||||
lastnf != NULL &&
|
||||
lastnf->nfreepools == nf &&
|
||||
(lastnf->nextarena == NULL ||
|
||||
nf < lastnf->nextarena->nfreepools)));
|
||||
if (lastnf == ao) {
|
||||
p = ao->prevarena;
|
||||
nfp2lasta[nf] = (p != NULL && p->nfreepools == nf) ? p : NULL;
|
||||
}
|
||||
ao->nfreepools = ++nf;
|
||||
if (nf == ao->ntotalpools && ao->nextarena != NULL) {
|
||||
assert(ao->prevarena == NULL ||
|
||||
ao->prevarena->address != 0);
|
||||
assert(ao ->nextarena == NULL ||
|
||||
ao->nextarena->address != 0);
|
||||
if (ao->prevarena == NULL) {
|
||||
usable_arenas = ao->nextarena;
|
||||
assert(usable_arenas == NULL ||
|
||||
usable_arenas->address != 0);
|
||||
}
|
||||
else {
|
||||
assert(ao->prevarena->nextarena == ao);
|
||||
ao->prevarena->nextarena =
|
||||
ao->nextarena;
|
||||
}
|
||||
if (ao->nextarena != NULL) {
|
||||
assert(ao->nextarena->prevarena == ao);
|
||||
ao->nextarena->prevarena =
|
||||
ao->prevarena;
|
||||
}
|
||||
ao->nextarena = unused_arena_objects;
|
||||
unused_arena_objects = ao;
|
||||
#if WITH_PYMALLOC_RADIX_TREE
|
||||
arena_map_mark_used(ao->address, 0);
|
||||
#endif
|
||||
_PyObject_Arena.free(_PyObject_Arena.ctx,
|
||||
(void *)ao->address, ARENA_SIZE);
|
||||
ao->address = 0;
|
||||
--narenas_currently_allocated;
|
||||
return;
|
||||
}
|
||||
if (nf == 1) {
|
||||
ao->nextarena = usable_arenas;
|
||||
ao->prevarena = NULL;
|
||||
if (usable_arenas)
|
||||
usable_arenas->prevarena = ao;
|
||||
usable_arenas = ao;
|
||||
assert(usable_arenas->address != 0);
|
||||
if (nfp2lasta[1] == NULL) {
|
||||
nfp2lasta[1] = ao;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (nfp2lasta[nf] == NULL) {
|
||||
nfp2lasta[nf] = ao;
|
||||
}
|
||||
if (ao == lastnf) {
|
||||
return;
|
||||
}
|
||||
assert(ao->nextarena != NULL);
|
||||
if (ao->prevarena != NULL) {
|
||||
assert(ao->prevarena->nextarena == ao);
|
||||
ao->prevarena->nextarena = ao->nextarena;
|
||||
}
|
||||
else {
|
||||
assert(usable_arenas == ao);
|
||||
usable_arenas = ao->nextarena;
|
||||
}
|
||||
ao->nextarena->prevarena = ao->prevarena;
|
||||
ao->prevarena = lastnf;
|
||||
ao->nextarena = lastnf->nextarena;
|
||||
if (ao->nextarena != NULL) {
|
||||
ao->nextarena->prevarena = ao;
|
||||
}
|
||||
lastnf->nextarena = ao;
|
||||
assert(ao->nextarena == NULL || nf <= ao->nextarena->nfreepools);
|
||||
assert(ao->prevarena == NULL || nf > ao->prevarena->nfreepools);
|
||||
assert(ao->nextarena == NULL || ao->nextarena->prevarena == ao);
|
||||
assert((usable_arenas == ao && ao->prevarena == NULL)
|
||||
|| ao->prevarena->nextarena == ao);
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve insert_to_freepool evaluation, assertion, control-flow, and pointer-update order"
|
||||
)
|
||||
print("obmalloc insert_to_freepool entry declarations and original operation order: PASS")
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard only insert_to_usedpool's entry locals and original operation order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"\ninsert_to_usedpool\(poolp pool\)\s*\{(.*?)\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "insert_to_usedpool definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
declarations = """
|
||||
uint size;
|
||||
poolp next;
|
||||
poolp prev;
|
||||
"""
|
||||
assert body.split()[:len(declarations.split())] == declarations.split(), (
|
||||
"insert_to_usedpool must declare size, next, and prev without initializers at function entry"
|
||||
)
|
||||
|
||||
# Keep the assertion before size evaluation and both lookups, then preserve
|
||||
# the four pointer-link writes in their original order.
|
||||
expected = declarations + """
|
||||
assert(pool->ref.count > 0);
|
||||
size = pool->szidx;
|
||||
next = usedpools[size + size];
|
||||
prev = next->prevpool;
|
||||
pool->nextpool = next;
|
||||
pool->prevpool = prev;
|
||||
next->prevpool = pool;
|
||||
prev->nextpool = pool;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve insert_to_usedpool assertion, size/lookup evaluation, and pointer-link mutation order"
|
||||
)
|
||||
print("obmalloc insert_to_usedpool entry declarations and original operation order: PASS")
|
||||
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard pymalloc_pool_extend's C89 declaration and operation order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"\npymalloc_pool_extend\(poolp pool, uint size\)\s*\{(.*?)\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "pymalloc_pool_extend definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
assert body.split()[:2] == ["poolp", "next;"], (
|
||||
"pymalloc_pool_extend must declare next without an initializer at function entry"
|
||||
)
|
||||
|
||||
# The extension path returns before reading nextpool. Keep the full-pool
|
||||
# unlink reads and writes in their original order.
|
||||
expected = """
|
||||
poolp next;
|
||||
if (UNLIKELY(pool->nextoffset <= pool->maxnextoffset)) {
|
||||
pool->freeblock = (block*)pool + pool->nextoffset;
|
||||
pool->nextoffset += INDEX2SIZE(size);
|
||||
*(block **)(pool->freeblock) = NULL;
|
||||
return;
|
||||
}
|
||||
next = pool->nextpool;
|
||||
pool = pool->prevpool;
|
||||
next->prevpool = pool;
|
||||
pool->nextpool = next;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve pymalloc_pool_extend extension, early return, and unlink evaluation order"
|
||||
)
|
||||
print("obmalloc pymalloc_pool_extend C89 declaration and original operation order: PASS")
|
||||
|
||||
|
||||
match = re.search(
|
||||
r"\nallocate_from_new_pool\(uint size\)\s*\{(.*?)\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "allocate_from_new_pool definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
assert body.split()[:2] == ["poolp", "pool;"], (
|
||||
"allocate_from_new_pool must declare pool without an initializer at function entry"
|
||||
)
|
||||
|
||||
assert body.split()[:6] == ["poolp", "pool;", "block", "*bp;", "poolp", "next;"], (
|
||||
"allocate_from_new_pool must declare bp and next without initializers at function entry"
|
||||
)
|
||||
assert re.findall(r"\bblock\s+\*bp\s*;", body) == ["block *bp;"], (
|
||||
"allocate_from_new_pool must declare bp exactly once"
|
||||
)
|
||||
assert re.findall(r"\bpoolp\s+next\b", body) == ["poolp next"], (
|
||||
"allocate_from_new_pool must declare next exactly once"
|
||||
)
|
||||
|
||||
# Keep the freepools read after arena creation and arena-list updates, and
|
||||
# preserve all subsequent cached-pool, new-pool, and initialization branches.
|
||||
# Keep next's usedpools read immediately before the frontlink writes.
|
||||
expected = """
|
||||
poolp pool;
|
||||
block *bp;
|
||||
poolp next;
|
||||
if (UNLIKELY(usable_arenas == NULL)) {
|
||||
#ifdef WITH_MEMORY_LIMITS
|
||||
if (narenas_currently_allocated >= MAX_ARENAS) {
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
usable_arenas = new_arena();
|
||||
if (usable_arenas == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
usable_arenas->nextarena = usable_arenas->prevarena = NULL;
|
||||
assert(nfp2lasta[usable_arenas->nfreepools] == NULL);
|
||||
nfp2lasta[usable_arenas->nfreepools] = usable_arenas;
|
||||
}
|
||||
assert(usable_arenas->address != 0);
|
||||
assert(usable_arenas->nfreepools > 0);
|
||||
if (nfp2lasta[usable_arenas->nfreepools] == usable_arenas) {
|
||||
nfp2lasta[usable_arenas->nfreepools] = NULL;
|
||||
}
|
||||
if (usable_arenas->nfreepools > 1) {
|
||||
assert(nfp2lasta[usable_arenas->nfreepools - 1] == NULL);
|
||||
nfp2lasta[usable_arenas->nfreepools - 1] = usable_arenas;
|
||||
}
|
||||
pool = usable_arenas->freepools;
|
||||
if (LIKELY(pool != NULL)) {
|
||||
usable_arenas->freepools = pool->nextpool;
|
||||
usable_arenas->nfreepools--;
|
||||
if (UNLIKELY(usable_arenas->nfreepools == 0)) {
|
||||
assert(usable_arenas->freepools == NULL);
|
||||
assert(usable_arenas->nextarena == NULL ||
|
||||
usable_arenas->nextarena->prevarena ==
|
||||
usable_arenas);
|
||||
usable_arenas = usable_arenas->nextarena;
|
||||
if (usable_arenas != NULL) {
|
||||
usable_arenas->prevarena = NULL;
|
||||
assert(usable_arenas->address != 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert(usable_arenas->freepools != NULL ||
|
||||
usable_arenas->pool_address <=
|
||||
(block*)usable_arenas->address +
|
||||
ARENA_SIZE - POOL_SIZE);
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert(usable_arenas->nfreepools > 0);
|
||||
assert(usable_arenas->freepools == NULL);
|
||||
pool = (poolp)usable_arenas->pool_address;
|
||||
assert((block*)pool <= (block*)usable_arenas->address +
|
||||
ARENA_SIZE - POOL_SIZE);
|
||||
pool->arenaindex = (uint)(usable_arenas - arenas);
|
||||
assert(&arenas[pool->arenaindex] == usable_arenas);
|
||||
pool->szidx = DUMMY_SIZE_IDX;
|
||||
usable_arenas->pool_address += POOL_SIZE;
|
||||
--usable_arenas->nfreepools;
|
||||
if (usable_arenas->nfreepools == 0) {
|
||||
assert(usable_arenas->nextarena == NULL ||
|
||||
usable_arenas->nextarena->prevarena ==
|
||||
usable_arenas);
|
||||
usable_arenas = usable_arenas->nextarena;
|
||||
if (usable_arenas != NULL) {
|
||||
usable_arenas->prevarena = NULL;
|
||||
assert(usable_arenas->address != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
next = usedpools[size + size];
|
||||
pool->nextpool = next;
|
||||
pool->prevpool = next;
|
||||
next->nextpool = pool;
|
||||
next->prevpool = pool;
|
||||
pool->ref.count = 1;
|
||||
if (pool->szidx == size) {
|
||||
bp = pool->freeblock;
|
||||
assert(bp != NULL);
|
||||
pool->freeblock = *(block **)bp;
|
||||
return bp;
|
||||
}
|
||||
pool->szidx = size;
|
||||
size = INDEX2SIZE(size);
|
||||
bp = (block *)pool + POOL_OVERHEAD;
|
||||
pool->nextoffset = POOL_OVERHEAD + (size << 1);
|
||||
pool->maxnextoffset = POOL_SIZE - size;
|
||||
pool->freeblock = bp + size;
|
||||
*(block **)(pool->freeblock) = NULL;
|
||||
return bp;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve allocate_from_new_pool arena updates, freepools assignment, and control-flow order"
|
||||
)
|
||||
print("obmalloc allocate_from_new_pool pool/bp/next declarations and original operation order: PASS")
|
||||
|
||||
|
||||
match = re.search(
|
||||
r"\npymalloc_alloc\(void \*ctx, size_t nbytes\)\s*\{(.*?)\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "pymalloc_alloc definition not found"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert body.split()[:6] == ["uint", "size;", "poolp", "pool;", "block", "*bp;"], (
|
||||
"pymalloc_alloc must declare size, pool, and bp without initializers at function entry"
|
||||
)
|
||||
|
||||
# Preserve the conditional Valgrind checks and request-size early returns before
|
||||
# evaluating size and reading usedpools, plus the exact bp branch operations.
|
||||
expected = """
|
||||
uint size;
|
||||
poolp pool;
|
||||
block *bp;
|
||||
#ifdef WITH_VALGRIND
|
||||
if (UNLIKELY(running_on_valgrind == -1)) {
|
||||
running_on_valgrind = RUNNING_ON_VALGRIND;
|
||||
}
|
||||
if (UNLIKELY(running_on_valgrind)) {
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
if (UNLIKELY(nbytes == 0)) {
|
||||
return NULL;
|
||||
}
|
||||
if (UNLIKELY(nbytes > SMALL_REQUEST_THRESHOLD)) {
|
||||
return NULL;
|
||||
}
|
||||
size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT;
|
||||
pool = usedpools[size + size];
|
||||
if (LIKELY(pool != pool->nextpool)) {
|
||||
++pool->ref.count;
|
||||
bp = pool->freeblock;
|
||||
assert(bp != NULL);
|
||||
if (UNLIKELY((pool->freeblock = *(block **)bp) == NULL)) {
|
||||
pymalloc_pool_extend(pool, size);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bp = allocate_from_new_pool(size);
|
||||
}
|
||||
return (void *)bp;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve pymalloc_alloc early returns, size/pool evaluation, and bp control-flow order"
|
||||
)
|
||||
print("obmalloc pymalloc_alloc size/pool/bp declarations and original operation order: PASS")
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard pymalloc_free's C89 locals and complete operation order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"\npymalloc_free\(void \*ctx, void \*p\)\s*\{(.*?)\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "pymalloc_free definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
declarations = """
|
||||
poolp pool;
|
||||
block *lastfree;
|
||||
"""
|
||||
assert body.split()[:len(declarations.split())] == declarations.split(), (
|
||||
"pymalloc_free must declare pool and lastfree without initializers at function entry"
|
||||
)
|
||||
|
||||
# Evaluate POOL_ADDR only after the Valgrind early return, and read freeblock
|
||||
# only after the address-range check and refcount assertion. Preserve all
|
||||
# free-list writes, refcount updates, branches, helper calls, and returns.
|
||||
expected = declarations + """
|
||||
assert(p != NULL);
|
||||
#ifdef WITH_VALGRIND
|
||||
if (UNLIKELY(running_on_valgrind > 0)) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
pool = POOL_ADDR(p);
|
||||
if (UNLIKELY(!address_in_range(p, pool))) {
|
||||
return 0;
|
||||
}
|
||||
assert(pool->ref.count > 0);
|
||||
lastfree = pool->freeblock;
|
||||
*(block **)p = lastfree;
|
||||
pool->freeblock = (block *)p;
|
||||
pool->ref.count--;
|
||||
if (UNLIKELY(lastfree == NULL)) {
|
||||
insert_to_usedpool(pool);
|
||||
return 1;
|
||||
}
|
||||
if (LIKELY(pool->ref.count != 0)) {
|
||||
return 1;
|
||||
}
|
||||
insert_to_freepool(pool);
|
||||
return 1;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve pymalloc_free initialization, evaluation, and control-flow order"
|
||||
)
|
||||
print("obmalloc pymalloc_free entry declarations and original operation order: PASS")
|
||||
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the exact wide-string helper as C89 and check operation ordering."""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
allocated = re.search(
|
||||
r"\n_Py_GetAllocatedBlocks\(void\)\s*\{(.*?)\n\}", source, re.S,
|
||||
)
|
||||
assert allocated, "_Py_GetAllocatedBlocks definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", allocated.group(1), flags=re.S)
|
||||
assert body.split() == """
|
||||
Py_ssize_t n = raw_allocated_blocks;
|
||||
uint i;
|
||||
uintptr_t base;
|
||||
for (i = 0; i < maxarenas; ++i) {
|
||||
if (arenas[i].address == 0) {
|
||||
continue;
|
||||
}
|
||||
base = (uintptr_t)_Py_ALIGN_UP(arenas[i].address, POOL_SIZE);
|
||||
assert(base <= (uintptr_t) arenas[i].pool_address);
|
||||
for (; base < (uintptr_t) arenas[i].pool_address; base += POOL_SIZE) {
|
||||
poolp p = (poolp)base;
|
||||
n += p->ref.count;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
""".split(), (
|
||||
"_Py_GetAllocatedBlocks must declare uint i and uintptr_t base at entry and preserve loop operations"
|
||||
)
|
||||
print("obmalloc allocated-blocks C89 loop declaration and operation order: PASS")
|
||||
assert re.search(
|
||||
r"_PyMem_Strdup\(const char \*str\)\s*\{\s*"
|
||||
r"size_t size;\s*char \*copy;\s*"
|
||||
r"assert\(str != NULL\);\s*size = strlen\(str\) \+ 1;\s*"
|
||||
r"copy = PyMem_Malloc\(size\);\s*"
|
||||
r"if \(copy == NULL\) \{\s*return NULL;\s*\}\s*"
|
||||
r"memcpy\(copy, str, size\);\s*return copy;\s*\}",
|
||||
source,
|
||||
), "_PyMem_Strdup must use C89 entry declarations and preserve operation order"
|
||||
assert re.search(
|
||||
r"_PyMem_RawStrdup\(const char \*str\)\s*\{\s*"
|
||||
r"size_t size;\s*char \*copy;\s*"
|
||||
r"assert\(str != NULL\);\s*size = strlen\(str\) \+ 1;\s*"
|
||||
r"copy = PyMem_RawMalloc\(size\);\s*"
|
||||
r"if \(copy == NULL\) \{\s*return NULL;\s*\}\s*"
|
||||
r"memcpy\(copy, str, size\);\s*return copy;\s*\}",
|
||||
source,
|
||||
), "_PyMem_RawStrdup must use C89 declarations and preserve operation order"
|
||||
match = re.search(
|
||||
r"wchar_t\*\s*\n_PyMem_RawWcsdup\s*\([^)]*\)\s*\{.*?\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "_PyMem_RawWcsdup definition not found"
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
#define PY_SSIZE_T_MAX 32767
|
||||
static size_t length_value, allocated_size;
|
||||
static int stage, fail_alloc;
|
||||
static wchar_t storage[8];
|
||||
static size_t checked_wcslen(const wchar_t *str) {
|
||||
assert(str != NULL && stage == 0);
|
||||
stage = 1;
|
||||
return length_value;
|
||||
}
|
||||
static void *PyMem_RawMalloc(size_t size) {
|
||||
assert(stage == 1);
|
||||
stage = 2;
|
||||
allocated_size = size;
|
||||
return fail_alloc ? NULL : storage;
|
||||
}
|
||||
static void *checked_memcpy(void *dst, const void *src, size_t size) {
|
||||
assert(stage == 2 && size == allocated_size);
|
||||
stage = 3;
|
||||
return memcpy(dst, src, size);
|
||||
}
|
||||
#define wcslen checked_wcslen
|
||||
#define memcpy checked_memcpy
|
||||
'''
|
||||
harness += match.group(0)
|
||||
harness += r'''
|
||||
int main(void) {
|
||||
const wchar_t input[] = L"abc";
|
||||
wchar_t *result;
|
||||
length_value = 3;
|
||||
result = _PyMem_RawWcsdup(input);
|
||||
assert(result == storage && stage == 3);
|
||||
assert(allocated_size == 4 * sizeof(wchar_t));
|
||||
assert(memcmp(result, input, allocated_size) == 0);
|
||||
stage = 0;
|
||||
length_value = 0;
|
||||
result = _PyMem_RawWcsdup(L"");
|
||||
assert(result == storage && stage == 3 && storage[0] == 0);
|
||||
assert(allocated_size == sizeof(wchar_t));
|
||||
stage = 0;
|
||||
fail_alloc = 1;
|
||||
length_value = (size_t)PY_SSIZE_T_MAX / sizeof(wchar_t) - 1;
|
||||
assert(_PyMem_RawWcsdup(input) == NULL && stage == 2);
|
||||
assert(allocated_size == (length_value + 1) * sizeof(wchar_t));
|
||||
stage = 0;
|
||||
length_value++;
|
||||
assert(_PyMem_RawWcsdup(input) == NULL && stage == 1);
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-rawwcsdup-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
str(path / "test.c"), "-o", str(path / "test"),
|
||||
], check=True)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("obmalloc RawWcsdup C89 compile and copy/empty/allocation-failure/overflow order: PASS")
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard the pymalloc setup block's C89 declarations and allocator order."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
setup = source[source.index("\n_PyMem_SetupAllocators("):]
|
||||
match = re.search(
|
||||
r"case PYMEM_ALLOCATOR_PYMALLOC:\s*"
|
||||
r"case PYMEM_ALLOCATOR_PYMALLOC_DEBUG:\s*\{(.*?)\n \}",
|
||||
setup,
|
||||
re.S,
|
||||
)
|
||||
assert match, "pymalloc setup block not found"
|
||||
body = match.group(1)
|
||||
declarations = """
|
||||
PyMemAllocatorEx malloc_alloc = MALLOC_ALLOC;
|
||||
PyMemAllocatorEx pymalloc = PYMALLOC_ALLOC;
|
||||
"""
|
||||
assert body.split()[:len(declarations.split())] == declarations.split(), (
|
||||
"both allocator initializers must precede the first statement for C89"
|
||||
)
|
||||
expected = declarations + """
|
||||
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &malloc_alloc);
|
||||
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &pymalloc);
|
||||
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &pymalloc);
|
||||
if (allocator == PYMEM_ALLOCATOR_PYMALLOC_DEBUG) {
|
||||
PyMem_SetupDebugHooks();
|
||||
}
|
||||
break;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"preserve allocator initializers, RAW/MEM/OBJ registration order, and debug hooks"
|
||||
)
|
||||
for name, values in (
|
||||
("MALLOC_ALLOC", "NULL, _PyMem_RawMalloc, _PyMem_RawCalloc, _PyMem_RawRealloc, _PyMem_RawFree"),
|
||||
("PYMALLOC_ALLOC", "NULL, _PyObject_Malloc, _PyObject_Calloc, _PyObject_Realloc, _PyObject_Free"),
|
||||
):
|
||||
macro = re.search(r"#\s*define\s+" + name + r"\s+\{([^}]+)\}", source)
|
||||
assert macro and macro.group(1).split() == values.split(), (
|
||||
name + " must preserve ctx/malloc/calloc/realloc/free values and order"
|
||||
)
|
||||
print("obmalloc setup allocators C89 declarations, field values, and RAW/MEM/OBJ/debug order: PASS")
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check tracemalloc's positional initializer against its header definition."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
header = Path(sys.argv[2]).read_text()
|
||||
header = re.sub(r"/\*.*?\*/", "", header, flags=re.S)
|
||||
layout = re.search(r"struct _PyTraceMalloc_Config \{(.*?)\n\};", header, re.S)
|
||||
assert layout, "_PyTraceMalloc_Config definition not found"
|
||||
fields = re.findall(r"\b(\w+)\s*;", layout.group(1))
|
||||
assert fields == ["initialized", "tracing", "max_nframe"], (
|
||||
"expected initialized, tracing, max_nframe field order"
|
||||
)
|
||||
macro = re.search(
|
||||
r"#define _PyTraceMalloc_Config_INIT\s*\\\s*\{(.*?)\}", header, re.S
|
||||
)
|
||||
assert macro, "_PyTraceMalloc_Config_INIT definition not found"
|
||||
defaults = re.findall(r"\.(\w+)\s*=\s*(\w+)", macro.group(1))
|
||||
assert defaults == list(zip(fields, ["TRACEMALLOC_NOT_INITIALIZED", "0", "1"])), (
|
||||
"expected uninitialized, tracing disabled, and one traceback frame defaults"
|
||||
)
|
||||
initializer = re.search(
|
||||
r"struct _PyTraceMalloc_Config _Py_tracemalloc_config\s*=\s*(.*?);",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert initializer, "_Py_tracemalloc_config initializer not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", initializer.group(1), flags=re.S).strip()
|
||||
assert body.startswith("{") and body.endswith("}") and not re.search(
|
||||
r"\.\s*\w+\s*=", body
|
||||
), "_Py_tracemalloc_config must use a VC6-compatible positional initializer"
|
||||
values = [value.strip() for value in body[1:-1].strip().rstrip(",").split(",")]
|
||||
assert values == [value for field, value in defaults], (
|
||||
"positional initializer must preserve every header default in field order"
|
||||
)
|
||||
print("obmalloc tracemalloc C89 positional initializer: PASS (initialized=TRACEMALLOC_NOT_INITIALIZED, tracing=0, max_nframe=1)")
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile exact odictiter_reduce as C89 and check timing and ownership."""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"static PyObject \*\nodictiter_reduce\(odictiterobject \*di, PyObject \*Py_UNUSED\(ignored\)\)\n\{.*?\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "odictiter_reduce definition not found"
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
typedef struct { int refs; } PyObject;
|
||||
typedef struct { PyObject *di_odict, *di_current; int position; } odictiterobject;
|
||||
static PyObject dict, current, list_obj, builtin, result_obj;
|
||||
static odictiterobject *original;
|
||||
static int stage, failure, consume;
|
||||
static int iter;
|
||||
#define Py_UNUSED(name) unused
|
||||
#define _Py_ID(name) name
|
||||
static void Py_XINCREF(PyObject *obj) {
|
||||
assert(stage < 2);
|
||||
assert(obj == (stage == 0 ? original->di_odict : original->di_current));
|
||||
if (obj != NULL) ++obj->refs;
|
||||
++stage;
|
||||
}
|
||||
static PyObject *PySequence_List(PyObject *obj) {
|
||||
odictiterobject *tmp = (odictiterobject *)obj;
|
||||
assert(stage == 2 && tmp != original);
|
||||
assert(tmp->di_odict == original->di_odict);
|
||||
assert(tmp->di_current == original->di_current);
|
||||
assert(tmp->position == 17);
|
||||
assert(dict.refs == (original->di_odict ? 2 : 1));
|
||||
assert(current.refs == (original->di_current ? 2 : 1));
|
||||
tmp->position = 99;
|
||||
if (consume) {
|
||||
if (tmp->di_odict) --tmp->di_odict->refs;
|
||||
if (tmp->di_current) --tmp->di_current->refs;
|
||||
tmp->di_odict = tmp->di_current = NULL;
|
||||
}
|
||||
stage = 3;
|
||||
list_obj.refs = failure == 1 ? 0 : 1;
|
||||
return failure == 1 ? NULL : &list_obj;
|
||||
}
|
||||
static void Py_XDECREF(PyObject *obj) {
|
||||
assert(stage == 3 || stage == 4);
|
||||
assert(obj == (consume ? NULL : (stage == 3 ? original->di_odict : original->di_current)));
|
||||
if (obj != NULL) --obj->refs;
|
||||
++stage;
|
||||
}
|
||||
static PyObject *_PyEval_GetBuiltin(int *id) {
|
||||
assert(stage == 5 && id == &iter && failure != 1);
|
||||
assert(dict.refs == 1 && current.refs == 1);
|
||||
stage = 6;
|
||||
builtin.refs = 1;
|
||||
return &builtin;
|
||||
}
|
||||
static PyObject *Py_BuildValue(const char *format, PyObject *func, PyObject *list) {
|
||||
assert(stage == 6 && strcmp(format, "N(N)") == 0);
|
||||
assert(func == &builtin && list == &list_obj);
|
||||
assert(func->refs == 1 && list->refs == 1);
|
||||
--func->refs;
|
||||
--list->refs;
|
||||
stage = 7;
|
||||
return failure == 2 ? NULL : &result_obj;
|
||||
}
|
||||
'''
|
||||
harness += match.group(0)
|
||||
harness += r'''
|
||||
int main(void) {
|
||||
int mask;
|
||||
odictiterobject di;
|
||||
PyObject *result;
|
||||
for (mask = 0; mask < 4; ++mask) {
|
||||
for (consume = 0; consume < 2; ++consume) {
|
||||
for (failure = 0; failure < 3; ++failure) {
|
||||
dict.refs = current.refs = 1;
|
||||
di.di_odict = mask & 1 ? &dict : NULL;
|
||||
di.di_current = mask & 2 ? ¤t : NULL;
|
||||
di.position = 17;
|
||||
original = &di;
|
||||
stage = 0;
|
||||
result = odictiter_reduce(&di, NULL);
|
||||
assert(result == (failure ? NULL : &result_obj));
|
||||
assert(stage == (failure == 1 ? 5 : 7));
|
||||
assert(dict.refs == 1 && current.refs == 1);
|
||||
assert(list_obj.refs == 0);
|
||||
assert(di.di_odict == (mask & 1 ? &dict : NULL));
|
||||
assert(di.di_current == (mask & 2 ? ¤t : NULL));
|
||||
assert(di.position == 17);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-odict-list-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
compiled = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
"-Wno-unused-parameter", str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if compiled.returncode:
|
||||
sys.exit(compiled.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("odictiter_reduce C89 compile and copy/incref/list/cleanup/ownership/failure order: PASS (24 cases)")
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the exact odict number table as C89 and check every named slot."""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source_path = Path(sys.argv[1])
|
||||
source = source_path.read_text()
|
||||
match = re.search(r"static PyNumberMethods odict_as_number = \{.*?\n\};", source, re.S)
|
||||
assert match, "odict_as_number initializer not found"
|
||||
header = (source_path.parent.parent / "Include/cpython/object.h").read_text()
|
||||
layout = re.search(r"typedef struct \{([^{}]*)\} PyNumberMethods;", header, re.S)
|
||||
assert layout, "PyNumberMethods layout not found"
|
||||
# Use the actual header's order and types; check slots by name, not position.
|
||||
body = re.sub(r"/\*.*?\*/", "", layout.group(1), flags=re.S)
|
||||
fields = re.findall(r"\b(nb_\w+)\s*;", body)
|
||||
assert len(fields) == 36 and len(set(fields)) == 36
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
typedef struct { int value; } PyObject;
|
||||
typedef PyObject *(*binaryfunc)(PyObject *, PyObject *);
|
||||
typedef PyObject *(*ternaryfunc)(PyObject *, PyObject *, PyObject *);
|
||||
typedef PyObject *(*unaryfunc)(PyObject *);
|
||||
typedef int (*inquiry)(PyObject *);
|
||||
static PyObject *odict_or(PyObject *a, PyObject *b) { (void)b; return a; }
|
||||
static PyObject *odict_inplace_or(PyObject *a, PyObject *b) { (void)a; return b; }
|
||||
'''
|
||||
harness += "typedef struct {" + body + "} PyNumberMethods;\n"
|
||||
harness += match.group(0) + "\nint main(void) {\n"
|
||||
for field in fields:
|
||||
expected = {"nb_or": "odict_or", "nb_inplace_or": "odict_inplace_or"}.get(field, "0")
|
||||
harness += f" assert(odict_as_number.{field} == {expected});\n"
|
||||
harness += " return 0;\n}\n"
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-odict-number-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
result = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if result.returncode:
|
||||
sys.exit(result.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("odict_as_number C89 compile and all 36 semantic field mappings: PASS")
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the exact odict_or as C89 and exercise its operation ordering."""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"static PyObject \*\nodict_or\(PyObject \*left, PyObject \*right\)\n\{.*?\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "odict_or definition not found"
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
typedef struct { int ordered; int dict; } PyObject;
|
||||
typedef PyObject PyTypeObject;
|
||||
static PyObject left_obj, right_obj, result_obj, not_implemented;
|
||||
static PyObject *expected_type;
|
||||
static int stage, fail_call, fail_update;
|
||||
static int PyODict_Check(PyObject *obj) {
|
||||
assert(stage == 0 && obj == &left_obj);
|
||||
stage = 1;
|
||||
return obj->ordered;
|
||||
}
|
||||
static PyTypeObject *Py_TYPE(PyObject *obj) {
|
||||
assert(stage == 1 && obj == expected_type);
|
||||
stage = 2;
|
||||
return obj;
|
||||
}
|
||||
static int PyDict_Check(PyObject *obj) {
|
||||
assert(stage == 2);
|
||||
assert(obj == (left_obj.ordered ? &right_obj : &left_obj));
|
||||
stage = 3;
|
||||
return obj->dict;
|
||||
}
|
||||
#define Py_RETURN_NOTIMPLEMENTED return ¬_implemented
|
||||
static PyObject *PyObject_CallOneArg(PyObject *type, PyObject *arg) {
|
||||
assert(stage == 3 && type == expected_type && arg == &left_obj);
|
||||
stage = 4;
|
||||
return fail_call ? NULL : &result_obj;
|
||||
}
|
||||
static int mutablemapping_update_arg(PyObject *obj, PyObject *arg) {
|
||||
assert(stage == 4 && obj == &result_obj && arg == &right_obj);
|
||||
stage = 5;
|
||||
return fail_update ? -1 : 0;
|
||||
}
|
||||
static void Py_DECREF(PyObject *obj) {
|
||||
assert(stage == 5 && fail_update && obj == &result_obj);
|
||||
stage = 6;
|
||||
}
|
||||
'''
|
||||
harness += match.group(0)
|
||||
harness += r'''
|
||||
int main(void) {
|
||||
int ordered, scenario;
|
||||
PyObject *result;
|
||||
for (ordered = 0; ordered < 2; ++ordered) {
|
||||
for (scenario = 0; scenario < 4; ++scenario) {
|
||||
left_obj.ordered = ordered;
|
||||
right_obj.ordered = 1;
|
||||
left_obj.dict = right_obj.dict = scenario != 0;
|
||||
expected_type = ordered ? &left_obj : &right_obj;
|
||||
fail_call = scenario == 1;
|
||||
fail_update = scenario == 2;
|
||||
stage = 0;
|
||||
result = odict_or(&left_obj, &right_obj);
|
||||
if (scenario == 0) {
|
||||
assert(result == ¬_implemented && stage == 3);
|
||||
} else if (scenario == 1) {
|
||||
assert(result == NULL && stage == 4);
|
||||
} else if (scenario == 2) {
|
||||
assert(result == NULL && stage == 6);
|
||||
} else {
|
||||
assert(result == &result_obj && stage == 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-odict-or-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
compiled = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if compiled.returncode:
|
||||
sys.exit(compiled.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("odict_or C89 compile and operand/rejection/construction/update/cleanup order: PASS (8 cases)")
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only update_arg's first declaration frontier, before later blockers."""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"static int\nmutablemapping_update_arg\(PyObject \*self, PyObject \*arg\)\n\{"
|
||||
r".*?(?= if \(func != NULL\) \{)", source, re.S,
|
||||
)
|
||||
assert match, "mutablemapping_update_arg prefix not found"
|
||||
# Compile the untouched prefix only: iterator/key declarations farther down
|
||||
# are independent frontier defects. The synthetic tail observes lookup output.
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
typedef struct { int unused; } PyObject;
|
||||
static PyObject self_obj, arg_obj, items_obj, func_obj;
|
||||
static int scenario, stage, keys;
|
||||
#define _Py_ID(name) name
|
||||
static int PyDict_CheckExact(PyObject *arg) {
|
||||
assert(stage == 0 && arg == &arg_obj);
|
||||
stage = 1;
|
||||
return scenario < 3;
|
||||
}
|
||||
static PyObject *PyDict_Items(PyObject *arg) {
|
||||
assert(stage == 1 && arg == &arg_obj);
|
||||
stage = 2;
|
||||
return scenario == 0 ? NULL : &items_obj;
|
||||
}
|
||||
static int mutablemapping_add_pairs(PyObject *self, PyObject *items) {
|
||||
assert(stage == 2 && self == &self_obj && items == &items_obj);
|
||||
stage = 3;
|
||||
return scenario == 1 ? -1 : 0;
|
||||
}
|
||||
static void Py_DECREF(PyObject *obj) {
|
||||
assert(stage == 3 && obj == &items_obj);
|
||||
stage = 4;
|
||||
}
|
||||
static int _PyObject_LookupAttr(PyObject *arg, int *id, PyObject **func) {
|
||||
assert(stage == 1 && arg == &arg_obj && id == &keys);
|
||||
stage = 5;
|
||||
*func = scenario == 5 ? &func_obj : NULL;
|
||||
return scenario == 3 ? -1 : scenario == 5;
|
||||
}
|
||||
'''
|
||||
harness += match.group(0)
|
||||
harness += r'''
|
||||
assert(stage == 5 && res == 0);
|
||||
assert(func == (scenario == 5 ? &func_obj : NULL));
|
||||
stage = 6;
|
||||
return 0;
|
||||
}
|
||||
int main(void) {
|
||||
int result;
|
||||
for (scenario = 0; scenario < 6; ++scenario) {
|
||||
stage = 0;
|
||||
result = mutablemapping_update_arg(&self_obj, &arg_obj);
|
||||
assert(result == ((scenario < 2 || scenario == 3) ? -1 : 0));
|
||||
assert(stage == (scenario == 0 ? 2 : scenario < 3 ? 4 :
|
||||
scenario == 3 ? 5 : 6));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-odict-update-arg-func-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
result = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if result.returncode:
|
||||
sys.exit(result.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("mutablemapping_update_arg prefix C89 compile and dict/lookup ordering: PASS (6 cases)")
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile update_arg's exact keys branch as C89; check evaluation/cleanup order."""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
function = source.split("mutablemapping_update_arg(PyObject *self, PyObject *arg)\n{", 1)[1]
|
||||
match = re.search(
|
||||
r" if \(func != NULL\) \{.*?(?= if \(_PyObject_LookupAttr\(arg, &_Py_ID\(items\))",
|
||||
function, re.S,
|
||||
)
|
||||
assert match, "update_arg keys branch not found"
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
typedef struct { int unused; } PyObject;
|
||||
static PyObject self_obj, arg_obj, func_obj, keys_obj, iter_obj, key_obj, value_obj;
|
||||
static int scenario, calls, error;
|
||||
static char events[64];
|
||||
static void event(char c) {
|
||||
size_t n = strlen(events);
|
||||
assert(n + 1 < sizeof(events));
|
||||
events[n] = c;
|
||||
events[n + 1] = '\0';
|
||||
}
|
||||
static PyObject * _PyObject_CallNoArgs(PyObject *obj) {
|
||||
assert(obj == &func_obj); event('C');
|
||||
return scenario == 0 ? NULL : &keys_obj;
|
||||
}
|
||||
static void Py_DECREF(PyObject *obj) {
|
||||
if (obj == &func_obj) event('f');
|
||||
else if (obj == &keys_obj) event('k');
|
||||
else if (obj == &iter_obj) event('i');
|
||||
else if (obj == &key_obj) event('q');
|
||||
else { assert(obj == &value_obj); event('v'); }
|
||||
}
|
||||
static PyObject *PyObject_GetIter(PyObject *obj) {
|
||||
assert(obj == &keys_obj); event('I');
|
||||
return scenario == 1 ? NULL : &iter_obj;
|
||||
}
|
||||
static PyObject *PyIter_Next(PyObject *obj) {
|
||||
assert(obj == &iter_obj); event('N');
|
||||
if (scenario == 2 || scenario == 3 || calls++ > 0) {
|
||||
error = scenario == 3 || scenario == 7;
|
||||
return NULL;
|
||||
}
|
||||
return &key_obj;
|
||||
}
|
||||
static PyObject *PyObject_GetItem(PyObject *obj, PyObject *key) {
|
||||
assert(obj == &arg_obj && key == &key_obj); event('G');
|
||||
return scenario == 4 ? NULL : &value_obj;
|
||||
}
|
||||
static int PyObject_SetItem(PyObject *obj, PyObject *key, PyObject *value) {
|
||||
assert(obj == &self_obj && key == &key_obj && value == &value_obj);
|
||||
event('S'); return scenario == 5 ? -1 : 0;
|
||||
}
|
||||
static int PyErr_Occurred(void) { event('E'); return error; }
|
||||
static int run(PyObject *self, PyObject *arg, PyObject *func) {
|
||||
int res = 0;
|
||||
'''
|
||||
harness += match.group(0)
|
||||
harness += r'''
|
||||
return 42;
|
||||
}
|
||||
int main(void) {
|
||||
static const char *expected[] = {
|
||||
"Cf", "CfIk", "CfIkNiE", "CfIkNiE",
|
||||
"CfIkNGqi", "CfIkNGSvqi", "CfIkNGSvqNiE", "CfIkNGSvqNiE"
|
||||
};
|
||||
int result;
|
||||
for (scenario = 0; scenario < 8; ++scenario) {
|
||||
events[0] = '\0'; calls = error = 0;
|
||||
result = run(&self_obj, &arg_obj, &func_obj);
|
||||
assert(result == (scenario == 2 || scenario == 6 ? 0 : -1));
|
||||
assert(strcmp(events, expected[scenario]) == 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-odict-update-arg-iterator-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
result = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if result.returncode:
|
||||
sys.exit(result.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("update_arg keys branch C89 compile and evaluation/cleanup order: PASS (8 cases)")
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the exact update body as C89 and check evaluation/dispatch order."""
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"static PyObject \*\nmutablemapping_update\(PyObject \*self, PyObject \*args, PyObject \*kwargs\)\n\{.*?\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "mutablemapping_update not found"
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
typedef long Py_ssize_t;
|
||||
typedef struct { int unused; } PyObject;
|
||||
static PyObject self_obj, args_obj, kwargs_obj, other_obj, items_obj, none_obj;
|
||||
static int scenario, PyExc_TypeError;
|
||||
static char events[64];
|
||||
static void event(char c) {
|
||||
size_t n = strlen(events);
|
||||
assert(n + 1 < sizeof(events));
|
||||
events[n] = c; events[n + 1] = '\0';
|
||||
}
|
||||
static int PyTuple_Check(PyObject *obj) {
|
||||
assert(obj == &args_obj); event('T'); return 1;
|
||||
}
|
||||
static Py_ssize_t PyTuple_GET_SIZE(PyObject *obj) {
|
||||
assert(obj == &args_obj && strcmp(events, "T") == 0);
|
||||
event('L'); return scenario == 2 ? 2 : scenario == 1 ? 0 : 1;
|
||||
}
|
||||
static void PyErr_Format(int exc, const char *msg, Py_ssize_t len) {
|
||||
assert(exc == PyExc_TypeError && len == 2);
|
||||
assert(strcmp(msg, "update() takes at most 1 positional argument (%zd given)") == 0);
|
||||
event('E');
|
||||
}
|
||||
static PyObject *PyTuple_GET_ITEM(PyObject *obj, int index) {
|
||||
assert(obj == &args_obj && index == 0); event('G'); return &other_obj;
|
||||
}
|
||||
static void Py_INCREF(PyObject *obj) { assert(obj == &other_obj); event('I'); }
|
||||
static void Py_DECREF(PyObject *obj) {
|
||||
assert(obj == &other_obj || obj == &items_obj);
|
||||
event(obj == &other_obj ? 'D' : 'd');
|
||||
}
|
||||
static int mutablemapping_update_arg(PyObject *self, PyObject *other) {
|
||||
assert(self == &self_obj && other == &other_obj); event('U');
|
||||
return scenario == 3 ? -1 : 0;
|
||||
}
|
||||
static int PyDict_Check(PyObject *obj) {
|
||||
assert(obj == &kwargs_obj); event('K'); return 1;
|
||||
}
|
||||
static int PyDict_GET_SIZE(PyObject *obj) {
|
||||
assert(obj == &kwargs_obj); event('S'); return scenario != 5;
|
||||
}
|
||||
static PyObject *PyDict_Items(PyObject *obj) {
|
||||
assert(obj == &kwargs_obj); event('M'); return scenario == 6 ? NULL : &items_obj;
|
||||
}
|
||||
static int mutablemapping_add_pairs(PyObject *self, PyObject *items) {
|
||||
assert(self == &self_obj && items == &items_obj); event('A');
|
||||
return scenario == 7 ? -1 : 0;
|
||||
}
|
||||
#define Py_RETURN_NONE return &none_obj
|
||||
'''
|
||||
harness += match.group(0)
|
||||
harness += r'''
|
||||
int main(void) {
|
||||
static const char *expected[] = {
|
||||
"", "TL", "TLE", "TLGIUD", "TLGIUD", "TLGIUDKS",
|
||||
"TLGIUDKSM", "TLGIUDKSMAd", "TLGIUDKSMAd", "KSMAd"
|
||||
};
|
||||
PyObject *result;
|
||||
for (scenario = 0; scenario < 10; ++scenario) {
|
||||
events[0] = '\0';
|
||||
result = mutablemapping_update(&self_obj,
|
||||
scenario == 0 || scenario == 9 ? NULL : &args_obj,
|
||||
scenario >= 5 ? &kwargs_obj : NULL);
|
||||
assert(result == (scenario == 2 || scenario == 3 || scenario == 6 ||
|
||||
scenario == 7 ? NULL : &none_obj));
|
||||
assert(strcmp(events, expected[scenario]) == 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-odict-update-len-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
result = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if result.returncode:
|
||||
sys.exit(result.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("mutablemapping_update C89 compile and evaluation/dispatch order: PASS (10 cases)")
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the exact PickleBuffer buffer table as C89 and check both slots."""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source_path = Path(sys.argv[1])
|
||||
source = source_path.read_text()
|
||||
match = re.search(r"static PyBufferProcs picklebuf_as_buffer = \{.*?\n\};", source, re.S)
|
||||
assert match, "picklebuf_as_buffer initializer not found"
|
||||
header = (source_path.parent.parent / "Include/cpython/object.h").read_text()
|
||||
layout = re.search(r"typedef struct \{([^{}]*)\} PyBufferProcs;", header, re.S)
|
||||
assert layout, "PyBufferProcs layout not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", layout.group(1), flags=re.S)
|
||||
assert re.findall(r"\b(bf_\w+)\s*;", body) == ["bf_getbuffer", "bf_releasebuffer"]
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
typedef struct { int unused; } PyObject;
|
||||
typedef struct { int unused; } Py_buffer;
|
||||
typedef int (*getbufferproc)(PyObject *, Py_buffer *, int);
|
||||
typedef void (*releasebufferproc)(PyObject *, Py_buffer *);
|
||||
static int picklebuf_getbuf(PyObject *self, Py_buffer *view, int flags)
|
||||
{
|
||||
(void)self; (void)view; return flags;
|
||||
}
|
||||
static void picklebuf_releasebuf(PyObject *self, Py_buffer *view)
|
||||
{
|
||||
(void)self; (void)view;
|
||||
}
|
||||
'''
|
||||
harness += "typedef struct {" + body + "} PyBufferProcs;\n"
|
||||
harness += match.group(0)
|
||||
harness += r'''
|
||||
int main(void)
|
||||
{
|
||||
assert(picklebuf_as_buffer.bf_getbuffer == (getbufferproc)picklebuf_getbuf);
|
||||
assert(picklebuf_as_buffer.bf_releasebuffer == (releasebufferproc)picklebuf_releasebuf);
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-picklebuf-buffer-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
result = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if result.returncode:
|
||||
sys.exit(result.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("picklebuf_as_buffer C89 compile and both semantic field mappings: PASS")
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the exact picklebuf_raw body as C89 and check its error order."""
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"static PyObject \*\npicklebuf_raw\([^\n]+\)\n\{.*?\n\}", source, re.S
|
||||
)
|
||||
assert match, "picklebuf_raw not found"
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
typedef struct { int unused; } PyObject;
|
||||
typedef struct {
|
||||
PyObject *obj;
|
||||
long *suboffsets;
|
||||
const char *format;
|
||||
int ndim;
|
||||
long itemsize, len;
|
||||
long *shape, *strides;
|
||||
} Py_buffer;
|
||||
typedef struct { Py_buffer view; } PyPickleBufferObject;
|
||||
typedef struct { Py_buffer view; int flags; } PyMemoryViewObject;
|
||||
#define Py_UNUSED(name) name
|
||||
#define _Py_MEMORYVIEW_C 1
|
||||
#define _Py_MEMORYVIEW_FORTRAN 2
|
||||
static int PyExc_ValueError = 1, PyExc_BufferError = 2;
|
||||
static int scenario;
|
||||
static char events[16];
|
||||
static PyPickleBufferObject self;
|
||||
static PyMemoryViewObject memory;
|
||||
static PyObject base;
|
||||
static void event(char c) {
|
||||
size_t n = strlen(events);
|
||||
assert(n + 1 < sizeof(events));
|
||||
events[n] = c; events[n + 1] = '\0';
|
||||
}
|
||||
static void PyErr_SetString(int exc, const char *message) {
|
||||
assert(exc == (scenario == 0 ? PyExc_ValueError : PyExc_BufferError));
|
||||
assert(strcmp(message, scenario == 0
|
||||
? "operation forbidden on released PickleBuffer object"
|
||||
: "cannot extract raw buffer from non-contiguous buffer") == 0);
|
||||
event('E');
|
||||
}
|
||||
static int PyBuffer_IsContiguous(Py_buffer *view, char order) {
|
||||
assert(view == &self.view && order == 'A'); event('C');
|
||||
return scenario != 2;
|
||||
}
|
||||
static PyObject *PyMemoryView_FromObject(PyObject *obj) {
|
||||
assert(obj == (PyObject *)&self); event('M');
|
||||
return scenario == 3 ? NULL : (PyObject *)&memory;
|
||||
}
|
||||
'''
|
||||
harness += match.group(0)
|
||||
harness += r'''
|
||||
int main(void) {
|
||||
static const char *expected[] = {"E", "E", "CE", "CM", "CM"};
|
||||
PyObject *result;
|
||||
PyMemoryViewObject before;
|
||||
Py_buffer original;
|
||||
long offset = 0;
|
||||
for (scenario = 0; scenario < 5; ++scenario) {
|
||||
memset(&memory, 0, sizeof(memory));
|
||||
memory.view.obj = &base;
|
||||
memory.view.format = "i";
|
||||
memory.view.ndim = 2;
|
||||
memory.view.itemsize = 4;
|
||||
memory.view.len = 24;
|
||||
memory.flags = 8;
|
||||
memcpy(&before, &memory, sizeof(memory));
|
||||
self.view = memory.view;
|
||||
self.view.obj = scenario == 0 ? NULL : &base;
|
||||
self.view.suboffsets = scenario == 1 ? &offset : NULL;
|
||||
memcpy(&original, &self.view, sizeof(original));
|
||||
events[0] = '\0';
|
||||
result = picklebuf_raw(&self, NULL);
|
||||
assert(strcmp(events, expected[scenario]) == 0);
|
||||
assert(memcmp(&original, &self.view, sizeof(original)) == 0);
|
||||
if (scenario < 4) {
|
||||
assert(result == NULL);
|
||||
assert(memcmp(&before, &memory, sizeof(memory)) == 0);
|
||||
} else {
|
||||
assert(result == (PyObject *)&memory);
|
||||
assert(memory.view.obj == &base && memory.view.len == 24);
|
||||
assert(memory.view.suboffsets == NULL);
|
||||
assert(strcmp(memory.view.format, "B") == 0);
|
||||
assert(memory.view.ndim == 1 && memory.view.itemsize == 1);
|
||||
assert(memory.view.shape == &memory.view.len);
|
||||
assert(memory.view.strides == &memory.view.itemsize);
|
||||
assert(memory.flags == (_Py_MEMORYVIEW_C | _Py_MEMORYVIEW_FORTRAN));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-picklebuf-raw-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
result = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
"-Wno-unused-parameter", str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if result.returncode:
|
||||
sys.exit(result.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("picklebuf_raw C89 compile and validation/allocation order: PASS (5 cases)")
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the exact PickleBuffer type as C89 and verify every static slot."""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source_path = Path(sys.argv[1])
|
||||
source = source_path.read_text()
|
||||
match = re.search(r"PyTypeObject PyPickleBuffer_Type = \{.*?\n\};", source, re.S)
|
||||
assert match, "PyPickleBuffer_Type initializer not found"
|
||||
header = (source_path.parent.parent / "Include/cpython/object.h").read_text()
|
||||
layout = re.search(r"struct _typeobject \{(.*?)\n\};", header, re.S)
|
||||
assert layout, "PyTypeObject layout not found"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", layout.group(1), flags=re.S)
|
||||
fields = re.findall(r"\b(tp_\w+)\b", body)
|
||||
assert len(fields) == len(set(fields)) == 48, "review changed PyTypeObject layout"
|
||||
expected = {
|
||||
"tp_name": '"pickle.PickleBuffer"',
|
||||
"tp_basicsize": "sizeof(PyPickleBufferObject)",
|
||||
"tp_dealloc": "(destructor)picklebuf_dealloc",
|
||||
"tp_as_buffer": "&picklebuf_as_buffer",
|
||||
"tp_flags": "Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC",
|
||||
"tp_doc": '"Wrapper for potentially out-of-band buffers"',
|
||||
"tp_traverse": "(traverseproc)picklebuf_traverse",
|
||||
"tp_clear": "(inquiry)picklebuf_clear",
|
||||
"tp_weaklistoffset": "offsetof(PyPickleBufferObject, weakreflist)",
|
||||
"tp_methods": "picklebuf_methods",
|
||||
"tp_new": "picklebuf_new",
|
||||
}
|
||||
assert expected.keys() <= set(fields)
|
||||
# Use the real field declarations/order, with minimal stand-ins for dependencies.
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
typedef long Py_ssize_t;
|
||||
typedef struct { int unused; } PyObject;
|
||||
typedef struct { int unused; } PyAsyncMethods;
|
||||
typedef struct { int unused; } PyNumberMethods;
|
||||
typedef struct { int unused; } PySequenceMethods;
|
||||
typedef struct { int unused; } PyMappingMethods;
|
||||
typedef struct { int unused; } PyBufferProcs;
|
||||
typedef struct { int unused; } PyMethodDef;
|
||||
typedef struct { int unused; } PyMemberDef;
|
||||
typedef struct { int unused; } PyGetSetDef;
|
||||
typedef struct _typeobject PyTypeObject;
|
||||
typedef struct { int view; PyObject *weakreflist; } PyPickleBufferObject;
|
||||
#define PyObject_VAR_HEAD long ob_refcnt; PyTypeObject *ob_type; long ob_size;
|
||||
#define PyVarObject_HEAD_INIT(type, size) 1, type, size,
|
||||
#define PyDoc_STR(value) value
|
||||
#define Py_TPFLAGS_DEFAULT 1UL
|
||||
#define Py_TPFLAGS_HAVE_GC 16384UL
|
||||
'''
|
||||
for name in (
|
||||
"destructor getattrfunc setattrfunc reprfunc hashfunc ternaryfunc "
|
||||
"getattrofunc setattrofunc traverseproc inquiry richcmpfunc getiterfunc "
|
||||
"iternextfunc descrgetfunc descrsetfunc initproc allocfunc newfunc "
|
||||
"freefunc vectorcallfunc"
|
||||
).split():
|
||||
harness += f"typedef void (*{name})(void);\n"
|
||||
harness += "struct _typeobject {" + body + "};\n"
|
||||
for name in ("new", "dealloc", "traverse", "clear"):
|
||||
harness += f"static void picklebuf_{name}(void) {{ }}\n"
|
||||
harness += "static PyBufferProcs picklebuf_as_buffer;\n"
|
||||
harness += "static PyMethodDef picklebuf_methods[1];\n"
|
||||
harness += match.group(0) + "\nint main(void)\n{\n"
|
||||
harness += " assert(PyPickleBuffer_Type.ob_refcnt == 1);\n"
|
||||
harness += " assert(PyPickleBuffer_Type.ob_type == NULL);\n"
|
||||
harness += " assert(PyPickleBuffer_Type.ob_size == 0);\n"
|
||||
for field in fields:
|
||||
value = expected.get(field, "0")
|
||||
if field in ("tp_name", "tp_doc"):
|
||||
harness += f" assert(strcmp(PyPickleBuffer_Type.{field}, {value}) == 0);\n"
|
||||
else:
|
||||
harness += f" assert(PyPickleBuffer_Type.{field} == ({value}));\n"
|
||||
harness += " return 0;\n}\n"
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-picklebuf-type-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
result = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if result.returncode:
|
||||
sys.exit(result.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("PyPickleBuffer_Type C89 compile and all 48 semantic field mappings: PASS")
|
||||
@@ -32,8 +32,8 @@ bad() { echo "not ok - $1"; fail=1; }
|
||||
# shellcheck source=scripts/vc6-probe.sh
|
||||
source "$PROBE"
|
||||
|
||||
prefix='Z:\home\ubuntu\projects\python-win9x\'
|
||||
input="Z:\\home\\ubuntu\\projects\\python-win9x\\cpython\\Programs\\python.c(1) : error C2061"
|
||||
prefix='Z:\home\ubuntu\Organized\Legacy-Ports\Projects\python-win9x\'
|
||||
input="Z:\\home\\ubuntu\\Organized\\Legacy-Ports\\Projects\\python-win9x\\cpython\\Programs\\python.c(1) : error C2061"
|
||||
expected="cpython\\Programs\\python.c(1) : error C2061"
|
||||
actual="$(printf '%s\n' "$input" | strip_prefix "$prefix")"
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the exact range_index body as C89 and check evaluation order."""
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"static PyObject \*\nrange_index\([^\n]+\)\n\{.*?\n\}", source, re.S
|
||||
)
|
||||
assert match, "range_index not found"
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
typedef long Py_ssize_t;
|
||||
typedef struct { int unused; } PyObject;
|
||||
typedef struct { PyObject *start, *step; } rangeobject;
|
||||
#define PY_ITERSEARCH_INDEX 2
|
||||
static int PyExc_ValueError = 1;
|
||||
static int scenario;
|
||||
static char events[16];
|
||||
static PyObject ob, start, step, one, idx, quotient, converted;
|
||||
static rangeobject range;
|
||||
static void event(char c) {
|
||||
size_t n = strlen(events);
|
||||
assert(n + 1 < sizeof(events));
|
||||
events[n] = c; events[n + 1] = '\0';
|
||||
}
|
||||
static int PyLong_CheckExact(PyObject *obj) {
|
||||
assert(obj == &ob); return scenario != 0 && scenario != 1 && scenario != 8;
|
||||
}
|
||||
static int PyBool_Check(PyObject *obj) {
|
||||
assert(obj == &ob); return scenario == 8;
|
||||
}
|
||||
static Py_ssize_t _PySequence_IterSearch(PyObject *obj, PyObject *value, int op) {
|
||||
assert(obj == (PyObject *)&range && value == &ob && op == PY_ITERSEARCH_INDEX);
|
||||
event('I'); return scenario == 0 ? -1 : 3;
|
||||
}
|
||||
static PyObject *PyLong_FromSsize_t(Py_ssize_t value) {
|
||||
assert(value == 3); event('L'); return &converted;
|
||||
}
|
||||
static int range_contains_long(rangeobject *r, PyObject *value) {
|
||||
assert(r == &range && value == &ob); event('C');
|
||||
return scenario == 2 ? -1 : scenario != 3;
|
||||
}
|
||||
static PyObject *PyNumber_Subtract(PyObject *value, PyObject *first) {
|
||||
assert(value == &ob && first == &start); event('S');
|
||||
return scenario == 4 ? NULL : &idx;
|
||||
}
|
||||
static PyObject *_PyLong_GetOne(void) { event('O'); return &one; }
|
||||
static PyObject *PyNumber_FloorDivide(PyObject *value, PyObject *divisor) {
|
||||
assert(value == &idx && divisor == &step); event('F');
|
||||
return scenario == 6 ? NULL : "ient;
|
||||
}
|
||||
static void Py_DECREF(PyObject *value) { assert(value == &idx); event('D'); }
|
||||
static void PyErr_Format(int exc, const char *format, PyObject *value) {
|
||||
assert(exc == PyExc_ValueError && value == &ob);
|
||||
assert(strcmp(format, "%R is not in range") == 0); event('E');
|
||||
}
|
||||
'''
|
||||
harness += match.group(0)
|
||||
harness += r'''
|
||||
int main(void) {
|
||||
static const char *expected[] = {
|
||||
"I", "IL", "C", "CE", "CS", "CSO", "CSOFD", "CSOFD", "CSOFD"
|
||||
};
|
||||
PyObject *result;
|
||||
for (scenario = 0; scenario < 9; ++scenario) {
|
||||
range.start = &start;
|
||||
range.step = scenario == 5 ? &one : &step;
|
||||
events[0] = '\0';
|
||||
result = range_index(&range, &ob);
|
||||
assert(strcmp(events, expected[scenario]) == 0);
|
||||
if (scenario == 1) assert(result == &converted);
|
||||
else if (scenario == 5) assert(result == &idx);
|
||||
else if (scenario == 7 || scenario == 8) assert(result == "ient);
|
||||
else assert(result == NULL);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-rangeobject-index-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
result = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
"-Wno-unused-parameter", str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if result.returncode:
|
||||
sys.exit(result.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("range_index C89 compile and evaluation/cleanup order: PASS (9 cases)")
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the exact range number table as C89 and check every named slot."""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source_path = Path(sys.argv[1])
|
||||
source = source_path.read_text()
|
||||
match = re.search(r"static PyNumberMethods range_as_number = \{.*?\n\};", source, re.S)
|
||||
assert match, "range_as_number initializer not found"
|
||||
header = (source_path.parent.parent / "Include/cpython/object.h").read_text()
|
||||
layout = re.search(r"typedef struct \{([^{}]*)\} PyNumberMethods;", header, re.S)
|
||||
assert layout, "PyNumberMethods layout not found"
|
||||
# Use the actual header's order and types; check slots by name, not position.
|
||||
body = re.sub(r"/\*.*?\*/", "", layout.group(1), flags=re.S)
|
||||
fields = re.findall(r"\b(nb_\w+)\s*;", body)
|
||||
assert len(fields) == 36 and len(set(fields)) == 36
|
||||
assert len(body.split(";")) == len(fields) + 1, "unparsed header field"
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
typedef struct { int value; } PyObject;
|
||||
typedef PyObject *(*binaryfunc)(PyObject *, PyObject *);
|
||||
typedef PyObject *(*ternaryfunc)(PyObject *, PyObject *, PyObject *);
|
||||
typedef PyObject *(*unaryfunc)(PyObject *);
|
||||
typedef int (*inquiry)(PyObject *);
|
||||
static int range_bool(PyObject *self) { (void)self; return 1; }
|
||||
'''
|
||||
harness += "typedef struct {" + body + "} PyNumberMethods;\n"
|
||||
harness += match.group(0) + "\nint main(void) {\n"
|
||||
for field in fields:
|
||||
expected = {"nb_bool": "(inquiry)range_bool"}.get(field, "0")
|
||||
harness += f" assert(range_as_number.{field} == {expected});\n"
|
||||
harness += " return 0;\n}\n"
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-rangeobject-number-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
result = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if result.returncode:
|
||||
sys.exit(result.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("range_as_number C89 compile and all 36 semantic field mappings: PASS")
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the exact range type as C89 and verify every static slot."""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source_path = Path(sys.argv[1])
|
||||
source = source_path.read_text()
|
||||
match = re.search(r"PyTypeObject PyRange_Type = \{.*?\n\};", source, re.S)
|
||||
assert match, "PyRange_Type initializer not found"
|
||||
header = (source_path.parent.parent / "Include/cpython/object.h").read_text()
|
||||
layout = re.search(r"struct _typeobject \{(.*?)\n\};", header, re.S)
|
||||
assert layout, "PyTypeObject layout not found"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", layout.group(1), flags=re.S)
|
||||
fields = re.findall(r"\b(tp_\w+)\b", body)
|
||||
assert len(fields) == len(set(fields)) == 48, "review changed PyTypeObject layout"
|
||||
expected = {
|
||||
"tp_name": '"range"',
|
||||
"tp_basicsize": "sizeof(rangeobject)",
|
||||
"tp_dealloc": "(destructor)range_dealloc",
|
||||
"tp_repr": "(reprfunc)range_repr",
|
||||
"tp_as_number": "&range_as_number",
|
||||
"tp_as_sequence": "&range_as_sequence",
|
||||
"tp_as_mapping": "&range_as_mapping",
|
||||
"tp_hash": "(hashfunc)range_hash",
|
||||
"tp_getattro": "PyObject_GenericGetAttr",
|
||||
"tp_flags": "Py_TPFLAGS_DEFAULT | Py_TPFLAGS_SEQUENCE",
|
||||
"tp_doc": "range_doc",
|
||||
"tp_richcompare": "range_richcompare",
|
||||
"tp_iter": "range_iter",
|
||||
"tp_methods": "range_methods",
|
||||
"tp_members": "range_members",
|
||||
"tp_new": "range_new",
|
||||
"tp_vectorcall": "(vectorcallfunc)range_vectorcall",
|
||||
}
|
||||
assert expected.keys() <= set(fields)
|
||||
# Use the real field declarations/order, with minimal stand-ins for dependencies.
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
typedef long Py_ssize_t;
|
||||
typedef struct { int unused; } PyObject;
|
||||
typedef struct { int unused; } PyAsyncMethods;
|
||||
typedef struct { int unused; } PyNumberMethods;
|
||||
typedef struct { int unused; } PySequenceMethods;
|
||||
typedef struct { int unused; } PyMappingMethods;
|
||||
typedef struct { int unused; } PyBufferProcs;
|
||||
typedef struct { int unused; } PyMethodDef;
|
||||
typedef struct { int unused; } PyMemberDef;
|
||||
typedef struct { int unused; } PyGetSetDef;
|
||||
typedef struct _typeobject PyTypeObject;
|
||||
typedef struct { PyObject *start, *stop, *step, *length; } rangeobject;
|
||||
#define PyObject_VAR_HEAD long ob_refcnt; PyTypeObject *ob_type; long ob_size;
|
||||
#define PyVarObject_HEAD_INIT(type, size) 1, type, size,
|
||||
#define Py_TPFLAGS_DEFAULT 1UL
|
||||
#define Py_TPFLAGS_SEQUENCE 32UL
|
||||
'''
|
||||
for name in (
|
||||
"destructor getattrfunc setattrfunc reprfunc hashfunc ternaryfunc "
|
||||
"getattrofunc setattrofunc traverseproc inquiry richcmpfunc getiterfunc "
|
||||
"iternextfunc descrgetfunc descrsetfunc initproc allocfunc newfunc "
|
||||
"freefunc vectorcallfunc"
|
||||
).split():
|
||||
harness += f"typedef void (*{name})(void);\n"
|
||||
harness += "struct _typeobject {" + body + "};\n"
|
||||
for name in (
|
||||
"range_new range_dealloc range_repr range_hash PyObject_GenericGetAttr "
|
||||
"range_richcompare range_iter range_vectorcall"
|
||||
).split():
|
||||
harness += f"static void {name}(void) {{ }}\n"
|
||||
harness += "static PyTypeObject PyType_Type;\n"
|
||||
harness += "static PyNumberMethods range_as_number;\n"
|
||||
harness += "static PySequenceMethods range_as_sequence;\n"
|
||||
harness += "static PyMappingMethods range_as_mapping;\n"
|
||||
harness += "static PyMethodDef range_methods[1];\n"
|
||||
harness += "static PyMemberDef range_members[1];\n"
|
||||
harness += 'static const char range_doc[] = "range documentation";\n'
|
||||
harness += match.group(0) + "\nint main(void)\n{\n"
|
||||
harness += " assert(PyRange_Type.ob_refcnt == 1);\n"
|
||||
harness += " assert(PyRange_Type.ob_type == &PyType_Type);\n"
|
||||
harness += " assert(PyRange_Type.ob_size == 0);\n"
|
||||
for field in fields:
|
||||
value = expected.get(field, "0")
|
||||
if field in ("tp_name", "tp_doc"):
|
||||
harness += f" assert(strcmp(PyRange_Type.{field}, {value}) == 0);\n"
|
||||
else:
|
||||
harness += f" assert(PyRange_Type.{field} == ({value}));\n"
|
||||
harness += " return 0;\n}\n"
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-rangeobject-vectorcall-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
result = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if result.returncode:
|
||||
sys.exit(result.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("PyRange_Type C89 compile and all 48 semantic field mappings: PASS")
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check frozenset_vectorcall's C89 declarations and evaluation order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"static PyObject \*\nfrozenset_vectorcall\(PyObject \*type, PyObject \* const\*args,"
|
||||
r"\s*size_t nargsf, PyObject \*kwnames\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "frozenset_vectorcall not found"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*Py_ssize_t nargs;\s*PyObject \*iterable;", body), (
|
||||
"nargs and iterable must be declared without initializers in the initial block"
|
||||
)
|
||||
expected = """
|
||||
Py_ssize_t nargs;
|
||||
PyObject *iterable;
|
||||
|
||||
if (!_PyArg_NoKwnames("frozenset", kwnames)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
nargs = PyVectorcall_NARGS(nargsf);
|
||||
if (!_PyArg_CheckPositional("frozenset", nargs, 0, 1)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
iterable = (nargs ? args[0] : NULL);
|
||||
return make_new_frozenset(_PyType_CAST(type), iterable);
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve keyword rejection, nargs evaluation, positional rejection, guarded "
|
||||
"iterable evaluation, and construction in their original order"
|
||||
)
|
||||
print("frozenset_vectorcall C89 declarations and evaluation order: PASS")
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check set_issuperset's C89 declarations and unchanged iterator ownership."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"static PyObject \*\nset_issuperset\(PySetObject \*so, PyObject \*other\)"
|
||||
r"\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "set_issuperset not found"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*PyObject \*key;\s*PyObject \*it;", body), (
|
||||
"set_issuperset: key and it must be declared in the initial declaration "
|
||||
"block; late PyObject declaration after PyAnySet_Check is not C89 compatible"
|
||||
)
|
||||
expected = """
|
||||
PyObject *key;
|
||||
PyObject *it;
|
||||
|
||||
if (PyAnySet_Check(other)) {
|
||||
return set_issubset((PySetObject *)other, (PyObject *)so);
|
||||
}
|
||||
|
||||
it = PyObject_GetIter(other);
|
||||
if (it == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
while ((key = PyIter_Next(it)) != NULL) {
|
||||
int rv = set_contains_key(so, key);
|
||||
Py_DECREF(key);
|
||||
if (rv < 0) {
|
||||
Py_DECREF(it);
|
||||
return NULL;
|
||||
}
|
||||
if (!rv) {
|
||||
Py_DECREF(it);
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
}
|
||||
Py_DECREF(it);
|
||||
if (PyErr_Occurred()) {
|
||||
return NULL;
|
||||
}
|
||||
Py_RETURN_TRUE;
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve the set fast path, iterator acquisition, loop, reference ownership, "
|
||||
"error handling, and return behavior"
|
||||
)
|
||||
print("set_issuperset C89 declarations and iterator ownership: PASS")
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check make_new_set's C89 declaration and assertion/allocation order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"static PyObject \*\nmake_new_set\(PyTypeObject \*type, PyObject \*iterable\)"
|
||||
r"\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "make_new_set not found"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*PySetObject \*so;", body), (
|
||||
"so must be declared without initializer before the assertion"
|
||||
)
|
||||
assert re.match(
|
||||
r"\s*PySetObject \*so;\s*assert\(PyType_Check\(type\)\);"
|
||||
r"\s*so = \(PySetObject \*\)type->tp_alloc\(type, 0\);"
|
||||
r"\s*if \(so == NULL\)\s*return NULL;",
|
||||
body,
|
||||
), "type assertion, allocation, and allocation failure check must retain their order"
|
||||
assert body.count("PySetObject *so;") == 1, "so must be declared exactly once"
|
||||
assert body.count("type->tp_alloc(") == 1, "allocation must occur exactly once"
|
||||
print("make_new_set C89 declaration and assertion/allocation order: PASS")
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the exact setiter_reduce as C89 and check ownership and call order."""
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"static PyObject \*\nsetiter_reduce\([^\n]+\)\n\{.*?\n\}", source, re.S
|
||||
)
|
||||
assert match, "setiter_reduce not found"
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
typedef struct { int refs; } PyObject;
|
||||
typedef struct {
|
||||
PyObject head;
|
||||
PyObject *si_set;
|
||||
long si_used, si_pos, len;
|
||||
} setiterobject;
|
||||
#define Py_UNUSED(name) name
|
||||
#define _Py_ID(name) name
|
||||
static int iter, mode, exhausted;
|
||||
static PyObject backing, list_value, builtin, built;
|
||||
static setiterobject original;
|
||||
static char events[32];
|
||||
static void event(char c) {
|
||||
size_t n = strlen(events);
|
||||
assert(n + 1 < sizeof(events));
|
||||
events[n] = c;
|
||||
events[n + 1] = '\0';
|
||||
}
|
||||
static void Py_XINCREF(PyObject *obj) {
|
||||
assert(obj == original.si_set);
|
||||
assert(strcmp(events, "") == 0);
|
||||
event('I');
|
||||
if (obj != NULL) ++obj->refs;
|
||||
}
|
||||
static void Py_XDECREF(PyObject *obj) {
|
||||
assert(obj == (mode == 1 ? NULL : original.si_set));
|
||||
assert(strcmp(events, "IL") == 0);
|
||||
event('D');
|
||||
if (obj != NULL) --obj->refs;
|
||||
}
|
||||
static PyObject *PySequence_List(PyObject *obj) {
|
||||
setiterobject *tmp = (setiterobject *)obj;
|
||||
assert(tmp != &original);
|
||||
assert(tmp->head.refs == original.head.refs);
|
||||
assert(tmp->si_set == original.si_set);
|
||||
assert(tmp->si_used == original.si_used);
|
||||
assert(tmp->si_pos == original.si_pos && tmp->len == original.len);
|
||||
assert(backing.refs == (exhausted ? 1 : 2));
|
||||
assert(strcmp(events, "I") == 0);
|
||||
event('L');
|
||||
tmp->si_pos = 99;
|
||||
tmp->len = 0;
|
||||
if (mode == 1 && tmp->si_set != NULL) {
|
||||
--tmp->si_set->refs;
|
||||
tmp->si_set = NULL;
|
||||
}
|
||||
return mode == 2 ? NULL : &list_value;
|
||||
}
|
||||
static PyObject *_PyEval_GetBuiltin(int *name) {
|
||||
assert(name == &iter);
|
||||
assert(strcmp(events, "ILD") == 0 && backing.refs == 1);
|
||||
event('G');
|
||||
return &builtin;
|
||||
}
|
||||
static PyObject *Py_BuildValue(const char *format, PyObject *func, PyObject *list) {
|
||||
assert(strcmp(format, "N(N)") == 0);
|
||||
assert(func == &builtin && list == &list_value);
|
||||
assert(strcmp(events, "ILDG") == 0 && backing.refs == 1);
|
||||
event('B');
|
||||
--func->refs;
|
||||
--list->refs;
|
||||
return mode == 3 ? NULL : &built;
|
||||
}
|
||||
'''
|
||||
harness += match.group(0)
|
||||
harness += r'''
|
||||
int main(void) {
|
||||
setiterobject before;
|
||||
PyObject *result;
|
||||
for (exhausted = 0; exhausted < 2; ++exhausted) {
|
||||
for (mode = 0; mode < 4; ++mode) {
|
||||
backing.refs = list_value.refs = builtin.refs = 1;
|
||||
memset(&original, 0, sizeof(original));
|
||||
original.head.refs = 7;
|
||||
original.si_set = exhausted ? NULL : &backing;
|
||||
original.si_used = 5;
|
||||
original.si_pos = 2;
|
||||
original.len = 3;
|
||||
memcpy(&before, &original, sizeof(before));
|
||||
events[0] = '\0';
|
||||
result = setiter_reduce(&original, NULL);
|
||||
assert(memcmp(&before, &original, sizeof(before)) == 0);
|
||||
assert(backing.refs == 1);
|
||||
assert(strcmp(events, mode == 2 ? "ILD" : "ILDGB") == 0);
|
||||
assert(result == (mode >= 2 ? NULL : &built));
|
||||
assert(list_value.refs == (mode == 2 ? 1 : 0));
|
||||
assert(builtin.refs == (mode == 2 ? 1 : 0));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-setiter-reduce-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
result = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
"-Wno-unused-parameter", str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if result.returncode:
|
||||
sys.exit(result.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("setiter_reduce C89 declaration/evaluation/cleanup/return: PASS (8 cases)")
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check set_table_resize's C89 declaration and preserve resize sizing order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"static int\nset_table_resize\(PySetObject \*so, Py_ssize_t minused\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "set_table_resize not found"
|
||||
body = match.group(1)
|
||||
assert re.search(r"^\s*size_t newsize;\s*$", body, re.M), (
|
||||
"newsize must be declared without initializer in the function declaration block"
|
||||
)
|
||||
assert re.search(
|
||||
r"assert\(minused >= 0\);\s*/\* Find the smallest table size > minused\. \*/"
|
||||
r"\s*/\* XXX speed-up with intrinsics \*/\s*newsize = PySet_MINSIZE;"
|
||||
r"\s*while \(newsize <= \(size_t\)minused\)",
|
||||
body,
|
||||
), "newsize initialization and resize loop must remain after the assertion in original order"
|
||||
print("set_table_resize C89 declaration and sizing order: PASS")
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the exact set types as C89 and verify every static slot."""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source_path = Path(sys.argv[1])
|
||||
source = source_path.read_text()
|
||||
initializers = {}
|
||||
for name in ("PySet_Type", "PyFrozenSet_Type"):
|
||||
match = re.search(r"PyTypeObject " + name + r" = \{.*?\n\};", source, re.S)
|
||||
assert match, name + " initializer not found"
|
||||
initializers[name] = match.group(0)
|
||||
header = (source_path.parent.parent / "Include/cpython/object.h").read_text()
|
||||
layout = re.search(r"struct _typeobject \{(.*?)\n\};", header, re.S)
|
||||
assert layout, "PyTypeObject layout not found"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", layout.group(1), flags=re.S)
|
||||
fields = re.findall(r"\b(tp_\w+)\b", body)
|
||||
assert len(fields) == len(set(fields)) == 48, "review changed PyTypeObject layout"
|
||||
expected = {
|
||||
"tp_basicsize": "sizeof(PySetObject)",
|
||||
"tp_dealloc": "(destructor)set_dealloc",
|
||||
"tp_repr": "(reprfunc)set_repr",
|
||||
"tp_as_sequence": "&set_as_sequence",
|
||||
"tp_getattro": "PyObject_GenericGetAttr",
|
||||
"tp_flags": "Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE | _Py_TPFLAGS_MATCH_SELF",
|
||||
"tp_traverse": "(traverseproc)set_traverse",
|
||||
"tp_clear": "(inquiry)set_clear_internal",
|
||||
"tp_richcompare": "(richcmpfunc)set_richcompare",
|
||||
"tp_weaklistoffset": "offsetof(PySetObject, weakreflist)",
|
||||
"tp_iter": "(getiterfunc)set_iter",
|
||||
"tp_alloc": "PyType_GenericAlloc",
|
||||
"tp_free": "PyObject_GC_Del",
|
||||
}
|
||||
assert expected.keys() <= set(fields)
|
||||
# Use the real field declarations/order, with minimal stand-ins for dependencies.
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
typedef long Py_ssize_t;
|
||||
typedef struct { int unused; } PyObject;
|
||||
typedef struct { int unused; } PyAsyncMethods;
|
||||
typedef struct { int unused; } PyNumberMethods;
|
||||
typedef struct { int unused; } PySequenceMethods;
|
||||
typedef struct { int unused; } PyMappingMethods;
|
||||
typedef struct { int unused; } PyBufferProcs;
|
||||
typedef struct { int unused; } PyMethodDef;
|
||||
typedef struct { int unused; } PyMemberDef;
|
||||
typedef struct { int unused; } PyGetSetDef;
|
||||
typedef struct _typeobject PyTypeObject;
|
||||
typedef struct { long head; PyObject *weakreflist; } PySetObject;
|
||||
#define PyObject_VAR_HEAD long ob_refcnt; PyTypeObject *ob_type; long ob_size;
|
||||
#define PyVarObject_HEAD_INIT(type, size) 1, type, size,
|
||||
#define Py_TPFLAGS_DEFAULT 1UL
|
||||
#define Py_TPFLAGS_HAVE_GC 2UL
|
||||
#define Py_TPFLAGS_BASETYPE 4UL
|
||||
#define _Py_TPFLAGS_MATCH_SELF 8UL
|
||||
'''
|
||||
for name in (
|
||||
"destructor getattrfunc setattrfunc reprfunc hashfunc ternaryfunc "
|
||||
"getattrofunc setattrofunc traverseproc inquiry richcmpfunc getiterfunc "
|
||||
"iternextfunc descrgetfunc descrsetfunc initproc allocfunc newfunc "
|
||||
"freefunc vectorcallfunc"
|
||||
).split():
|
||||
harness += f"typedef void (*{name})(void);\n"
|
||||
harness += "struct _typeobject {" + body + "};\n"
|
||||
for name in (
|
||||
"set_new frozenset_new set_dealloc set_repr PyObject_HashNotImplemented "
|
||||
"frozenset_hash PyObject_GenericGetAttr set_traverse set_clear_internal "
|
||||
"set_richcompare set_iter set_init PyType_GenericAlloc PyObject_GC_Del "
|
||||
"set_vectorcall frozenset_vectorcall"
|
||||
).split():
|
||||
harness += f"static void {name}(void) {{ }}\n"
|
||||
harness += "static PyTypeObject PyType_Type;\n"
|
||||
harness += "static PySequenceMethods set_as_sequence;\n"
|
||||
for prefix in ("set", "frozenset"):
|
||||
harness += f"static PyNumberMethods {prefix}_as_number;\n"
|
||||
harness += f"static PyMethodDef {prefix}_methods[1];\n"
|
||||
harness += f'static const char {prefix}_doc[] = "{prefix} documentation";\n'
|
||||
harness += "\n".join(initializers.values()) + "\nint main(void)\n{\n"
|
||||
for name, prefix in (("PySet_Type", "set"), ("PyFrozenSet_Type", "frozenset")):
|
||||
values = dict(expected, tp_name=f'"{prefix}"',
|
||||
tp_as_number=f"&{prefix}_as_number", tp_doc=f"{prefix}_doc",
|
||||
tp_methods=f"{prefix}_methods", tp_new=f"{prefix}_new",
|
||||
tp_vectorcall=f"{prefix}_vectorcall",
|
||||
tp_hash="PyObject_HashNotImplemented" if prefix == "set" else "frozenset_hash",
|
||||
tp_init="(initproc)set_init" if prefix == "set" else "0")
|
||||
assert values.keys() <= set(fields)
|
||||
harness += f" assert({name}.ob_refcnt == 1);\n"
|
||||
harness += f" assert({name}.ob_type == &PyType_Type);\n"
|
||||
harness += f" assert({name}.ob_size == 0);\n"
|
||||
for field in fields:
|
||||
value = values.get(field, "0")
|
||||
if field in ("tp_name", "tp_doc"):
|
||||
harness += f" assert(strcmp({name}.{field}, {value}) == 0);\n"
|
||||
else:
|
||||
harness += f" assert({name}.{field} == ({value}));\n"
|
||||
harness += " return 0;\n}\n"
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-setobject-type-vectorcall-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
result = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if result.returncode:
|
||||
sys.exit(result.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("PySet_Type and PyFrozenSet_Type C89 compile and all 48 semantic field mappings each: PASS")
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check set_vectorcall's C89 declaration and unchanged evaluation order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
match = re.search(
|
||||
r"static PyObject\*\nset_vectorcall\(PyObject \*type, PyObject \* const\*args,"
|
||||
r"\s*size_t nargsf, PyObject \*kwnames\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "set_vectorcall not found"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*Py_ssize_t nargs;", body), (
|
||||
"set_vectorcall: nargs must be declared without an initializer before "
|
||||
"assert(PyType_Check(type)); late declaration is not C89 compatible"
|
||||
)
|
||||
expected = """
|
||||
Py_ssize_t nargs;
|
||||
|
||||
assert(PyType_Check(type));
|
||||
|
||||
if (!_PyArg_NoKwnames("set", kwnames)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
nargs = PyVectorcall_NARGS(nargsf);
|
||||
if (!_PyArg_CheckPositional("set", nargs, 0, 1)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (nargs) {
|
||||
return make_new_set(_PyType_CAST(type), args[0]);
|
||||
}
|
||||
|
||||
return make_new_set(_PyType_CAST(type), NULL);
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve type assertion, keyword rejection, nargs evaluation, positional "
|
||||
"rejection, guarded args[0] access, and construction in their original order"
|
||||
)
|
||||
print("set_vectorcall C89 declaration and evaluation order: PASS")
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regression for PySlice_New declarations accepted by VC6 C89."""
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/sliceobject.c")
|
||||
text = source.read_text()
|
||||
match = re.search(r"PySlice_New\(PyObject \*start, PyObject \*stop, PyObject \*step\)\s*\{(.*?)\n\}", text, re.S)
|
||||
assert match, "PySlice_New definition not found"
|
||||
body = match.group(1)
|
||||
assert re.search(r"^\s*PyInterpreterState \*interp;\s*$", body, re.M), "interp must be declared at function entry"
|
||||
assert re.search(r"^\s*PySliceObject \*obj;\s*$", body, re.M), "obj must be declared at function entry"
|
||||
assert "PyInterpreterState *interp = _PyInterpreterState_GET();" not in body, "interp initialization must remain a separate assignment"
|
||||
assert re.search(r"interp\s*=\s*_PyInterpreterState_GET\(\);", body), "preserve interpreter lookup"
|
||||
print("PySlice_New C89 declaration placement and lookup: PASS")
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard slice_richcompare C89 locals and allocation/refcount order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/sliceobject.c")
|
||||
match = re.search(
|
||||
r"\nslice_richcompare\(PyObject \*v, PyObject \*w, int op\)\s*\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "slice_richcompare definition not found"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
declarations = re.match(
|
||||
r"\s*PyObject \*t1;\s*PyObject \*t2;\s*PyObject \*res;", body
|
||||
)
|
||||
assert declarations, "slice_richcompare must declare t1, t2, res without initializers at function entry"
|
||||
|
||||
expected_statements = """
|
||||
if (!PySlice_Check(v) || !PySlice_Check(w))
|
||||
Py_RETURN_NOTIMPLEMENTED;
|
||||
if (v == w) {
|
||||
switch (op) {
|
||||
case Py_EQ:
|
||||
case Py_LE:
|
||||
case Py_GE:
|
||||
res = Py_True;
|
||||
break;
|
||||
default:
|
||||
res = Py_False;
|
||||
break;
|
||||
}
|
||||
Py_INCREF(res);
|
||||
return res;
|
||||
}
|
||||
t1 = PyTuple_Pack(3,
|
||||
((PySliceObject *)v)->start,
|
||||
((PySliceObject *)v)->stop,
|
||||
((PySliceObject *)v)->step);
|
||||
if (t1 == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
t2 = PyTuple_Pack(3,
|
||||
((PySliceObject *)w)->start,
|
||||
((PySliceObject *)w)->stop,
|
||||
((PySliceObject *)w)->step);
|
||||
if (t2 == NULL) {
|
||||
Py_DECREF(t1);
|
||||
return NULL;
|
||||
}
|
||||
res = PyObject_RichCompare(t1, t2, op);
|
||||
Py_DECREF(t1);
|
||||
Py_DECREF(t2);
|
||||
return res;
|
||||
"""
|
||||
assert body[declarations.end():].split() == expected_statements.split(), (
|
||||
"slice_richcompare must preserve shortcut/allocation/failure/comparison/refcount order without late declarations"
|
||||
)
|
||||
print("slice_richcompare C89 declarations and allocation/refcount order: PASS")
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check structseq_dealloc's C89 declarations and teardown order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/structseq.c")
|
||||
match = re.search(
|
||||
r"static void\nstructseq_dealloc\(PyStructSequence \*obj\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "structseq_dealloc definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*Py_ssize_t i, size;\s*PyTypeObject \*tp;", body), (
|
||||
"structseq_dealloc: tp must be declared without initializer alongside i and "
|
||||
"size before GC untracking; declaration after statement is not C89 compatible"
|
||||
)
|
||||
expected = """
|
||||
Py_ssize_t i, size;
|
||||
PyTypeObject *tp;
|
||||
PyObject_GC_UnTrack(obj);
|
||||
|
||||
tp = Py_TYPE(obj);
|
||||
size = REAL_SIZE(obj);
|
||||
for (i = 0; i < size; ++i) {
|
||||
Py_XDECREF(obj->ob_item[i]);
|
||||
}
|
||||
PyObject_GC_Del(obj);
|
||||
if (_PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)) {
|
||||
Py_DECREF(tp);
|
||||
}
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve GC untracking before type capture and size lookup, item decref "
|
||||
"order, GC deletion, and conditional heap-type decref"
|
||||
)
|
||||
print("structseq_dealloc C89 declarations and teardown order: PASS")
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check initialize_structseq_dict's C89 declarations and operation order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/structseq.c")
|
||||
match = re.search(
|
||||
r"static int\ninitialize_structseq_dict\(.*?\) \{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "initialize_structseq_dict definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(
|
||||
r"\s*PyObject \*v;\s*Py_ssize_t i, k;\s*PyObject\* keys;", body
|
||||
), "i, k, and uninitialized keys must precede all statements for C89"
|
||||
assert re.search(
|
||||
r"for \(i = k = 0; i < desc->n_in_sequence; \+\+i\) \{"
|
||||
r"\s*PyObject\* new_member;\s*if", body
|
||||
), "uninitialized new_member must precede the loop's unnamed-field check for C89"
|
||||
|
||||
expected = r"""
|
||||
PyObject *v;
|
||||
Py_ssize_t i, k;
|
||||
PyObject* keys;
|
||||
|
||||
#define SET_DICT_FROM_SIZE(key, value) \
|
||||
do { \
|
||||
v = PyLong_FromSsize_t(value); \
|
||||
if (v == NULL) { \
|
||||
return -1; \
|
||||
} \
|
||||
if (PyDict_SetItemString(dict, key, v) < 0) { \
|
||||
Py_DECREF(v); \
|
||||
return -1; \
|
||||
} \
|
||||
Py_DECREF(v); \
|
||||
} while (0)
|
||||
|
||||
SET_DICT_FROM_SIZE(visible_length_key, desc->n_in_sequence);
|
||||
SET_DICT_FROM_SIZE(real_length_key, n_members);
|
||||
SET_DICT_FROM_SIZE(unnamed_fields_key, n_unnamed_members);
|
||||
|
||||
// Prepare and set __match_args__
|
||||
keys = PyTuple_New(desc->n_in_sequence);
|
||||
if (keys == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = k = 0; i < desc->n_in_sequence; ++i) {
|
||||
PyObject* new_member;
|
||||
if (desc->fields[i].name == PyStructSequence_UnnamedField) {
|
||||
continue;
|
||||
}
|
||||
new_member = PyUnicode_FromString(desc->fields[i].name);
|
||||
if (new_member == NULL) {
|
||||
goto error;
|
||||
}
|
||||
PyTuple_SET_ITEM(keys, k, new_member);
|
||||
k++;
|
||||
}
|
||||
|
||||
if (_PyTuple_Resize(&keys, k) == -1) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (PyDict_SetItemString(dict, match_args_key, keys) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
Py_DECREF(keys);
|
||||
return 0;
|
||||
|
||||
error:
|
||||
Py_DECREF(keys);
|
||||
return -1;
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve metadata writes and early returns before tuple allocation, skip "
|
||||
"unnamed fields before Unicode allocation, and preserve insertion, resize, "
|
||||
"match-args write, refcount, and cleanup order"
|
||||
)
|
||||
print("initialize_structseq_dict C89 declarations and operation order: PASS")
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check PyStructSequence_New's C89 declaration and size lookup order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/structseq.c")
|
||||
match = re.search(
|
||||
r"PyStructSequence_New\(PyTypeObject \*type\)\s*\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "PyStructSequence_New definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(
|
||||
r"\s*PyStructSequence \*obj;"
|
||||
r"\s*Py_ssize_t size = REAL_SIZE_TP\(type\), i;"
|
||||
r"\s*Py_ssize_t vsize;",
|
||||
body,
|
||||
), "vsize must be declared without initializer before the size < 0 check"
|
||||
assert re.match(
|
||||
r"\s*PyStructSequence \*obj;"
|
||||
r"\s*Py_ssize_t size = REAL_SIZE_TP\(type\), i;"
|
||||
r"\s*Py_ssize_t vsize;"
|
||||
r"\s*if \(size < 0\) \{\s*return NULL;\s*\}"
|
||||
r"\s*vsize = VISIBLE_SIZE_TP\(type\);"
|
||||
r"\s*if \(vsize < 0\) \{\s*return NULL;\s*\}"
|
||||
r"\s*obj = PyObject_GC_NewVar\(PyStructSequence, type, size\);",
|
||||
body,
|
||||
), "preserve size early return, visible-size lookup, and allocation order"
|
||||
assert body.count("VISIBLE_SIZE_TP(type)") == 1, "visible size must be read exactly once"
|
||||
assert body.count("Py_ssize_t vsize;") == 1, "vsize must be declared exactly once"
|
||||
print("PyStructSequence_New C89 declaration and size lookup order: PASS")
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check NewType's C89 slot assignments and preserve all eight slot pairs."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/structseq.c")
|
||||
match = re.search(
|
||||
r"\n_PyStructSequence_NewType\([^\n]*\)\n\{.*?"
|
||||
r"/\* Initialize Slots \*/(.*?)/\* Initialize Spec \*/",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "_PyStructSequence_NewType slots initialization not found"
|
||||
body = match.group(1)
|
||||
assert not re.search(r"\(\s*PyType_Slot\s*\)\s*\{", body), (
|
||||
"_PyStructSequence_NewType: compound-literal slot initializers are not C89 compatible"
|
||||
)
|
||||
pairs = [
|
||||
("Py_tp_dealloc", "(destructor)structseq_dealloc"),
|
||||
("Py_tp_repr", "(reprfunc)structseq_repr"),
|
||||
("Py_tp_doc", "(void *)desc->doc"),
|
||||
("Py_tp_methods", "structseq_methods"),
|
||||
("Py_tp_new", "structseq_new"),
|
||||
("Py_tp_members", "members"),
|
||||
("Py_tp_traverse", "(traverseproc)structseq_traverse"),
|
||||
("0", "0"),
|
||||
]
|
||||
expected = "\n".join(
|
||||
f"slots[{i}].slot = {slot}; slots[{i}].pfunc = {value};"
|
||||
for i, (slot, value) in enumerate(pairs)
|
||||
)
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve all slot values, ordering, casts, and the zero sentinel"
|
||||
)
|
||||
print("_PyStructSequence_NewType C89 slots, values, ordering, casts, and sentinel: PASS")
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check structseq_repr's loop declaration and unchanged iteration order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/structseq.c")
|
||||
match = re.search(
|
||||
r"static PyObject \*\nstructseq_repr\(PyStructSequence \*obj\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "structseq_repr definition not found"
|
||||
body = match.group(1)
|
||||
assert not re.search(r"for\s*\(\s*Py_ssize_t\b", body), (
|
||||
"structseq_repr: VC6 rejects the C99 loop initializer declaration; "
|
||||
"declare i in the function declaration block"
|
||||
)
|
||||
assert re.match(
|
||||
r"\s*PyTypeObject \*typ = Py_TYPE\(obj\);"
|
||||
r"\s*_PyUnicodeWriter writer;\s*Py_ssize_t i;",
|
||||
body,
|
||||
), "i must be declared without initialization in the function declaration block"
|
||||
assert len(re.findall(r"\bPy_ssize_t\s+i\s*;", body)) == 1, (
|
||||
"declare i exactly once"
|
||||
)
|
||||
assert re.search(
|
||||
r"if \(_PyUnicodeWriter_WriteChar\(&writer, '\('\) < 0\) "
|
||||
r"\{\s*goto error;\s*\}\s*"
|
||||
r"for \(i\s*=\s*0; i < VISIBLE_SIZE\(obj\); i\+\+\) \{",
|
||||
body,
|
||||
), "preserve zero initialization after '(' output, per-iteration size lookup, and increment"
|
||||
assert len(re.findall(r"\bfor\s*\(", body)) == 1, "preserve the single item loop"
|
||||
assert "typ->tp_members[i].name" in body, "preserve member-name indexing"
|
||||
assert "PyStructSequence_GET_ITEM(obj, i)" in body, "preserve item indexing"
|
||||
print("structseq_repr C89 loop declaration and iteration order: PASS")
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check structseq_repr's name declaration and decoding/refcount order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/structseq.c")
|
||||
match = re.search(
|
||||
r"static PyObject \*\nstructseq_repr\(PyStructSequence \*obj\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "structseq_repr definition not found"
|
||||
body = match.group(1)
|
||||
assert re.search(
|
||||
r"for \(i=0; i < VISIBLE_SIZE\(obj\); i\+\+\) \{"
|
||||
r"\s*const char \*name_utf8;\s*PyObject \*name;\s*if \(i > 0\)",
|
||||
body,
|
||||
), "structseq_repr: VC6 requires name declared before loop-body statements"
|
||||
assert len(re.findall(r"\bPyObject\s*\*name\b", body)) == 1, (
|
||||
"declare name exactly once without initialization"
|
||||
)
|
||||
expected = '''
|
||||
if (i > 0) {
|
||||
/* Write ", " */
|
||||
if (_PyUnicodeWriter_WriteASCIIString(&writer, ", ", 2) < 0) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write "name=repr" */
|
||||
name_utf8 = typ->tp_members[i].name;
|
||||
if (name_utf8 == NULL) {
|
||||
PyErr_Format(PyExc_SystemError, "In structseq_repr(), member %zd name is NULL"
|
||||
" for type %.500s", i, typ->tp_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
name = PyUnicode_DecodeUTF8(name_utf8, strlen(name_utf8), NULL);
|
||||
if (name == NULL) {
|
||||
goto error;
|
||||
}
|
||||
if (_PyUnicodeWriter_WriteStr(&writer, name) < 0) {
|
||||
Py_DECREF(name);
|
||||
goto error;
|
||||
}
|
||||
Py_DECREF(name);
|
||||
|
||||
if (_PyUnicodeWriter_WriteChar(&writer, '=') < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
value = PyStructSequence_GET_ITEM(obj, i);
|
||||
'''
|
||||
assert re.sub(r"\s+", "", expected) in re.sub(r"\s+", "", body), (
|
||||
"preserve separator, member lookup, NULL check, decoding, write/decref, "
|
||||
"error handling, and value lookup order"
|
||||
)
|
||||
assert len(re.findall(r"^\s*name\s*=(?!=)", body, re.M)) == 1, "assign name exactly once"
|
||||
assert body.count("Py_DECREF(name)") == 2, "preserve both name decref paths"
|
||||
print("structseq_repr name C89 declaration and decoding/refcount order: PASS")
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only structseq_repr's name_utf8 declaration and evaluation order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/structseq.c")
|
||||
match = re.search(
|
||||
r"static PyObject \*\nstructseq_repr\(PyStructSequence \*obj\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "structseq_repr definition not found"
|
||||
body = match.group(1)
|
||||
assert re.search(
|
||||
r"for \(i=0; i < VISIBLE_SIZE\(obj\); i\+\+\) \{"
|
||||
r"\s*const char \*name_utf8;\s*PyObject \*name;\s*if \(i > 0\)",
|
||||
body,
|
||||
), "structseq_repr: VC6 requires name_utf8 declared before loop-body statements"
|
||||
assert len(re.findall(r"const char \*name_utf8\b", body)) == 1, (
|
||||
"declare name_utf8 exactly once"
|
||||
)
|
||||
assert re.search(
|
||||
r'if \(i > 0\) \{\s*/\* Write ", " \*/\s*'
|
||||
r'if \(_PyUnicodeWriter_WriteASCIIString\(&writer, ", ", 2\) < 0\) '
|
||||
r'\{\s*goto error;\s*\}\s*\}\s*'
|
||||
r'/\* Write "name=repr" \*/\s*'
|
||||
r'name_utf8 = typ->tp_members\[i\].name;\s*'
|
||||
r'if \(name_utf8 == NULL\) \{\s*'
|
||||
r'PyErr_Format\(PyExc_SystemError, "In structseq_repr\(\), member %zd name is NULL"'
|
||||
r'\s*" for type %.500s", i, typ->tp_name\);\s*goto error;\s*\}\s*'
|
||||
r'name = PyUnicode_DecodeUTF8\(name_utf8, strlen\(name_utf8\), NULL\);',
|
||||
body,
|
||||
), "preserve separator/error handling before member lookup, NULL check, and decoding"
|
||||
assert len(re.findall(r"\bname_utf8\s*=", body)) == 2, (
|
||||
"preserve the single assignment and NULL comparison"
|
||||
)
|
||||
print("structseq_repr name_utf8 C89 declaration and evaluation order: PASS")
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check structseq_repr's value/repr declarations and ownership/order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/structseq.c")
|
||||
match = re.search(
|
||||
r"static PyObject \*\nstructseq_repr\(PyStructSequence \*obj\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "structseq_repr definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(
|
||||
r"\s*PyTypeObject \*typ = Py_TYPE\(obj\);"
|
||||
r"\s*_PyUnicodeWriter writer;\s*Py_ssize_t i;"
|
||||
r"\s*PyObject \*value;\s*PyObject \*repr;",
|
||||
body,
|
||||
), "structseq_repr: VC6 requires value and repr in the function entry declaration block"
|
||||
for variable in ("value", "repr"):
|
||||
assert len(re.findall(r"\bPyObject\s*\*" + variable + r"\b", body)) == 1, (
|
||||
"declare " + variable + " exactly once"
|
||||
)
|
||||
assert len(re.findall(r"\b" + variable + r"\s*=(?!=)", body)) == 1, (
|
||||
"assign " + variable + " exactly once at its original evaluation point"
|
||||
)
|
||||
expected = """
|
||||
Py_DECREF(name);
|
||||
|
||||
if (_PyUnicodeWriter_WriteChar(&writer, '=') < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
value = PyStructSequence_GET_ITEM(obj, i);
|
||||
assert(value != NULL);
|
||||
repr = PyObject_Repr(value);
|
||||
if (repr == NULL) {
|
||||
goto error;
|
||||
}
|
||||
if (_PyUnicodeWriter_WriteStr(&writer, repr) < 0) {
|
||||
Py_DECREF(repr);
|
||||
goto error;
|
||||
}
|
||||
Py_DECREF(repr);
|
||||
}
|
||||
|
||||
if (_PyUnicodeWriter_WriteChar(&writer, ')') < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
return _PyUnicodeWriter_Finish(&writer);
|
||||
|
||||
error:
|
||||
_PyUnicodeWriter_Dealloc(&writer);
|
||||
return NULL;
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body).endswith(re.sub(r"\s+", "", expected)), (
|
||||
"preserve name cleanup, '=' output, borrowed value lookup, repr creation, "
|
||||
"write/decref paths, and writer finish/error cleanup order"
|
||||
)
|
||||
assert len(re.findall(r"\bvalue\b", body)) == 4, "preserve borrowed value ownership"
|
||||
assert len(re.findall(r"\brepr\b", re.sub(r"/\*.*?\*/", "", body, flags=re.S))) == 6, (
|
||||
"preserve repr ownership and both decref paths"
|
||||
)
|
||||
print("structseq_repr value/repr C89 declarations and ownership/order: PASS")
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check structseq_traverse's C89 declarations and visit/size lookup order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/structseq.c")
|
||||
match = re.search(
|
||||
r"static int\nstructseq_traverse\(PyStructSequence \*obj, visitproc visit, void \*arg\)"
|
||||
r"\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "structseq_traverse definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*Py_ssize_t i, size;", body), (
|
||||
"structseq_traverse: i and size must be declared before the heap-type check; "
|
||||
"declaration after statement is not C89 compatible"
|
||||
)
|
||||
expected = """
|
||||
Py_ssize_t i, size;
|
||||
if (Py_TYPE(obj)->tp_flags & Py_TPFLAGS_HEAPTYPE) {
|
||||
Py_VISIT(Py_TYPE(obj));
|
||||
}
|
||||
size = REAL_SIZE(obj);
|
||||
for (i = 0; i < size; ++i) {
|
||||
Py_VISIT(obj->ob_item[i]);
|
||||
}
|
||||
return 0;
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve heap-type visit and its early return before the single real-size "
|
||||
"lookup, item visit order, loop initialization, and return behavior"
|
||||
)
|
||||
print("structseq_traverse C89 declarations and visit/size lookup order: PASS")
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check tuple_alloc's C89 declaration and validation/allocation order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"static PyTupleObject \*\ntuple_alloc\(Py_ssize_t size\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "tuple_alloc definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*PyTupleObject \*op;", body), (
|
||||
"tuple_alloc: op must be declared without initializer before the size check; "
|
||||
"declaration after statement is not C89 compatible"
|
||||
)
|
||||
expected = """
|
||||
PyTupleObject *op;
|
||||
if (size < 0) {
|
||||
PyErr_BadInternalCall();
|
||||
return NULL;
|
||||
}
|
||||
#ifdef Py_DEBUG
|
||||
assert(size != 0); // The empty tuple is statically allocated.
|
||||
#endif
|
||||
|
||||
op = maybe_freelist_pop(size);
|
||||
if (op == NULL) {
|
||||
/* Check for overflow */
|
||||
if ((size_t)size > ((size_t)PY_SSIZE_T_MAX - (sizeof(PyTupleObject) -
|
||||
sizeof(PyObject *))) / sizeof(PyObject *)) {
|
||||
return (PyTupleObject *)PyErr_NoMemory();
|
||||
}
|
||||
op = PyObject_GC_NewVar(PyTupleObject, &PyTuple_Type, size);
|
||||
if (op == NULL)
|
||||
return NULL;
|
||||
}
|
||||
return op;
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve size validation, debug assertion, single freelist lookup, overflow "
|
||||
"check, GC allocation, error/return order, and absence of refcount operations"
|
||||
)
|
||||
print("tuple_alloc C89 declaration and validation/allocation order: PASS")
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep tupleconcat's b declaration C89-safe and assignment after validation."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else 'cpython/Objects/tupleobject.c')
|
||||
match = re.search(r'tupleconcat\(PyTupleObject \*a, PyObject \*bb\)\n\{(.*?)\n\}', source.read_text(), re.S)
|
||||
assert match, 'tupleconcat definition missing'
|
||||
body = match.group(1)
|
||||
assert re.match(r'\s*Py_ssize_t size;\s*Py_ssize_t i;\s*PyObject \*\*src, \*\*dest;\s*PyTupleObject \*np;\s*PyTupleObject \*b;\s*if', body), 'tupleconcat: b must be declared in the opening declaration group'
|
||||
assert 'PyTupleObject *b =' not in body, 'tupleconcat: late initialized b declaration is not C89'
|
||||
validation = re.search(r'if \(!PyTuple_Check\(bb\)\) \{.*?return NULL;\s*\}\s*b = \(PyTupleObject \*\)bb;\s*if \(Py_SIZE\(b\) == 0 && PyTuple_CheckExact\(a\)\)', body, re.S)
|
||||
assert validation, 'tupleconcat: b assignment must follow type-check/error return and precede empty-b check'
|
||||
assert body.index('return bb;') < validation.start(), 'tupleconcat: empty-a exact-b fast return must remain first'
|
||||
assert body.count('b = (PyTupleObject *)bb;') == 1, 'tupleconcat: exactly one b assignment required'
|
||||
assert body.index('b = (PyTupleObject *)bb;') < body.index('np = tuple_alloc(size);') < body.index('src = b->ob_item;') < body.index('_PyObject_GC_TRACK(np);'), 'tupleconcat: cast/allocation/copy/GC order changed'
|
||||
print('tupleconcat b C89 declaration and validation/assignment order: PASS')
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check tupledealloc's C89 declaration and exact teardown evaluation order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"static void\ntupledealloc\(PyTupleObject \*op\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "tupledealloc definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*Py_ssize_t i;", body), (
|
||||
"tupledealloc: i must be declared without initializer before the empty "
|
||||
"tuple check; declaration after Py_TRASHCAN_BEGIN is not C89 compatible"
|
||||
)
|
||||
expected = """
|
||||
Py_ssize_t i;
|
||||
if (Py_SIZE(op) == 0) {
|
||||
/* The empty tuple is statically allocated. */
|
||||
if (op == &_Py_SINGLETON(tuple_empty)) {
|
||||
#ifdef Py_DEBUG
|
||||
_Py_FatalRefcountError("deallocating the empty tuple singleton");
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
#ifdef Py_DEBUG
|
||||
/* tuple subclasses have their own empty instances. */
|
||||
assert(!PyTuple_CheckExact(op));
|
||||
#endif
|
||||
}
|
||||
|
||||
PyObject_GC_UnTrack(op);
|
||||
Py_TRASHCAN_BEGIN(op, tupledealloc)
|
||||
|
||||
i = Py_SIZE(op);
|
||||
while (--i >= 0) {
|
||||
Py_XDECREF(op->ob_item[i]);
|
||||
}
|
||||
// This will abort on the empty singleton (if there is one).
|
||||
if (!maybe_freelist_push(op)) {
|
||||
Py_TYPE(op)->tp_free((PyObject *)op);
|
||||
}
|
||||
|
||||
Py_TRASHCAN_END
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve empty-tuple guards, GC untracking, size assignment immediately "
|
||||
"after trashcan entry, reverse item decrefs, freelist/free, and trashcan exit"
|
||||
)
|
||||
print("tupledealloc C89 declaration and exact teardown evaluation order: PASS")
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only _PyTuple_DebugMallocStats's C89 index and reporting loop."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"void\n_PyTuple_DebugMallocStats\(FILE \*out\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "_PyTuple_DebugMallocStats definition missing"
|
||||
body = match.group(1)
|
||||
assert re.match(
|
||||
r"\s*#if PyTuple_NFREELISTS > 0\s*"
|
||||
r"PyInterpreterState \*interp = _PyInterpreterState_GET\(\);\s*int i;",
|
||||
body,
|
||||
), "_PyTuple_DebugMallocStats: i must be declared before the loop for C89"
|
||||
expected = """
|
||||
#if PyTuple_NFREELISTS > 0
|
||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||
int i;
|
||||
for (i = 0; i < PyTuple_NFREELISTS; i++) {
|
||||
int len = i + 1;
|
||||
char buf[128];
|
||||
PyOS_snprintf(buf, sizeof(buf),
|
||||
"free %d-sized PyTupleObject", len);
|
||||
_PyDebugAllocatorStats(out, buf, STATE.numfree[i],
|
||||
_PyObject_VAR_SIZE(&PyTuple_Type, len));
|
||||
}
|
||||
#endif
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"_PyTuple_DebugMallocStats: preserve loop assignment, bounds, increment, "
|
||||
"tuple length, buffer, label, freelist count, and allocation size"
|
||||
)
|
||||
print("_PyTuple_DebugMallocStats C89 index and reporting loop: PASS")
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only maybe_freelist_push's index declaration and evaluation order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"static inline int\nmaybe_freelist_push\(PyTupleObject \*op\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "maybe_freelist_push definition missing"
|
||||
body = match.group(1)
|
||||
assert re.match(
|
||||
r"\s*#if PyTuple_NFREELISTS > 0\s*"
|
||||
r"PyInterpreterState \*interp = _PyInterpreterState_GET\(\);\s*"
|
||||
r"Py_ssize_t index;\s*#ifdef Py_DEBUG",
|
||||
body,
|
||||
), "maybe_freelist_push: index must be declared before the debug assertion and empty-tuple check"
|
||||
assert len(re.findall(r"\bPy_ssize_t\s+index\b", body)) == 1, (
|
||||
"maybe_freelist_push: exactly one index declaration required"
|
||||
)
|
||||
assert re.search(
|
||||
r"if \(Py_SIZE\(op\) == 0\)\s*\{\s*return 0;\s*\}\s*"
|
||||
r"index = Py_SIZE\(op\) - 1;\s*if \(index < PyTuple_NFREELISTS",
|
||||
body,
|
||||
), "maybe_freelist_push: assign index at its original evaluation point after the empty-tuple return"
|
||||
assert len(re.findall(r"\bindex\s*=", body)) == 1, (
|
||||
"maybe_freelist_push: exactly one index assignment required"
|
||||
)
|
||||
print("maybe_freelist_push index C89 declaration and evaluation order: PASS")
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check _PyTuple_FromArray's C89 declarations and preserve its order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"PyObject \*\n_PyTuple_FromArray\(PyObject \*const \*src, Py_ssize_t n\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "_PyTuple_FromArray definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(
|
||||
r"\s*PyTupleObject \*tuple;\s*PyObject \*\*dst;\s*Py_ssize_t i;"
|
||||
r"\s*if \(n == 0\)", body
|
||||
), (
|
||||
"_PyTuple_FromArray: tuple, dst and i must be declared without initializers "
|
||||
"before the empty-tuple check; a loop declaration is not C89 compatible"
|
||||
)
|
||||
expected = """
|
||||
PyTupleObject *tuple;
|
||||
PyObject **dst;
|
||||
Py_ssize_t i;
|
||||
if (n == 0) {
|
||||
return tuple_get_empty();
|
||||
}
|
||||
|
||||
tuple = tuple_alloc(n);
|
||||
if (tuple == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
dst = tuple->ob_item;
|
||||
for (i = 0; i < n; i++) {
|
||||
PyObject *item = src[i];
|
||||
Py_INCREF(item);
|
||||
dst[i] = item;
|
||||
}
|
||||
_PyObject_GC_TRACK(tuple);
|
||||
return (PyObject *)tuple;
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve early empty return before single allocation, NULL handling, "
|
||||
"dst assignment immediately after NULL check, i initialization/condition/increment, "
|
||||
"unchanged item declaration, "
|
||||
"INCREF/store, GC tracking and return order"
|
||||
)
|
||||
print("_PyTuple_FromArray C89 declarations and evaluation/refcount/error order: PASS")
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only _PyTuple_FromArraySteal's allocation-failure and success loop C89 fixes."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"PyObject \*\n_PyTuple_FromArraySteal\(PyObject \*const \*src, Py_ssize_t n\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "_PyTuple_FromArraySteal definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(
|
||||
r"\s*PyTupleObject \*tuple;\s*Py_ssize_t i;\s*PyObject \*\*dst;\s*if \(n == 0\)", body
|
||||
), "_PyTuple_FromArraySteal: function-scope Py_ssize_t i must precede statements"
|
||||
assert "for (Py_ssize_t i =" not in body, (
|
||||
"_PyTuple_FromArraySteal: success loop must reuse function-scope i with assignment"
|
||||
)
|
||||
expected = """
|
||||
PyTupleObject *tuple;
|
||||
Py_ssize_t i;
|
||||
PyObject **dst;
|
||||
if (n == 0) {
|
||||
return tuple_get_empty();
|
||||
}
|
||||
tuple = tuple_alloc(n);
|
||||
if (tuple == NULL) {
|
||||
for (i = 0; i < n; i++) {
|
||||
Py_DECREF(src[i]);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
dst = tuple->ob_item;
|
||||
for (i = 0; i < n; i++) {
|
||||
PyObject *item = src[i];
|
||||
dst[i] = item;
|
||||
}
|
||||
_PyObject_GC_TRACK(tuple);
|
||||
return (PyObject *)tuple;
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve empty return/allocation/NULL check, failure loop initialization, "
|
||||
"condition, increment, DECREF and NULL return order; retain later "
|
||||
"dst/item declarations and success-loop initialization/condition/increment/order"
|
||||
)
|
||||
print("_PyTuple_FromArraySteal failure/success C89 loops and evaluation/DECREF/return order: PASS")
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only _PyTuple_FromArraySteal's dst declaration and evaluation point."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"PyObject \*\n_PyTuple_FromArraySteal\(PyObject \*const \*src, Py_ssize_t n\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "_PyTuple_FromArraySteal definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(
|
||||
r"\s*PyTupleObject \*tuple;\s*Py_ssize_t i;\s*PyObject \*\*dst;\s*if \(n == 0\)",
|
||||
body,
|
||||
), "_PyTuple_FromArraySteal: function-scope PyObject **dst must precede statements"
|
||||
expected = """
|
||||
PyTupleObject *tuple;
|
||||
Py_ssize_t i;
|
||||
PyObject **dst;
|
||||
if (n == 0) {
|
||||
return tuple_get_empty();
|
||||
}
|
||||
tuple = tuple_alloc(n);
|
||||
if (tuple == NULL) {
|
||||
for (i = 0; i < n; i++) {
|
||||
Py_DECREF(src[i]);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
dst = tuple->ob_item;
|
||||
for (i = 0; i < n; i++) {
|
||||
PyObject *item = src[i];
|
||||
dst[i] = item;
|
||||
}
|
||||
_PyObject_GC_TRACK(tuple);
|
||||
return (PyObject *)tuple;
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve dst assignment immediately after successful allocation and NULL "
|
||||
"check, failure DECREFs/return, loop index and item declarations, stores, "
|
||||
"GC tracking and return order"
|
||||
)
|
||||
print("_PyTuple_FromArraySteal dst declaration placement and evaluation/refcount/error order: PASS")
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only _PyTuple_FromArraySteal's tuple declaration placement and order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"PyObject \*\n_PyTuple_FromArraySteal\(PyObject \*const \*src, Py_ssize_t n\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "_PyTuple_FromArraySteal definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*PyTupleObject \*tuple;\s*Py_ssize_t i;\s*PyObject \*\*dst;\s*if \(n == 0\)", body), (
|
||||
"_PyTuple_FromArraySteal: tuple must be declared without an initializer "
|
||||
"before the empty-tuple check"
|
||||
)
|
||||
expected = """
|
||||
PyTupleObject *tuple;
|
||||
Py_ssize_t i;
|
||||
PyObject **dst;
|
||||
if (n == 0) {
|
||||
return tuple_get_empty();
|
||||
}
|
||||
tuple = tuple_alloc(n);
|
||||
if (tuple == NULL) {
|
||||
for (i = 0; i < n; i++) {
|
||||
Py_DECREF(src[i]);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
dst = tuple->ob_item;
|
||||
for (i = 0; i < n; i++) {
|
||||
PyObject *item = src[i];
|
||||
dst[i] = item;
|
||||
}
|
||||
_PyObject_GC_TRACK(tuple);
|
||||
return (PyObject *)tuple;
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve empty return before single allocation, allocation failure DECREFs "
|
||||
"and NULL return, unchanged dst/loop/item declarations, item stores, "
|
||||
"GC tracking and return order"
|
||||
)
|
||||
print("_PyTuple_FromArraySteal tuple declaration placement and evaluation/refcount/error order: PASS")
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only maybe_freelist_clear's C89 index and teardown order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"static void\nmaybe_freelist_clear\(PyInterpreterState \*interp, int fini\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "maybe_freelist_clear definition missing"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*#if PyTuple_NFREELISTS > 0\s*Py_ssize_t i;", body), (
|
||||
"maybe_freelist_clear: i must be declared before the loop for C89"
|
||||
)
|
||||
expected = """
|
||||
#if PyTuple_NFREELISTS > 0
|
||||
Py_ssize_t i;
|
||||
for (i = 0; i < PyTuple_NFREELISTS; i++) {
|
||||
PyTupleObject *p = STATE.free_list[i];
|
||||
STATE.free_list[i] = NULL;
|
||||
STATE.numfree[i] = fini ? -1 : 0;
|
||||
while (p) {
|
||||
PyTupleObject *q = p;
|
||||
p = (PyTupleObject *)(p->ob_item[0]);
|
||||
PyObject_GC_Del(q);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"maybe_freelist_clear: preserve loop assignment, bounds, increment, "
|
||||
"freelist reset, fini count, and linked-list deletion order"
|
||||
)
|
||||
print("maybe_freelist_clear C89 index and teardown order: PASS")
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check PyTuple_New's C89 loop declaration and initialization order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"PyObject \*\nPyTuple_New\(Py_ssize_t size\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "PyTuple_New definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*PyTupleObject \*op;\s*Py_ssize_t i;", body), (
|
||||
"PyTuple_New: i must be declared before the size check; "
|
||||
"for-loop declarations are not C89 compatible"
|
||||
)
|
||||
expected = """
|
||||
PyTupleObject *op;
|
||||
Py_ssize_t i;
|
||||
if (size == 0) {
|
||||
return tuple_get_empty();
|
||||
}
|
||||
op = tuple_alloc(size);
|
||||
if (op == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
for (i = 0; i < size; i++) {
|
||||
op->ob_item[i] = NULL;
|
||||
}
|
||||
_PyObject_GC_TRACK(op);
|
||||
return (PyObject *) op;
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve empty-tuple return, single allocation and failure return, loop "
|
||||
"initialization/condition/increment, item clearing, GC tracking, and return order"
|
||||
)
|
||||
print("PyTuple_New C89 loop declaration and initialization order: PASS")
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check PyTuple_Pack's C89 declaration and evaluation/refcount/error order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"PyObject \*\nPyTuple_Pack\(Py_ssize_t n, \.\.\.\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "PyTuple_Pack definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(
|
||||
r"\s*Py_ssize_t i;\s*PyObject \*o;\s*PyObject \*\*items;"
|
||||
r"\s*va_list vargs;\s*PyTupleObject \*result;", body
|
||||
), (
|
||||
"PyTuple_Pack: result must be declared without initializer before the empty "
|
||||
"tuple check; declaration after statement is not C89 compatible"
|
||||
)
|
||||
expected = """
|
||||
Py_ssize_t i;
|
||||
PyObject *o;
|
||||
PyObject **items;
|
||||
va_list vargs;
|
||||
PyTupleObject *result;
|
||||
|
||||
if (n == 0) {
|
||||
return tuple_get_empty();
|
||||
}
|
||||
|
||||
va_start(vargs, n);
|
||||
result = tuple_alloc(n);
|
||||
if (result == NULL) {
|
||||
va_end(vargs);
|
||||
return NULL;
|
||||
}
|
||||
items = result->ob_item;
|
||||
for (i = 0; i < n; i++) {
|
||||
o = va_arg(vargs, PyObject *);
|
||||
Py_INCREF(o);
|
||||
items[i] = o;
|
||||
}
|
||||
va_end(vargs);
|
||||
_PyObject_GC_TRACK(result);
|
||||
return (PyObject *)result;
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve empty-tuple return, va_start before single allocation, failure "
|
||||
"va_end/NULL return, item extraction/INCREF/store, final va_end, GC tracking, "
|
||||
"and return order"
|
||||
)
|
||||
print("PyTuple_Pack C89 declaration and evaluation/refcount/error order: PASS")
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only tuplerepeat's dest declaration and allocation-time assignment."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"tuplerepeat\(PyTupleObject \*a, Py_ssize_t n\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "tuplerepeat definition missing"
|
||||
body = match.group(1)
|
||||
assert re.match(
|
||||
r"\s*Py_ssize_t size;\s*PyTupleObject \*np;\s*PyObject \*\*dest;"
|
||||
r"\s*PyObject \*\*dest_end;\s*if",
|
||||
body,
|
||||
), "tuplerepeat: dest must be declared in the opening declaration block"
|
||||
assert len(re.findall(r"PyObject\s*\*\*dest\b", body)) == 1, (
|
||||
"tuplerepeat: exactly one uninitialized dest declaration required"
|
||||
)
|
||||
assert re.search(
|
||||
r"np = tuple_alloc\(size\);\s*if \(np == NULL\)\s*return NULL;"
|
||||
r"\s*dest = np->ob_item;\s*dest_end = dest \+ size;",
|
||||
body,
|
||||
), "tuplerepeat: assign dest immediately after allocation/NULL check, before unchanged dest_end"
|
||||
assert len(re.findall(r"\bdest\s*=", body)) == 1, (
|
||||
"tuplerepeat: exactly one dest assignment required"
|
||||
)
|
||||
print("tuplerepeat dest C89 declaration and allocation/assignment order: PASS")
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only tuplerepeat's dest_end declaration and assignment order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"tuplerepeat\(PyTupleObject \*a, Py_ssize_t n\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "tuplerepeat definition missing"
|
||||
body = match.group(1)
|
||||
assert re.match(
|
||||
r"\s*Py_ssize_t size;\s*PyTupleObject \*np;\s*PyObject \*\*dest;"
|
||||
r"\s*PyObject \*\*dest_end;\s*if",
|
||||
body,
|
||||
), "tuplerepeat: dest_end must be declared in the opening declaration block"
|
||||
assert len(re.findall(r"PyObject\s*\*\*dest_end\b", body)) == 1, (
|
||||
"tuplerepeat: exactly one uninitialized dest_end declaration required"
|
||||
)
|
||||
assert re.search(
|
||||
r"dest = np->ob_item;\s*dest_end = dest \+ size;",
|
||||
body,
|
||||
), "tuplerepeat: assign dest_end immediately after dest at its original evaluation point"
|
||||
assert len(re.findall(r"\bdest_end\s*=", body)) == 1, (
|
||||
"tuplerepeat: exactly one dest_end assignment required"
|
||||
)
|
||||
print("tuplerepeat dest_end C89 declaration and assignment order: PASS")
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compile the exact tuple type as C89 and verify every static slot."""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
source_path = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
source = source_path.read_text()
|
||||
initializers = {}
|
||||
for name in ("PyTuple_Type",):
|
||||
match = re.search(r"PyTypeObject " + name + r" = \{.*?\n\};", source, re.S)
|
||||
assert match, name + " initializer not found"
|
||||
initializers[name] = match.group(0)
|
||||
header = (source_path.parent.parent / "Include/cpython/object.h").read_text()
|
||||
layout = re.search(r"struct _typeobject \{(.*?)\n\};", header, re.S)
|
||||
assert layout, "PyTypeObject layout not found"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", layout.group(1), flags=re.S)
|
||||
fields = re.findall(r"\b(tp_\w+)\b", body)
|
||||
assert len(fields) == len(set(fields)) == 48, "review changed PyTypeObject layout"
|
||||
expected = {
|
||||
"tp_name": '"tuple"',
|
||||
"tp_basicsize": "sizeof(PyTupleObject) - sizeof(PyObject *)",
|
||||
"tp_itemsize": "sizeof(PyObject *)",
|
||||
"tp_dealloc": "(destructor)tupledealloc",
|
||||
"tp_repr": "(reprfunc)tuplerepr",
|
||||
"tp_as_sequence": "&tuple_as_sequence",
|
||||
"tp_as_mapping": "&tuple_as_mapping",
|
||||
"tp_hash": "(hashfunc)tuplehash",
|
||||
"tp_getattro": "PyObject_GenericGetAttr",
|
||||
"tp_flags": "Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TUPLE_SUBCLASS | _Py_TPFLAGS_MATCH_SELF | Py_TPFLAGS_SEQUENCE",
|
||||
"tp_doc": "tuple_new__doc__",
|
||||
"tp_traverse": "(traverseproc)tupletraverse",
|
||||
"tp_richcompare": "tuplerichcompare",
|
||||
"tp_iter": "tuple_iter",
|
||||
"tp_methods": "tuple_methods",
|
||||
"tp_new": "tuple_new",
|
||||
"tp_free": "PyObject_GC_Del",
|
||||
"tp_vectorcall": "tuple_vectorcall",
|
||||
}
|
||||
assert expected.keys() <= set(fields)
|
||||
# Use the real field declarations/order, with minimal stand-ins for dependencies.
|
||||
harness = r'''
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
typedef long Py_ssize_t;
|
||||
typedef struct { int unused; } PyObject;
|
||||
typedef struct { int unused; } PyAsyncMethods;
|
||||
typedef struct { int unused; } PyNumberMethods;
|
||||
typedef struct { int unused; } PySequenceMethods;
|
||||
typedef struct { int unused; } PyMappingMethods;
|
||||
typedef struct { int unused; } PyBufferProcs;
|
||||
typedef struct { int unused; } PyMethodDef;
|
||||
typedef struct { int unused; } PyMemberDef;
|
||||
typedef struct { int unused; } PyGetSetDef;
|
||||
typedef struct _typeobject PyTypeObject;
|
||||
typedef struct { long head; PyObject *ob_item[1]; } PyTupleObject;
|
||||
#define PyObject_VAR_HEAD long ob_refcnt; PyTypeObject *ob_type; long ob_size;
|
||||
#define PyVarObject_HEAD_INIT(type, size) 1, type, size,
|
||||
#define Py_TPFLAGS_DEFAULT 1UL
|
||||
#define Py_TPFLAGS_HAVE_GC 2UL
|
||||
#define Py_TPFLAGS_BASETYPE 4UL
|
||||
#define _Py_TPFLAGS_MATCH_SELF 8UL
|
||||
#define Py_TPFLAGS_TUPLE_SUBCLASS 16UL
|
||||
#define Py_TPFLAGS_SEQUENCE 32UL
|
||||
'''
|
||||
for name in (
|
||||
"destructor getattrfunc setattrfunc reprfunc hashfunc ternaryfunc "
|
||||
"getattrofunc setattrofunc traverseproc inquiry richcmpfunc getiterfunc "
|
||||
"iternextfunc descrgetfunc descrsetfunc initproc allocfunc newfunc "
|
||||
"freefunc vectorcallfunc"
|
||||
).split():
|
||||
harness += f"typedef void (*{name})(void);\n"
|
||||
harness += "struct _typeobject {" + body + "};\n"
|
||||
for name in (
|
||||
"tuple_new tupledealloc tuplerepr tuplehash PyObject_GenericGetAttr "
|
||||
"tupletraverse tuplerichcompare tuple_iter PyObject_GC_Del tuple_vectorcall"
|
||||
).split():
|
||||
harness += f"static void {name}(void) {{ }}\n"
|
||||
harness += "static PyTypeObject PyType_Type;\n"
|
||||
harness += "static PySequenceMethods tuple_as_sequence;\n"
|
||||
harness += "static PyMappingMethods tuple_as_mapping;\n"
|
||||
harness += "static PyMethodDef tuple_methods[1];\n"
|
||||
harness += 'static const char tuple_new__doc__[] = "tuple documentation";\n'
|
||||
harness += "\n".join(initializers.values()) + "\nint main(void)\n{\n"
|
||||
for name in initializers:
|
||||
values = expected
|
||||
harness += f" assert({name}.ob_refcnt == 1);\n"
|
||||
harness += f" assert({name}.ob_type == &PyType_Type);\n"
|
||||
harness += f" assert({name}.ob_size == 0);\n"
|
||||
for field in fields:
|
||||
value = values.get(field, "0")
|
||||
if field in ("tp_name", "tp_doc"):
|
||||
harness += f" assert(strcmp({name}.{field}, {value}) == 0);\n"
|
||||
else:
|
||||
harness += f" assert({name}.{field} == ({value}));\n"
|
||||
harness += " return 0;\n}\n"
|
||||
with tempfile.TemporaryDirectory(prefix="vc6-tupleobject-type-vectorcall-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.c").write_text(harness)
|
||||
result = subprocess.run([
|
||||
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
||||
str(path / "test.c"), "-o", str(path / "test"),
|
||||
])
|
||||
if result.returncode:
|
||||
sys.exit(result.returncode)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
print("PyTuple_Type C89 compile and all 48 semantic field mappings: PASS")
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check tuple_vectorcall's C89 nargs declaration and evaluation order."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c")
|
||||
match = re.search(
|
||||
r"static PyObject \*\ntuple_vectorcall\([^)]*\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "tuple_vectorcall definition not found"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*Py_ssize_t nargs;", body), (
|
||||
"tuple_vectorcall: nargs must be declared in the initial declaration block "
|
||||
"before the keyword check for C89 compatibility"
|
||||
)
|
||||
expected = """
|
||||
Py_ssize_t nargs;
|
||||
if (!_PyArg_NoKwnames("tuple", kwnames)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
nargs = PyVectorcall_NARGS(nargsf);
|
||||
if (!_PyArg_CheckPositional("tuple", nargs, 0, 1)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (nargs) {
|
||||
return tuple_new_impl(_PyType_CAST(type), args[0]);
|
||||
}
|
||||
else {
|
||||
return tuple_get_empty();
|
||||
}
|
||||
"""
|
||||
assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), (
|
||||
"preserve keyword rejection before the single nargs evaluation, positional "
|
||||
"validation, and nonempty/empty tuple return paths"
|
||||
)
|
||||
print("tuple_vectorcall C89 nargs declaration and evaluation order: PASS")
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check descriptor dictionary locals and evaluation after the NULL guard."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
failures = []
|
||||
for function, local_type, local, factory in (
|
||||
("type_add_members", "PyMemberDef", "memb", "PyDescr_NewMember"),
|
||||
("type_add_getset", "PyGetSetDef", "gsp", "PyDescr_NewGetSet"),
|
||||
):
|
||||
match = re.search(r"\nstatic int\n" + function + r"\(PyTypeObject \*type\)\n\{(.*?)\n\}", source, re.S)
|
||||
assert match, function + " definition missing"
|
||||
body = match.group(1)
|
||||
opening = rf"\s*{local_type} \*{local} = type->tp_\w+;\s*PyObject \*dict;\s*if"
|
||||
if not re.match(opening, body):
|
||||
failures.append(function + ": dict must be in opening C89 declarations")
|
||||
continue
|
||||
assert len(re.findall(r"\bPyObject\s*\*\s*dict\b", body)) == 1
|
||||
assert len(re.findall(r"\bdict\s*=(?!=)", body)) == 1
|
||||
guard = f"if ({local} == NULL) {{\n return 0;\n }}"
|
||||
ordered = [guard, "dict = type->tp_dict;", f"for (; {local}->name != NULL; {local}++)", f"PyObject *descr = {factory}(type, {local});", "if (descr == NULL)", "return -1;", "if (PyDict_SetDefault(dict, PyDescr_NAME(descr), descr) == NULL)", "Py_DECREF(descr);", "return -1;", "Py_DECREF(descr);", "return 0;"]
|
||||
cursor = 0
|
||||
for token in ordered:
|
||||
position = body.find(token, cursor)
|
||||
assert position >= 0, f"{function}: missing/reordered operation: {token}"
|
||||
cursor = position + len(token)
|
||||
print(function + ": C89 dictionary declaration, NULL guard and cleanup: PASS")
|
||||
assert not failures, "; ".join(failures)
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep type_add_method locals C89-compatible without moving API calls."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(r"\nstatic int\ntype_add_method\(PyTypeObject \*type, PyMethodDef \*meth\)\n\{(.*?)\n\}", source, re.S)
|
||||
assert match, "type_add_method definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert re.match(r"\s*PyObject \*descr;\s*PyObject \*name;\s*int err;\s*int isdescr = 1;\s*if", body), (
|
||||
"type_add_method: name and err must be in the opening C89 declaration block"
|
||||
)
|
||||
for declaration in (r"\bPyObject\s*\*\s*name\b", r"\bint\s+err\b"):
|
||||
assert len(re.findall(declaration, body)) == 1, "declare each moved local exactly once"
|
||||
|
||||
# Search in execution order: duplicate cleanup calls occur on error paths.
|
||||
ordered = [
|
||||
"if (meth->ml_flags & METH_CLASS)",
|
||||
"if (meth->ml_flags & METH_STATIC)",
|
||||
"descr = PyDescr_NewClassMethod(type, meth);",
|
||||
"else if (meth->ml_flags & METH_STATIC)",
|
||||
"PyObject *cfunc = PyCFunction_NewEx(meth, (PyObject*)type, NULL);",
|
||||
"descr = PyStaticMethod_New(cfunc);",
|
||||
"isdescr = 0;",
|
||||
"Py_DECREF(cfunc);",
|
||||
"descr = PyDescr_NewMethod(type, meth);",
|
||||
"if (descr == NULL)",
|
||||
"if (isdescr)",
|
||||
"name = PyDescr_NAME(descr);",
|
||||
"name = PyUnicode_FromString(meth->ml_name);",
|
||||
"if (name == NULL)",
|
||||
"Py_DECREF(descr);",
|
||||
"if (!(meth->ml_flags & METH_COEXIST))",
|
||||
"err = PyDict_SetDefault(type->tp_dict, name, descr) == NULL;",
|
||||
"err = PyDict_SetItem(type->tp_dict, name, descr) < 0;",
|
||||
"if (!isdescr)",
|
||||
"Py_DECREF(name);",
|
||||
"Py_DECREF(descr);",
|
||||
"if (err)",
|
||||
"return -1;",
|
||||
"return 0;",
|
||||
]
|
||||
cursor = 0
|
||||
for token in ordered:
|
||||
position = body.find(token, cursor)
|
||||
assert position >= 0, f"type_add_method: missing or reordered operation: {token}"
|
||||
cursor = position + len(token)
|
||||
assert len(re.findall(r"\bname\s*=(?!=)", body)) == 2, "retain both name assignment branches"
|
||||
assert len(re.findall(r"\berr\s*=(?!=)", body)) == 2, "retain both dictionary insertion branches"
|
||||
print("type_add_method C89 locals, branch ordering and reference cleanup: PASS")
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only assign_version_tag's C89 loop index and unchanged loop."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nstatic int\nassign_version_tag\(struct type_cache \*cache, PyTypeObject \*type\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "assign_version_tag definition missing"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
assert re.match(
|
||||
r"\s*PyObject \*bases;\s*Py_ssize_t n;\s*Py_ssize_t i;\s*if\b", body
|
||||
), "assign_version_tag: Py_ssize_t i; must precede statements at function scope"
|
||||
assert len(re.findall(r"\bPy_ssize_t\s+i\b", body)) == 1, (
|
||||
"assign_version_tag: declare i exactly once"
|
||||
)
|
||||
loop = re.search(r"\bfor\s*\(.*?\n \}", body, re.S)
|
||||
expected = """
|
||||
for (i = 0; i < n; i++) {
|
||||
PyObject *b = PyTuple_GET_ITEM(bases, i);
|
||||
if (!assign_version_tag(cache, _PyType_CAST(b)))
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
assert loop and loop.group().split() == expected.split(), (
|
||||
"assign_version_tag: preserve loop initializer, bounds, increment, and body"
|
||||
)
|
||||
print("assign_version_tag loop index C89 declaration and unchanged loop: PASS")
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check assign_version_tag's n declaration and preserve evaluation order."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nstatic int\nassign_version_tag\(struct type_cache \*cache, PyTypeObject \*type\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "assign_version_tag definition missing"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
assert re.match(r"\s*PyObject \*bases;\s*Py_ssize_t n;\s*Py_ssize_t i;\s*if\b", body), (
|
||||
"assign_version_tag: Py_ssize_t n; must be in the opening declaration block"
|
||||
)
|
||||
assert len(re.findall(r"\bPy_ssize_t\s+n\b", body)) == 1, (
|
||||
"assign_version_tag: declare n exactly once"
|
||||
)
|
||||
expected = """
|
||||
PyObject *bases;
|
||||
Py_ssize_t n;
|
||||
Py_ssize_t i;
|
||||
if (_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
|
||||
return 1;
|
||||
}
|
||||
if (!_PyType_HasFeature(type, Py_TPFLAGS_READY)) {
|
||||
return 0;
|
||||
}
|
||||
if (next_version_tag == 0) {
|
||||
return 0;
|
||||
}
|
||||
type->tp_version_tag = next_version_tag++;
|
||||
assert (type->tp_version_tag != 0);
|
||||
bases = type->tp_bases;
|
||||
n = PyTuple_GET_SIZE(bases);
|
||||
for (i = 0; i < n; i++) {
|
||||
PyObject *b = PyTuple_GET_ITEM(bases, i);
|
||||
if (!assign_version_tag(cache, _PyType_CAST(b)))
|
||||
return 0;
|
||||
}
|
||||
type->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG;
|
||||
return 1;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"assign_version_tag: preserve n initialization timing, guards, and loop"
|
||||
)
|
||||
print("assign_version_tag n C89 declaration and unchanged evaluation order: PASS")
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check best_base's base_i declaration and post-validation cast timing."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nstatic PyTypeObject \*\nbest_base\(PyObject \*bases\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "best_base definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
loop = body.split("for (i = 0; i < n; i++) {", 1)[1]
|
||||
assert re.match(
|
||||
r"\s*PyObject \*base_proto = PyTuple_GET_ITEM\(bases, i\);"
|
||||
r"\s*PyTypeObject \*base_i;\s*if\b",
|
||||
loop,
|
||||
), "best_base: base_i must be in the loop's opening declaration block (C89)"
|
||||
assert len(re.findall(r"\bPyTypeObject\s*\*\s*base_i\b", body)) == 1, (
|
||||
"declare base_i exactly once"
|
||||
)
|
||||
assert len(re.findall(r"\bbase_i\s*=(?!=)", body)) == 1, (
|
||||
"assign base_i exactly once"
|
||||
)
|
||||
expected = """
|
||||
PyObject *base_proto = PyTuple_GET_ITEM(bases, i);
|
||||
PyTypeObject *base_i;
|
||||
if (!PyType_Check(base_proto)) {
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError,
|
||||
"bases must be types");
|
||||
return NULL;
|
||||
}
|
||||
base_i = (PyTypeObject *)base_proto;
|
||||
|
||||
if (!_PyType_IsReady(base_i)) {
|
||||
if (PyType_Ready(base_i) < 0)
|
||||
return NULL;
|
||||
}
|
||||
"""
|
||||
prefix = loop.split("if (!_PyType_HasFeature(base_i, Py_TPFLAGS_BASETYPE))", 1)[0]
|
||||
assert prefix.split() == expected.split(), (
|
||||
"best_base: preserve item retrieval, successful type validation, cast "
|
||||
"assignment, and first base_i use in their original order"
|
||||
)
|
||||
print("best_base base_i C89 declaration and post-validation cast timing: PASS")
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check type_cache_clear's C89 index declaration and unchanged loop body."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/typeobject.c")
|
||||
match = re.search(
|
||||
r"static void\ntype_cache_clear\(struct type_cache \*cache, PyObject \*value\)\n\{(.*?)\n\}",
|
||||
source.read_text(),
|
||||
re.S,
|
||||
)
|
||||
assert match, "type_cache_clear definition missing"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*Py_ssize_t i;", body), (
|
||||
"type_cache_clear: Py_ssize_t i; must be declared at function entry"
|
||||
)
|
||||
assert re.fullmatch(
|
||||
r"\s*Py_ssize_t i;\s*"
|
||||
r"for \(i = 0; i < \(1 << MCACHE_SIZE_EXP\); i\+\+\) \{\s*"
|
||||
r"struct type_cache_entry \*entry = &cache->hashtable\[i\];\s*"
|
||||
r"entry->version = 0;\s*"
|
||||
r"Py_XSETREF\(entry->name, _Py_XNewRef\(value\)\);\s*"
|
||||
r"entry->value = NULL;\s*\}\s*",
|
||||
body,
|
||||
), "type_cache_clear: preserve loop assignment, bound, increment, body and order"
|
||||
print("type_cache_clear index C89 declaration and unchanged loop body: PASS")
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check custom_mro's C89 declaration and lookup/decref evaluation order."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
start = source.index("type_mro_modified(PyTypeObject *type, PyObject *bases) {")
|
||||
end = source.index("\n}", start)
|
||||
function = source[start:end]
|
||||
start = function.index(" if (custom) {")
|
||||
end = function.index("\n n = PyTuple_GET_SIZE(bases);", start)
|
||||
block = function[start:end]
|
||||
assert re.match(
|
||||
r"if \(custom\) \{\s*PyObject \*mro_meth, \*type_mro_meth;\s*"
|
||||
r"int custom_mro;\s*mro_meth =",
|
||||
block.strip(),
|
||||
), "custom_mro must be declared in the opening declaration block"
|
||||
assert len(re.findall(r"\bint\s+custom_mro\b", function)) == 1, (
|
||||
"declare custom_mro exactly once"
|
||||
)
|
||||
expected = """
|
||||
if (custom) {
|
||||
PyObject *mro_meth, *type_mro_meth;
|
||||
int custom_mro;
|
||||
mro_meth = lookup_maybe_method(
|
||||
(PyObject *)type, &_Py_ID(mro), &unbound);
|
||||
if (mro_meth == NULL) {
|
||||
goto clear;
|
||||
}
|
||||
type_mro_meth = lookup_maybe_method(
|
||||
(PyObject *)&PyType_Type, &_Py_ID(mro), &unbound);
|
||||
if (type_mro_meth == NULL) {
|
||||
Py_DECREF(mro_meth);
|
||||
goto clear;
|
||||
}
|
||||
custom_mro = (mro_meth != type_mro_meth);
|
||||
Py_DECREF(mro_meth);
|
||||
Py_DECREF(type_mro_meth);
|
||||
if (custom_mro) {
|
||||
goto clear;
|
||||
}
|
||||
}
|
||||
"""
|
||||
assert block.split() == expected.split(), (
|
||||
"preserve both successful lookups before custom_mro assignment, "
|
||||
"then decrefs and conditional, including lookup failure handling"
|
||||
)
|
||||
print("type_mro_modified custom_mro C89 declaration and evaluation order: PASS")
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep find_name_in_mro mro/res C89-safe without changing lookup timing."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nfind_name_in_mro\(PyTypeObject \*type, PyObject \*name, int \*error\)\n"
|
||||
r"\{(.*?)\n\}", source, re.S,
|
||||
)
|
||||
assert match, "find_name_in_mro definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert re.match(r"\s*Py_hash_t hash;\s*PyObject \*mro;\s*PyObject \*res;\s*Py_ssize_t n;\s*(?:Py_ssize_t i;\s*)?if", body), (
|
||||
"find_name_in_mro: mro and res must be declared at function entry (C89)"
|
||||
)
|
||||
assert not re.search(r"for\s*\(\s*Py_ssize_t\s+i\b", body), (
|
||||
"find_name_in_mro: i remains declared in the for initializer (C89)"
|
||||
)
|
||||
expected = """
|
||||
Py_hash_t hash;
|
||||
PyObject *mro;
|
||||
PyObject *res;
|
||||
Py_ssize_t n;
|
||||
Py_ssize_t i;
|
||||
if (!PyUnicode_CheckExact(name) ||
|
||||
(hash = _PyASCIIObject_CAST(name)->hash) == -1)
|
||||
{
|
||||
hash = PyObject_Hash(name);
|
||||
if (hash == -1) {
|
||||
*error = -1;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
mro = type->tp_mro;
|
||||
if (mro == NULL) {
|
||||
if ((type->tp_flags & Py_TPFLAGS_READYING) == 0) {
|
||||
if (PyType_Ready(type) < 0) {
|
||||
*error = -1;
|
||||
return NULL;
|
||||
}
|
||||
mro = type->tp_mro;
|
||||
}
|
||||
if (mro == NULL) {
|
||||
*error = 1;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
res = NULL;
|
||||
|
||||
Py_INCREF(mro);
|
||||
n = PyTuple_GET_SIZE(mro);
|
||||
for (i = 0; i < n; i++) {
|
||||
PyObject *base = PyTuple_GET_ITEM(mro, i);
|
||||
PyObject *dict = _PyType_CAST(base)->tp_dict;
|
||||
assert(dict && PyDict_Check(dict));
|
||||
res = _PyDict_GetItem_KnownHash(dict, name, hash);
|
||||
if (res != NULL) {
|
||||
break;
|
||||
}
|
||||
if (PyErr_Occurred()) {
|
||||
*error = -1;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
*error = 0;
|
||||
done:
|
||||
Py_DECREF(mro);
|
||||
return res;"""
|
||||
assert body.split() == expected.split(), (
|
||||
"find_name_in_mro: preserve hash/readiness checks, mro/res assignment timing, "
|
||||
"reference handling, lookup, errors, and opening n/i declarations"
|
||||
)
|
||||
print("find_name_in_mro mro/res C89 declarations and same-point assignments: PASS")
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only find_name_in_mro's n declaration and initialization timing."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = (Path(__file__).resolve().parents[1] / "cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nfind_name_in_mro\(PyTypeObject \*type, PyObject \*name, int \*error\)\n"
|
||||
r"\{(.*?)\n\}", source, re.S,
|
||||
)
|
||||
assert match, "find_name_in_mro definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert re.match(
|
||||
r"\s*Py_hash_t hash;\s*PyObject \*mro;\s*PyObject \*res;"
|
||||
r"\s*Py_ssize_t n;\s*Py_ssize_t i;\s*if\b", body,
|
||||
), "find_name_in_mro: Py_ssize_t n; must be in the opening declaration block"
|
||||
assert len(re.findall(r"\bPy_ssize_t\s+n\b", body)) == 1, (
|
||||
"find_name_in_mro: declare n exactly once"
|
||||
)
|
||||
assert len(re.findall(r"\bn\s*=", body)) == 1, (
|
||||
"find_name_in_mro: initialize n exactly once"
|
||||
)
|
||||
assert re.search(
|
||||
r"\bres = NULL;\s*Py_INCREF\(mro\);\s*n = PyTuple_GET_SIZE\(mro\);"
|
||||
r"\s*for \(i = 0; i < n; i\+\+\) \{", body,
|
||||
), "find_name_in_mro: preserve n assignment after INCREF and the separate i loop"
|
||||
print("find_name_in_mro n C89 declaration and same-point assignment: PASS")
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep type-spec doc locals C89-safe without moving their evaluations."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nPyObject \*\nPyType_FromModuleAndSpec\(PyObject \*module, PyType_Spec \*spec, PyObject \*bases\)\n"
|
||||
r"\{(.*?)\n\}", source, re.S,
|
||||
)
|
||||
assert match, "PyType_FromModuleAndSpec definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
match = re.search(
|
||||
r"else if \(slot->slot == Py_tp_doc\) \{(.*?)\n \}"
|
||||
r"\s*else if \(slot->slot == Py_tp_members\)", body, re.S,
|
||||
)
|
||||
assert match, "Py_tp_doc branch missing"
|
||||
branch = match.group(1)
|
||||
assert re.match(r"\s*size_t len;\s*char \*tp_doc;\s*if", branch), (
|
||||
"len and tp_doc must be declared at the start of the Py_tp_doc block (C89)"
|
||||
)
|
||||
expected = """
|
||||
size_t len;
|
||||
char *tp_doc;
|
||||
if (slot->pfunc == NULL) {
|
||||
type->tp_doc = NULL;
|
||||
continue;
|
||||
}
|
||||
len = strlen(slot->pfunc)+1;
|
||||
tp_doc = PyObject_Malloc(len);
|
||||
if (tp_doc == NULL) {
|
||||
type->tp_doc = NULL;
|
||||
PyErr_NoMemory();
|
||||
goto fail;
|
||||
}
|
||||
memcpy(tp_doc, slot->pfunc, len);
|
||||
type->tp_doc = tp_doc;
|
||||
"""
|
||||
assert branch.split() == expected.split(), (
|
||||
"preserve NULL guard, length/allocation evaluation order, failure handling, and copy"
|
||||
)
|
||||
print("PyType_FromModuleAndSpec doc locals C89 declarations and evaluation order: PASS")
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep the type-spec member loop C89-safe without moving its initialization."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nPyObject \*\nPyType_FromModuleAndSpec\(PyObject \*module, PyType_Spec \*spec, PyObject \*bases\)\n"
|
||||
r"\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "PyType_FromModuleAndSpec definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert re.search(
|
||||
r"if \(slot->slot == Py_tp_members\)\s*\{"
|
||||
r"\s*const PyMemberDef \*memb;\s*nmembers = 0;"
|
||||
r"\s*for \(memb = slot->pfunc; memb->name != NULL; memb\+\+\)\s*\{",
|
||||
body,
|
||||
), "memb must be declared at block start (C89), with initialization still in the loop header"
|
||||
assert len(re.findall(r"\bconst PyMemberDef\s*\*memb\b", body)) == 1, (
|
||||
"declare memb exactly once"
|
||||
)
|
||||
scan = body[body.index(" nmembers = weaklistoffset"):body.index(" res = (PyHeapTypeObject*)")]
|
||||
expected = """
|
||||
nmembers = weaklistoffset = dictoffset = vectorcalloffset = 0;
|
||||
for (slot = spec->slots; slot->slot; slot++) {
|
||||
if (slot->slot == Py_tp_members) {
|
||||
const PyMemberDef *memb;
|
||||
nmembers = 0;
|
||||
for (memb = slot->pfunc; memb->name != NULL; memb++) {
|
||||
nmembers++;
|
||||
if (strcmp(memb->name, "__weaklistoffset__") == 0) {
|
||||
assert(memb->type == T_PYSSIZET);
|
||||
assert(memb->flags == READONLY);
|
||||
weaklistoffset = memb->offset;
|
||||
}
|
||||
if (strcmp(memb->name, "__dictoffset__") == 0) {
|
||||
assert(memb->type == T_PYSSIZET);
|
||||
assert(memb->flags == READONLY);
|
||||
dictoffset = memb->offset;
|
||||
}
|
||||
if (strcmp(memb->name, "__vectorcalloffset__") == 0) {
|
||||
assert(memb->type == T_PYSSIZET);
|
||||
assert(memb->flags == READONLY);
|
||||
vectorcalloffset = memb->offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
assert scan.split() == expected.split(), (
|
||||
"preserve member scan guards, evaluation order, bounds, increments, and offset handling"
|
||||
)
|
||||
print("PyType_FromModuleAndSpec memb C89 declaration and same-point initialization: PASS")
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep the type-spec name buffer length C89-safe at its evaluation point."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nPyObject \*\nPyType_FromModuleAndSpec\(PyObject \*module, PyType_Spec \*spec, PyObject \*bases\)\n"
|
||||
r"\{(.*?)\n\}", source, re.S,
|
||||
)
|
||||
assert match, "PyType_FromModuleAndSpec definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
opening = body[:body.index(" nmembers = weaklistoffset")]
|
||||
assert re.search(r"\bPy_ssize_t name_buf_len;", opening), (
|
||||
"name_buf_len must be declared in the opening declaration block (C89), before statements"
|
||||
)
|
||||
assert len(re.findall(r"\bPy_ssize_t\s+name_buf_len\b", body)) == 1, (
|
||||
"declare name_buf_len exactly once"
|
||||
)
|
||||
assert len(re.findall(r"\bname_buf_len\s*=", body)) == 1, (
|
||||
"assign name_buf_len exactly once"
|
||||
)
|
||||
expected = """
|
||||
res->ht_name = PyUnicode_FromString(s);
|
||||
if (!res->ht_name) {
|
||||
goto fail;
|
||||
}
|
||||
res->ht_qualname = Py_NewRef(res->ht_name);
|
||||
name_buf_len = strlen(spec->name) + 1;
|
||||
res->_ht_tpname = PyMem_Malloc(name_buf_len);
|
||||
if (res->_ht_tpname == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
type->tp_name = memcpy(res->_ht_tpname, spec->name, name_buf_len);
|
||||
res->ht_module = Py_XNewRef(module);
|
||||
"""
|
||||
actual = body[body.index(" res->ht_name = PyUnicode_FromString(s);"):
|
||||
body.index(" if (!bases)")]
|
||||
assert actual.split() == expected.split(), (
|
||||
"preserve name creation, length evaluation timing, allocation, failure handling, and copy"
|
||||
)
|
||||
print("PyType_FromModuleAndSpec name_buf_len C89 declaration and same-point initialization: PASS")
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep the type-spec name pointer C89-safe and initialize it after flags."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nPyObject \*\nPyType_FromModuleAndSpec\(PyObject \*module, PyType_Spec \*spec, PyObject \*bases\)\n"
|
||||
r"\{(.*?)\n\}", source, re.S,
|
||||
)
|
||||
assert match, "PyType_FromModuleAndSpec definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
opening = body[:body.index(" nmembers = weaklistoffset")]
|
||||
assert re.search(r"\bconst char \*s;", opening), (
|
||||
"s must be declared in the opening declaration block (C89), before statements"
|
||||
)
|
||||
assert len(re.findall(r"\bconst char\s*\*s\b", body)) == 1, "declare s exactly once"
|
||||
name_setup = body[body.index(" type->tp_flags = spec->flags | Py_TPFLAGS_HEAPTYPE;"):]
|
||||
name_setup = name_setup[:name_setup.index(" res->ht_name = PyUnicode_FromString(s);")]
|
||||
assert len(re.findall(r"strrchr\(spec->name, '\.'\)", name_setup)) == 1, (
|
||||
"evaluate name search exactly once in name setup"
|
||||
)
|
||||
expected = """
|
||||
if (spec->name == NULL) {
|
||||
PyErr_SetString(PyExc_SystemError,
|
||||
"Type spec does not define the name field.");
|
||||
goto fail;
|
||||
}
|
||||
type = &res->ht_type;
|
||||
type->tp_flags = spec->flags | Py_TPFLAGS_HEAPTYPE;
|
||||
s = strrchr(spec->name, '.');
|
||||
if (s == NULL) {
|
||||
s = spec->name;
|
||||
}
|
||||
else {
|
||||
s++;
|
||||
}
|
||||
res->ht_name = PyUnicode_FromString(s);
|
||||
"""
|
||||
actual = body[body.index(" if (spec->name == NULL)"):
|
||||
body.index(" if (!res->ht_name)")]
|
||||
assert actual.split() == expected.split(), (
|
||||
"preserve name validation, flags, search initialization timing, and name selection"
|
||||
)
|
||||
print("PyType_FromModuleAndSpec s C89 declaration and same-point initialization: PASS")
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check type_get_annotations's C89 local and preserve every executable statement."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nstatic PyObject \*\ntype_get_annotations\(PyTypeObject \*type, void \*context\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "type_get_annotations definition missing"
|
||||
body = match.group(1)
|
||||
assert re.match(r"\s*PyObject \*annotations;\s*if\b", body), (
|
||||
"type_get_annotations: annotations must be declared before the heap-type guard"
|
||||
)
|
||||
assert len(re.findall(r"\bPyObject\s*\*\s*annotations\b", body)) == 1, (
|
||||
"type_get_annotations: declare annotations exactly once"
|
||||
)
|
||||
expected = """
|
||||
PyObject *annotations;
|
||||
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
|
||||
PyErr_Format(PyExc_AttributeError, "type object '%s' has no attribute '__annotations__'", type->tp_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
annotations = PyDict_GetItemWithError(type->tp_dict, &_Py_ID(__annotations__));
|
||||
if (annotations) {
|
||||
if (Py_TYPE(annotations)->tp_descr_get) {
|
||||
annotations = Py_TYPE(annotations)->tp_descr_get(
|
||||
annotations, NULL, (PyObject *)type);
|
||||
} else {
|
||||
Py_INCREF(annotations);
|
||||
}
|
||||
}
|
||||
else if (!PyErr_Occurred()) {
|
||||
annotations = PyDict_New();
|
||||
if (annotations) {
|
||||
int result = PyDict_SetItem(
|
||||
type->tp_dict, &_Py_ID(__annotations__), annotations);
|
||||
if (result) {
|
||||
Py_CLEAR(annotations);
|
||||
} else {
|
||||
PyType_Modified(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
return annotations;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"type_get_annotations: preserve guard, lookup, descriptor handling, references, "
|
||||
"dictionary creation, error handling, invalidation, and return in their original order"
|
||||
)
|
||||
print("type_get_annotations annotations C89 declaration and unchanged executable body: PASS")
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep heap-type/module locals at entry and their reads after the static-type skip."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nPyObject \*\nPyType_GetModuleByDef\(PyTypeObject \*type, PyModuleDef \*def\)\n"
|
||||
r"\{(.*?)\n\}", source, re.S,
|
||||
)
|
||||
assert match, "PyType_GetModuleByDef definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert re.match(r"\s*PyObject \*mro;\s*Py_ssize_t n;\s*Py_ssize_t i;\s*PyHeapTypeObject \*ht;\s*PyObject \*module;\s*assert\(", body), (
|
||||
"PyType_GetModuleByDef: ht and module must be declared in the opening declaration block (C89)"
|
||||
)
|
||||
for declaration in (r"\bPyHeapTypeObject\s*\*ht\b", r"\bPyObject\s*\*module\b"):
|
||||
assert len(re.findall(declaration, body)) == 1, (
|
||||
"PyType_GetModuleByDef: declare ht and module exactly once"
|
||||
)
|
||||
expected = """
|
||||
PyObject *mro;
|
||||
Py_ssize_t n;
|
||||
Py_ssize_t i;
|
||||
PyHeapTypeObject *ht;
|
||||
PyObject *module;
|
||||
assert(PyType_Check(type));
|
||||
|
||||
mro = type->tp_mro;
|
||||
assert(mro != NULL);
|
||||
assert(PyTuple_Check(mro));
|
||||
assert(PyTuple_GET_SIZE(mro) >= 1);
|
||||
|
||||
n = PyTuple_GET_SIZE(mro);
|
||||
for (i = 0; i < n; i++) {
|
||||
PyObject *super = PyTuple_GET_ITEM(mro, i);
|
||||
if(!_PyType_HasFeature((PyTypeObject *)super, Py_TPFLAGS_HEAPTYPE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ht = (PyHeapTypeObject*)super;
|
||||
module = ht->ht_module;
|
||||
if (module && _PyModule_GetDef(module) == def) {
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"PyType_GetModuleByDef: No superclass of '%s' has the given module",
|
||||
type->tp_name);
|
||||
return NULL;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"PyType_GetModuleByDef: preserve ht/module assignments after the static-type skip and before module lookup, "
|
||||
"loop, module lookup, and error handling"
|
||||
)
|
||||
print("PyType_GetModuleByDef ht/module C89 declarations and same-point assignments: PASS")
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep PyType_GetModuleByDef's loop index in opening C89 locals."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nPyObject \*\nPyType_GetModuleByDef\(PyTypeObject \*type, PyModuleDef \*def\)\n"
|
||||
r"\{(.*?)\n\}", source, re.S,
|
||||
)
|
||||
assert match, "PyType_GetModuleByDef definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert re.match(r"\s*PyObject \*mro;\s*Py_ssize_t n;\s*Py_ssize_t i;\s*PyHeapTypeObject \*ht;\s*PyObject \*module;\s*assert\(", body), (
|
||||
"PyType_GetModuleByDef: i must be declared in the opening declaration block (C89)"
|
||||
)
|
||||
assert len(re.findall(r"\bPy_ssize_t\s+i\b", body)) == 1, (
|
||||
"PyType_GetModuleByDef: declare i exactly once"
|
||||
)
|
||||
expected = """
|
||||
PyObject *mro;
|
||||
Py_ssize_t n;
|
||||
Py_ssize_t i;
|
||||
PyHeapTypeObject *ht;
|
||||
PyObject *module;
|
||||
assert(PyType_Check(type));
|
||||
|
||||
mro = type->tp_mro;
|
||||
assert(mro != NULL);
|
||||
assert(PyTuple_Check(mro));
|
||||
assert(PyTuple_GET_SIZE(mro) >= 1);
|
||||
|
||||
n = PyTuple_GET_SIZE(mro);
|
||||
for (i = 0; i < n; i++) {
|
||||
PyObject *super = PyTuple_GET_ITEM(mro, i);
|
||||
if(!_PyType_HasFeature((PyTypeObject *)super, Py_TPFLAGS_HEAPTYPE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ht = (PyHeapTypeObject*)super;
|
||||
module = ht->ht_module;
|
||||
if (module && _PyModule_GetDef(module) == def) {
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"PyType_GetModuleByDef: No superclass of '%s' has the given module",
|
||||
type->tp_name);
|
||||
return NULL;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"PyType_GetModuleByDef: preserve loop initialization, bound, increment, body, and surrounding "
|
||||
"loop, module lookup, and error handling"
|
||||
)
|
||||
print("PyType_GetModuleByDef C89 loop index and preserved loop semantics: PASS")
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep PyType_GetModuleByDef's mro declaration C89-safe and read in place."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nPyObject \*\nPyType_GetModuleByDef\(PyTypeObject \*type, PyModuleDef \*def\)\n"
|
||||
r"\{(.*?)\n\}", source, re.S,
|
||||
)
|
||||
assert match, "PyType_GetModuleByDef definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert re.match(r"\s*PyObject \*mro;\s*Py_ssize_t n;\s*Py_ssize_t i;\s*PyHeapTypeObject \*ht;\s*PyObject \*module;\s*assert\(", body), (
|
||||
"PyType_GetModuleByDef: mro must be declared at function entry (C89)"
|
||||
)
|
||||
expected = """
|
||||
PyObject *mro;
|
||||
Py_ssize_t n;
|
||||
Py_ssize_t i;
|
||||
PyHeapTypeObject *ht;
|
||||
PyObject *module;
|
||||
assert(PyType_Check(type));
|
||||
|
||||
mro = type->tp_mro;
|
||||
assert(mro != NULL);
|
||||
assert(PyTuple_Check(mro));
|
||||
assert(PyTuple_GET_SIZE(mro) >= 1);
|
||||
|
||||
n = PyTuple_GET_SIZE(mro);
|
||||
for (i = 0; i < n; i++) {
|
||||
PyObject *super = PyTuple_GET_ITEM(mro, i);
|
||||
if(!_PyType_HasFeature((PyTypeObject *)super, Py_TPFLAGS_HEAPTYPE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ht = (PyHeapTypeObject*)super;
|
||||
module = ht->ht_module;
|
||||
if (module && _PyModule_GetDef(module) == def) {
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"PyType_GetModuleByDef: No superclass of '%s' has the given module",
|
||||
type->tp_name);
|
||||
return NULL;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"PyType_GetModuleByDef: preserve mro read timing, checks, later declarations, "
|
||||
"loop, module lookup, and error handling"
|
||||
)
|
||||
print("PyType_GetModuleByDef mro C89 declaration and same-point read: PASS")
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep PyType_GetModuleByDef's n declaration C89-safe and evaluated in place."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nPyObject \*\nPyType_GetModuleByDef\(PyTypeObject \*type, PyModuleDef \*def\)\n"
|
||||
r"\{(.*?)\n\}", source, re.S,
|
||||
)
|
||||
assert match, "PyType_GetModuleByDef definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert re.match(r"\s*PyObject \*mro;\s*Py_ssize_t n;\s*Py_ssize_t i;\s*PyHeapTypeObject \*ht;\s*PyObject \*module;\s*assert\(", body), (
|
||||
"PyType_GetModuleByDef: n must be declared in the opening declaration block (C89)"
|
||||
)
|
||||
assert len(re.findall(r"\bPy_ssize_t\s+n\b", body)) == 1, (
|
||||
"PyType_GetModuleByDef: declare n exactly once"
|
||||
)
|
||||
expected = """
|
||||
PyObject *mro;
|
||||
Py_ssize_t n;
|
||||
Py_ssize_t i;
|
||||
PyHeapTypeObject *ht;
|
||||
PyObject *module;
|
||||
assert(PyType_Check(type));
|
||||
|
||||
mro = type->tp_mro;
|
||||
assert(mro != NULL);
|
||||
assert(PyTuple_Check(mro));
|
||||
assert(PyTuple_GET_SIZE(mro) >= 1);
|
||||
|
||||
n = PyTuple_GET_SIZE(mro);
|
||||
for (i = 0; i < n; i++) {
|
||||
PyObject *super = PyTuple_GET_ITEM(mro, i);
|
||||
if(!_PyType_HasFeature((PyTypeObject *)super, Py_TPFLAGS_HEAPTYPE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ht = (PyHeapTypeObject*)super;
|
||||
module = ht->ht_module;
|
||||
if (module && _PyModule_GetDef(module) == def) {
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"PyType_GetModuleByDef: No superclass of '%s' has the given module",
|
||||
type->tp_name);
|
||||
return NULL;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"PyType_GetModuleByDef: preserve n evaluation timing and mro read timing, checks, later declarations, "
|
||||
"loop, module lookup, and error handling"
|
||||
)
|
||||
print("PyType_GetModuleByDef n C89 declaration and same-point evaluation: PASS")
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep PyType_GetModule's heap-type declaration C89-safe and cast in place."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nPyObject \*\nPyType_GetModule\(PyTypeObject \*type\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "PyType_GetModule definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert re.match(r"\s*PyHeapTypeObject \*et;\s*assert\(", body), (
|
||||
"PyType_GetModule: et must be declared at function entry (C89)"
|
||||
)
|
||||
expected = """
|
||||
PyHeapTypeObject *et;
|
||||
assert(PyType_Check(type));
|
||||
if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"PyType_GetModule: Type '%s' is not a heap type",
|
||||
type->tp_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
et = (PyHeapTypeObject*)type;
|
||||
if (!et->ht_module) {
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"PyType_GetModule: Type '%s' has no associated module",
|
||||
type->tp_name);
|
||||
return NULL;
|
||||
}
|
||||
return et->ht_module;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"PyType_GetModule: preserve validation, cast timing, module check, and return"
|
||||
)
|
||||
print("PyType_GetModule et C89 declaration and same-point cast: PASS")
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep _PyType_GetSubclasses's index in opening C89 locals."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nPyObject\*\n_PyType_GetSubclasses\(PyTypeObject \*self\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "_PyType_GetSubclasses definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert re.match(
|
||||
r"\s*PyObject \*subclasses;\s*Py_ssize_t i;\s*"
|
||||
r"PyObject \*list = PyList_New\(0\);",
|
||||
body,
|
||||
), "_PyType_GetSubclasses: i must be declared in the opening declaration block (C89)"
|
||||
assert len(re.findall(r"\bPy_ssize_t\s+i\b", body)) == 1, (
|
||||
"_PyType_GetSubclasses: declare i exactly once"
|
||||
)
|
||||
expected = """
|
||||
PyObject *subclasses;
|
||||
Py_ssize_t i;
|
||||
PyObject *list = PyList_New(0);
|
||||
PyObject *ref;
|
||||
PyObject *obj;
|
||||
if (list == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
subclasses = self->tp_subclasses;
|
||||
if (subclasses == NULL) {
|
||||
return list;
|
||||
}
|
||||
assert(PyDict_CheckExact(subclasses));
|
||||
|
||||
|
||||
|
||||
i = 0;
|
||||
while (PyDict_Next(subclasses, &i, NULL, &ref)) {
|
||||
assert(PyWeakref_CheckRef(ref));
|
||||
obj = PyWeakref_GET_OBJECT(ref);
|
||||
if (obj == Py_None) {
|
||||
continue;
|
||||
}
|
||||
assert(PyType_Check(obj));
|
||||
|
||||
if (PyList_Append(list, obj) < 0) {
|
||||
Py_DECREF(list);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"_PyType_GetSubclasses: preserve index initialization timing, guards, loop, "
|
||||
"and ref/obj uses"
|
||||
)
|
||||
print("_PyType_GetSubclasses C89 index declaration and preserved initialization timing: PASS")
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep _PyType_GetSubclasses's obj in opening C89 locals."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nPyObject\*\n_PyType_GetSubclasses\(PyTypeObject \*self\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "_PyType_GetSubclasses definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert re.match(
|
||||
r"\s*PyObject \*subclasses;\s*Py_ssize_t i;\s*"
|
||||
r"PyObject \*list = PyList_New\(0\);\s*PyObject \*ref;\s*"
|
||||
r"PyObject \*obj;",
|
||||
body,
|
||||
), "_PyType_GetSubclasses: obj must be in the opening declaration block (C89)"
|
||||
assert len(re.findall(r"\bPyObject\s*\*\s*obj\b", body)) == 1, (
|
||||
"_PyType_GetSubclasses: declare obj exactly once without initialization"
|
||||
)
|
||||
assert body.count("PyWeakref_GET_OBJECT(ref)") == 1, (
|
||||
"_PyType_GetSubclasses: preserve the single weakref object read"
|
||||
)
|
||||
assert re.search(
|
||||
r"while \(PyDict_Next\(subclasses, &i, NULL, &ref\)\) \{\s*"
|
||||
r"assert\(PyWeakref_CheckRef\(ref\)\);\s*"
|
||||
r"obj = PyWeakref_GET_OBJECT\(ref\);\s*"
|
||||
r"if \(obj == Py_None\) \{\s*continue;\s*\}\s*"
|
||||
r"assert\(PyType_Check\(obj\)\);\s*"
|
||||
r"if \(PyList_Append\(list, obj\) < 0\) \{\s*"
|
||||
r"Py_DECREF\(list\);\s*return NULL;\s*\}\s*\}\s*return list;\s*$",
|
||||
body,
|
||||
), "_PyType_GetSubclasses: preserve obj assignment timing and loop control flow"
|
||||
print("_PyType_GetSubclasses obj C89 declaration and preserved assignment timing: PASS")
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep _PyType_GetSubclasses's ref in opening C89 locals."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nPyObject\*\n_PyType_GetSubclasses\(PyTypeObject \*self\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "_PyType_GetSubclasses definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert re.match(
|
||||
r"\s*PyObject \*subclasses;\s*Py_ssize_t i;\s*"
|
||||
r"PyObject \*list = PyList_New\(0\);\s*PyObject \*ref;",
|
||||
body,
|
||||
), "_PyType_GetSubclasses: ref must be in the opening declaration block (C89)"
|
||||
assert len(re.findall(r"\bPyObject\s*\*\s*ref\b", body)) == 1, (
|
||||
"_PyType_GetSubclasses: declare ref exactly once without initialization"
|
||||
)
|
||||
expected = """
|
||||
PyObject *subclasses;
|
||||
Py_ssize_t i;
|
||||
PyObject *list = PyList_New(0);
|
||||
PyObject *ref;
|
||||
PyObject *obj;
|
||||
if (list == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
subclasses = self->tp_subclasses;
|
||||
if (subclasses == NULL) {
|
||||
return list;
|
||||
}
|
||||
assert(PyDict_CheckExact(subclasses));
|
||||
i = 0;
|
||||
while (PyDict_Next(subclasses, &i, NULL, &ref)) {
|
||||
assert(PyWeakref_CheckRef(ref));
|
||||
obj = PyWeakref_GET_OBJECT(ref);
|
||||
if (obj == Py_None) {
|
||||
continue;
|
||||
}
|
||||
assert(PyType_Check(obj));
|
||||
if (PyList_Append(list, obj) < 0) {
|
||||
Py_DECREF(list);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"_PyType_GetSubclasses: preserve guards, iteration, ref uses, "
|
||||
"and obj assignment timing"
|
||||
)
|
||||
print("_PyType_GetSubclasses ref C89 declaration and unchanged use order: PASS")
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep _PyType_GetSubclasses's subclasses declaration C89-safe."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nPyObject\*\n_PyType_GetSubclasses\(PyTypeObject \*self\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "_PyType_GetSubclasses definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
assert re.match(r"\s*PyObject \*subclasses;", body), (
|
||||
"_PyType_GetSubclasses: subclasses must be declared at function entry (C89)"
|
||||
)
|
||||
assert len(re.findall(r"\bPyObject\s*\*\s*subclasses\b", body)) == 1, (
|
||||
"_PyType_GetSubclasses: declare subclasses exactly once"
|
||||
)
|
||||
assert re.match(
|
||||
r"\s*PyObject \*subclasses;\s*"
|
||||
r"(?:Py_ssize_t i;\s*)?"
|
||||
r"PyObject \*list = PyList_New\(0\);\s*"
|
||||
r"PyObject \*ref;\s*"
|
||||
r"PyObject \*obj;\s*"
|
||||
r"if \(list == NULL\) \{\s*return NULL;\s*\}\s*"
|
||||
r"subclasses = self->tp_subclasses;\s*"
|
||||
r"if \(subclasses == NULL\) \{\s*return list;\s*\}\s*"
|
||||
r"assert\(PyDict_CheckExact\(subclasses\)\);",
|
||||
body,
|
||||
), "_PyType_GetSubclasses: preserve allocation, guard, and tp_subclasses read timing"
|
||||
assert body.count("self->tp_subclasses") == 1, (
|
||||
"_PyType_GetSubclasses: preserve the single tp_subclasses read"
|
||||
)
|
||||
print("_PyType_GetSubclasses subclasses C89 declaration and read timing: PASS")
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only _PyType_Lookup's cache-update entry declaration and timing."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = (Path(__file__).resolve().parents[1] / "cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\n_PyType_Lookup\(PyTypeObject \*type, PyObject \*name\)\n\{(.*?)\n\}",
|
||||
source, re.S,
|
||||
)
|
||||
assert match, "_PyType_Lookup definition missing"
|
||||
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
||||
guard = "if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(cache, type)) {"
|
||||
assert body.count(guard) == 1, "cache-update block must be unique"
|
||||
update = body.split(guard, 1)[1]
|
||||
assert re.match(r"\s*struct type_cache_entry \*entry;\s*h =", update), (
|
||||
"_PyType_Lookup cache update: entry must precede statements (C89)"
|
||||
)
|
||||
assert len(re.findall(r"\bstruct type_cache_entry\s*\*entry\b", update)) == 1, (
|
||||
"cache update: declare entry exactly once"
|
||||
)
|
||||
assert len(re.findall(r"\bentry\s*=", update)) == 1, (
|
||||
"cache update: initialize entry exactly once"
|
||||
)
|
||||
assert re.match(
|
||||
r"\s*struct type_cache_entry \*entry;"
|
||||
r"\s*h = MCACHE_HASH_METHOD\(type, name\);"
|
||||
r"\s*entry = &cache->hashtable\[h\];"
|
||||
r"\s*entry->version = type->tp_version_tag;"
|
||||
r"\s*entry->value = res;", update,
|
||||
), "cache update: preserve entry assignment between hash recalculation and writes"
|
||||
print("_PyType_Lookup cache-update entry C89 declaration and same-point assignment: PASS")
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep lookup_maybe_method's descriptor getter C89-safe without reordering."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nstatic PyObject \*\nlookup_maybe_method\(PyObject \*self, PyObject \*attr, int \*unbound\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "lookup_maybe_method definition missing"
|
||||
body = match.group(1)
|
||||
assert re.match(
|
||||
r"\s*PyObject \*res = _PyType_Lookup\(Py_TYPE\(self\), attr\);\s*descrgetfunc f;",
|
||||
body,
|
||||
), "lookup_maybe_method: f must be declared in the opening declaration block (C89)"
|
||||
assert len(re.findall(r"\bdescrgetfunc\s+f\b", body)) == 1, (
|
||||
"lookup_maybe_method: declare the descriptor getter exactly once"
|
||||
)
|
||||
expected = """
|
||||
PyObject *res = _PyType_Lookup(Py_TYPE(self), attr);
|
||||
descrgetfunc f;
|
||||
if (res == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
|
||||
/* Avoid temporary PyMethodObject */
|
||||
*unbound = 1;
|
||||
Py_INCREF(res);
|
||||
}
|
||||
else {
|
||||
*unbound = 0;
|
||||
f = Py_TYPE(res)->tp_descr_get;
|
||||
if (f == NULL) {
|
||||
Py_INCREF(res);
|
||||
}
|
||||
else {
|
||||
res = f(res, self, (PyObject *)(Py_TYPE(self)));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"lookup_maybe_method: preserve getter evaluation after *unbound = 0 and all control flow"
|
||||
)
|
||||
print("lookup_maybe_method descriptor getter C89 declaration and unchanged evaluation/control flow: PASS")
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only PyType_Modified's loop-index declaration and initialization timing."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nvoid\nPyType_Modified\(PyTypeObject \*type\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "PyType_Modified definition missing"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
assert re.match(r"\s*PyObject \*subclasses;\s*Py_ssize_t i;", body), (
|
||||
"PyType_Modified: Py_ssize_t i; must be declared at function entry"
|
||||
)
|
||||
assert len(re.findall(r"\bPy_ssize_t\s+i\b", body)) == 1, (
|
||||
"PyType_Modified: declare i exactly once"
|
||||
)
|
||||
assert re.search(
|
||||
r"if \(subclasses != NULL\) \{\s*"
|
||||
r"assert\(PyDict_CheckExact\(subclasses\)\);\s*"
|
||||
r"i = 0;\s*"
|
||||
r"while \(PyDict_Next\(subclasses, &i, NULL, &ref\)\) \{",
|
||||
body,
|
||||
), "PyType_Modified: initialize i at its original point before the while loop"
|
||||
assert len(re.findall(r"\bi\s*=", body)) == 1, (
|
||||
"PyType_Modified: initialize i exactly once"
|
||||
)
|
||||
print("PyType_Modified index C89 declaration and initialization timing: PASS")
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check PyType_Modified's ref/obj declarations and unchanged use order."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nvoid\nPyType_Modified\(PyTypeObject \*type\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "PyType_Modified definition missing"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
assert re.match(
|
||||
r"\s*PyObject \*subclasses;\s*Py_ssize_t i;\s*PyObject \*ref;",
|
||||
body,
|
||||
), "PyType_Modified: PyObject *ref; must be in the opening declaration block"
|
||||
assert len(re.findall(r"\bPyObject\s*\*\s*ref\b", body)) == 1, (
|
||||
"PyType_Modified: declare ref exactly once without initialization"
|
||||
)
|
||||
assert re.match(
|
||||
r"\s*PyObject \*subclasses;\s*Py_ssize_t i;\s*PyObject \*ref;\s*"
|
||||
r"PyObject \*obj;",
|
||||
body,
|
||||
), "PyType_Modified: PyObject *obj; must be in the opening declaration block"
|
||||
assert len(re.findall(r"\bPyObject\s*\*\s*obj\b", body)) == 1, (
|
||||
"PyType_Modified: declare obj exactly once without initialization"
|
||||
)
|
||||
assert re.fullmatch(
|
||||
r"\s*PyObject \*subclasses;\s*Py_ssize_t i;\s*PyObject \*ref;\s*"
|
||||
r"PyObject \*obj;\s*"
|
||||
r"if \(!_PyType_HasFeature\(type, Py_TPFLAGS_VALID_VERSION_TAG\)\) \{\s*"
|
||||
r"return;\s*\}\s*"
|
||||
r"subclasses = type->tp_subclasses;\s*"
|
||||
r"if \(subclasses != NULL\) \{\s*"
|
||||
r"assert\(PyDict_CheckExact\(subclasses\)\);\s*i = 0;\s*"
|
||||
r"while \(PyDict_Next\(subclasses, &i, NULL, &ref\)\) \{\s*"
|
||||
r"assert\(PyWeakref_CheckRef\(ref\)\);\s*"
|
||||
r"obj = PyWeakref_GET_OBJECT\(ref\);\s*"
|
||||
r"if \(obj == Py_None\) \{\s*continue;\s*\}\s*"
|
||||
r"PyType_Modified\(_PyType_CAST\(obj\)\);\s*\}\s*\}\s*"
|
||||
r"type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;\s*"
|
||||
r"type->tp_version_tag = 0;\s*",
|
||||
body,
|
||||
), "PyType_Modified: preserve all assignments, uses and evaluation order"
|
||||
print("PyType_Modified ref/obj C89 declarations and unchanged use order: PASS")
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only PyType_Modified's subclasses declaration and read timing."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nvoid\nPyType_Modified\(PyTypeObject \*type\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "PyType_Modified definition missing"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
assert re.match(r"\s*PyObject \*subclasses;", body), (
|
||||
"PyType_Modified: PyObject *subclasses; must be declared at function entry"
|
||||
)
|
||||
assert len(re.findall(r"\bPyObject\s*\*\s*subclasses\b", body)) == 1, (
|
||||
"PyType_Modified: declare subclasses exactly once"
|
||||
)
|
||||
assert re.match(
|
||||
r"\s*PyObject \*subclasses;\s*"
|
||||
r"(?:Py_ssize_t i;\s*)?"
|
||||
r"PyObject \*ref;\s*"
|
||||
r"PyObject \*obj;\s*"
|
||||
r"if \(!_PyType_HasFeature\(type, Py_TPFLAGS_VALID_VERSION_TAG\)\) \{\s*"
|
||||
r"return;\s*\}\s*"
|
||||
r"subclasses = type->tp_subclasses;\s*"
|
||||
r"if \(subclasses != NULL\) \{",
|
||||
body,
|
||||
), "PyType_Modified: read tp_subclasses immediately after the early-return guard"
|
||||
assert body.count("type->tp_subclasses") == 1, (
|
||||
"PyType_Modified: preserve the single tp_subclasses read"
|
||||
)
|
||||
print("PyType_Modified subclasses C89 declaration and read timing: PASS")
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check only the late bases declaration in assign_version_tag near MRO code."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
source = Path("cpython/Objects/typeobject.c").read_text()
|
||||
match = re.search(
|
||||
r"\nstatic int\nassign_version_tag\(struct type_cache \*cache, PyTypeObject \*type\)\n\{(.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "assign_version_tag definition missing"
|
||||
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
||||
assert re.match(r"\s*PyObject \*bases;", body), (
|
||||
"assign_version_tag: PyObject *bases; must be in the opening declarations"
|
||||
)
|
||||
assert len(re.findall(r"\bPyObject\s*\*\s*bases\b", body)) == 1, (
|
||||
"assign_version_tag: declare bases exactly once"
|
||||
)
|
||||
expected = """
|
||||
PyObject *bases;
|
||||
Py_ssize_t n;
|
||||
Py_ssize_t i;
|
||||
if (_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
|
||||
return 1;
|
||||
}
|
||||
if (!_PyType_HasFeature(type, Py_TPFLAGS_READY)) {
|
||||
return 0;
|
||||
}
|
||||
if (next_version_tag == 0) {
|
||||
return 0;
|
||||
}
|
||||
type->tp_version_tag = next_version_tag++;
|
||||
assert (type->tp_version_tag != 0);
|
||||
bases = type->tp_bases;
|
||||
n = PyTuple_GET_SIZE(bases);
|
||||
for (i = 0; i < n; i++) {
|
||||
PyObject *b = PyTuple_GET_ITEM(bases, i);
|
||||
if (!assign_version_tag(cache, _PyType_CAST(b)))
|
||||
return 0;
|
||||
}
|
||||
type->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG;
|
||||
return 1;
|
||||
"""
|
||||
assert body.split() == expected.split(), (
|
||||
"assign_version_tag: preserve bases read timing, guards, n, and loop index"
|
||||
)
|
||||
print("assign_version_tag bases C89 declaration and unchanged evaluation order: PASS")
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user