#!/usr/bin/env python3 """Compile the exact setiter_reduce as C89 and check ownership and call order.""" import re import subprocess import sys import tempfile from pathlib import Path source = Path(sys.argv[1]).read_text() match = re.search( r"static PyObject \*\nsetiter_reduce\([^\n]+\)\n\{.*?\n\}", source, re.S ) assert match, "setiter_reduce not found" harness = r''' #include #include #include typedef struct { int refs; } PyObject; typedef struct { PyObject head; PyObject *si_set; long si_used, si_pos, len; } setiterobject; #define Py_UNUSED(name) name #define _Py_ID(name) name static int iter, mode, exhausted; static PyObject backing, list_value, builtin, built; static setiterobject original; static char events[32]; static void event(char c) { size_t n = strlen(events); assert(n + 1 < sizeof(events)); events[n] = c; events[n + 1] = '\0'; } static void Py_XINCREF(PyObject *obj) { assert(obj == original.si_set); assert(strcmp(events, "") == 0); event('I'); if (obj != NULL) ++obj->refs; } static void Py_XDECREF(PyObject *obj) { assert(obj == (mode == 1 ? NULL : original.si_set)); assert(strcmp(events, "IL") == 0); event('D'); if (obj != NULL) --obj->refs; } static PyObject *PySequence_List(PyObject *obj) { setiterobject *tmp = (setiterobject *)obj; assert(tmp != &original); assert(tmp->head.refs == original.head.refs); assert(tmp->si_set == original.si_set); assert(tmp->si_used == original.si_used); assert(tmp->si_pos == original.si_pos && tmp->len == original.len); assert(backing.refs == (exhausted ? 1 : 2)); assert(strcmp(events, "I") == 0); event('L'); tmp->si_pos = 99; tmp->len = 0; if (mode == 1 && tmp->si_set != NULL) { --tmp->si_set->refs; tmp->si_set = NULL; } return mode == 2 ? NULL : &list_value; } static PyObject *_PyEval_GetBuiltin(int *name) { assert(name == &iter); assert(strcmp(events, "ILD") == 0 && backing.refs == 1); event('G'); return &builtin; } static PyObject *Py_BuildValue(const char *format, PyObject *func, PyObject *list) { assert(strcmp(format, "N(N)") == 0); assert(func == &builtin && list == &list_value); assert(strcmp(events, "ILDG") == 0 && backing.refs == 1); event('B'); --func->refs; --list->refs; return mode == 3 ? NULL : &built; } ''' harness += match.group(0) harness += r''' int main(void) { setiterobject before; PyObject *result; for (exhausted = 0; exhausted < 2; ++exhausted) { for (mode = 0; mode < 4; ++mode) { backing.refs = list_value.refs = builtin.refs = 1; memset(&original, 0, sizeof(original)); original.head.refs = 7; original.si_set = exhausted ? NULL : &backing; original.si_used = 5; original.si_pos = 2; original.len = 3; memcpy(&before, &original, sizeof(before)); events[0] = '\0'; result = setiter_reduce(&original, NULL); assert(memcmp(&before, &original, sizeof(before)) == 0); assert(backing.refs == 1); assert(strcmp(events, mode == 2 ? "ILD" : "ILDGB") == 0); assert(result == (mode >= 2 ? NULL : &built)); assert(list_value.refs == (mode == 2 ? 1 : 0)); assert(builtin.refs == (mode == 2 ? 1 : 0)); } } return 0; } ''' with tempfile.TemporaryDirectory(prefix="vc6-setiter-reduce-") as directory: path = Path(directory) (path / "test.c").write_text(harness) result = subprocess.run([ "cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror", "-Wno-unused-parameter", str(path / "test.c"), "-o", str(path / "test"), ]) if result.returncode: sys.exit(result.returncode) subprocess.run([str(path / "test")], check=True) print("setiter_reduce C89 declaration/evaluation/cleanup/return: PASS (8 cases)")