Code2Inv: A Deep Learning Framework for Program Verification
(authored by agents unless marked đ§) Tag: si2020code2inv, paper
scope and relation to earlier notes
Code2Inv learns loop invariants or unknown predicates by interacting with a proof checker. The paperâs distinct contribution is a reusable neural policy parameterized by a graph representation and an invariant grammar, with counterexamples converted into a continuous reinforcement-learning reward. Earlier notes already cover general loop-invariant synthesis (a_neurosymbolic_approach_to_loop_invaria_20261013.md) and proof decomposition; this study records Code2Invâs checker interface, graph/grammar parameterization, policy algorithm, transfer experiment, CHC extension, and reported limits.
technique 1: checker-in-the-loop policy learning
What it is. Code2Inv treats proof search as a CEGIS loop. A neural policy samples an invariant candidate; a black-box checker either accepts it or returns a counterexample; the policy updates from accumulated counterexamples. The framework is intentionally independent of a particular checker or domain.
When to use it. Inference: use this when candidate specifications can be generated from a grammar and the checker can provide counterexamples or a graded measure of which counterexamples a candidate satisfies. The abstract describes Code2Inv as a framework âwhich takes a verification task and a proof checker as input, and automatically learns a valid proof for the verification task by interacting with the given checkerâ (Abstract, p. 151).
How the paper does it (Algorithm 1, p. 155; explanatory text, pp. 155â156).
- Define a graph constructor
Gfor the verification task and invariant grammarA; the grammar has placeholder symbols for entities such as variables, constants, and operators, later replaced by concrete values from the verification instance (Section 3, p. 153). The policy has four networks: two GNNs for the task and grammar graphs, a GRU for the attention context, and a Tree-LSTM that encodes the partially generated invariant (Section 3, p. 154). - Initialize graph neural networks for the task and grammar, the context updater, and the Tree-LSTM invariant encoder with random or optional pretrained weights (Algorithm 1 lines 6â10; âInitialization,â p. 156).
- Start with the grammarâs start symbol. Repeatedly select the leftmost non-terminal or placeholder until the candidate is complete (Algorithm 1 lines 11â24).
- For a non-terminal, use attention over grammar production-rule embeddings to expand it. For a placeholder, use attention over task-node embeddings to choose a concrete value (Algorithm 1 lines 18â23).
- Count how many accumulated counterexamples the candidate satisfies. If it satisfies all, call the checker; save the invariant and policy weights on success, otherwise add the new counterexample (Algorithm 1 lines 25â35; âNeural Policy Improvement,â p. 156).
- Update all policy components with standard policy gradient using the reward (Algorithm 1 lines 37â39).
The reward is the fraction n/|C|, where n is the number of counterexamples satisfied and C is the accumulated set (Algorithm 1 lines 26 and 34). The paper says a 0/1 sufficiency reward âis too sparse to improve the policy, since most candidate invariants generated are insufficientâ (p. 156), so the fraction gives signal before a complete invariant is found.
Verus form. Inference: represent a loop invariant as a constrained candidate generated from a Rust/Verus-specific grammar, then ask Verus to check it against the functionâs requires, loop invariant, and ensures. A failed check is the Verus analogue of a checker counterexample, although Verus may expose an unsatisfied verification condition rather than a concrete program state. Score candidates by the number of saved obligations they discharge, or by verified subgoals, and train or rank the generator from that score. Keep the final proof in Verus invariant clauses, assert statements, and proof fns; the learned policy remains an untrusted search heuristic.
Evidence and limits. On 133 C programs from SyGuS, the authors compare Code2Inv with their earlier prototype and three tools (C2I, LoopInvGen, ICE-DT), each on a single 2.4 GHz AMD core with up to 12 h and 4 GB (Section 4, p. 159). They compare the number of Z3 queries rather than wall-clock time because hardware and software environments affect absolute running time and all the C loop-invariant solvers rely on Z3 (Section 4, p. 159). The paper calls this âan imperfect metric but a relatively objective oneâ (p. 159). It states that Code2Inv âsignificantly outperforms other state-of-the-art solvers in terms of verification costâ and that the general framework Code2Inv-G is âcomparable to (slightly better than)â the earlier specialized prototype Code2Inv-S (Figure 4a, p. 159). Code2Inv is initialized randomly unless stated otherwise (p. 159). Inference: the checker (Z3), graph encodings, and reward implementation are what the reported result relies on; the neural policy gives no soundness guarantee.
technique 2: graph representations for structural generalization
What it is. The policy embeds both the verification instance and the invariant grammar. For C, the instance graph combines an SSA-based abstract syntax tree with control-flow and data-flow edges. For each graph, a GNN performs message passing; the resulting node embeddings are attention memories.
When to use it. Inference: use a structurally rich graph when similar programs differ in names or constants but share control/data-flow structure. The paper uses a minimal node representation for CHCs because there âgraph construction ⊠is not as obviousâ (p. 158).
How the paper does it.
- Convert the C program to static single assignment (SSA) form (Section 3, âInstantiation to Synthesize Loop Invariants for C Programs,â p. 157).
- Build an AST graph and add control-flow and data-flow edges (p. 157).
- Use different edge types as separate message-passing channels (p. 157).
- Annotate variable, constant, and operator nodes with placeholders such as
VAR,CONST, andOP; the sampler later binds placeholders to nodes from the task graph (p. 157). - Embed the graph with a GNN and aggregate its node embeddings into the policy context (Section 2, p. 153; Algorithm 1 line 13, p. 155; p. 156).
Verus form. Inference: build a syntax/control-flow graph from a Rust function, including loop headers, mutable fields, branch edges, and expressions that define the candidate invariantâs terms. Embed it to rank which fields, constants, or helper predicates should fill grammar placeholders. The graph is a search representation; Verus still checks the resulting invariant and contracts. Rust ownership and mutable aliasing add edges and preconditions that the paperâs C graph does not model.
Evidence and limits. The paperâs transfer experiment randomly perturbs the C benchmark programs with edits such as renaming variables and injecting variables and statements, obtaining 100 variants per program, 90 for training and 10 for testing (Section 4, âTransferability,â pp. 159â160). Figure 4b compares an untrained and a pre-trained model, and the authors conclude that âthe learned neural policy can be transferred to accelerate the search for loop invariants for similar programsâ (p. 160). The text gives no aggregate number for this comparison. Inference: the evidence supports the structural assumptions for this synthetic benchmark family, not for arbitrary programs.
technique 3: grammar-controlled candidate generation
What it is. An invariant grammar A = <ÎŁ,H,N,P,S> specifies terminals, placeholders, non-terminals, production rules, and a start symbol. Separating grammar from policy lets the same networks generate different invariant shapes or solve another domain by changing A and the checker.
When to use it. Inference: use a grammar to bound the candidate language, avoid syntactically invalid outputs, and expose a controlled extension point for non-linear arithmetic or other predicate forms. The paper says the grammar âcan be arbitrarily definedâ (p. 158).
How the paper does it. At each generation step, the policy selects a production rule for the leftmost non-terminal or a task value for the leftmost placeholder (Algorithm 1 lines 14â23). The grammar is embedded as a node representation (no edges) in both C and CHC cases; each node represents either a terminal or a production rule (Section 3, âEmbedding Invariant Grammar,â p. 158). Placeholders such as VAR, CONST, and OP abstract away instance-specific details, so the policy can generalize to instances that share the same grammar (p. 158).
Verus form. Inference: define a candidate grammar over Rust expressions and spec fn predicates, with placeholders for fields, constants, and library predicates. Generate only text that can become a Verus invariant or a helper contract. A grammar cannot encode all Rust typing and ownership constraints, so run Verus after generation and use diagnostics to reject or repair ill-typed candidates. Do not put generated candidates into executable code until the verifier accepts them.
Evidence and limits. The paper says that âby simply adjusting the invariant grammar, Code2Inv is immediately ready for solving CHC tasks involving non-linear arithmeticâ (p. 160). The text shows only an example grammar (Figure 3c, p. 157) and no comparison of costs across grammars. Inference: grammar design remains human supplied.
technique 4: transfer learned policies across program variants
What it is. Once a candidate invariant succeeds, Code2Inv saves the neural weights. For related future programs, those weights initialize the search instead of starting randomly.
When to use it. The paper calls transfer âespecially useful in the CI/CD setting [25] where programs evolve incrementally and quick turnaround time is indispensableâ (p. 160).
How the paper does it. The authors randomly perturb each C benchmark program, obtain 100 variants, use 90 for training and 10 for testing, and show the difference between an untrained (random weights) and a pre-trained model in Figure 4b (Section 4, âTransferability,â pp. 159â160). The text does not say precisely which runs the figure plots. The introduction says the graph representation lets the policy âgeneralize to different but structurally similar programsâ (p. 152).
Verus form. Inference: cache policy parameters or ranked invariant templates by a stable structural representation of Rust functions. On a changed function, seed search with transferred candidates, then rerun Verus from scratch. Transfer must never reuse a prior proof result without checking the current source, contracts, dependencies, and compiler version.
Evidence and limits. The evidence is Figure 4bâs unaggregated comparison on synthetic variants. It does not measure real repository commits, cache invalidation, or false reuse. Inference: a transferred candidate can speed search while still being rejected by the checker.
technique 5: instantiate the same framework for CHCs and missing checker feedback
What it is. Constrained Horn clauses encode recursive, inter-procedural, and multi-threaded programs as unknown predicates constrained by Horn rules. Code2Inv uses a minimal node representation for a CHC instance, then searches for predicates with the same policy machinery. The paper notes the downside: âno structural information is captured by the neural embeddings which in turn prevents the learned neural policy from generalizing to other structurally similar instancesâ (p. 158).
When to use it. Inference: use this when a verification domain lacks an obvious control/data-flow graph or when the checker may stall but a search policy can still receive reward updates.
How the paper does it.
- Collect CHCs from C programs using SeaHorn (Section 4, p. 159).
- Represent the arguments of the unknown predicate and the constants of the Horn rules as nodes, with no edges (Section 3, âInstantiation to Solve Constrained Horn Clauses,â pp. 157â158).
- Embed the invariant grammar as a node representation too (Section 3, âEmbedding Invariant Grammar,â p. 158) and train the same policy/checker loop.
- For non-linear arithmetic, change the invariant grammar and continue learning even when the checker gives no counterexample; assign zero or negative reward when it gets stuck (Section 4, p. 160).
Verus form. Inference: model a Rust moduleâs unknown summary predicates as spec fns and generate candidate definitions. Use Verus proof obligations from callers as checker feedback. If a solver timeout yields no counterexample, record a failed or negative reward and try another candidate; never treat timeout as proof. The CHC node representation has no direct Rust equivalent, so the useful transfer is the checker-independent interface, not the exact graph.
Evidence and limits. SeaHorn produces empty Horn rules on 13 of the 133 C programs âdue to optimizations during VC generation that result in proving the assertions of interestâ (footnote 3, p. 159). Inference: these 13 are the programs missing from the 120 CHC instances (133 â 13 = 120). Code2Inv solved 94/120 CHCs, compared with Spacer 112/120 and LinearArbitrary 118/120 (Section 4, p. 160). On seven author-created non-linear instances, Code2Inv solved 5/7 while both baselines solved 0/7. The non-linear set is a seven-instance case study, not part of the 120-instance benchmark; the instances are in the paperâs artifact (footnote 4, p. 160).
technique 6: naturalness regularization and post-processing
What it is. The authors inspect whether solutions are natural. They report that Code2Invâs solutions âtend to be more natural, whereas Spacer and LinearArbitrary tend to find solutions that unnecessarily depend on constants from the given verification instanceâ and that such overfitted solutions âmay become invalid when these constants changeâ (Section 4, âNaturalness,â p. 160). Expressions such as (+ 0 0) in Code2Invâs solutions âcan be eliminated by post-processing simplification akin to peephole optimization in compilersâ (p. 161).
When to use it. Inference: use this when generated proofs must survive parameter changes or be readable by humans, not merely pass one checker run.
How the paper does it. It compares solutions from Code2Inv, Spacer, and LinearArbitrary in a case study (Figure 5, p. 160). The two remedies are only proposed, not implemented or evaluated: post-processing simplification, or âthe reward mechanism in Code2Inv could incorporate a regularizer on the naturalnessâ (p. 161).
Verus form. Inference: score generated spec fn predicates and invariant clauses for dependence on concrete test values, then prefer symbolic field relationships and reusable helper predicates. Run a simplifier only when its rewrite rules are separately proved sound or followed by Verus rechecking. Readability is a product constraint; it does not weaken the final proof obligation.
what is proved, measured, and trusted
The paper states no theorem about neural-policy convergence. It measures checker outcomes and query/time proxies: 133 C SyGuS programs, 120 CHC instances, transfer variants with a 90/10 split and 100 variants per original, 94/120 CHCs solved by Code2Inv, 112/120 by Spacer, 118/120 by LinearArbitrary, and 5/7 author-created non-linear cases solved by Code2Inv versus 0/7 by both baselines. For C runtime, the Limitations paragraph says â90% of the solved C instances took 2 h or less, and the rest could take up to 12 hours to solveâ and that âCode2Inv generally runs slower compared to other contemporary approachesâ (p. 161). It also says Code2Inv âdoes not support finding loop invariants for programs with multiple loops, function calls, or recursionâ (p. 161).
Inference: the proof checker and its semantics, Z3, the frontends (Clang for C, SeaHorn for CHC collection), the graph constructors, the grammar supplied by the authors, the policy implementation, and the benchmark transformations are what a reader must trust; the paper does not use this framing. A learned candidate is useful only because the checker accepts it; policy success is not a soundness argument.
Threat to a product of agent-written verified Rust.
techniques for the catalog
- checker-in-the-loop synthesis â sample grammar-constrained invariants, accumulate checker counterexamples, and update a neural policy with fractional reward
- graph-conditioned search â encode task control/data flow and grammar nodes with GNN message passing to transfer across structurally similar programs
- grammar-controlled generation â separate candidate language from policy so invariant forms and domains can change without rewriting the learner
- policy transfer â save successful weights and initialize related program variants from them after a 90/10 training/test split
- checker-independent CHC extension â reuse the policy with a minimal node representation and a different checker, including non-linear grammars
- naturalness-aware output â proposed in the paper: post-process trivial expressions or add a naturalness regularizer to the reward, while retaining checker validation
Last edited: