Lectures onType Theory
Chapter 69
Chapter 69Optional

Symbolic Execution, Path Conditions, and Concolic Testing

Prerequisites. Direct starred prerequisites: none. No later core chapter depends on this route.

Run the following program at input 2. It takes the left branch and the assertion succeeds. That run says nothing about the right branch. Run it at input 4, and the right branch reaches a failed assertion. (Fork)input x;if x0 then y:=x+1 else y:=x1;assert y2. An interval execution beginning with x[,+] joins the two stores at the end of the conditional. It may retain a safe over-approximation of y, but it no longer carries the concrete equation relating x to the selected branch. To generate the failing test, we instead keep one store per path and a formula describing the inputs on that path.

This chapter fixes a deliberately small calculus, SymImp-DL. Its constraints are decidable by checkable difference-logic certificates. That choice puts the proof boundary in the open: an untrusted solver may propose a model, a negative cycle, or unknown; only the first two are accepted after local checking.

Concrete commands and symbolic integers

Let x,y,z range over a finite set Var, k over integers, and αi over input symbols. Concrete right-hand sides and guards are a::=kx+k,g::=a1a2k. The notation includes xk by taking a2=0, and x=y+k as two inequalities. Commands are c::=skipinput xx:=ac;cif g then c else cassert gwhile g do c. Assignments are intentionally only copy-plus-constant. This closure property ensures that substituting a symbolic store into a guard again yields difference logic.

A concrete store is a total map s:VarZ. An input oracle I:NZ fixes a run, and a cursor n records the next unread input. We write c,s,I,ncc,s,I,n; failed assertions step to fail(s,I,n). Assignment, input, conditionals, assertions, and while-unfolding have the expected rules; sequencing propagates a step and erases a leading skip. Appendix A displays the complete rules.

A symbolic integer is t::=kαi+k. A symbolic store σ:VarSTerm maps variables to such terms. A valuation ρ:NZ evaluates terms by [[k]]ρ=k and [[αi+k]]ρ=ρ(i)+k. Symbolic evaluation [[a]]σ replaces the variable in a by its symbolic term and adds the constant.

Lemma 69.1 — Expression correspondence

If s(x)=[[σ(x)]]ρ for every x, then [[[[a]]σ]]ρ=[[a]]s. The corresponding statement holds for difference guards.

Proof of Lemma 69.1 — Expression correspondence

Proof. For a constant, both sides are k. For x+k, [[[[x+k]]σ]]ρ=symbolicevaluation[[σ(x)]]ρ+k=storehypothesiss(x)+k=concreteevaluation[[x+k]]s. Subtract the two expression equalities to obtain the guard case. ◻

For the input 4, begin with σ0(x)=σ0(y)=0. After input, σ1=σ0[xα0]. On the right branch, σ2=σ1[yα01]. With ρ(0)=4, its concrete instance has s(x)=4 and s(y)=3.

Exercise 69.1

★☆☆ Calculate the left-branch symbolic store of Fork, and instantiate it at ρ(0)=2.

Paths and the symbolic machine

A path condition Φ is a finite conjunction of symbolic difference atoms; the empty conjunction is . Negation stays inside the language: over integers, (neg)¬(tuk)meansutk1. A symbolic state is c,σ,Φ,n. The transition relation s does not ask whether a newly extended condition is satisfiable. Consequently no solver result can delete a state during the semantic proof.

The characteristic rules are Xinput x,σ,Φ,nsskip,σ[xαn],Φ,n+1SInput Xx:=a,σ,Φ,nsskip,σ[x[[a]]σ],Φ,nSAssign Xif g then c1 else c2,σ,Φ,nsc1,σ,Φ[[g]]σ,nSIfT Xif g then c1 else c2,σ,Φ,nsc2,σ,Φ¬[[g]]σ,nSIfF. An assertion has a successful successor with Φ[g]σ and a terminal obligation fail(σ,Φ¬[g]σ,n). A while has the same two forks as an if: the true successor is c;while g do c, and the false successor is skip. The sequencing rules are shared with the concrete machine. Thus a symbolic transition denotes a path choice, not a guess about satisfiability.

