port: advance VC6 typeobject frontier and preserve regression coverage

This commit is contained in:
RayPals
2026-10-26 18:09:33 +00:00
parent 35caf1a558
commit 5ab291a68c
162 changed files with 15675 additions and 85 deletions
+4210 -43
View File
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;"):
+32
View File
@@ -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")
+36
View File
@@ -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")
+361
View File
@@ -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")
+40
View File
@@ -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")
+121
View File
@@ -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)")
+114
View File
@@ -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 ? &current : 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 ? &current : 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)")
+47
View File
@@ -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")
+95
View File
@@ -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 &not_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 == &not_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)")
+98
View File
@@ -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)")
+54
View File
@@ -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")
+111
View File
@@ -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)")
+90
View File
@@ -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")
+2 -2
View File
@@ -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")"
+95
View File
@@ -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 : &quotient;
}
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 == &quotient);
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")
+38
View File
@@ -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")
+33
View File
@@ -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")
+36
View File
@@ -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")
+46
View File
@@ -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")
+39
View File
@@ -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")
+54
View File
@@ -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