#!/usr/bin/env python3 """Check tupledealloc's C89 declaration and exact teardown evaluation order.""" import re import sys from pathlib import Path source = Path(sys.argv[1] if len(sys.argv) > 1 else "cpython/Objects/tupleobject.c") match = re.search( r"static void\ntupledealloc\(PyTupleObject \*op\)\n\{(.*?)\n\}", source.read_text(), re.S, ) assert match, "tupledealloc definition not found" body = match.group(1) assert re.match(r"\s*Py_ssize_t i;", body), ( "tupledealloc: i must be declared without initializer before the empty " "tuple check; declaration after Py_TRASHCAN_BEGIN is not C89 compatible" ) expected = """ Py_ssize_t i; if (Py_SIZE(op) == 0) { /* The empty tuple is statically allocated. */ if (op == &_Py_SINGLETON(tuple_empty)) { #ifdef Py_DEBUG _Py_FatalRefcountError("deallocating the empty tuple singleton"); #else return; #endif } #ifdef Py_DEBUG /* tuple subclasses have their own empty instances. */ assert(!PyTuple_CheckExact(op)); #endif } PyObject_GC_UnTrack(op); Py_TRASHCAN_BEGIN(op, tupledealloc) i = Py_SIZE(op); while (--i >= 0) { Py_XDECREF(op->ob_item[i]); } // This will abort on the empty singleton (if there is one). if (!maybe_freelist_push(op)) { Py_TYPE(op)->tp_free((PyObject *)op); } Py_TRASHCAN_END """ assert re.sub(r"\s+", "", body) == re.sub(r"\s+", "", expected), ( "preserve empty-tuple guards, GC untracking, size assignment immediately " "after trashcan entry, reverse item decrefs, freelist/free, and trashcan exit" ) print("tupledealloc C89 declaration and exact teardown evaluation order: PASS")