Definition 69.2 — Representation

Write (ρ,I)Rsymc,σ,Φ,n,c,s,I,n when ρDLΦ, s(x)=[[σ(x)]]ρ for every variable, and I(j)=ρ(j) for every j. Failure states are represented by the same store, path, and cursor clauses.

Full oracle agreement makes S-Input a genuine lock-step rule. For a terminal model, model-to-test constructs the oracle Iρ(j)=ρ(j); only its consumed prefix is emitted as the finite test.

Theorem 69.3 — One-step simulation

Suppose a symbolic state q^ represents q, q^sq^, and ρ satisfies the path condition of q^. Then qcq for a q represented by q^.

Proof of Theorem 69.3 — One-step simulation

Proof. Proceed by cases on the displayed symbolic rule. S-Assign uses lemma 69.1; S-Input extends input agreement at the old cursor. In S-If-T, satisfaction of the new conjunct and expression correspondence make the concrete guard true, selecting the same branch. Equation (neg) gives the false case. Assertions use the same argument and agree on success or failure. Sequence and while cases rebuild the corresponding concrete rule. Store and path components not changed by a rule retain their representation clauses. ◻

Corollary 69.4 — Finite simulation and path soundness

If q^0sq^m, ρ satisfies the final path condition, and q^0 represents q0, then q0cqm with the same terminal command or failed assertion, and q^m represents qm.

Proof of Corollary 69.4 — Finite simulation and path soundness

Proof. Induct on the finite symbolic derivation. Path conditions grow by conjunction, so satisfaction of the final condition implies satisfaction of every prefix. Apply theorem 69.3 at the last step. ◻

For Fork, the four terminal obligations are branchassertionpath condition x0passα00α0+12x0failα00α02x>0passα01α012x>0failα01α013. The models 2,1,4 cover the first, third, and fourth rows. The second row is infeasible.

Theorem 69.5 — Loop-free finite-path coverage

For a loop-free command, unpruned symbolic exploration produces a finite tree. Every terminating concrete execution from an agreeing initial state is represented by a leaf whose path condition its input valuation satisfies.

Proof of Theorem 69.5 — Loop-free finite-path coverage

Proof. Induct on the concrete derivation. Deterministic concrete guard evaluation selects exactly one of the two symbolic successors, and lemma 69.1 proves that its added atom is satisfied. Assignment, input, assertion, and sequence follow their matching rules. Without while, each successor contains a proper command subterm after the finite administrative sequence steps, so the finitely branching tree is finite. ◻

Exercise 69.2

★★☆ Derive both successors of the conditional in Fork. Give a model of each feasible assertion obligation and show the concrete replay.

A certificate boundary for difference logic

Normalize a difference atom to uvk, allowing a distinguished zero symbol 0. Its graph edge is vku. A certificate is a list of existing edge indices that forms a directed cycle. The trusted checker verifies incidence, closure, and a strictly negative sum.

Lemma 69.6 — Negative-cycle certificate soundness

If the local checker accepts a negative-cycle certificate for Φ, then there is no integer valuation ρ such that ρDLΦ.

Proof of Lemma 69.6 — Negative-cycle certificate soundness

Proof. Let the checked cycle be v0k0v1kr1vr=v0. Satisfaction gives ρ(vi+1)ρ(vi)ki. Sum the inequalities: 0=ρ(vr)ρ(v0)i<rki<0, a contradiction. This proof uses only the checker facts, not the algorithm that proposed the edge list. ◻

The infeasible left-failure condition contains α000 and 0α02. Their two graph edges form a cycle of weight 2, so the checker may prune it. A zero-weight cycle is not a certificate.

Theorem 69.7 — Certificate-checked pruning preserves coverage

