port: advance VC6 compatibility frontier
This commit is contained in:
@@ -14,6 +14,19 @@
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#if UINT16_MAX != 0xFFFFU
|
||||
#error "VC6 uint16_t maximum is incorrect"
|
||||
#endif
|
||||
#if SIZE_MAX != 0xFFFFFFFFUL
|
||||
#error "VC6 size_t maximum must match the 32-bit Win32 target"
|
||||
#endif
|
||||
#if INT64_MAX != 0x7FFFFFFFFFFFFFFFi64
|
||||
#error "VC6 int64_t maximum is incorrect"
|
||||
#endif
|
||||
#if UINT64_MAX != 0xFFFFFFFFFFFFFFFFui64
|
||||
#error "VC6 uint64_t maximum is incorrect"
|
||||
#endif
|
||||
|
||||
typedef char vc6_stdint_check_uint8_is_1_byte[(sizeof(uint8_t) == 1) ? 1 : -1];
|
||||
typedef char vc6_stdint_check_uint32_is_4_bytes[(sizeof(uint32_t) == 4) ? 1 : -1];
|
||||
typedef char vc6_stdint_check_uint64_is_8_bytes[(sizeof(uint64_t) == 8) ? 1 : -1];
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Source-level C89 and ownership-order check for bytearray_join."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit("usage: vc6-bytearray-join-c89.test.py bytearrayobject.c")
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
signature = "bytearray_join(PyByteArrayObject *self, PyObject *iterable_of_bytes)"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("{", start)
|
||||
body_end = source.index("\n}", body_start)
|
||||
body = source[body_start + 1:body_end]
|
||||
|
||||
if not body.lstrip().startswith("PyObject *ret;"):
|
||||
raise SystemExit("ret must be declared in bytearray_join's opening declaration group")
|
||||
if len(re.findall(r"\bPyObject\s*\*\s*ret\s*;", body)) != 1:
|
||||
raise SystemExit("bytearray_join must declare ret exactly once")
|
||||
if re.search(r"\bPyObject\s*\*\s*ret\s*=", body):
|
||||
raise SystemExit("bytearray_join must assign ret separately from its declaration")
|
||||
pattern = re.compile(
|
||||
r"self->ob_exports\+\+;.*?"
|
||||
r"ret\s*=\s*stringlib_bytes_join\(\s*\(PyObject\*\)self,\s*"
|
||||
r"iterable_of_bytes\s*\);\s*"
|
||||
r"self->ob_exports--;\s*(?://[^\n]*\s*)?return ret;",
|
||||
re.DOTALL,
|
||||
)
|
||||
if not pattern.search(body):
|
||||
raise SystemExit("bytearray_join export/join/unexport/return order changed")
|
||||
print("bytearray_join C89 declaration and export order: PASS")
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check C89 declaration placement in generated bytes Clinic wrappers."""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit("usage: vc6-bytes-clinic-c89.test.py bytesobject.c.h")
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
checks = {
|
||||
"bytes_decode(PyBytesObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)": (
|
||||
"encoding_length", "errors_length"),
|
||||
"bytes_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)": (
|
||||
"encoding_length", "errors_length"),
|
||||
}
|
||||
for signature, names in checks.items():
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}", body_start)
|
||||
body = source[body_start:body_end]
|
||||
unpack = re.search(r"^\s*(?:args|fastargs) = _PyArg_UnpackKeywords", body, re.MULTILINE)
|
||||
if unpack is None:
|
||||
raise SystemExit(signature.split("(", 1)[0] + ": argument unpack statement missing")
|
||||
opening = body[:unpack.start()]
|
||||
execution = body[unpack.end():]
|
||||
for name in names:
|
||||
declaration = "Py_ssize_t " + name + ";"
|
||||
if opening.count(declaration) != 1 or body.count(declaration) != 1:
|
||||
raise SystemExit(signature.split("(", 1)[0] + ": " + name + " must be declared once before argument unpacking")
|
||||
if re.search(r"\bPy_ssize_t\s+" + re.escape(name) + r"\b", execution):
|
||||
raise SystemExit(signature.split("(", 1)[0] + ": late declaration remains for " + name)
|
||||
for name in names:
|
||||
if "&" + name not in execution:
|
||||
raise SystemExit(signature.split("(", 1)[0] + ": original UTF-8 length use missing for " + name)
|
||||
print("bytes Clinic C89 declaration placement: PASS")
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check C89 locals in bytes find and repeat helpers."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit("usage: vc6-bytes-core-c89.test.py bytesobject.c")
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
|
||||
def function_body(signature):
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
return source[body_start:body_end]
|
||||
|
||||
find = function_body("_PyBytes_Find(const char *haystack")
|
||||
if not find.lstrip().startswith("Py_ssize_t res;"):
|
||||
raise SystemExit("_PyBytes_Find res must be declared in its entry group")
|
||||
if "Py_ssize_t res =" in find:
|
||||
raise SystemExit("_PyBytes_Find res declaration must be separate from assignment")
|
||||
if "if (res == -1) {\n Py_ssize_t last_align;" not in find:
|
||||
raise SystemExit("_PyBytes_Find last_align must be declared at the conditional block entry")
|
||||
if not (find.index("res = stringlib_find(") < find.index("if (res == -1)") < find.index("last_align = len_haystack - len_needle;") < find.index("memcmp(haystack + last_align")):
|
||||
raise SystemExit("_PyBytes_Find search and trailing-alignment order changed")
|
||||
|
||||
repeat = function_body("_PyBytes_Repeat(char* dest")
|
||||
if "else {\n Py_ssize_t copied;\n if (src != dest)" not in repeat:
|
||||
raise SystemExit("_PyBytes_Repeat copied must be declared at the else-block entry")
|
||||
if "Py_ssize_t copied =" in repeat:
|
||||
raise SystemExit("_PyBytes_Repeat copied declaration must be separate from assignment")
|
||||
if not (repeat.index("memcpy(dest, src, len_src);") < repeat.index("copied = len_src;") < repeat.index("while (copied < len_dest)")):
|
||||
raise SystemExit("_PyBytes_Repeat copy loop initialization order changed")
|
||||
print("bytes find/repeat C89 declarations and execution order: PASS")
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check debug-only before pointer is C89-declared in bytes formatting."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit("usage: vc6-bytes-format-c89.test.py bytesobject.c")
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
signature = "_PyBytes_FormatEx(const char *format, Py_ssize_t format_len,"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
branch_marker = "else {\n /* Got a format specifier */"
|
||||
branch_start = body.index(branch_marker)
|
||||
branch_end = body.index("\n fmt++;", branch_start)
|
||||
branch = body[branch_start:branch_end]
|
||||
expected_decl = "#ifndef NDEBUG\n char *before;\n#endif"
|
||||
if expected_decl not in branch:
|
||||
raise SystemExit("debug before pointer must be declared in the format-specifier block's declaration group")
|
||||
fmt_statement = body.index("\n fmt++;", branch_start)
|
||||
if branch_start + branch.index(expected_decl) > fmt_statement:
|
||||
raise SystemExit("debug before declaration occurs after executable statements")
|
||||
if source.count("char *before;") != 1 or "char *before = res;" in source:
|
||||
raise SystemExit("debug before pointer must have one declaration and a separate assignment")
|
||||
prepare = body.index("_PyBytesWriter_Prepare(&writer, res, alloc - 2)")
|
||||
assignment = body.index("before = res;")
|
||||
assertion = body.index("assert((res - before) == alloc);")
|
||||
if not (prepare < assignment < body.index("/* Write the sign if needed */") < assertion):
|
||||
raise SystemExit("bytes format pointer capture / output / size assertion order changed")
|
||||
print("bytes format debug pointer C89 declaration and capture order: PASS")
|
||||
@@ -0,0 +1,412 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check the first VC6 C89 frontier in Objects/call.c."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit("usage: vc6-call-first-c89.test.py call.c")
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
signature = "_PyObject_FastCallDictTstate(PyThreadState *tstate, PyObject *callable,"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_statement = body.index("assert(callable != NULL);")
|
||||
for declaration in ("Py_ssize_t nargs;", "vectorcallfunc func;", "PyObject *res;"):
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
|
||||
raise SystemExit("_PyObject_FastCallDictTstate locals must be declared before the first statement")
|
||||
if "Py_ssize_t nargs =" in body or "vectorcallfunc func =" in body:
|
||||
raise SystemExit("_PyObject_FastCallDictTstate declarations must be separate from assignments")
|
||||
order = (
|
||||
"assert(callable != NULL);",
|
||||
"assert(!_PyErr_Occurred(tstate));",
|
||||
"nargs = PyVectorcall_NARGS(nargsf);",
|
||||
"func = _PyVectorcall_Function(callable);",
|
||||
"if (func == NULL)",
|
||||
"if (kwargs == NULL || PyDict_GET_SIZE(kwargs) == 0)",
|
||||
"return _Py_CheckFunctionResult(tstate, callable, res, NULL);",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("vectorcall validation/dispatch/result-check order changed")
|
||||
|
||||
signature = "_PyObject_MakeTpCall(PyThreadState *tstate, PyObject *callable,"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_statement = body.index("assert(nargs >= 0);")
|
||||
for declaration in ("ternaryfunc call;", "PyObject *argstuple;", "PyObject *kwdict;", "PyObject *result;"):
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
|
||||
raise SystemExit("_PyObject_MakeTpCall locals must be declared before the first statement")
|
||||
if any(declaration in body for declaration in ("ternaryfunc call =", "PyObject *argstuple =", "PyObject *result =")):
|
||||
raise SystemExit("_PyObject_MakeTpCall locals must be declared separately from assignment")
|
||||
order = (
|
||||
"assert(nargs >= 0);",
|
||||
"assert(keywords == NULL || PyTuple_Check(keywords) || PyDict_Check(keywords));",
|
||||
"call = Py_TYPE(callable)->tp_call;",
|
||||
"if (call == NULL)",
|
||||
"argstuple = _PyTuple_FromArray(args, nargs);",
|
||||
"if (keywords == NULL || PyDict_Check(keywords))",
|
||||
"result = NULL;",
|
||||
"if (_Py_EnterRecursiveCallTstate(tstate,",
|
||||
"_Py_LeaveRecursiveCallTstate(tstate);",
|
||||
"\n Py_DECREF(argstuple);\n if (kwdict != keywords)",
|
||||
"if (kwdict != keywords)",
|
||||
"return _Py_CheckFunctionResult(tstate, callable, result, NULL);",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("tp_call argument construction/reference cleanup order changed")
|
||||
|
||||
signature = "_PyVectorcall_Call(PyThreadState *tstate, vectorcallfunc func,"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_statement = body.index("assert(func != NULL);")
|
||||
for declaration in ("Py_ssize_t nargs;", "PyObject *const *args;", "PyObject *kwnames;", "PyObject *result;"):
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
|
||||
raise SystemExit("_PyVectorcall_Call locals must be declared before the first statement")
|
||||
if "Py_ssize_t nargs =" in body or "PyObject *result =" in body:
|
||||
raise SystemExit("_PyVectorcall_Call declarations must be separate from assignments")
|
||||
order = (
|
||||
"assert(func != NULL);",
|
||||
"nargs = PyTuple_GET_SIZE(tuple);",
|
||||
"if (kwargs == NULL || PyDict_GET_SIZE(kwargs) == 0)",
|
||||
"args = _PyStack_UnpackDict(tstate,",
|
||||
"if (args == NULL)",
|
||||
"result = func(callable, args,",
|
||||
"_PyStack_UnpackDict_Free(args, nargs, kwnames);",
|
||||
"return _Py_CheckFunctionResult(tstate, callable, result, NULL);",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("vectorcall argument unpack/call/free/check order changed")
|
||||
|
||||
signature = "PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *kwargs)"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
for declaration in ("PyThreadState *tstate;", "Py_ssize_t offset;", "vectorcallfunc func;"):
|
||||
if body.count(declaration) != 1 or body.index(declaration) > body.index("tstate = _PyThreadState_GET();"):
|
||||
raise SystemExit("PyVectorcall_Call locals must be declared before initialization")
|
||||
if "PyThreadState *tstate =" in body or "Py_ssize_t offset =" in body:
|
||||
raise SystemExit("PyVectorcall_Call declarations must be separate from assignments")
|
||||
order = (
|
||||
"tstate = _PyThreadState_GET();",
|
||||
"offset = Py_TYPE(callable)->tp_vectorcall_offset;",
|
||||
"if (offset <= 0)",
|
||||
"assert(PyCallable_Check(callable));",
|
||||
"memcpy(&func, (char *) callable + offset, sizeof(func));",
|
||||
"if (func == NULL)",
|
||||
"return _PyVectorcall_Call(tstate, func, callable, tuple, kwargs);",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("PyVectorcall_Call validation/copy/dispatch order changed")
|
||||
|
||||
signature = "_PyObject_Call(PyThreadState *tstate, PyObject *callable,"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_statement = body.index("assert(!_PyErr_Occurred(tstate));")
|
||||
if body.count("vectorcallfunc vector_func;") != 1 or body.index("vectorcallfunc vector_func;") > first_statement:
|
||||
raise SystemExit("_PyObject_Call vectorcall local must be declared before validation")
|
||||
if "vectorcallfunc vector_func =" in body:
|
||||
raise SystemExit("_PyObject_Call vectorcall declaration must be separate from lookup")
|
||||
order = (
|
||||
"assert(!_PyErr_Occurred(tstate));",
|
||||
"assert(PyTuple_Check(args));",
|
||||
"vector_func = _PyVectorcall_Function(callable);",
|
||||
"if (vector_func != NULL)",
|
||||
"call = Py_TYPE(callable)->tp_call;",
|
||||
"if (call == NULL)",
|
||||
"if (_Py_EnterRecursiveCallTstate(tstate,",
|
||||
"result = (*call)(callable, args, kwargs);",
|
||||
"_Py_LeaveRecursiveCallTstate(tstate);",
|
||||
"return _Py_CheckFunctionResult(tstate, callable, result, NULL);",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("_PyObject_Call validation/dispatch/recursion order changed")
|
||||
|
||||
signature = "PyObject_CallOneArg(PyObject *func, PyObject *arg)"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_statement = body.index("assert(arg != NULL);")
|
||||
for declaration in ("PyObject *_args[2];", "PyObject **args;", "PyThreadState *tstate;", "size_t nargsf;"):
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
|
||||
raise SystemExit("PyObject_CallOneArg locals must be declared before the assertion")
|
||||
if any(text in body for text in ("PyObject **args =", "PyThreadState *tstate =", "size_t nargsf =")):
|
||||
raise SystemExit("PyObject_CallOneArg declarations must be separate from assignments")
|
||||
order = (
|
||||
"assert(arg != NULL);",
|
||||
"args = _args + 1;",
|
||||
"args[0] = arg;",
|
||||
"tstate = _PyThreadState_GET();",
|
||||
"nargsf = 1 | PY_VECTORCALL_ARGUMENTS_OFFSET;",
|
||||
"return _PyObject_VectorcallTstate(tstate, func, args, nargsf, NULL);",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("PyObject_CallOneArg vectorcall argument setup order changed")
|
||||
|
||||
signature = "_PyFunction_Vectorcall(PyObject *func, PyObject* const* stack,"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_statement = body.index("assert(PyFunction_Check(func));")
|
||||
for declaration in ("PyFunctionObject *f;", "Py_ssize_t nargs;", "PyThreadState *tstate;"):
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
|
||||
raise SystemExit("_PyFunction_Vectorcall locals must be declared before the assertion")
|
||||
if any(text in body for text in ("PyFunctionObject *f =", "Py_ssize_t nargs =", "PyThreadState *tstate =")):
|
||||
raise SystemExit("_PyFunction_Vectorcall declarations must be separate from assignments")
|
||||
order = (
|
||||
"assert(PyFunction_Check(func));",
|
||||
"f = (PyFunctionObject *)func;",
|
||||
"nargs = PyVectorcall_NARGS(nargsf);",
|
||||
"assert(nargs >= 0);",
|
||||
"tstate = _PyThreadState_GET();",
|
||||
"assert(nargs == 0 || stack != NULL);",
|
||||
"if (((PyCodeObject *)f->func_code)->co_flags & CO_OPTIMIZED)",
|
||||
"return _PyEval_Vector(tstate, f, NULL, stack, nargs, kwnames);",
|
||||
"return _PyEval_Vector(tstate, f, f->func_globals, stack, nargs, kwnames);",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("function vectorcall validation/state/dispatch order changed")
|
||||
|
||||
signature = "_PyObject_Call_Prepend(PyThreadState *tstate, PyObject *callable,"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_statement = body.index("assert(PyTuple_Check(args));")
|
||||
for declaration in (
|
||||
"PyObject *small_stack[_PY_FASTCALL_SMALL_STACK];",
|
||||
"PyObject **stack;",
|
||||
"Py_ssize_t argcount;",
|
||||
"PyObject *result;",
|
||||
):
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
|
||||
raise SystemExit("_PyObject_Call_Prepend locals must be declared before the assertion")
|
||||
if "Py_ssize_t argcount =" in body or "PyObject *result =" in body:
|
||||
raise SystemExit("_PyObject_Call_Prepend declarations must be separate from assignments")
|
||||
order = (
|
||||
"assert(PyTuple_Check(args));",
|
||||
"argcount = PyTuple_GET_SIZE(args);",
|
||||
"if (argcount + 1 <= (Py_ssize_t)Py_ARRAY_LENGTH(small_stack))",
|
||||
"stack = PyMem_Malloc((argcount + 1) * sizeof(PyObject *));",
|
||||
"stack[0] = obj;",
|
||||
"memcpy(&stack[1],",
|
||||
"result = _PyObject_FastCallDictTstate(tstate, callable,",
|
||||
"if (stack != small_stack)",
|
||||
"return result;",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("_PyObject_Call_Prepend stack/call/free order changed")
|
||||
|
||||
signature = "_PyObject_CallFunction_SizeT(PyObject *callable, const char *format, ...)"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_call = body.index("va_start(va, format);")
|
||||
for declaration in ("va_list va;", "PyObject *result;"):
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_call:
|
||||
raise SystemExit("_PyObject_CallFunction_SizeT locals must be declared before va_start")
|
||||
if "PyObject *result =" in body:
|
||||
raise SystemExit("_PyObject_CallFunction_SizeT result must be assigned after va_start")
|
||||
order = (
|
||||
"tstate = _PyThreadState_GET();",
|
||||
"va_start(va, format);",
|
||||
"result = _PyObject_CallFunctionVa(tstate, callable, format, va, 1);",
|
||||
"va_end(va);",
|
||||
"return result;",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("SizeT wrapper thread/va/result lifecycle order changed")
|
||||
|
||||
method_wrappers = (
|
||||
("PyObject_CallMethod(PyObject *obj, const char *name, const char *format, ...)", "PyObject_GetAttrString(obj, name)", 0),
|
||||
("PyEval_CallMethod(PyObject *obj, const char *name, const char *format, ...)", "PyObject_GetAttrString(obj, name)", 0),
|
||||
("_PyObject_CallMethod(PyObject *obj, PyObject *name,", "PyObject_GetAttr(obj, name)", 1),
|
||||
("_PyObject_CallMethodId(PyObject *obj, _Py_Identifier *name,", "_PyObject_GetAttrId(obj, name)", 0),
|
||||
("_PyObject_CallMethodFormat(PyThreadState *tstate, PyObject *callable,", None, 0),
|
||||
("_PyObject_CallMethod_SizeT(PyObject *obj, const char *name,", "PyObject_GetAttrString(obj, name)", 1),
|
||||
("_PyObject_CallMethodId_SizeT(PyObject *obj, _Py_Identifier *name,", "_PyObject_GetAttrId(obj, name)", 1),
|
||||
)
|
||||
for signature, lookup, size_t in method_wrappers:
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_statement = body.index("if (obj == NULL || name == NULL)") if lookup else body.index("va_start(va, format);")
|
||||
required = ["va_list va;", "PyObject *retval;"]
|
||||
if lookup:
|
||||
required.append("PyObject *callable;")
|
||||
for declaration in required:
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
|
||||
raise SystemExit(signature.split("(", 1)[0] + " locals must be declared before execution")
|
||||
if "PyObject *retval =" in body or (lookup and "PyObject *callable =" in body):
|
||||
raise SystemExit(signature.split("(", 1)[0] + " locals must be declared separately from assignments")
|
||||
if lookup:
|
||||
order = (
|
||||
"if (obj == NULL || name == NULL)",
|
||||
lookup + ";",
|
||||
"if (callable == NULL)",
|
||||
"va_start(va, format);",
|
||||
"retval = callmethod(tstate, callable, format, va, " + str(size_t) + ");",
|
||||
"va_end(va);",
|
||||
"Py_DECREF(callable);",
|
||||
"return retval;",
|
||||
)
|
||||
else:
|
||||
order = (
|
||||
"va_start(va, format);",
|
||||
"retval = callmethod(tstate, callable, format, va, " + str(size_t) + ");",
|
||||
"va_end(va);",
|
||||
"return retval;",
|
||||
)
|
||||
missing = [item for item in order if item not in body]
|
||||
if missing:
|
||||
raise SystemExit(signature.split("(", 1)[0] + " missing expected order text: " + repr(missing))
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit(signature.split("(", 1)[0] + " lookup/va/cleanup order changed")
|
||||
|
||||
signature = "PyObject_VectorcallMethod(PyObject *name, PyObject *const *args,"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_statement = body.index("assert(name != NULL);")
|
||||
for declaration in ("PyThreadState *tstate;", "PyObject *callable;", "int unbound;", "PyObject *result;"):
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
|
||||
raise SystemExit("PyObject_VectorcallMethod locals must be declared before validation")
|
||||
if "PyObject *callable =" in body or "int unbound =" in body or "PyObject *result =" in body:
|
||||
raise SystemExit("PyObject_VectorcallMethod declarations must be separate from initialization")
|
||||
order = (
|
||||
"assert(name != NULL);",
|
||||
"assert(args != NULL);",
|
||||
"assert(PyVectorcall_NARGS(nargsf) >= 1);",
|
||||
"tstate = _PyThreadState_GET();",
|
||||
"callable = NULL;",
|
||||
"unbound = _PyObject_GetMethod(args[0], name, &callable);",
|
||||
"if (callable == NULL)",
|
||||
"if (unbound)",
|
||||
"else {",
|
||||
"result = _PyObject_VectorcallTstate(tstate, callable,",
|
||||
"Py_DECREF(callable);",
|
||||
"return result;",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("vectorcall method lookup/argument adjustment/cleanup order changed")
|
||||
|
||||
method_objargs = (
|
||||
("PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...)", False),
|
||||
("_PyObject_CallMethodIdObjArgs(PyObject *obj, _Py_Identifier *name, ...)", True),
|
||||
)
|
||||
for signature, has_oname in method_objargs:
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_statement = body.index("if (obj == NULL || name == NULL)")
|
||||
required = ["PyObject *callable;", "int is_method;", "va_list vargs;", "PyObject *result;"]
|
||||
if has_oname:
|
||||
required.append("PyObject *oname;")
|
||||
for declaration in required:
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
|
||||
raise SystemExit(signature.split("(", 1)[0] + " locals must be declared before execution")
|
||||
if "PyObject *callable =" in body or "int is_method =" in body or "PyObject *result =" in body:
|
||||
raise SystemExit(signature.split("(", 1)[0] + " declarations must be separate from initialization")
|
||||
order = ["if (obj == NULL || name == NULL)"]
|
||||
if has_oname:
|
||||
order.extend(("oname = _PyUnicode_FromId(name);", "if (!oname)"))
|
||||
order.extend((
|
||||
"callable = NULL;",
|
||||
"is_method = _PyObject_GetMethod(obj, " + ("oname" if has_oname else "name") + ", &callable);",
|
||||
"if (callable == NULL)",
|
||||
"obj = is_method ? obj : NULL;",
|
||||
"va_start(vargs, name);",
|
||||
"result = object_vacall(tstate, obj, callable, vargs);",
|
||||
"va_end(vargs);",
|
||||
"Py_DECREF(callable);",
|
||||
"return result;",
|
||||
))
|
||||
missing = [item for item in order if item not in body]
|
||||
if missing:
|
||||
raise SystemExit(signature.split("(", 1)[0] + " missing expected order text: " + repr(missing))
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit(signature.split("(", 1)[0] + " lookup/va/cleanup order changed")
|
||||
|
||||
signature = "_PyStack_UnpackDict(PyThreadState *tstate,"
|
||||
start = source.rindex(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_statement = body.index("assert(nargs >= 0);")
|
||||
declarations = (
|
||||
"Py_ssize_t nkwargs;", "Py_ssize_t maxnargs;", "PyObject **stack;",
|
||||
"PyObject *kwnames;", "PyObject **kwstack;", "Py_ssize_t pos;",
|
||||
"Py_ssize_t i;", "PyObject *key;", "PyObject *value;",
|
||||
"unsigned long keys_are_strings;",
|
||||
)
|
||||
for declaration in declarations:
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
|
||||
raise SystemExit("_PyStack_UnpackDict locals must be declared before the first assertion")
|
||||
if "for (Py_ssize_t i =" in body or "Py_ssize_t nkwargs =" in body or "Py_ssize_t maxnargs =" in body:
|
||||
raise SystemExit("_PyStack_UnpackDict declaration/initialization separation failed")
|
||||
order = (
|
||||
"assert(nargs >= 0);",
|
||||
"assert(kwargs != NULL);",
|
||||
"nkwargs = PyDict_GET_SIZE(kwargs);",
|
||||
"maxnargs = PY_SSIZE_T_MAX / sizeof(args[0]) - 1;",
|
||||
"if (nargs > maxnargs - nkwargs)",
|
||||
"stack = PyMem_Malloc((1 + nargs + nkwargs) * sizeof(args[0]));",
|
||||
"kwnames = PyTuple_New(nkwargs);",
|
||||
"stack++; /* For PY_VECTORCALL_ARGUMENTS_OFFSET */",
|
||||
"for (i = 0; i < nargs; i++)",
|
||||
"kwstack = stack + nargs;",
|
||||
"pos = 0;",
|
||||
"\n i = 0;\n",
|
||||
"keys_are_strings = Py_TPFLAGS_UNICODE_SUBCLASS;",
|
||||
"while (PyDict_Next(kwargs, &pos, &key, &value))",
|
||||
"if (!keys_are_strings)",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("_PyStack_UnpackDict validation/allocation/iteration order changed: " + repr(list(zip(order, positions))))
|
||||
|
||||
signature = "_PyStack_UnpackDict_Free(PyObject *const *stack, Py_ssize_t nargs,"
|
||||
start = source.rindex(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
loop = body.index("for (i = 0;")
|
||||
if body.count("Py_ssize_t i;") != 1 or body.index("Py_ssize_t i;") > loop:
|
||||
raise SystemExit("_PyStack_UnpackDict_Free loop index must be declared before execution")
|
||||
if "for (Py_ssize_t i =" in body:
|
||||
raise SystemExit("_PyStack_UnpackDict_Free loop must use its entry-declared index")
|
||||
order = (
|
||||
"Py_ssize_t n = PyTuple_GET_SIZE(kwnames) + nargs;",
|
||||
"for (i = 0; i < n; i++)",
|
||||
"Py_DECREF(stack[i]);",
|
||||
"PyMem_Free((PyObject **)stack - 1);",
|
||||
"Py_DECREF(kwnames);",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("_PyStack_UnpackDict_Free release order changed")
|
||||
print("call.c C89 declarations and stack cleanup order: PASS")
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check C89 locals and mutation/cleanup order in method_vectorcall."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit("usage: vc6-classobject-c89.test.py classobject.c")
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
signature = "method_vectorcall(PyObject *method, PyObject *const *args,"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_statement = body.index("assert(Py_IS_TYPE(method, &PyMethod_Type));")
|
||||
for declaration in ("PyThreadState *tstate;", "PyObject *self;", "PyObject *func;", "Py_ssize_t nargs;", "PyObject *result;"):
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
|
||||
raise SystemExit("method_vectorcall locals must be declared before the assertion")
|
||||
if any(text in body for text in ("PyThreadState *tstate =", "PyObject *self =", "PyObject *func =", "Py_ssize_t nargs =")):
|
||||
raise SystemExit("method_vectorcall declarations must be separate from assignments")
|
||||
branch_start = body.index("if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) {")
|
||||
branch_end = body.index("\n else {", branch_start)
|
||||
branch = body[branch_start:branch_end]
|
||||
if "PyObject **newargs;\n PyObject *tmp;" not in branch:
|
||||
raise SystemExit("offset branch locals must be declared at block entry")
|
||||
if not (branch.index("newargs = (PyObject**)args - 1;") < branch.index("nargs += 1;") < branch.index("tmp = newargs[0];") < branch.index("newargs[0] = self;") < branch.index("result = _PyObject_VectorcallTstate") < branch.index("newargs[0] = tmp;")):
|
||||
raise SystemExit("offset argument mutation and restoration order changed")
|
||||
else_start = body.index("\n else {", branch_start)
|
||||
else_body = body[else_start:]
|
||||
first_exec = else_body.index("nkwargs =")
|
||||
for declaration in ("Py_ssize_t nkwargs;", "Py_ssize_t totalargs;", "PyObject *newargs_stack[_PY_FASTCALL_SMALL_STACK];", "PyObject **newargs;"):
|
||||
if else_body.count(declaration) != 1 or else_body.index(declaration) > first_exec:
|
||||
raise SystemExit("non-offset branch locals must be declared before initialization")
|
||||
if not (else_body.index("nkwargs =") < else_body.index("totalargs =") < else_body.index("if (totalargs == 0)") < else_body.index("if (totalargs <=") < else_body.index("newargs = newargs_stack;") < else_body.index("newargs[0] = self;") < else_body.index("result = _PyObject_VectorcallTstate") < else_body.index("if (newargs != newargs_stack)")):
|
||||
raise SystemExit("non-offset allocation/call/free order changed")
|
||||
|
||||
signature = "PyMethod_New(PyObject *func, PyObject *self)"
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
first_statement = body.index("if (self == NULL)")
|
||||
if body.count("PyMethodObject *im;") != 1 or body.index("PyMethodObject *im;") > first_statement:
|
||||
raise SystemExit("PyMethod_New object local must be declared before validation")
|
||||
if "PyMethodObject *im =" in body:
|
||||
raise SystemExit("PyMethod_New allocation must remain at its original execution point")
|
||||
order = (
|
||||
"if (self == NULL)",
|
||||
"im = PyObject_GC_New(PyMethodObject, &PyMethod_Type);",
|
||||
"if (im == NULL)",
|
||||
"im->im_weakreflist = NULL;",
|
||||
"Py_INCREF(func);",
|
||||
"im->im_func = func;",
|
||||
"Py_INCREF(self);",
|
||||
"im->im_self = self;",
|
||||
"im->vectorcall = method_vectorcall;",
|
||||
"_PyObject_GC_TRACK(im);",
|
||||
"return (PyObject *)im;",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("PyMethod_New allocation/reference/GC order changed")
|
||||
print("classobject C89 declarations and method initialization order: PASS")
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify legacy MSVC gets runtime-assigned classobject type fields."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
raise SystemExit("usage: vc6-classobject-typeinit.test.py classobject.c object.c")
|
||||
class_source = Path(sys.argv[1]).read_text()
|
||||
object_source = Path(sys.argv[2]).read_text()
|
||||
legacy_condition = "!defined(MS_WINDOWS) || !defined(_MSC_VER) || _MSC_VER >= 1300"
|
||||
for symbol in ("PyMethod_Type", "PyInstanceMethod_Type"):
|
||||
start = class_source.index("PyTypeObject " + symbol + " = {")
|
||||
end = class_source.index("\n};", start)
|
||||
initializer = class_source[start:end]
|
||||
if "#if " + legacy_condition not in initializer:
|
||||
raise SystemExit(symbol + " designated initializer must be excluded for VC6 Windows")
|
||||
|
||||
start = object_source.index("_PyTypes_InitTypes(PyInterpreterState *interp)")
|
||||
body_start = object_source.index("\n{\n", start) + 2
|
||||
body_end = object_source.index("\n}\n", body_start)
|
||||
body = object_source[body_start:body_end]
|
||||
ready = body.index("PyType_Ready(type)")
|
||||
call = body.index("_PyClassObject_InitTypes();")
|
||||
if call > ready:
|
||||
raise SystemExit("legacy type fields must be initialized before PyType_Ready")
|
||||
|
||||
helper = class_source.index("_PyClassObject_InitTypes(void)")
|
||||
helper_body_start = class_source.index("\n{\n", helper) + 2
|
||||
helper_body_end = class_source.index("\n}\n", helper_body_start)
|
||||
helper_body = class_source[helper_body_start:helper_body_end]
|
||||
assignments = {
|
||||
"PyMethod_Type": (
|
||||
"tp_name", "tp_basicsize", "tp_dealloc", "tp_vectorcall_offset", "tp_repr",
|
||||
"tp_hash", "tp_call", "tp_getattro", "tp_setattro", "tp_flags", "tp_doc",
|
||||
"tp_traverse", "tp_richcompare", "tp_weaklistoffset", "tp_methods", "tp_members",
|
||||
"tp_getset", "tp_new",
|
||||
),
|
||||
"PyInstanceMethod_Type": (
|
||||
"tp_name", "tp_basicsize", "tp_dealloc", "tp_repr", "tp_call", "tp_getattro",
|
||||
"tp_setattro", "tp_flags", "tp_doc", "tp_traverse", "tp_richcompare",
|
||||
"tp_members", "tp_getset", "tp_descr_get", "tp_new",
|
||||
),
|
||||
}
|
||||
for symbol, fields in assignments.items():
|
||||
for field in fields:
|
||||
assignment = symbol + "." + field + " ="
|
||||
if assignment not in helper_body:
|
||||
raise SystemExit("missing legacy setup assignment " + assignment)
|
||||
print("classobject VC6 type initialization coverage: PASS")
|
||||
@@ -0,0 +1,353 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check early codeobject.c C89 locals and evaluation order."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit("usage: vc6-codeobject-c89.test.py codeobject.c")
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
|
||||
def body_for(signature):
|
||||
start = source.index(signature)
|
||||
brace = source.index("{", start)
|
||||
depth = 0
|
||||
for pos in range(brace, len(source)):
|
||||
if source[pos] == "{":
|
||||
depth += 1
|
||||
elif source[pos] == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return source[brace + 1:pos]
|
||||
raise SystemExit("unterminated function " + signature)
|
||||
|
||||
body = body_for("intern_string_constants(PyObject *tuple, int *modified)")
|
||||
if body.count("Py_ssize_t i;") != 1 or "for (Py_ssize_t i =" in body:
|
||||
raise SystemExit("intern_string_constants loop index must be declared at function entry")
|
||||
if body.index("Py_ssize_t i;") > body.index("for (i ="):
|
||||
raise SystemExit("intern_string_constants loop index is declared too late")
|
||||
frozen = body[body.index("else if (PyFrozenSet_CheckExact(v))") :]
|
||||
if frozen.index("int tmp_modified;") > frozen.index("if (tmp == NULL)"):
|
||||
raise SystemExit("tmp_modified must be declared before the first statement in the frozen-set block")
|
||||
order = ("PyObject *tmp = PySequence_Tuple(v);", "if (tmp == NULL)", "tmp_modified = 0;", "intern_string_constants(tmp, &tmp_modified)")
|
||||
if [frozen.index(x) for x in order] != sorted(frozen.index(x) for x in order):
|
||||
raise SystemExit("frozen-set tuple construction/error/intern order changed")
|
||||
|
||||
body = body_for("get_localsplus_counts(PyObject *names, PyObject *kinds,")
|
||||
if body.count("int i;") != 1 or "for (int i =" in body:
|
||||
raise SystemExit("get_localsplus_counts loop index must be declared at function entry")
|
||||
if body.index("int i;") > body.index("for (i ="):
|
||||
raise SystemExit("get_localsplus_counts loop index is declared too late")
|
||||
|
||||
body = body_for("get_localsplus_names(PyCodeObject *co, _PyLocals_Kind kind, int num)")
|
||||
if "int index;" not in body or "int offset;" not in body:
|
||||
raise SystemExit("get_localsplus_names indices must be declared at function entry")
|
||||
if "for (int offset =" in body or body.index("int index;") > body.index("if (names == NULL)"):
|
||||
raise SystemExit("get_localsplus_names declarations must precede execution")
|
||||
order = ("if (names == NULL)", "index = 0;", "for (offset =", "_PyLocals_Kind k;", "k = _PyLocals_GetKind", "assert(index < num);", "name = PyTuple_GET_ITEM", "Py_INCREF(name);", "PyTuple_SET_ITEM(names, index, name);")
|
||||
if [body.index(x) for x in order] != sorted(body.index(x) for x in order):
|
||||
raise SystemExit("get_localsplus_names allocation/reference/insertion order changed")
|
||||
|
||||
body = body_for("_PyCode_Validate(struct _PyCodeConstructor *con)")
|
||||
for name in ("nlocals", "nplainlocals"):
|
||||
if "int " + name + ";" not in body:
|
||||
raise SystemExit("_PyCode_Validate " + name + " must be declared at function entry")
|
||||
if body.index("int nlocals;") > body.index("/* Check argument types */"):
|
||||
raise SystemExit("_PyCode_Validate locals must precede validation")
|
||||
if "int nplainlocals =" in body:
|
||||
raise SystemExit("_PyCode_Validate nplainlocals initializer must stay at its execution point")
|
||||
order = ("/* Check argument types */", "/* Make sure that code is indexable", "get_localsplus_counts(", "nplainlocals = nlocals -")
|
||||
if [body.index(x) for x in order] != sorted(body.index(x) for x in order):
|
||||
raise SystemExit("_PyCode_Validate validation/count computation order changed")
|
||||
|
||||
body = body_for("init_code(PyCodeObject *co, struct _PyCodeConstructor *con)")
|
||||
if "int entry_point;" not in body or body.index("int entry_point;") > body.index("get_localsplus_counts("):
|
||||
raise SystemExit("init_code entry_point must be declared before execution")
|
||||
order = ("memcpy(_PyCode_CODE(co)", "entry_point = 0;", "while (entry_point < Py_SIZE(co)", "co->_co_firsttraceable = entry_point;")
|
||||
if [body.index(x) for x in order] != sorted(body.index(x) for x in order):
|
||||
raise SystemExit("init_code byte copy / entry-point scan order changed")
|
||||
|
||||
body = body_for("remove_column_info(PyObject *locations)")
|
||||
for name in ("uint8_t *output;", "Py_ssize_t write_offset;"):
|
||||
if name not in body:
|
||||
raise SystemExit("remove_column_info must declare " + name)
|
||||
if body.index("uint8_t *output;") > body.index("if (res == NULL)"):
|
||||
raise SystemExit("remove_column_info output declaration must precede execution")
|
||||
loop = body[body.index("while (offset <") : body.index("\n }\n write_offset =")]
|
||||
if loop.index("int code;") > loop.index("write_offset = output -"):
|
||||
raise SystemExit("remove_column_info loop code local must be declared at block entry")
|
||||
order = ("if (res == NULL)", "output = (uint8_t *)PyBytes_AS_STRING(res);", "while (offset <", "code = (data[offset] >> 3) & 15;", "\n }\n write_offset = output -", "if (_PyBytes_Resize(&res, write_offset))")
|
||||
if [body.index(x) for x in order] != sorted(body.index(x) for x in order):
|
||||
raise SystemExit("remove_column_info allocation/resizing order changed")
|
||||
|
||||
body = body_for("_PyCode_New(struct _PyCodeConstructor *con)")
|
||||
for declaration in ("PyObject *replacement_locations;", "Py_ssize_t size;", "PyCodeObject *co;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("/* Ensure that strings are ready Unicode string */"):
|
||||
raise SystemExit("_PyCode_New entry declaration missing or late: " + declaration)
|
||||
if "PyObject *replacement_locations =" in body or "Py_ssize_t size =" in body or "PyCodeObject *co =" in body:
|
||||
raise SystemExit("_PyCode_New initializers must remain at their original execution points")
|
||||
order = ("/* Ensure that strings are ready Unicode string */", "intern_strings(con->names)", "intern_string_constants(con->consts", "replacement_locations = NULL;", "if (!_Py_GetConfig()->code_debug_ranges)", "replacement_locations = remove_column_info", "con->linetable = replacement_locations;", "size = PyBytes_GET_SIZE(con->code)", "co = PyObject_NewVar", "if (co == NULL)", "init_code(co, con);")
|
||||
if [body.index(x) for x in order] != sorted(body.index(x) for x in order):
|
||||
raise SystemExit("_PyCode_New validation/allocation order changed")
|
||||
if not (body.index("init_code(co, con);") < body.rindex("Py_XDECREF(replacement_locations);") < body.index("return co;")):
|
||||
raise SystemExit("_PyCode_New success cleanup order changed")
|
||||
|
||||
body = body_for("PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount,")
|
||||
for declaration in ("int nvarnames;", "int ncellvars;", "int nfreevars;", "int nlocalsplus;", "int offset;", "int i;", "int j;", "PyObject *local_name;", "int argoffset;", "int cmp;", "_PyLocals_Kind kind;", "struct _PyCodeConstructor con = {0};"):
|
||||
if declaration not in body or body.index(declaration) > body.index("if (varnames == NULL"):
|
||||
raise SystemExit("PyCode_NewWithPosOnlyArgs entry declaration missing or late: " + declaration)
|
||||
if "for (int " in body or "int nvarnames =" in body or "int offset =" in body:
|
||||
raise SystemExit("PyCode_NewWithPosOnlyArgs contains a C99 local declaration")
|
||||
fields = ("filename", "name", "qualname", "flags", "code", "firstlineno", "linetable", "consts", "names", "localsplusnames", "localspluskinds", "argcount", "posonlyargcount", "kwonlyargcount", "stacksize", "exceptiontable")
|
||||
field_positions = [body.index("con." + field + " =") for field in fields]
|
||||
if field_positions != sorted(field_positions):
|
||||
raise SystemExit("code constructor field initialization order changed")
|
||||
order = ("if (varnames == NULL", "nvarnames =", "ncellvars =", "nfreevars =", "nlocalsplus =", "localsplusnames = PyTuple_New", "localspluskinds = PyBytes_FromStringAndSize", "offset = 0;", "for (i = 0;", "for (i = 0;", "for (j = 0;", "for (i = 0;", "_PyTuple_Resize", "con.filename =", "_PyCode_Validate(&con)", "co = _PyCode_New(&con);", "error:", "Py_XDECREF(localsplusnames);", "Py_XDECREF(localspluskinds);")
|
||||
positions = []
|
||||
for item in order:
|
||||
positions.append(body.index(item, positions[-1] + 1 if positions else 0))
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("legacy constructor iteration/validation/cleanup order changed")
|
||||
|
||||
body = body_for("PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)")
|
||||
if "struct _PyCodeConstructor con = {0};" not in body:
|
||||
raise SystemExit("PyCode_NewEmpty constructor must be zero-initialized at function entry")
|
||||
fields = ("filename", "name", "qualname", "code", "firstlineno", "linetable", "consts", "names", "localsplusnames", "localspluskinds", "exceptiontable", "stacksize")
|
||||
field_positions = [body.index("con." + field + " =") for field in fields]
|
||||
if field_positions != sorted(field_positions):
|
||||
raise SystemExit("PyCode_NewEmpty constructor field order changed")
|
||||
order = ("nulltuple = PyTuple_New", "funcname_ob = PyUnicode_FromString", "filename_ob = PyUnicode_DecodeFSDefault", "code_ob = PyBytes_FromStringAndSize", "linetable_ob = PyBytes_FromStringAndSize", "con.filename =", "con.name =", "con.qualname =", "con.code =", "con.firstlineno =", "con.linetable =", "con.consts =", "con.names =", "con.localsplusnames =", "con.localspluskinds =", "con.exceptiontable =", "con.stacksize =", "result = _PyCode_New(&con);", "failed:", "Py_XDECREF(nulltuple);", "return result;")
|
||||
positions = [body.index(x) for x in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("PyCode_NewEmpty allocation/cleanup order changed")
|
||||
|
||||
body = body_for("_PyCode_CreateLineArray(PyCodeObject *co)")
|
||||
for declaration in ("PyCodeAddressRange bounds;", "int size;", "int max_line;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("assert(co->_co_linearray == NULL);"):
|
||||
raise SystemExit("line-array entry declaration missing or late: " + declaration)
|
||||
if body.count("for (int index =") != 0:
|
||||
raise SystemExit("line-array inner loop must not declare its index in the for header")
|
||||
order = ("assert(co->_co_linearray == NULL);", "max_line = 0;", "_PyCode_InitAddressRange(co, &bounds);", "while(_PyLineTable_NextAddressRange(&bounds))", "if (max_line < (1 << 15))", "co->_co_linearray = PyMem_Malloc", "if (co->_co_linearray == NULL)", "co->_co_linearray_entry_size = size;", "_PyCode_InitAddressRange(co, &bounds);", "for (index = start; index < end; index++)")
|
||||
positions = []
|
||||
for item in order:
|
||||
positions.append(body.index(item, positions[-1] + 1 if positions else 0))
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("line-array range/allocation/fill order changed")
|
||||
body = body_for("PyCode_Addr2Line(PyCodeObject *co, int addrq)")
|
||||
if body.count("PyCodeAddressRange bounds;") != 1 or body.index("PyCodeAddressRange bounds;") > body.index("if (addrq < 0)"):
|
||||
raise SystemExit("PyCode_Addr2Line bounds must be declared before execution")
|
||||
order = ("if (addrq < 0)", "return co->co_firstlineno;", "assert(addrq >= 0", "if (co->_co_linearray)", "return _PyCode_LineNumberFromArray", "_PyCode_InitAddressRange(co, &bounds);", "return _PyCode_CheckLineNumber(addrq, &bounds);")
|
||||
positions = [body.index(x) for x in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("PyCode_Addr2Line early-return/lookup/range initialization order changed")
|
||||
|
||||
body = body_for("_PyCode_InitAddressRange(PyCodeObject* co, PyCodeAddressRange *bounds)")
|
||||
first_statement = body.index("assert(co->co_linetable != NULL);")
|
||||
for declaration in ("const char *linetable;", "Py_ssize_t length;"):
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
|
||||
raise SystemExit("_PyCode_InitAddressRange entry declaration missing or late: " + declaration)
|
||||
order = (
|
||||
"assert(co->co_linetable != NULL);",
|
||||
"\n linetable = PyBytes_AS_STRING(co->co_linetable);",
|
||||
"\n length = PyBytes_GET_SIZE(co->co_linetable);",
|
||||
"_PyLineTable_InitAddressRange(linetable, length, co->co_firstlineno, bounds);",
|
||||
"return bounds->ar_line;",
|
||||
)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("_PyCode_InitAddressRange assertion/byte access/length/range initialization order changed")
|
||||
|
||||
body = body_for("previous_code_delta(PyCodeAddressRange *bounds)")
|
||||
if body.count("const uint8_t *ptr;") != 1 or body.index("const uint8_t *ptr;") > body.index("if (bounds->ar_start == 0)"):
|
||||
raise SystemExit("previous_code_delta ptr must be declared before execution")
|
||||
if "const uint8_t *ptr =" in body:
|
||||
raise SystemExit("previous_code_delta ptr assignment must remain after the first-entry return")
|
||||
order = ("if (bounds->ar_start == 0)", "return 1;", "ptr = bounds->opaque.lo_next-1;", "while (((*ptr) & 128) == 0)", "ptr--;", "return (((*ptr) & 7) + 1) * sizeof(_Py_CODEUNIT);")
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("previous_code_delta first-entry return and backward scan order changed")
|
||||
|
||||
body = body_for("advance_with_locations(PyCodeAddressRange *bounds, int *endline, int *column, int *endcolumn)")
|
||||
first_statement = body.index("ASSERT_VALID_BOUNDS(bounds);")
|
||||
for declaration in ("int first_byte;", "int code;"):
|
||||
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
|
||||
raise SystemExit("advance_with_locations entry declaration missing or late: " + declaration)
|
||||
if "int first_byte =" in body or "int code =" in body:
|
||||
raise SystemExit("advance_with_locations initializers must remain at their execution points")
|
||||
execution_prefix = """ASSERT_VALID_BOUNDS(bounds);
|
||||
first_byte = read_byte(bounds);
|
||||
code = (first_byte >> 3) & 15;
|
||||
bounds->ar_start = bounds->ar_end;
|
||||
bounds->ar_end = bounds->ar_start + ((first_byte & 7) + 1) * sizeof(_Py_CODEUNIT);
|
||||
switch(code) {"""
|
||||
if not body[first_statement:].startswith(execution_prefix):
|
||||
raise SystemExit("advance_with_locations assertion/read/decode/range/dispatch order changed")
|
||||
|
||||
body = body_for("PyCode_Addr2Location(PyCodeObject *co, int addrq,")
|
||||
if body.count("PyCodeAddressRange bounds;") != 1 or body.index("PyCodeAddressRange bounds;") > body.index("if (addrq < 0)"):
|
||||
raise SystemExit("PyCode_Addr2Location bounds must be declared before execution")
|
||||
order = (
|
||||
"if (addrq < 0)",
|
||||
"*start_line = *end_line = co->co_firstlineno;",
|
||||
"*start_column = *end_column = 0;",
|
||||
"return 1;",
|
||||
"assert(addrq >= 0 && addrq < _PyCode_NBYTES(co));",
|
||||
"_PyCode_InitAddressRange(co, &bounds);",
|
||||
"_PyCode_CheckLineNumber(addrq, &bounds);",
|
||||
"retreat(&bounds);",
|
||||
"advance_with_locations(&bounds, end_line, start_column, end_column);",
|
||||
"*start_line = bounds.ar_line;",
|
||||
"return 1;",
|
||||
)
|
||||
positions = []
|
||||
for item in order:
|
||||
positions.append(body.index(item, positions[-1] + 1 if positions else 0))
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("PyCode_Addr2Location early-return/assertion/range initialization/lookup order changed")
|
||||
|
||||
body = body_for("_PyLineTable_StartsLine(PyCodeAddressRange *range)")
|
||||
for declaration in ("const uint8_t *ptr;", "int code;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("if (range->ar_start <= 0)"):
|
||||
raise SystemExit("StartsLine entry declaration missing or late: " + declaration)
|
||||
order = ("if (range->ar_start <= 0)", "ptr = range->opaque.lo_next;", "do {", "ptr--;")
|
||||
positions = [body.index(x) for x in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("StartsLine backward-scan order changed")
|
||||
|
||||
body = body_for("emit_pair(PyObject **bytes, int *offset, int a, int b)")
|
||||
if "unsigned char *lnotab;" not in body or body.index("unsigned char *lnotab;") > body.index("if (*offset + 2 >= len)"):
|
||||
raise SystemExit("emit_pair lnotab pointer must be declared before possible resize")
|
||||
order = ("if (*offset + 2 >= len)", "if (_PyBytes_Resize(bytes, len * 2) < 0)", "lnotab = (unsigned char *) PyBytes_AS_STRING(*bytes);", "lnotab += *offset;", "*lnotab++ = a;", "*lnotab++ = b;", "*offset += 2;")
|
||||
if [body.index(x) for x in order] != sorted(body.index(x) for x in order):
|
||||
raise SystemExit("emit_pair resize/pointer/write order changed")
|
||||
|
||||
body = body_for("lineiter_next(lineiterator *li)")
|
||||
for declaration in ("PyObject *start;", "PyObject *end;", "PyObject *line;", "PyObject *result;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("if (!_PyLineTable_NextAddressRange(bounds))"):
|
||||
raise SystemExit("lineiter_next entry declaration missing or late: " + declaration)
|
||||
order = ("if (!_PyLineTable_NextAddressRange(bounds))", "start = NULL;", "end = NULL;", "line = NULL;", "result = PyTuple_New(3);", "start = PyLong_FromLong", "end = PyLong_FromLong", "if (bounds->ar_line < 0)", "if (result == NULL", "PyTuple_SET_ITEM(result, 0, start);", "return result;", "error:", "Py_XDECREF(start);", "Py_XDECREF(result);")
|
||||
if [body.index(x) for x in order] != sorted(body.index(x) for x in order):
|
||||
raise SystemExit("lineiter_next conversion/insertion/cleanup order changed")
|
||||
|
||||
# Extra-slot locals must be C89 declarations; preserve the complete execution body.
|
||||
body = body_for('_PyCode_GetExtra(PyObject *code, Py_ssize_t index, void **extra)')
|
||||
entry = 'PyCodeObject *o;\n_PyCodeObjectExtra *co_extra;'
|
||||
if " ".join(body.split()).startswith(" ".join(entry.split())) is False:
|
||||
raise SystemExit("_PyCode_GetExtra locals must be declared at function entry")
|
||||
execution = body
|
||||
for declaration in entry.splitlines():
|
||||
execution = execution.replace(declaration, "", 1)
|
||||
expected_execution = """if (!PyCode_Check(code)) {
|
||||
PyErr_BadInternalCall();
|
||||
return -1;
|
||||
}
|
||||
|
||||
o = (PyCodeObject*) code;
|
||||
co_extra = (_PyCodeObjectExtra*) o->co_extra;
|
||||
|
||||
if (co_extra == NULL || index < 0 || co_extra->ce_size <= index) {
|
||||
*extra = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
*extra = co_extra->ce_extras[index];
|
||||
return 0;"""
|
||||
if " ".join(execution.split()) != " ".join(expected_execution.split()):
|
||||
raise SystemExit("_PyCode_GetExtra branches/allocation/free/assignment order changed")
|
||||
|
||||
body = body_for('_PyCode_SetExtra(PyObject *code, Py_ssize_t index, void *extra)')
|
||||
entry = 'PyInterpreterState *interp = _PyInterpreterState_GET();\nPyCodeObject *o;\n_PyCodeObjectExtra *co_extra;\nPy_ssize_t i;\nfreefunc free;'
|
||||
if " ".join(body.split()).startswith(" ".join(entry.split())) is False:
|
||||
raise SystemExit("_PyCode_SetExtra locals must be declared at function entry")
|
||||
execution = body
|
||||
for declaration in entry.splitlines():
|
||||
execution = execution.replace(declaration, "", 1)
|
||||
expected_execution = """if (!PyCode_Check(code) || index < 0 ||
|
||||
index >= interp->co_extra_user_count) {
|
||||
PyErr_BadInternalCall();
|
||||
return -1;
|
||||
}
|
||||
|
||||
o = (PyCodeObject*) code;
|
||||
co_extra = (_PyCodeObjectExtra *) o->co_extra;
|
||||
|
||||
if (co_extra == NULL || co_extra->ce_size <= index) {
|
||||
i = (co_extra == NULL ? 0 : co_extra->ce_size);
|
||||
co_extra = PyMem_Realloc(
|
||||
co_extra,
|
||||
sizeof(_PyCodeObjectExtra) +
|
||||
(interp->co_extra_user_count-1) * sizeof(void*));
|
||||
if (co_extra == NULL) {
|
||||
return -1;
|
||||
}
|
||||
for (; i < interp->co_extra_user_count; i++) {
|
||||
co_extra->ce_extras[i] = NULL;
|
||||
}
|
||||
co_extra->ce_size = interp->co_extra_user_count;
|
||||
o->co_extra = co_extra;
|
||||
}
|
||||
|
||||
if (co_extra->ce_extras[index] != NULL) {
|
||||
free = interp->co_extra_freefuncs[index];
|
||||
if (free != NULL) {
|
||||
free(co_extra->ce_extras[index]);
|
||||
}
|
||||
}
|
||||
|
||||
co_extra->ce_extras[index] = extra;
|
||||
return 0;"""
|
||||
if " ".join(execution.split()) != " ".join(expected_execution.split()):
|
||||
raise SystemExit("_PyCode_SetExtra branches/allocation/free/assignment order changed")
|
||||
|
||||
body = body_for("deopt_code(_Py_CODEUNIT *instructions, Py_ssize_t len)")
|
||||
if "int i;" not in body or "for (int i =" in body:
|
||||
raise SystemExit("deopt_code loop index must be declared before the loop")
|
||||
order = ("for (i = 0; i < len; i++)", "_Py_CODEUNIT instruction = instructions[i];", "int opcode =", "int caches =", "instructions[i] =", "while (caches--)", "instructions[++i] =")
|
||||
if [body.index(x) for x in order] != sorted(body.index(x) for x in order):
|
||||
raise SystemExit("deopt_code instruction/cache rewrite order changed")
|
||||
|
||||
body = body_for("_PyCode_GetCode(PyCodeObject *co)")
|
||||
if "PyObject *code;" not in body or body.index("PyObject *code;") > body.index("if (co->_co_code != NULL)"):
|
||||
raise SystemExit("_PyCode_GetCode code object must be declared at entry")
|
||||
if "PyObject *code =" in body:
|
||||
raise SystemExit("_PyCode_GetCode allocation must remain after cache check")
|
||||
order = ("if (co->_co_code != NULL)", "return Py_NewRef(co->_co_code);", "code = PyBytes_FromStringAndSize", "if (code == NULL)", "deopt_code(", "co->_co_code = Py_NewRef(code);", "return code;")
|
||||
if [body.index(x) for x in order] != sorted(body.index(x) for x in order):
|
||||
raise SystemExit("_PyCode_GetCode cached return/allocation/deopt/cache order changed")
|
||||
|
||||
body = body_for("code_dealloc(PyCodeObject *co)")
|
||||
if "Py_ssize_t i;" not in body or "for (Py_ssize_t i =" in body:
|
||||
raise SystemExit("code_dealloc loop index must be declared before its loop")
|
||||
order = ("if (co->co_extra != NULL)", "Py_ssize_t i;", "for (i = 0; i < co_extra->ce_size; i++)", "free_extra = interp->co_extra_freefuncs[i];", "free_extra(co_extra->ce_extras[i]);", "PyMem_Free(co_extra);", "Py_XDECREF(co->co_consts);", "PyObject_Free(co);")
|
||||
if [body.index(x) for x in order] != sorted(body.index(x) for x in order):
|
||||
raise SystemExit("code_dealloc extra cleanup/refcount order changed")
|
||||
|
||||
body = body_for("code_richcompare(PyObject *self, PyObject *other, int op)")
|
||||
if "int i;" not in body or "for (int i =" in body:
|
||||
raise SystemExit("code_richcompare loop index must be declared before its loop")
|
||||
order = ("if ((op != Py_EQ", "co = (PyCodeObject *)self;", "cp = (PyCodeObject *)other;", "for (i = 0; i < Py_SIZE(co); i++)", "_Py_SET_OPCODE(co_instr", "eq = co_instr == cp_instr;", "i += _PyOpcode_Caches", "consts1 = _PyCode_ConstantKey", "Py_DECREF(consts1);", "Py_DECREF(consts2);", "Py_INCREF(res);", "return res;")
|
||||
if [body.index(x) for x in order] != sorted(body.index(x) for x in order):
|
||||
raise SystemExit("code_richcompare validation/instruction/constants order changed")
|
||||
|
||||
body = body_for("code_hash(PyCodeObject *co)")
|
||||
if "Py_hash_t h4, h5;" not in body or body.index("Py_hash_t h4, h5;") > body.index("h0 = PyObject_Hash"):
|
||||
raise SystemExit("code_hash h4/h5 must be declared before hash evaluation")
|
||||
if "Py_hash_t h4 =" in body or "Py_hash_t h5 =" in body:
|
||||
raise SystemExit("code_hash h4/h5 hash calls must remain at their original execution points")
|
||||
order = ("h3 = PyObject_Hash", "h4 = PyObject_Hash", "if (h4 == -1)", "h5 = PyObject_Hash", "if (h5 == -1)", "h = h0 ^ h1 ^ h2 ^ h3 ^ h4 ^ h5 ^")
|
||||
if [body.index(x) for x in order] != sorted(body.index(x) for x in order):
|
||||
raise SystemExit("code_hash error handling/hash combination order changed")
|
||||
body = body_for("code_replace_impl(PyCodeObject *self, int co_argcount,")
|
||||
for declaration in ("PyObject *code;", "PyCodeObject *co;", "PyObject *varnames;", "PyObject *cellvars;", "PyObject *freevars;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("#define CHECK_INT_ARG(ARG)"):
|
||||
raise SystemExit("code_replace_impl entry declaration missing or late: " + declaration)
|
||||
for initializer in ("PyObject *code =", "PyCodeObject *co =", "PyObject *varnames =", "PyObject *cellvars =", "PyObject *freevars ="):
|
||||
if initializer in body:
|
||||
raise SystemExit("code_replace_impl initializer must remain after validation/audit: " + initializer)
|
||||
order = ("CHECK_INT_ARG(co_firstlineno);", "code = NULL;", "if (co_code == NULL)", "if (PySys_Audit", "co = NULL;", "varnames = NULL;", "cellvars = NULL;", "freevars = NULL;", "if (co_varnames == NULL)", "if (co_cellvars == NULL)", "if (co_freevars == NULL)", "co = PyCode_NewWithPosOnlyArgs(", "error:")
|
||||
if [body.index(x) for x in order] != sorted(body.index(x) for x in order):
|
||||
raise SystemExit("code_replace_impl validation/audit/allocation order changed")
|
||||
cleanup = [body.rindex(x) for x in ("Py_XDECREF(code);", "Py_XDECREF(varnames);", "Py_XDECREF(cellvars);", "Py_XDECREF(freevars);")]
|
||||
if cleanup != sorted(cleanup) or cleanup[-1] > body.index("return (PyObject *)co;"):
|
||||
raise SystemExit("code_replace_impl cleanup order changed")
|
||||
|
||||
print("codeobject C89 declarations and ownership order: PASS")
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check classmethod_get C89 local placement and validation order."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
raise SystemExit("usage: vc6-descrobject-classmethod-c89.test.py descrobject.c object.c")
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
object_source = Path(sys.argv[2]).read_text()
|
||||
|
||||
def body_for(signature, text=None):
|
||||
if text is None:
|
||||
text = source
|
||||
start = text.index(signature)
|
||||
brace = text.index("{", start)
|
||||
depth = 0
|
||||
for pos in range(brace, len(text)):
|
||||
if text[pos] == "{":
|
||||
depth += 1
|
||||
elif text[pos] == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return text[brace + 1:pos]
|
||||
raise SystemExit("unterminated function: " + signature)
|
||||
|
||||
signature = "classmethod_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type)"
|
||||
body = body_for(signature)
|
||||
if "PyTypeObject *cls;" not in body or body.index("PyTypeObject *cls;") > body.index("if (type == NULL)"):
|
||||
raise SystemExit("classmethod_get cls must be declared before validation")
|
||||
if "PyTypeObject *cls =" in body:
|
||||
raise SystemExit("classmethod_get cls initialization must remain after subtype validation")
|
||||
order = ("if (type == NULL)", "if (!PyType_Check(type))", "if (!PyType_IsSubtype", "cls = NULL;", "if (descr->d_method->ml_flags & METH_METHOD)", "cls = descr->d_common.d_type;", "return PyCMethod_New(descr->d_method, type, NULL, cls);")
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("classmethod_get validation/class-selection/construction order changed")
|
||||
|
||||
|
||||
def require_entry_declarations(signature, declarations, first_statement):
|
||||
body = body_for(signature)
|
||||
first = body.index(first_statement)
|
||||
for declaration in declarations:
|
||||
if declaration not in body or body.index(declaration) > first:
|
||||
raise SystemExit(signature + " local missing or late: " + declaration)
|
||||
return body
|
||||
|
||||
body = require_entry_declarations("method_check_args(PyObject *func,", ("PyObject *self;",), "assert(!PyErr_Occurred());")
|
||||
if body.index("if (nargs < 1)") > body.index("self = args[0];") or body.index("self = args[0];") > body.index("descr_check((PyDescrObject *)func, self)"):
|
||||
raise SystemExit("method_check_args validation/self-check order changed")
|
||||
|
||||
wrappers = (
|
||||
("method_vectorcall_VARARGS(\n", ("PyObject *argstuple;", "PyCFunction meth;", "PyObject *result;"), "if (method_check_args(func, args, nargs, kwnames))", ("argstuple = _PyTuple_FromArray", "meth = (PyCFunction)method_enter_call", "result = _PyCFunction_TrampolineCall", "_Py_LeaveRecursiveCallTstate(tstate);", "return result;")),
|
||||
("method_vectorcall_VARARGS_KEYWORDS(\n", ("PyObject *argstuple;", "PyObject *result;", "PyObject *kwdict;", "PyCFunctionWithKeywords meth;"), "if (method_check_args(func, args, nargs, NULL))", ("argstuple = _PyTuple_FromArray", "result = NULL;", "kwdict = NULL;", "if (kwnames != NULL", "meth = (PyCFunctionWithKeywords)", "result = _PyCFunctionWithKeywords_TrampolineCall", "_Py_LeaveRecursiveCallTstate(tstate);", "exit:", "Py_DECREF(argstuple);", "Py_XDECREF(kwdict);", "return result;")),
|
||||
("method_vectorcall_FASTCALL_KEYWORDS_METHOD(\n", ("PyCMethod meth;", "PyObject *result;"), "if (method_check_args(func, args, nargs, NULL))", ("meth = (PyCMethod) method_enter_call", "result = meth(args[0],", "_Py_LeaveRecursiveCall();", "return result;")),
|
||||
("method_vectorcall_FASTCALL(\n", ("_PyCFunctionFast meth;", "PyObject *result;"), "if (method_check_args(func, args, nargs, kwnames))", ("meth = (_PyCFunctionFast)", "result = meth(args[0],", "_Py_LeaveRecursiveCallTstate(tstate);", "return result;")),
|
||||
("method_vectorcall_FASTCALL_KEYWORDS(\n", ("_PyCFunctionFastWithKeywords meth;", "PyObject *result;"), "if (method_check_args(func, args, nargs, NULL))", ("meth = (_PyCFunctionFastWithKeywords)", "result = meth(args[0],", "_Py_LeaveRecursiveCallTstate(tstate);", "return result;")),
|
||||
("method_vectorcall_NOARGS(\n", ("PyObject *funcstr;", "PyCFunction meth;", "PyObject *result;"), "if (method_check_args(func, args, nargs, kwnames))", ("if (nargs != 1)", "funcstr = _PyObject_FunctionStr(func);", "meth = (PyCFunction)method_enter_call", "result = _PyCFunction_TrampolineCall", "_Py_LeaveRecursiveCallTstate(tstate);", "return result;")),
|
||||
("method_vectorcall_O(\n", ("PyObject *funcstr;", "PyCFunction meth;", "PyObject *result;"), "if (method_check_args(func, args, nargs, kwnames))", ("if (nargs != 2)", "funcstr = _PyObject_FunctionStr(func);", "meth = (PyCFunction)method_enter_call", "result = _PyCFunction_TrampolineCall", "_Py_LeaveRecursiveCallTstate(tstate);", "return result;")),
|
||||
)
|
||||
for signature, declarations, first_statement, order in wrappers:
|
||||
body = require_entry_declarations(signature, declarations, first_statement)
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit(signature + " dispatch/cleanup order changed")
|
||||
|
||||
body = require_entry_declarations("classmethoddescr_call(PyMethodDescrObject *descr,", ("PyObject *self;", "PyObject *bound;", "PyObject *res;"), "if (argc < 1)")
|
||||
order = ("argc = PyTuple_GET_SIZE(args);", "if (argc < 1)", "self = PyTuple_GET_ITEM(args, 0);", "bound = classmethod_get(descr, NULL, self);", "if (bound == NULL)", "res = PyObject_VectorcallDict", "Py_DECREF(bound);", "return res;")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("classmethoddescr_call bind/call/decref order changed")
|
||||
mapping_guard = "#if !defined(MS_WINDOWS) || !defined(_MSC_VER) || _MSC_VER >= 1300"
|
||||
if mapping_guard not in source or "static PyNumberMethods mappingproxy_as_number = {0};" not in source:
|
||||
raise SystemExit("mappingproxy number table must avoid designated initialization on VC6")
|
||||
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)")
|
||||
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")
|
||||
|
||||
body = body_for("PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method)")
|
||||
for declaration in ("PyMethodDescrObject *descr;", "vectorcallfunc vectorcall;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("switch (method->ml_flags"):
|
||||
raise SystemExit("PyDescr_NewMethod local missing or late: " + declaration)
|
||||
if body.index("PyMethodDescrObject *descr;") > body.index("switch (method->ml_flags"):
|
||||
raise SystemExit("PyDescr_NewMethod descr must be declared before flag dispatch")
|
||||
order = ("switch (method->ml_flags", "vectorcall = method_vectorcall_VARARGS;", "default:", "descr = (PyMethodDescrObject *)descr_new", "descr->d_method = method;", "descr->vectorcall = vectorcall;", "return (PyObject *)descr;")
|
||||
positions = [body.index(item) for item in order]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("PyDescr_NewMethod dispatch/allocation/initialization order changed")
|
||||
body = body_for("property_set_name(PyObject *self, PyObject *args)")
|
||||
for declaration in ("propertyobject *prop;", "PyObject *name;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("if (PyTuple_GET_SIZE(args) != 2)"):
|
||||
raise SystemExit("property_set_name local missing or late: " + declaration)
|
||||
if "propertyobject *prop =" in body or "PyObject *name =" in body:
|
||||
raise SystemExit("property_set_name name/property assignments must remain after arity validation")
|
||||
order = ("if (PyTuple_GET_SIZE(args) != 2)", "prop = (propertyobject *)self;", "name = PyTuple_GET_ITEM(args, 1);", "Py_XINCREF(name);", "Py_XSETREF(prop->prop_name, name);", "Py_RETURN_NONE;")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("property_set_name arity/reference-replacement order changed")
|
||||
|
||||
body = body_for("property_descr_get(PyObject *self, PyObject *obj, PyObject *type)")
|
||||
if "propertyobject *gs;" not in body or body.index("propertyobject *gs;") > body.index("if (obj == NULL || obj == Py_None)"):
|
||||
raise SystemExit("property_descr_get gs must be declared before its early return")
|
||||
if "propertyobject *gs =" in body:
|
||||
raise SystemExit("property_descr_get property cast must remain after its early return")
|
||||
order = ("if (obj == NULL || obj == Py_None)", "return self;", "gs = (propertyobject *)self;", "if (gs->prop_get == NULL)", "PyObject *qualname = PyType_GetQualName", "return PyObject_CallOneArg(gs->prop_get, obj);")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("property_descr_get early return/getter error/call order changed")
|
||||
print("descrobject C89 declarations, dispatch, and VC6 type init: PASS")
|
||||
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check C89 declarations and logic order in unicode equality and dict checks."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
raise SystemExit("usage: vc6-dict-consistency-c89.test.py eq.h dictobject.c")
|
||||
eq_source = Path(sys.argv[1]).read_text()
|
||||
dict_source = Path(sys.argv[2]).read_text()
|
||||
|
||||
def body_for(source, signature):
|
||||
start = source.index(signature)
|
||||
brace = source.index("{", start)
|
||||
depth = 0
|
||||
for pos in range(brace, len(source)):
|
||||
if source[pos] == "{":
|
||||
depth += 1
|
||||
elif source[pos] == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return source[brace + 1:pos]
|
||||
raise SystemExit("unterminated function: " + signature)
|
||||
|
||||
body = body_for(eq_source, "unicode_eq(PyObject *aa, PyObject *bb)")
|
||||
for declaration in ("PyUnicodeObject *a;", "PyUnicodeObject *b;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("assert(PyUnicode_Check(aa));"):
|
||||
raise SystemExit("unicode_eq local missing or late: " + declaration)
|
||||
if "PyUnicodeObject *a =" in body or "PyUnicodeObject *b =" in body:
|
||||
raise SystemExit("unicode_eq object casts must remain after readiness assertions")
|
||||
order = ("assert(PyUnicode_Check(aa));", "assert(PyUnicode_Check(bb));", "assert(PyUnicode_IS_READY(aa));", "assert(PyUnicode_IS_READY(bb));", "a = (PyUnicodeObject *)aa;", "b = (PyUnicodeObject *)bb;", "if (PyUnicode_GET_LENGTH(a) !=", "return memcmp(")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("unicode_eq validation/cast/comparison order changed")
|
||||
|
||||
body = body_for(dict_source, "_PyDict_CheckConsistency(PyObject *op, int check_content)")
|
||||
declarations = ("PyDictObject *mp;", "PyDictKeysObject *keys;", "int splitted;", "Py_ssize_t usable;", "Py_ssize_t i;", "Py_ssize_t ix;", "PyDictKeyEntry *key_entries;", "PyDictKeyEntry *dict_entry;", "PyDictUnicodeEntry *unicode_entries;", "PyDictUnicodeEntry *unicode_entry;", "PyObject *key;", "Py_hash_t hash;", "int duplicate_check;", "int index;")
|
||||
for declaration in declarations:
|
||||
if declaration not in body or body.index(declaration) > body.index("assert(op != NULL);"):
|
||||
raise SystemExit("_PyDict_CheckConsistency local missing or late: " + declaration)
|
||||
for declaration in declarations:
|
||||
prefix = declaration.split(";")[0]
|
||||
if prefix + " =" in body:
|
||||
raise SystemExit("_PyDict_CheckConsistency initializer must remain at execution point: " + declaration)
|
||||
if "for (Py_ssize_t i" in body:
|
||||
raise SystemExit("_PyDict_CheckConsistency loop index must be declared before the loops")
|
||||
order = ("assert(op != NULL);", "CHECK(PyDict_Check(op));", "mp = (PyDictObject *)op;", "keys = mp->ma_keys;", "splitted = _PyDict_HasSplitTable(mp);", "usable = USABLE_FRACTION", "if (check_content)", "for (i=0; i < DK_SIZE(keys); i++)", "ix = dictkeys_get_index", "key_entries = DK_ENTRIES(keys);", "for (i=0; i < usable; i++)", "dict_entry = &key_entries[i];", "unicode_entries = DK_UNICODE_ENTRIES(keys);", "unicode_entry = &unicode_entries[i];", "duplicate_check = 0;", "for (i=0; i < mp->ma_used; i++)", "index = get_index_from_order(mp, i);", "return 1;")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("_PyDict_CheckConsistency validation/table traversal order changed")
|
||||
body = body_for(dict_source, "new_keys_object(uint8_t log2_size, bool unicode)")
|
||||
if "struct _Py_dict_state *state;" not in body or body.index("struct _Py_dict_state *state;") > body.index("assert(log2_size >= PyDict_LOG_MINSIZE);"):
|
||||
raise SystemExit("new_keys_object freelist state must be declared at function entry")
|
||||
if "struct _Py_dict_state *state =" in body:
|
||||
raise SystemExit("new_keys_object state lookup must remain after size calculation")
|
||||
order = ("assert(log2_size >= PyDict_LOG_MINSIZE);", "usable = USABLE_FRACTION", "if (log2_size < 8)", "state = get_dict_state();", "assert(state->keys_numfree != -1);", "if (log2_size == PyDict_LOG_MINSIZE", "dk = PyObject_Malloc", "dk->dk_refcnt = 1;")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("new_keys_object size/freelist/allocation initialization order changed")
|
||||
body = body_for(dict_source, "static void\nfree_keys_object(PyDictKeysObject *keys)")
|
||||
for declaration in ("PyDictUnicodeEntry *unicode_entries;", "PyDictKeyEntry *key_entries;", "Py_ssize_t i, n;", "struct _Py_dict_state *state;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("assert(keys != Py_EMPTY_KEYS);"):
|
||||
raise SystemExit("free_keys_object local missing or late: " + declaration)
|
||||
if "struct _Py_dict_state *state =" in body or "for (Py_ssize_t i" in body:
|
||||
raise SystemExit("free_keys_object assignments and indices must remain C89-compatible")
|
||||
order = ("assert(keys != Py_EMPTY_KEYS);", "if (DK_IS_UNICODE(keys))", "unicode_entries = DK_UNICODE_ENTRIES(keys);", "for (i = 0, n = keys->dk_nentries;", "else {", "key_entries = DK_ENTRIES(keys);", "for (i = 0, n = keys->dk_nentries;", "state = get_dict_state();", "assert(state->keys_numfree != -1);", "if (DK_LOG_SIZE(keys) ==", "PyObject_Free(keys);")
|
||||
positions = []
|
||||
for item in order:
|
||||
positions.append(body.index(item, positions[-1] + 1 if positions else 0))
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("free_keys_object entry traversal/freelist/free order changed")
|
||||
|
||||
body = body_for(dict_source, "new_values(Py_ssize_t size)")
|
||||
for declaration in ("size_t prefix_size;", "size_t n;", "uint8_t *mem;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("assert(size > 0);"):
|
||||
raise SystemExit("new_values local missing or late: " + declaration)
|
||||
if "size_t prefix_size =" in body or "size_t n =" in body or "uint8_t *mem =" in body:
|
||||
raise SystemExit("new_values arithmetic/allocation must remain after its assertions")
|
||||
order = ("assert(size > 0);", "prefix_size = _Py_SIZE_ROUND_UP", "assert(prefix_size < 256);", "n = prefix_size +", "mem = PyMem_Malloc(n);", "if (mem == NULL)", "mem[prefix_size-1] =", "return (PyDictValues*)")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("new_values size/assert/allocation/initialization order changed")
|
||||
|
||||
body = body_for(dict_source, "new_dict(PyDictKeysObject *keys,")
|
||||
if "struct _Py_dict_state *state;" not in body or body.index("struct _Py_dict_state *state;") > body.index("assert(keys != NULL);"):
|
||||
raise SystemExit("new_dict freelist state must be declared at function entry")
|
||||
if "struct _Py_dict_state *state =" in body:
|
||||
raise SystemExit("new_dict state lookup must remain at its execution point")
|
||||
order = ("assert(keys != NULL);", "state = get_dict_state();", "assert(state->numfree != -1);", "if (state->numfree)", "mp = state->free_list", "mp = PyObject_GC_New", "mp->ma_keys = keys;", "ASSERT_CONSISTENT(mp);", "return (PyObject *)mp;")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("new_dict freelist/allocation/object initialization order changed")
|
||||
body = body_for(dict_source, "clone_combined_dict_keys(PyDictObject *orig)")
|
||||
declarations = ("size_t keys_size;", "PyDictKeysObject *keys;", "PyObject **pkey;", "PyObject **pvalue;", "size_t offs;", "Py_ssize_t n;", "Py_ssize_t i;", "PyDictUnicodeEntry *unicode_ep0;", "PyDictKeyEntry *key_ep0;")
|
||||
for declaration in declarations:
|
||||
if declaration not in body or body.index(declaration) > body.index("assert(PyDict_Check(orig));"):
|
||||
raise SystemExit("clone_combined_dict_keys local missing or late: " + declaration)
|
||||
for initializer in ("size_t keys_size =", "PyDictKeysObject *keys =", "PyObject **pkey,", "size_t offs;\n if", "Py_ssize_t n =", "for (Py_ssize_t i ="):
|
||||
if initializer in body:
|
||||
raise SystemExit("clone_combined_dict_keys local initializer/declaration remains late: " + initializer)
|
||||
order = ("assert(PyDict_Check(orig));", "keys_size = _PyDict_KeysSize", "keys = PyObject_Malloc(keys_size);", "memcpy(keys, orig->ma_keys, keys_size);", "if (DK_IS_UNICODE(orig->ma_keys))", "unicode_ep0 = DK_UNICODE_ENTRIES(keys);", "pkey = &unicode_ep0->me_key;", "else {", "key_ep0 = DK_ENTRIES(keys);", "pkey = &key_ep0->me_key;", "n = keys->dk_nentries;", "for (i = 0; i < n; i++)", "Py_INCREF(value);", "pvalue += offs;", "_Py_RefTotal++;", "return keys;")
|
||||
positions = []
|
||||
for item in order:
|
||||
positions.append(body.index(item, positions[-1] + 1 if positions else 0))
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("clone_combined_dict_keys assertions/copy/reference transfer order changed")
|
||||
body = body_for(dict_source, "unicodekeys_lookup_generic(PyDictObject *mp,")
|
||||
if "int cmp;" not in body or body.index("int cmp;") > body.index("for (;;)"):
|
||||
raise SystemExit("unicodekeys_lookup_generic comparison result must be declared before probing")
|
||||
if "int cmp =" in body:
|
||||
raise SystemExit("unicodekeys_lookup_generic compare must remain after retaining the key")
|
||||
order = ("if (unicode_get_hash(ep->me_key) == hash)", "PyObject *startkey = ep->me_key;", "Py_INCREF(startkey);", "cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);", "Py_DECREF(startkey);", "if (cmp < 0)", "if (dk == mp->ma_keys && ep->me_key == startkey)")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("unicodekeys_lookup_generic retain/compare/decref/restart order changed")
|
||||
body = body_for(dict_source, "dictkeys_generic_lookup(PyDictObject *mp,")
|
||||
if "int cmp;" not in body or body.index("int cmp;") > body.index("for (;;)"):
|
||||
raise SystemExit("dictkeys_generic_lookup comparison result must be declared before probing")
|
||||
if "int cmp =" in body:
|
||||
raise SystemExit("dictkeys_generic_lookup compare must remain after retaining the key")
|
||||
order = ("if (ep->me_hash == hash)", "PyObject *startkey = ep->me_key;", "Py_INCREF(startkey);", "cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);", "Py_DECREF(startkey);", "if (cmp < 0)", "if (dk == mp->ma_keys && ep->me_key == startkey)")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("dictkeys_generic_lookup retain/compare/decref/restart order changed")
|
||||
body = body_for(dict_source, "_PyDictKeys_StringLookup(PyDictKeysObject* dk,")
|
||||
for declaration in ("DictKeysKind kind;", "Py_hash_t hash;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("kind = dk->dk_kind;"):
|
||||
raise SystemExit("_PyDictKeys_StringLookup local missing or late: " + declaration)
|
||||
if "Py_hash_t hash =" in body:
|
||||
raise SystemExit("_PyDictKeys_StringLookup hash calculation must remain after key validation")
|
||||
order = ("kind = dk->dk_kind;", "if (!PyUnicode_CheckExact(key)", "hash = unicode_get_hash(key);", "if (hash == -1)", "return unicodekeys_lookup_unicode(dk, key, hash);")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("_PyDictKeys_StringLookup validation/hash/fallback order changed")
|
||||
|
||||
body = body_for(dict_source, "find_empty_slot(PyDictKeysObject *keys,")
|
||||
for declaration in ("size_t mask;", "size_t i;", "size_t perturb;", "Py_ssize_t ix;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("assert(keys != NULL);"):
|
||||
raise SystemExit("find_empty_slot local missing or late: " + declaration)
|
||||
if "for (size_t perturb" in body:
|
||||
raise SystemExit("find_empty_slot perturb index must be declared before probing")
|
||||
order = ("assert(keys != NULL);", "mask = DK_MASK(keys);", "i = hash & mask;", "ix = dictkeys_get_index(keys, i);", "for (perturb = hash;", "return i;")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("find_empty_slot validation/probe order changed")
|
||||
|
||||
body = body_for(dict_source, "insert_into_dictkeys(PyDictKeysObject *keys,")
|
||||
for declaration in ("Py_hash_t hash;", "Py_ssize_t ix;", "Py_ssize_t hashpos;", "PyDictUnicodeEntry *ep;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("assert(PyUnicode_CheckExact(name));"):
|
||||
raise SystemExit("insert_into_dictkeys local missing or late: " + declaration)
|
||||
if any(token in body for token in ("Py_hash_t hash =", "Py_ssize_t ix =", "Py_ssize_t hashpos =", "PyDictUnicodeEntry *ep =")):
|
||||
raise SystemExit("insert_into_dictkeys insertion state must be assigned at its execution point")
|
||||
order = ("assert(PyUnicode_CheckExact(name));", "hash = unicode_get_hash(name);", "ix = unicodekeys_lookup_unicode", "Py_INCREF(name);", "hashpos = find_empty_slot(keys, hash);", "dictkeys_set_index(keys, hashpos, ix);", "ep->me_key = name;")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("insert_into_dictkeys hash/refcount/table-update order changed")
|
||||
|
||||
body = body_for(dict_source, "insert_to_emptydict(PyDictObject *mp,")
|
||||
for declaration in ("int unicode;", "PyDictKeysObject *newkeys;", "size_t hashpos;", "PyDictUnicodeEntry *unicode_ep;", "PyDictKeyEntry *key_ep;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("assert(mp->ma_keys == Py_EMPTY_KEYS);"):
|
||||
raise SystemExit("insert_to_emptydict local missing or late: " + declaration)
|
||||
order = ("assert(mp->ma_keys == Py_EMPTY_KEYS);", "unicode = PyUnicode_CheckExact(key);", "newkeys = new_keys_object", "if (newkeys == NULL)", "dictkeys_decref(Py_EMPTY_KEYS);", "MAINTAIN_TRACKING(mp, key, value);", "hashpos = (size_t)hash", "dictkeys_set_index", "if (unicode)", "mp->ma_used++;")
|
||||
if [body.index(item) for item in order] != sorted(body.index(item) for item in order):
|
||||
raise SystemExit("insert_to_emptydict validation/allocation/tracking/insertion order changed")
|
||||
|
||||
for signature in ("build_indices_generic(PyDictKeysObject *keys,", "build_indices_unicode(PyDictKeysObject *keys,"):
|
||||
body = body_for(dict_source, signature)
|
||||
for declaration in ("size_t mask;", "size_t i;", "size_t perturb;", "Py_ssize_t ix;", "Py_hash_t hash;"):
|
||||
if declaration not in body or body.index(declaration) > body.index("mask = DK_MASK(keys);"):
|
||||
raise SystemExit("build_indices local missing or late: " + declaration)
|
||||
if any(token in body for token in ("for (Py_ssize_t ix", "for (size_t perturb", "size_t i = hash")):
|
||||
raise SystemExit("build_indices loop locals must be declared at function entry")
|
||||
if body.index("for (ix = 0; ix != n") > body.index("dictkeys_set_index(keys, i, ix);"):
|
||||
raise SystemExit("build_indices table index insertion order changed")
|
||||
|
||||
body = body_for(dict_source, "dictresize(PyDictObject *mp, uint8_t log2_newsize, int unicode)")
|
||||
declarations = ("Py_ssize_t numentries;", "Py_ssize_t i;", "int index;", "PyDictUnicodeEntry *old_unicode_entries;", "PyDictUnicodeEntry *new_unicode_entries;", "PyDictUnicodeEntry *unicode_entry;", "PyDictKeyEntry *old_key_entries;", "PyDictKeyEntry *new_key_entries;", "PyDictKeyEntry *key_entry;", "struct _Py_dict_state *state;")
|
||||
for declaration in declarations:
|
||||
if declaration not in body or body.index(declaration) > body.index("if (log2_newsize >="):
|
||||
raise SystemExit("dictresize local missing or late: " + declaration)
|
||||
if any(token in body for token in ("for (Py_ssize_t i", "PyDictKeyEntry *newentries =", "PyDictUnicodeEntry *newentries =", "struct _Py_dict_state *state =")):
|
||||
raise SystemExit("dictresize branch/loop locals must be declared at function entry")
|
||||
order = ("if (log2_newsize >=", "oldkeys = mp->ma_keys;", "mp->ma_keys = new_keys_object", "numentries = mp->ma_used;", "if (oldvalues != NULL)", "old_unicode_entries = DK_UNICODE_ENTRIES(oldkeys);", "dictkeys_decref(oldkeys);", "else { // oldkeys is combined.", "old_key_entries = DK_ENTRIES(oldkeys);", "old_unicode_entries = DK_UNICODE_ENTRIES(oldkeys);", "state = get_dict_state();", "mp->ma_keys->dk_usable -= numentries;", "ASSERT_CONSISTENT(mp);", "return 0;")
|
||||
positions = []
|
||||
for item in order:
|
||||
positions.append(body.index(item, positions[-1] + 1 if positions else 0))
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("dictresize allocation/conversion/freelist/commit order changed")
|
||||
body = body_for(dict_source, "dict_keys(PyDictObject *mp)")
|
||||
opening, execution = body.split("again:", 1)
|
||||
if opening.split() != "PyObject *v; Py_ssize_t n; Py_ssize_t j, pos; PyObject *key;".split():
|
||||
raise SystemExit("dict_keys locals must be declared at function entry without initializers")
|
||||
expected_execution = """
|
||||
n = mp->ma_used;
|
||||
v = PyList_New(n);
|
||||
if (v == NULL)
|
||||
return NULL;
|
||||
if (n != mp->ma_used) {
|
||||
/* Durnit. The allocations caused the dict to resize.
|
||||
* Just start over, this shouldn't normally happen.
|
||||
*/
|
||||
Py_DECREF(v);
|
||||
goto again;
|
||||
}
|
||||
|
||||
/* Nothing we do below makes any function calls. */
|
||||
j = 0;
|
||||
pos = 0;
|
||||
while (_PyDict_Next((PyObject*)mp, &pos, &key, NULL, NULL)) {
|
||||
assert(j < n);
|
||||
Py_INCREF(key);
|
||||
PyList_SET_ITEM(v, j, key);
|
||||
j++;
|
||||
}
|
||||
assert(j == n);
|
||||
return v;
|
||||
"""
|
||||
if execution.split() != expected_execution.split():
|
||||
raise SystemExit("dict_keys allocation/retry/initialization/iteration/refcount order changed")
|
||||
|
||||
print("dict consistency C89 declarations and logic order: PASS")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check C89 declaration placement in immutable stringlib partitions."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit("usage: vc6-stringlib-partition-c89.test.py partition.h")
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
expected_orders = {
|
||||
"partition": (
|
||||
"Py_INCREF(str_obj);",
|
||||
"empty = (PyObject*)STRINGLIB_GET_EMPTY();",
|
||||
"assert(empty != NULL);",
|
||||
"Py_INCREF(empty);",
|
||||
"PyTuple_SET_ITEM(out, 1, empty);",
|
||||
"Py_INCREF(empty);",
|
||||
"PyTuple_SET_ITEM(out, 2, empty);",
|
||||
),
|
||||
"rpartition": (
|
||||
"empty = (PyObject*)STRINGLIB_GET_EMPTY();",
|
||||
"assert(empty != NULL);",
|
||||
"Py_INCREF(empty);",
|
||||
"PyTuple_SET_ITEM(out, 0, empty);",
|
||||
"Py_INCREF(empty);",
|
||||
"PyTuple_SET_ITEM(out, 1, empty);",
|
||||
"Py_INCREF(str_obj);",
|
||||
"PyTuple_SET_ITEM(out, 2, (PyObject*) str_obj);",
|
||||
),
|
||||
}
|
||||
for name, order in expected_orders.items():
|
||||
signature = "STRINGLIB(" + name + ")("
|
||||
start = source.index(signature)
|
||||
body_start = source.index("\n{\n", start) + 2
|
||||
body_end = source.index("\n}\n", body_start)
|
||||
body = source[body_start:body_end]
|
||||
opening, no_match = body.split(" if (pos < 0)", 1)
|
||||
if "#if !STRINGLIB_MUTABLE\n PyObject *empty;\n#endif" not in opening:
|
||||
raise SystemExit(name + ": empty must be declared in the C89 entry declaration group")
|
||||
if body.count("PyObject *empty;") != 1 or "PyObject *empty =" in body:
|
||||
raise SystemExit(name + ": empty must have one separate declaration")
|
||||
branch_start = no_match.index("#else")
|
||||
branch_end = no_match.index("#endif", branch_start)
|
||||
branch = no_match[branch_start:branch_end]
|
||||
cursor = 0
|
||||
for statement in order:
|
||||
position = branch.find(statement, cursor)
|
||||
if position < 0:
|
||||
raise SystemExit(name + ": empty reference-count / tuple insertion order changed")
|
||||
cursor = position + len(statement)
|
||||
print("stringlib partition C89 declarations and ownership order: PASS")
|
||||
Reference in New Issue
Block a user