380 likes | 404 Views
Decidable Verification of Uninterpreted Programs. Umang Mathur. P. Madhusudan. Mahesh Viswanathan. Program Verification. In general, verification over infinite domains is undecidable Requires manual effort - contracts, loop invariants. Existing Decidable Classes. Unnatural program models.
E N D
Decidable Verification of Uninterpreted Programs Umang Mathur P. Madhusudan Mahesh Viswanathan
Program Verification • In general, verification over infinite domains is undecidable • Requires manual effort - contracts, loop invariants Existing Decidable Classes Unnatural program models Finite domain (Boolean programs) Undecidable
Uninterpreted Programs • Programs with constants, functions and predicates that are completely uninterpreted • Interpretations are given by data model • Satisfies a post-condition φ if φ holds in all data models
Contributions • Verification of uninterpreted programs is undecidable • Coherent and k-Coherent programs – Admit Decidable Verification First class of programs with infinite domain to admit decidable verification Decidable with recursive function calls Maximally decidable - simple extensions become undecidable PSPACE without recursion EXPTIME with recursion
Uninterpreted Programs • <prog> := <stmt><post-condition> <stmt> := x ← y x ← f(z1, z2, …, zk) if<cond> then<stmt>else<stmt> while<cond><stmt> • skip • assume(<cond>) <stmt>;<stmt> <cond> := x= y • R(z1, z2, …, zk) ¬<cond> <cond> ∨ <cond> Program Syntax • <post-condition>:= x= y • R(z1, z2, …, zk) • ¬<post-condition> • <post-condition>∨<post-condition> Post-conditions
Uninterpreted Programs Verification (P ⊨ φ) • P ⊨ φ, if • for every data model M (interpretation for constants, functions and relations in P), and • for every execution ρ of P that is feasible in M, • φholds in M at the end of ρ
Uninterpreted Programs b ← F; while(x ≠ y){if(key(x) = k)then{ b ← T; r ← x;} x ← n(x);} @post:b=T ⇒ key(r)=k Check if • for all interpretations of n and key, • and for all initial values of x, y, b, r and k the following formula holds at the end of each executionb=T ⇒ key(r)=k This is a coherent program and we can verify it without loop invariants ! Search key k in list segment from x to y
Algebraic View of Program Executions • Sequence of basic statements • x← y • x← f(z1, z2, …, zk) • assume(x = y) • assume(x≠ y) • Algebraic view of executions: • Compute terms using constants and function symbols • Accumulate assumptions involving terms
Algebraic View of Program Executions • Sequence of basic statements • x← y • x← f(z1, z2, …, zk) • assume(x = y) • assume(x≠ y) • Algebraic view of executions: • Compute terms using constants and function symbols • Accumulate assumptions involving terms
Algebraic View of Program Executions assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{ b ← T;} x ← n(x);} Assumptions Terms
Algebraic View of Program Executions assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{ b ← T;} x ← n(x);} • b0 • T0 • y0 • k0 • d0 • x0 • F0 • b • T • x • y • d • F • k Assumptions Terms
Algebraic View of Program Executions assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{ b ← T;} x ← n(x);} • T0 ≠ F0 • x0 • T0 • F0 • d0 • k0 • b0 • y0 • x • d • y • F • T • b • k Assumptions Terms
Algebraic View of Program Executions assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{ b ← T;} x ← n(x);} • T0 ≠ F0 • T0 • y0 • F0 • k0 • x0 • b0 • d0 • k • T • F • x • d • b • y Assumptions Terms
Algebraic View of Program Executions assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{ b ← T;} x ← n(x);} • T0 ≠ F0 • x0 ≠ y0 • d0 • y0 • F0 • x0 • b0 • k0 • T0 • y • x • d • F • T • b • k Assumptions Terms
Algebraic View of Program Executions assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{ b ← T;} x ← n(x);} • T0 ≠ F0 • x0 ≠ y0 • x0 • T0 • F0 • d0 • b0 • k0 • y0 • key(x0) • x • F • T • b • y • k • d Assumptions Terms
Algebraic View of Program Executions assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{ b ← T;} x ← n(x);} • T0 ≠ F0 • x0 ≠ y0 • x0 • T0 • F0 • d0 • b0 • k0 • y0 • key(x0) • key(x0) ≠ k0 • d • x • F • T • b • y • k Assumptions Terms
Algebraic View of Program Executions assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{ b ← T;}x ← n(x);} • T0 ≠ F0 • x0 ≠ y0 • n(x0) • x0 • d0 • b0 • F0 • T0 • k0 • y0 • key(x0) • key(x0) ≠ k0 • x • T • b • y • k • d • F Assumptions Terms
Algebraic View of Program Executions assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{ b ← T;} x ← n(x);} • T0 ≠ F0 • x0 ≠ y0 • n(x0) • x0 • y0 • F0 • d0 • b0 • k0 • T0 • key(x0) • key(x0) ≠ k0 • n(x0) ≠ y0 • F • T • b • y • k • d • x Assumptions Terms
Algebraic View of Program Executions assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{ b ← T;} x ← n(x);} • T0 ≠ F0 • x0 ≠ y0 • key(n(x0)) • n(x0) • x0 • d0 • b0 • F0 • T0 • k0 • y0 • key(x0) • key(x0) ≠ k0 • n(x0) ≠ y0 • F • T • b • y • k • x • d Assumptions Terms
Algebraic View of Program Executions assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{ b ← T;} x ← n(x);} • T0 ≠ F0 • x0 ≠ y0 • key(n(x0)) • n(x0) • x0 • y0 • F0 • d0 • b0 • k0 • T0 • key(x0) • key(x0) ≠ k0 • n(x0) ≠ y0 • key(n(x0)) = k0 • F • T • b • y • k • x • d Assumptions Terms
Algebraic View of Program Executions assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{b ← T;} x ← n(x);} • T0 ≠ F0 • x0 ≠ y0 • key(n(x0)) • n(x0) • x0 • T0 • F0 • d0 • b0 • k0 • y0 • key(x0) • key(x0) ≠ k0 • n(x0) ≠ y0 • key(n(x0)) = k0 • F • T • y • k • x • d • b Assumptions Terms
Algebraic View of Program Executions assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{ b ← T;}x ← n(x);} • T0 ≠ F0 • x0 ≠ y0 • key(n(x0)) • n(n(x0)) • n(x0) • x0 • d0 • b0 • F0 • T0 • k0 • y0 • key(x0) • key(x0) ≠ k0 • n(x0) ≠ y0 • key(n(x0)) = k0 • F • T • y • k • d • b • x Assumptions Terms
Coherence A program execution is coherent if it is memoizing, and has early assumes Coherence Memoizing Early Assumes = +
Coherence Memoizing Don’t recompute terms.If a term is recomputed, it must already be present in some variable • Make equality assumptions early. • Make equality assumptions between terms before forgetting a computed superterm Early Assumes
Coherence • If a term is recomputed, then it must already be present in some variable • Let 𝞼 · ”x ← f(y)” be an execution such that t = f(y)was computed earlier in 𝞼. • Then some program variable z must store t after 𝞼. Memoizing d d
Coherence • If a term is recomputed, then it must already be present in some variable • Let 𝞼 · ”x ← f(y)” be an execution such that t = f(y)or some t’~t was computed earlier in 𝞼. • Then some program variable z must store t or some t”~t after 𝞼. Memoizing d Equivalent according the assumptions seen so far in 𝞼 d
Coherence All executions of this program are memoizing Memoizing assume(T ≠ F) b ← F; while(x ≠ y){ d ← key(x);if(d = k)then{ b ← T;} x ← n(x);}
Coherence Memoizing Don’t recompute terms.If a term is recomputed, it must already be present in some variable • Make equality assumptions early. • Make equality assumptions between terms before forgetting a computed superterm Early Assumes
Coherence • If two terms are assumed to be equal, make the assumption early • Let 𝞼 · ”assume(x = y)” be an execution. If s, a superterm of the term stored in either x or y (modulo ~)has been computed in 𝞼, • then some program variable z must store a term equivalent to s at the end of 𝞼. Early Assumes
Verification of Coherent Programs A program is coherent if all its executions are coherent Verification of coherent programs is decidable • PSPACE-complete for coherent programs without recursion • EXPTIME-complete for recursive coherent programs First class of infinite domain programs to admit decidable verification Maximally decidable – relaxing either memoizing or early assumes leads to undecidability
Verification of Coherent Programs Key Idea Streaming Congruence Closure
Streaming Congruence Closure • Constant memory streaming algorithm for analyzing executions • Maintain congruence closure of the set of terms corresponding only to current values of the program variables • Coherence makes such an algorithm possible • Bounded Path decomposition
Streaming Congruence Closure • Automaton A for streaming congruence closure accepting executions not satisfying postcondition φ • States of A are(~, d, P) Functional relationship between variables Equalities between variables Dis-equalities between variables x = y b= f(a) x ≠ w
Streaming Congruence Closure • P ⊨ φ if and only if Executions(P) ∩ L(A) = ∅ • For while programs, both Executions(P) and L(A) are regular - decidable. • For recursive programs, both Executions(P) and L(A) are visibly pushdown languages - decidable.
Verification of Coherent Programs A program is coherent if all its executions are coherent Verification of coherent programs is decidable • PSPACE-complete for coherent programs without recursion • EXPTIME-complete for recursive coherent programs Checking if a program is coherent is also decidable
k-Coherence assume(x ≠ NIL); y ← n(x); assume(y ≠ NIL); y ← n(y); while(y ≠ NIL){ x ← n(x); y ← n(y);} assume(x ≠ NIL); y ← n(x); g ← y; assume(y ≠ NIL); y ← n(y); while(y ≠ NIL){ x ← n(x); g ← y; y ← n(y);} Add 1 ghost variable 1-coherent Non-coherent Coherent
k-Coherence A program isk-coherent if it can be made coherent by adding k ∈ ℕ extra (write-only) ghost variables. Given k ∈ ℕ, • theproblem of checking if P is k-coherentprogram is decidable in PSPACE • verification of k-coherent programs is PSPACE-complete (EXPTIME-complete with recursion)
Concluding Remarks Summary Future Work • Evaluation as a program verifier (see EUForia, VMCAI’19) • Theories and axioms • Trace abstraction [Heizmann et al] • Coherent and k-Coherent programs • Completely automatic verification • First natural decidable class over infinite domain • Efficiently decidable • PSPACE for non-recursive • EXPTIME for recursive • Checking coherence and k-coherence is decidable