If exploration prunes only states carrying a locally accepted negative-cycle certificate, theorem 69.5 still holds.

Proof of Theorem 69.7 — Certificate-checked pruning preserves coverage

Proof. The leaf representing a concrete execution has a satisfying valuation by the coverage induction. By lemma 69.6, no prefix of that leaf can carry an accepted certificate. Hence the exploration never prunes the representing branch. ◻

Satisfiability evidence has the opposite shape. A proposed integer map is accepted only after evaluating every atom. It then determines the consumed input prefix, and the concrete machine is replayed deterministically.

Theorem 69.8 — Model-to-test correctness

If the model checker accepts ρ for a terminal symbolic path and concrete replay agrees with its recorded branches and outcome, the input prefix ρ(0),,ρ(n1) is a concrete test exhibiting that outcome.

Proof of Theorem 69.8 — Model-to-test correctness

Proof. The model check establishes the satisfaction premise of corollary 69.4. That corollary produces the concrete execution. The replay check independently compares every branch and the terminal assertion, so a malformed trace or an unrelated model cannot be reported as the test for this path. ◻

An adapter result therefore has three trusted interpretations: model(ρ)check atoms and replay,unsat(C)check C,unknownretain the path. King introduced symbolic execution as a program-testing method; the modern substitution and path-simulation pattern appears explicitly in the rules and correctness/completeness theorems of de Boer and Bonsangue [Kin76, dBB21]. WiSE mechanizes a richer reachability connection in Coq and separates soundness from the exhaustiveness assumptions of bug search [CS23]. Our theorem is the local SymImp-DL proof, not a transfer of either richer result.

Exercise 69.3

★★☆ An erroneous S-Assign rule stores the source variable x instead of [x+1]σ for y:=x+1. Give a one-step counterexample to simulation, then repair the rule and identify the line of lemma 69.1 that the repaired proof uses.

Explosion, subsumption, and guarded merging

A sequence of m independent conditionals has 2m syntactic leaves. In conjunctive SymImp-DL we may safely discard a state c,σ,Φ2,n when a retained state has the same c,σ,n and a checked implication Φ2DLΦ1: every store represented by the discarded state is already represented by the first. For difference logic, each demanded implication may be checked by adding the complement of its consequent atom and accepting a negative-cycle certificate.

Proposition 69.9 — Subsumption soundness

The subsumption test above preserves the represented union of concrete states.

Proof of Proposition 69.9 — Subsumption soundness

Proof. If ρDLΦ2, the checked implication gives ρDLΦ1. Equality of command, store, and cursor leaves every other clause of definition 69.2 unchanged. ◻

General merging does not fit that representation. The separate extension SymImp-DL admits Boolean path formulas and guarded terms ite(B,t1,t2). Two states at the same command and cursor merge as (σ1,Φ1),(σ2,Φ2)(xite(Φ1,σ1(x),σ2(x)),Φ1Φ2).

Proposition 69.10 — Guarded-merge representation

The merged SymImp-DL state represents exactly the union of the two input states, provided their path conditions are disjoint.

Proof of Proposition 69.10 — Guarded-merge representation

Proof. Evaluate the disjunction. If Φ1 holds, disjointness excludes Φ2, and every guarded store component evaluates to σ1(x). Otherwise satisfaction forces Φ2, and it evaluates to σ2(x). Conversely, a valuation represented by either input satisfies the disjunction and the corresponding guard. ◻

The unguarded merge Φ1Φ2 with a single arbitrarily selected store is unsound. From branches x=0,y=0 and x=1,y=1, selecting the first store under the disjunction admits input x=1 paired with y=0, a state in neither branch. No simulation or coverage theorem transfers from SymImp-DL to the extension until its guarded-expression cases are proved.

Exercise 69.4

★★★ At a common command and store, decide whether x0y1 subsumes x1y1. Give the negative-cycle implication certificate. Then explain why changing only one symbolic store invalidates the test.

