Decidable Verification of Uninterpreted Programs Umang Mathur P
Decidable Verification of Uninterpreted Programs Umang Mathur P. Madhusudan Mahesh Viswanathan
Program Verification Finite domain (Boolean programs) Existing Decidable Classes Unnatural program models • In general, verification over infinite domains is undecidable • Requires manual effort contracts, loop invariants 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 PSPACE without recursion EXPTIME with recursion Maximally decidable - simple extensions become undecidable
Uninterpreted Programs <prog> : = <stmt> <post-condition> <cond> : = <stmt> : = x ← y x ← f(z 1, z 2, …, zk) if <cond> then <stmt> else <stmt> while <cond> <stmt> skip assume( <cond>) <stmt>; <stmt> Program Syntax x = y R(z 1, z 2, …, zk) ¬ <cond> ∨ <cond> <post-condition> : = x = y R(z 1, z 2, …, zk) ¬ <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 Search key k in list segment from x to y Check if ● for all interpretations of n and key, This is a coherent program ● and for all initial values of x, y, b, r and k and we can verify it the following formula holds at the end of each without loop invariants ! execution b=T ⇒ key(r)=k
Algebraic View of Program Executions ● Sequence of basic statements ■ x ← y ■ x ← f(z 1, z 2, …, 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(z 1, z 2, …, 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); } Terms Assumptions
Algebraic View of Program Executions assume (T ≠ F) y 0 b ← F; while (x ≠ y){ x 0 d ← key(x); if(d = k) then k y k 0 x d d 0 { b ← T; T 0 T F 0 F } x ← n(x); } b 0 b Terms Assumptions
Algebraic View of Program Executions assume (T ≠ F) y 0 b ← F; while (x ≠ y){ x 0 d ← key(x); if(d = k) then k y k 0 T 0 ≠ F 0 x d d 0 { b ← T; T 0 T F 0 F } x ← n(x); } b 0 b Terms Assumptions
Algebraic View of Program Executions assume (T ≠ F) y 0 b ← F; while (x ≠ y){ x 0 d ← key(x); if(d = k) then k y k 0 T 0 ≠ F 0 x d d 0 { b ← T; T 0 } x ← n(x); } T F 0 F b b 0 Terms Assumptions
Algebraic View of Program Executions assume (T ≠ F) y 0 b ← F; while (x ≠ y){ x 0 d ← key(x); if(d = k) then k y k 0 T 0 ≠ F 0 x x 0 ≠ y 0 d d 0 { b ← T; T 0 } x ← n(x); } T F 0 F b b 0 Terms Assumptions
Algebraic View of Program Executions assume (T ≠ F) y 0 b ← F; while (x ≠ y){ x 0 k y k 0 x x 0 ≠ y 0 d ← key(x); if(d = k) then T 0 ≠ F 0 d d 0 key(x 0) { b ← T; T 0 } x ← n(x); } T F 0 F b b 0 Terms Assumptions
Algebraic View of Program Executions assume (T ≠ F) y 0 b ← F; while (x ≠ y){ x 0 k y k 0 x x 0 ≠ y 0 d ← key(x); if(d = k) then T 0 ≠ F 0 d d 0 key(x 0) ≠ k 0 { b ← T; T 0 } x ← n(x); } T F 0 F b b 0 Terms Assumptions
Algebraic View of Program Executions assume (T ≠ F) y 0 k y k 0 b ← F; while (x ≠ y){ x 0 n(x 0) d ← key(x); if(d = k) then T 0 ≠ F 0 x x 0 ≠ y 0 d d 0 key(x 0) ≠ k 0 { b ← T; T 0 } x ← n(x); } T F 0 F b b 0 Terms Assumptions
Algebraic View of Program Executions assume (T ≠ F) y 0 k y k 0 b ← F; while (x ≠ y){ x 0 n(x 0) d ← key(x); if(d = k) then T 0 ≠ F 0 x x 0 ≠ y 0 d d 0 key(x 0) ≠ k 0 { b ← T; T 0 } x ← n(x); } T F 0 F n(x 0) ≠ y 0 b b 0 Terms Assumptions
Algebraic View of Program Executions assume (T ≠ F) y 0 k y k 0 b ← F; while (x ≠ y){ x 0 n(x 0) d 0 key(x 0) T 0 ≠ F 0 x x 0 ≠ y 0 d ← key(x); if(d = k) then key(n(x 0)) d key(x 0) ≠ k 0 { b ← T; T 0 } x ← n(x); } T F 0 F n(x 0) ≠ y 0 b b 0 Terms Assumptions
Algebraic View of Program Executions assume (T ≠ F) y 0 k y k 0 b ← F; while (x ≠ y){ x 0 n(x 0) d 0 key(x 0) T 0 ≠ F 0 x x 0 ≠ y 0 d ← key(x); if(d = k) then key(n(x 0)) d key(x 0) ≠ k 0 { b ← T; T 0 } x ← n(x); } T F 0 F n(x 0) ≠ y 0 b key(n(x 0)) = k 0 b 0 Terms Assumptions
Algebraic View of Program Executions assume (T ≠ F) y 0 k y k 0 b ← F; while (x ≠ y){ x 0 n(x 0) d 0 key(x 0) T 0 ≠ F 0 x x 0 ≠ y 0 d ← key(x); if(d = k) then key(n(x 0)) d key(x 0) ≠ k 0 { b ← T; T 0 } x ← n(x); } T F 0 F n(x 0) ≠ y 0 b key(n(x 0)) = k 0 b 0 Terms Assumptions
Algebraic View of Program Executions assume (T ≠ F) y 0 k y k 0 T 0 ≠ F 0 b ← F; while (x ≠ y){ x 0 n(x 0) n(n(x 0)) d 0 key(x 0) key(n(x 0)) x x 0 ≠ y 0 d ← key(x); if(d = k) then d key(x 0) ≠ k 0 { b ← T; T 0 } x ← n(x); } T F 0 F n(x 0) ≠ y 0 b key(n(x 0)) = k 0 b 0 Terms Assumptions
Coherence A program execution is coherent if it is memoizing, and has early assumes Coherence = Memoizing + Early Assumes
Coherence Don’t recompute terms. Memoizing If a term is recomputed, it must already be present in some variable Make equality assumptions early. Early Assumes Make equality assumptions between terms before forgetting a computed superterm
Coherence If a term is recomputed, then it must already be Memoizing present in some variable d Let �� · ”x ← f( y ) ” be an execution such that t d = f( y ) was computed earlier in ��. Then some program variable z must store t after ��.
Coherence If a term is recomputed, then it must already be Memoizing present in some variable d Let �� · ”x ← f( y ) ” be an execution such that t Equivalent d according the = f( y ) or some t’~ t was computed earlier in ��. assumptions seen so far in �� Then some program variable z must store t or some t” ~ t after ��.
Coherence assume (T ≠ F) Memoizing b ← F; while (x ≠ y){ d ← key(x); if(d = k) then { b ← T; } x ← n(x); } All executions of this program are memoizing
Coherence Don’t recompute terms. Memoizing If a term is recomputed, it must already be present in some variable Make equality assumptions early. Early Assumes Make equality assumptions between terms before forgetting a computed superterm
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 Early Assumes (modulo ~)has been computed in �� , then some program variable z must store a term equivalent to s at the end of ��.
Verification of Coherent Programs A program is coherent if all its executions are coherent Maximally Verification of coherent programs is decidable First class of decidable – infinite domain relaxing either ■PSPACE-complete without programs to admit for coherent programs memoizing or decidable early assumes recursion verification leads to ■EXPTIME-complete for recursive coherent programs undecidability
Verification of Coherent Programs Key Idea Streaming Congruence Closure
Streaming Congruence Closure ● Constant memory streaming algorithmfor 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) Equalities between variables Dis-equalities between variables Functional relationship between variables
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 Checking if a program is coherent is also decidable recursion ■EXPTIME-complete for recursive coherent programs
k-Coherence assume (x ≠ NIL); y ← n(x); g ← y; assume (y ≠ NIL); y ← n(y); while (y ≠ NIL){ Add 1 ghost variable while (y ≠ NIL){ x ← n(x); y ← n(y); g ← y; y ← n(y); } } Non-coherent 1 -coherent Coherent
k-Coherence A program is k-coherent if it can be made coherent by adding k ∈ ℕ extra (write-only) ghost variables. Given k ∈ ℕ, ■ the problem of checking if P is k-coherent program is decidable in PSPACE ■ verification of k-coherentprograms is PSPACEcomplete (EXPTIME-complete with recursion)
Concluding Remarks Summary Future Work ✓ 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 ✓ Evaluation as a program verifier (see EUForia, VMCAI’ 19) ✓ Theories and axioms ✓ Trace abstraction [Heizmann et al]
- Slides: 38