#!/usr/bin/env python3 """Compile the exact set types as C89 and verify every static slot.""" import re import subprocess import sys import tempfile from pathlib import Path source_path = Path(sys.argv[1]) source = source_path.read_text() initializers = {} for name in ("PySet_Type", "PyFrozenSet_Type"): match = re.search(r"PyTypeObject " + name + r" = \{.*?\n\};", source, re.S) assert match, name + " initializer not found" initializers[name] = match.group(0) header = (source_path.parent.parent / "Include/cpython/object.h").read_text() layout = re.search(r"struct _typeobject \{(.*?)\n\};", header, re.S) assert layout, "PyTypeObject layout not found" body = re.sub(r"/\*.*?\*/|//[^\n]*", "", layout.group(1), flags=re.S) fields = re.findall(r"\b(tp_\w+)\b", body) assert len(fields) == len(set(fields)) == 48, "review changed PyTypeObject layout" expected = { "tp_basicsize": "sizeof(PySetObject)", "tp_dealloc": "(destructor)set_dealloc", "tp_repr": "(reprfunc)set_repr", "tp_as_sequence": "&set_as_sequence", "tp_getattro": "PyObject_GenericGetAttr", "tp_flags": "Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE | _Py_TPFLAGS_MATCH_SELF", "tp_traverse": "(traverseproc)set_traverse", "tp_clear": "(inquiry)set_clear_internal", "tp_richcompare": "(richcmpfunc)set_richcompare", "tp_weaklistoffset": "offsetof(PySetObject, weakreflist)", "tp_iter": "(getiterfunc)set_iter", "tp_alloc": "PyType_GenericAlloc", "tp_free": "PyObject_GC_Del", } assert expected.keys() <= set(fields) # Use the real field declarations/order, with minimal stand-ins for dependencies. harness = r''' #include #include #include typedef long Py_ssize_t; typedef struct { int unused; } PyObject; typedef struct { int unused; } PyAsyncMethods; typedef struct { int unused; } PyNumberMethods; typedef struct { int unused; } PySequenceMethods; typedef struct { int unused; } PyMappingMethods; typedef struct { int unused; } PyBufferProcs; typedef struct { int unused; } PyMethodDef; typedef struct { int unused; } PyMemberDef; typedef struct { int unused; } PyGetSetDef; typedef struct _typeobject PyTypeObject; typedef struct { long head; PyObject *weakreflist; } PySetObject; #define PyObject_VAR_HEAD long ob_refcnt; PyTypeObject *ob_type; long ob_size; #define PyVarObject_HEAD_INIT(type, size) 1, type, size, #define Py_TPFLAGS_DEFAULT 1UL #define Py_TPFLAGS_HAVE_GC 2UL #define Py_TPFLAGS_BASETYPE 4UL #define _Py_TPFLAGS_MATCH_SELF 8UL ''' for name in ( "destructor getattrfunc setattrfunc reprfunc hashfunc ternaryfunc " "getattrofunc setattrofunc traverseproc inquiry richcmpfunc getiterfunc " "iternextfunc descrgetfunc descrsetfunc initproc allocfunc newfunc " "freefunc vectorcallfunc" ).split(): harness += f"typedef void (*{name})(void);\n" harness += "struct _typeobject {" + body + "};\n" for name in ( "set_new frozenset_new set_dealloc set_repr PyObject_HashNotImplemented " "frozenset_hash PyObject_GenericGetAttr set_traverse set_clear_internal " "set_richcompare set_iter set_init PyType_GenericAlloc PyObject_GC_Del " "set_vectorcall frozenset_vectorcall" ).split(): harness += f"static void {name}(void) {{ }}\n" harness += "static PyTypeObject PyType_Type;\n" harness += "static PySequenceMethods set_as_sequence;\n" for prefix in ("set", "frozenset"): harness += f"static PyNumberMethods {prefix}_as_number;\n" harness += f"static PyMethodDef {prefix}_methods[1];\n" harness += f'static const char {prefix}_doc[] = "{prefix} documentation";\n' harness += "\n".join(initializers.values()) + "\nint main(void)\n{\n" for name, prefix in (("PySet_Type", "set"), ("PyFrozenSet_Type", "frozenset")): values = dict(expected, tp_name=f'"{prefix}"', tp_as_number=f"&{prefix}_as_number", tp_doc=f"{prefix}_doc", tp_methods=f"{prefix}_methods", tp_new=f"{prefix}_new", tp_vectorcall=f"{prefix}_vectorcall", tp_hash="PyObject_HashNotImplemented" if prefix == "set" else "frozenset_hash", tp_init="(initproc)set_init" if prefix == "set" else "0") assert values.keys() <= set(fields) harness += f" assert({name}.ob_refcnt == 1);\n" harness += f" assert({name}.ob_type == &PyType_Type);\n" harness += f" assert({name}.ob_size == 0);\n" for field in fields: value = values.get(field, "0") if field in ("tp_name", "tp_doc"): harness += f" assert(strcmp({name}.{field}, {value}) == 0);\n" else: harness += f" assert({name}.{field} == ({value}));\n" harness += " return 0;\n}\n" with tempfile.TemporaryDirectory(prefix="vc6-setobject-type-vectorcall-") as directory: path = Path(directory) (path / "test.c").write_text(harness) result = subprocess.run([ "cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror", str(path / "test.c"), "-o", str(path / "test"), ]) if result.returncode: sys.exit(result.returncode) subprocess.run([str(path / "test")], check=True) print("PySet_Type and PyFrozenSet_Type C89 compile and all 48 semantic field mappings each: PASS")