Skip to content
GUIDES

Recursive object ownership

RFC 0027 extends inferred container contracts to finite acyclic trees and child/sibling forests. For example:

#include <stdlib.h>
struct node { unsigned value; struct node *left, *right; };
static void destroy(struct node *p) {
if (!p) return;
destroy(p->left);
destroy(p->right);
free(p);
}

The generic helper requires a finite initialized structure with independently owned nodes and a matching release family. It proves complete consumption using proper-child induction. A closed caller must establish that requirement from actual allocations and initialized links. Borrowed stack trees support reads; they cannot satisfy release permission. Runtime construction and cleanup use inductive invariants, independent of the number of nodes allocated at runtime.

Structural validity and complete cleanup are separate facts. Reports expose container-preserved, container-consumed, container-partition, and container-combined outputs alongside their structural and separation premises. A detachment can partition an input between its remaining parent and returned child. Both allocations still need cleanup or transfer. A reverse wrapper may preserve every allocation, while a wrapper that discards its head cannot settle its caller’s cleanup obligation. Entry release permission by itself does not transfer a caller’s cleanup duty to a helper that only partially releases input.

Supported initialized integer flag tests can distinguish owned children and payloads from borrowed references. An inactive ownership edge grants no permission to access or release its pointee. Changing a flag or link retires old evidence; it cannot make a lost allocation disappear. Nonowning previous links may cycle, but owning links must remain acyclic and separate. Unknown callback targets, unproved mutation and exhausted metadata limits remain incomplete.

The unchanged cJSON validation clients explicitly establish default allocation hooks before creating, attaching, traversing, detaching and deleting objects. This covers the selected lifecycle slice, not the parser, printer or arbitrary custom hooks. Generic direct recursive destruction and supported helper wrappers are inferred from bodies; no library-name certificate or new annotation is used. Mutual recursive cleanup, arbitrary shared graphs and general logical ownership predicates remain unsupported. The domain bounds metadata to 64 footprint variables and relations; exceeding those bounds loses proof.

Source analysis, compiler objects and validated checkpoints transport the same contracts. Cross-unit callers need compatible object evidence for recursive contracts; a forward declaration alone does not supply it. RFC 0028 transports that evidence from verified constructors and preserves supported private hook state across separate translation units, as described below. Summary format 23, sidecar format 24 and checked encoding 9 reject older metadata; rebuild old objects. Expanded JSON version 2 and compact version 3 retain their existing meanings. See validation for fixed populations, counterexamples, test results and cost observations.