Gradual C0: Incremental Verification
- Gradual C0 is a gradual verifier that combines static symbolic execution with dynamic runtime checks, supporting partial specifications for heap-manipulating C0 programs.
- It leverages imprecise specifications marked with '?' to enable incremental refinement, reducing runtime overhead compared to fully dynamic verification.
- The system builds on the Viper framework, extending it with mechanisms like unfolding expressions and pure function support to enhance verification of recursive heap data structures.
Searching arXiv for papers on Gradual C0 and related extensions.
Gradual C0 is a gradual verifier for imperative programs that manipulate recursive mutable heap data structures. It targets C0, a safe educational subset of C, and combines static verification where possible with run-time checking where specifications are incomplete or imprecise. Its central design goal is incremental verification: specifications may be complete, partial, or missing, with the unknown component written ?, and the verifier uses symbolic execution to discharge what it can statically while inserting residual dynamic checks to preserve soundness (DiVincenzo et al., 2022).
1. Origins, setting, and verification model
Gradual C0 was introduced to address a specific limitation of conventional static verification: full verification typically requires complete, detailed auxiliary specifications up front, including preconditions, postconditions, loop invariants, predicate definitions, framing information, and fold/unfold structure. The system therefore adopts the perspective of gradual verification, in which one may begin with partially specified code and refine the specification incrementally. In this setting, the primary semantic role of ? is not arbitrary underspecification but controlled imprecision: non-contradictory strengthening of imprecise formulas marked with ? can be assumed statically, with run-time checks added where needed (DiVincenzo et al., 2022).
The system is built over Viper and inherits the permission-based verification style of implicit dynamic frames together with recursive abstract predicates. This matters because Gradual C0 is aimed specifically at heap-manipulating programs. Accessibility predicates such as acc(x.f) represent permission to access field f of object x, while recursive predicates package permissions and structural properties of lists, trees, and related inductive heap structures. A linked-list predicate such as acyclicSeg or acyclic is therefore both a shape invariant and a framing device. This design makes Gradual C0 closer to practical separation-logic and implicit-dynamic-frames verifiers than to earlier gradual-verification accounts based on weakest liberal preconditions (Gupta, 17 Jul 2025).
A common misconception is that gradual verification merely postpones all verification to run time. Gradual C0 was explicitly proposed against that outcome. Earlier work on gradual verification for recursive heap structures allowed partial specifications, but refining those specifications did not reduce dynamic checking: all properties were still checked dynamically regardless of static guarantees. Gradual C0’s main conceptual move was to make symbolic execution, rather than weakest liberal preconditions, the core reasoning engine, so that partial specifications could be exploited to eliminate checks that had already been established statically (DiVincenzo et al., 2022).
2. Imprecision, specifications, and the role of ?
The source-level specification language supports ordinary formulas together with imprecise formulas marked by ?. In the GVC0 syntax reported for the system, formulas include pure boolean expressions, recursive predicate instances, accessibility predicates, separating conjunction, conditional formulas inside specifications, and imprecision. The intended progression is explicit: fully omitted specifications correspond to mostly dynamic checking, partially specified code to mixed static and dynamic checking, and fully precise specifications to mostly or entirely static verification (DiVincenzo et al., 2022).
This design is particularly significant for recursive mutable data structures. In a conventional permission-based verifier, proving a simple list-manipulating method may require loop invariants, induction-like predicate structure, and explicit fold/unfold operations that are often much longer than the code being verified. Gradual C0 permits one to leave some of that structure imprecise. Verification can then proceed under optimistic assumptions induced by ?, and the system emits run-time checks only for those proof obligations that were not justified purely statically. This suggests a verification workflow in which specifications evolve with the proof rather than preceding it in full detail (DiVincenzo et al., 2022).
At the semantic level, imprecision is tracked operationally. The system distinguishes precise and imprecise symbolic states, and it also uses an equi-recursive notion of imprecision in some places: a predicate instance may count as imprecise if fully unfolding its body reveals ?. This is important at method-call boundaries and loop-entry points, because the dynamic semantics of runtime checks treats predicates equi-recursively. The resulting treatment is conservative but consistent with the gradual-verification goal that making specifications less precise should not create spurious static rejection (DiVincenzo et al., 2022).
3. Symbolic execution architecture
Gradual C0’s backend, called Gradual Viper, extends Viper’s symbolic-execution verifier to support imprecision. Its symbolic state is a 6-tuple
where isImprecise records whether the current state is imprecise, is the ordinary symbolic heap, is the optimistic heap, is the symbolic store, is the path condition, and is the runtime-check structure (DiVincenzo et al., 2022).
The optimistic heap is the most distinctive addition. It stores heap chunks that are available only because the verifier has made optimistic assumptions justified by imprecision. Unlike the ordinary heap, it does not maintain the separation invariant. Its purpose is not merely representational: it allows the verifier to avoid generating duplicate dynamic checks when the same missing permission or fact is used repeatedly. This is one of the mechanisms by which Gradual C0 improves over fully dynamic checking even when specifications are partial (DiVincenzo et al., 2022).
The symbolic executor is organized around four major continuation-passing operations inherited from Viper and adapted to the gradual setting: eval, produce, consume, and exec. Producing a formula adds permissions and constraints to the symbolic state. Consuming a formula checks constraints, removes permissions, and may emit run-time checks when verification succeeds only because the state is imprecise. Expression evaluation and statement execution are then layered on top of those primitives. This arrangement lets Gradual C0 ask, for each obligation, whether it is derivable from the current symbolic state, derivable only by optimistic strengthening, or inconsistent (Gupta, 17 Jul 2025).
Branching is handled in both program statements and specification formulas. If the current state is precise, both branches of a conditional must verify. If the current state is imprecise and exactly one branch verifies, Gradual Viper may still succeed optimistically by assuming that the failing branch is unreachable because the unknown part of the specification implies the successful branch condition. To preserve soundness, it records a run-time check guarded by the relevant branch condition. The system therefore treats path sensitivity, branch-origin tracking, and residual check insertion as first-class parts of the symbolic state rather than as afterthoughts (DiVincenzo et al., 2022).
The implementation also minimizes checks by computing only the undischarged fragment of a proof obligation. For pure constraints, Gradual Viper uses a diff operation that keeps only those conjuncts not already implied by the symbolic path condition. This is the technical basis for the claim that refining a specification can decrease run-time overhead rather than merely changing where failure occurs (DiVincenzo et al., 2022).
4. Specification expressiveness and later extensions
The original Gradual C0 system was practical but not maximally expressive. A major limitation was that it did not support Viper’s unfolding expressions,
which temporarily expose a predicate body so that an expression can be evaluated using permissions and facts packaged inside a recursive predicate. This mattered because recursive predicates are treated iso-recursively: their bodies are not automatically available. Without unfolding expressions, even simple properties such as sortedness of a linked list had to be encoded indirectly, for example by introducing an auxiliary parameter prev into the predicate definition. The 2025 extension adds unfolding expressions to Gradual C0 and adapts the symbolic eval rule so that temporary unfolding remains sound in the presence of imprecision and runtime checks (Gupta, 17 Jul 2025).
That extension is not merely syntactic. It modifies how the optimistic heap is restored after evaluating an unfolding expression. If the unfolded predicate body is precise, newly assumed optimistic permissions can be retained, because they must have come from imprecision outside the predicate body. If the predicate body is imprecise, the verifier restores but discards newly assumed optimistic chunks from inside the unfolding, because their origin cannot be attributed safely. The same work also refines branch-origin tracking for conditionals arising during predicate-body production and notes that branch joining is currently not supported in Gradual Viper (Gupta, 17 Jul 2025).
A second expressiveness extension adds pure functions to Gradual C0. Pure functions are marked with //@ pure;, are side-effect free, and can appear in specifications, including postconditions and loop invariants. This supports the standard observer-style idiom of verification systems: recursive mathematical definitions such as factorial, Fibonacci, length, height, or similar query functions can now be used directly in contracts rather than encoded indirectly through impure methods or relational specifications. The motivating example is a recursive pureFactorial function used to specify an iterative factorial implementation with a natural loop invariant (Mutlu, 27 Nov 2025).
The technical challenge for pure functions is axiomatization under imprecise specifications. In ordinary Viper, pure functions are axiomatized using function snapshots derived from their preconditions. Under gradual specifications, a precondition containing ? may not mention all heap locations accessed by the body or postcondition. The proposed solution is to optimistically extend the function precondition and snapshot when a missing permission can be justified by the imprecise part, while also requiring later call sites to consume those additional permissions. The extension is deliberately restricted: imprecise recursive predicates cannot be used in pure-function preconditions, because that would require a permanent change in the shape of predicate snapshots and would compromise the function-snapshot mechanism (Mutlu, 27 Nov 2025).
5. Empirical behavior and performance
Gradual C0 provides the first empirical performance evaluation of a gradual verifier. The evaluation uses four fully specified recursive-data-structure benchmarks—Binary Search Tree, Linked List, Composite Tree, and AVL Tree—and adapts Takikawa et al.’s performance-lattice methodology by sampling partial specifications along random lattice paths between fully imprecise and fully precise configurations (DiVincenzo et al., 2022).
The central quantitative result is that Gradual C0 reduces run-time overhead on average by to 0 compared to a fully dynamic verifier that checks everything at run time. The abstract summarizes this as an average reduction of 1–2. The improvement is not uniform across all specification points, but it is systematic enough to support the central thesis that symbolic execution plus residual-check generation rewards specification refinement with lower run-time cost (DiVincenzo et al., 2022).
The evaluation also identifies where costs come from. Overhead spikes are associated especially with adding recursive predicates to preconditions or postconditions while those obligations are still dynamic, and with removing ? from contracts while expensive owned-fields passing at method boundaries is still required. Conversely, large decreases in overhead occur when a critical mass of specification is reached and entire methods become statically verified, eliminating both dynamic checks and ownership-passing overhead. Near 3 specification, the paper observes additional spikes caused by owned-fields passing; these are engineering artifacts of the current implementation strategy rather than evidence against gradual verification as such (DiVincenzo et al., 2022).
Worst-case scenarios do exist. The paper reports slowdowns of 4 to 5 relative to the dynamic baseline, concentrated under about 6 specified. The authors characterize these cases as predictable and avoidable, and note that even on those bad paths Gradual C0 still outperforms the dynamic verifier for most steps. The empirical picture is therefore not monotone in the strongest possible sense, but it is decisively inconsistent with the older view that partial specifications force fully dynamic checking (DiVincenzo et al., 2022).
6. Limitations and research trajectory
The original Gradual C0 paper is explicit about several limitations. Its frontend lacks arrays and strings, fractional permissions, ghost variables, ghost functions, and full Viper expressiveness. Alias handling in heap-chunk removal is conservative, which can induce avoidable runtime checks, and ownership passing across method boundaries can be costly. These are implementation limitations rather than contradictions of the gradual-verification model, but they bound the immediate scope of the system (DiVincenzo et al., 2022).
The expressiveness extensions also document open problems. The unfolding-expression work does not yet provide a full soundness proof, notes that branch joining is currently not supported in Gradual Viper, and identifies framing-choice optimization as incomplete: in imprecise formulas there may be multiple possible ways to frame an expression, but the current implementation simply assumes the minimum option. The pure-function work is likewise a design and formalization paper rather than a full empirical implementation study, and it leaves implementation of the gradual parts and a soundness proof for future work (Gupta, 17 Jul 2025, Mutlu, 27 Nov 2025).
Taken together, these papers establish a clear research trajectory. The 2022 system provides the first practicable symbolic-execution-based gradual verifier for recursive heap data structures; the 2025 extension adds unfolding expressions so that recursive heap predicates can be specified more naturally; and the 2025 pure-function proposal extends the specification language toward observer methods and richer mathematical contracts. A plausible implication is that Gradual C0 is evolving from a verifier that primarily reduces annotation burden into a platform for permission-based gradual verification with increasingly competitive specification expressiveness, while still retaining the defining commitment that partial specifications should be useful rather than merely tolerated (DiVincenzo et al., 2022, Gupta, 17 Jul 2025, Mutlu, 27 Nov 2025).