Loops and concolic guidance

Loops require three different contracts.

  1. A depth bound explores only paths with at most that many loop unfoldings. It is path fuel, not evidence that the source loop terminates.

  2. A user invariant J is checked by initiation ΦJ, preservation Jgwp(c,J), and exit J¬gQ. The conclusion is the stated partial-correctness postcondition Q, not complete test generation.

  3. Unbounded exploration retains every generated path but has no search termination claim.

Concolic execution carries a concrete oracle beside the symbolic state. It follows the concrete branch while recording the symbolic atom. To schedule an alternate, keep the prefix before branch j, negate its atom, obtain checked model ρ, and replay from the beginning.

Theorem 69.11 — Concolic alternate-input justification

If the checked model ρ satisfies the retained prefix and negated branch atom, deterministic replay follows that prefix and takes the opposite branch at j.

Proof of Theorem 69.11 — Concolic alternate-input justification

Proof. Apply expression correspondence at every prefix guard. Each has the recorded truth value. At j, equation (neg) gives the opposite truth value. Determinism fixes the replayed control path through that point. ◻

This is the feedback loop used by DART and CUTE [GKS05, SMA05]. Complete branch coverage follows only when the chosen exploration schedule is fair over the finite loop-free tree and the constraint procedure eventually returns a checked model or certificate for every supported condition. Timeouts and unknown invalidate completeness, not soundness.

From paths to intervals

There is an explicit abstraction to the interval domain of chapter 25. For a finite family of symbolic states at one command, concretize each by its path condition and symbolic store, take their union, then take the least interval box containing that union. For Fork, the two post-branch symbolic relations are x0y=x+1,x1y=x1. Their interval hull is x,y[,+]. The implication x4y3, which isolates the failed assertion, is lost. The abstraction is sound because every represented store lies in its hull; it is deliberately incomplete because the hull contains stores satisfying neither relation. Symbolic execution and abstract interpretation are related by this map, not identified as implementation variants.

Chapter seminar

The Kappa companion at artifacts/ch69-symimp-dl/ implements the finite SymImp-DL command and path AST, a fuel-bounded symbolic work-list explorer, concrete replay, model checking, and certificate-edge incidence plus negative-cycle checking. Its Agda companion states the store-representation relation and proves expression correspondence for the copy-plus-constant fragment; appendix E records the mechanized boundary.

The first two problems form the practical route. The remaining problems test which hypotheses would have to change in a larger executor.

Suggested first pass.

Replay Fork, inspect the rejected zero-weight cycle, and trace one concolic branch reversal before changing the language.

Exercise 69.5

★★★ Practical project.symimp-dl-certificate-checker Run the accepted corpus. Add a command havoc[l,u]x, give its concrete and symbolic rules, and extend the one-step simulation proof. Mutate the cycle checker to accept weight zero and confirm that the unchanged oracle rejects it. Finally record a solver unknown and verify that the path remains scheduled.

Exercise 69.6

★★★ Construct the m-conditional family with 2m feasible paths. Compare depth-first, breadth-first, and concolic-negation schedules on the first four new branches; state which fairness property each completeness argument needs.

Exercise 69.7

★★★ Prove the assignment case for SymImp-DL guarded stores. Give one additional unguarded-merge counterexample in which the path condition is preserved but a correlation between two variables is lost.

Exercise 69.8

★★★ Extend the representation relation with a finite heap and commands x:=[p], [p]:=x. State the address-definedness premises required by expression correspondence and prove the read case of simulation. Do not assume separation-logic entailment.

The proved core ends at conjunctive difference constraints, finite symbolic derivations, local certificate/model checking, and deterministic replay. General SMT completeness, unrestricted-loop coverage, separation-logic heaps, concurrency, quantified theories, and floating point require new semantics or solver theorems. Symbolic execution produces paths and tests; it does not by itself produce a residual executable program.

Search the book

Type to search the local edition.