#!/usr/bin/env python3 """Compile exact odictiter_reduce as C89 and check timing and ownership.""" import re import subprocess import sys import tempfile from pathlib import Path source = Path(sys.argv[1]).read_text() match = re.search( r"static PyObject \*\nodictiter_reduce\(odictiterobject \*di, PyObject \*Py_UNUSED\(ignored\)\)\n\{.*?\n\}", source, re.S, ) assert match, "odictiter_reduce definition not found" harness = r''' #include #include #include typedef struct { int refs; } PyObject; typedef struct { PyObject *di_odict, *di_current; int position; } odictiterobject; static PyObject dict, current, list_obj, builtin, result_obj; static odictiterobject *original; static int stage, failure, consume; static int iter; #define Py_UNUSED(name) unused #define _Py_ID(name) name static void Py_XINCREF(PyObject *obj) { assert(stage < 2); assert(obj == (stage == 0 ? original->di_odict : original->di_current)); if (obj != NULL) ++obj->refs; ++stage; } static PyObject *PySequence_List(PyObject *obj) { odictiterobject *tmp = (odictiterobject *)obj; assert(stage == 2 && tmp != original); assert(tmp->di_odict == original->di_odict); assert(tmp->di_current == original->di_current); assert(tmp->position == 17); assert(dict.refs == (original->di_odict ? 2 : 1)); assert(current.refs == (original->di_current ? 2 : 1)); tmp->position = 99; if (consume) { if (tmp->di_odict) --tmp->di_odict->refs; if (tmp->di_current) --tmp->di_current->refs; tmp->di_odict = tmp->di_current = NULL; } stage = 3; list_obj.refs = failure == 1 ? 0 : 1; return failure == 1 ? NULL : &list_obj; } static void Py_XDECREF(PyObject *obj) { assert(stage == 3 || stage == 4); assert(obj == (consume ? NULL : (stage == 3 ? original->di_odict : original->di_current))); if (obj != NULL) --obj->refs; ++stage; } static PyObject *_PyEval_GetBuiltin(int *id) { assert(stage == 5 && id == &iter && failure != 1); assert(dict.refs == 1 && current.refs == 1); stage = 6; builtin.refs = 1; return &builtin; } static PyObject *Py_BuildValue(const char *format, PyObject *func, PyObject *list) { assert(stage == 6 && strcmp(format, "N(N)") == 0); assert(func == &builtin && list == &list_obj); assert(func->refs == 1 && list->refs == 1); --func->refs; --list->refs; stage = 7; return failure == 2 ? NULL : &result_obj; } ''' harness += match.group(0) harness += r''' int main(void) { int mask; odictiterobject di; PyObject *result; for (mask = 0; mask < 4; ++mask) { for (consume = 0; consume < 2; ++consume) { for (failure = 0; failure < 3; ++failure) { dict.refs = current.refs = 1; di.di_odict = mask & 1 ? &dict : NULL; di.di_current = mask & 2 ? ¤t : NULL; di.position = 17; original = &di; stage = 0; result = odictiter_reduce(&di, NULL); assert(result == (failure ? NULL : &result_obj)); assert(stage == (failure == 1 ? 5 : 7)); assert(dict.refs == 1 && current.refs == 1); assert(list_obj.refs == 0); assert(di.di_odict == (mask & 1 ? &dict : NULL)); assert(di.di_current == (mask & 2 ? ¤t : NULL)); assert(di.position == 17); } } } return 0; } ''' with tempfile.TemporaryDirectory(prefix="vc6-odict-list-") as directory: path = Path(directory) (path / "test.c").write_text(harness) compiled = subprocess.run([ "cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror", "-Wno-unused-parameter", str(path / "test.c"), "-o", str(path / "test"), ]) if compiled.returncode: sys.exit(compiled.returncode) subprocess.run([str(path / "test")], check=True) print("odictiter_reduce C89 compile and copy/incref/list/cleanup/ownership/failure order: PASS (24 cases)")