Higher-Order Verification With Liquid Types Ranjit Jhala, UC San Diego (with Pat Rondon, Ming Kawaguchi)
Part I First-Order Verification Part II Higher-Order Verification
char* rev_copy(char* a, int n){ i = 0; j = n – 1; b = malloc(n); while(0<=j){ b[i] = a[j]; i++; j--; } return b; } First-Order Verification
char* rev_copy(char* a, int n){ i = 0; j = n – 1; b = malloc(n); while(0<=j){ b[i] = a[j]; i++; j--; } return b; } Example: Memory Safety Access Within Array Bounds
char* rev_copy(char* a, int n){ i = 0; j = n – 1; b = malloc(n); while(j>=0){ b[i] = a[j]; i++; j--; } return b; } assert (0<=i && i<n); 0: 1: 2: How to prove assert never fails ? assert (i<n); 0: i = 0; j = n–1; 1: while (0<=j){ 2: assert(i<n); i = i+1; j = j–1; } Access Within Array Bounds
How to prove asserts? Invariants [Floyd-Hoare]
Invariants Predicate that is always Program Location 0: i = 0; j = n–1; 1: while (0<=j){ 2: assert(i<n); i = i+1; j = j–1; } true i+j=n-1 i+j=n-1 Æ 0 · j Invariant Proves Assert
How to Prove Asserts?How to Find Invariants?
0: i = 0; j = n–1; 1: while (0<=j){ 2: assert(i<n); i = i+1; j = j–1; } ? What are Invariants ? ? ?
Let X i = location i
0: i = 0; j = n–1; 1: while (0<=j){ 2: assert(i<n); i = i+1; j = j–1; } ? What are Invariants ? ? ? X0X0 X1X1 X2X2 Properties of X 0,X 1,X 2 ?
0: i = 0; j = n–1; 1: while (0<=j){ 2: assert(i<n); i = i+1; j = j–1; } What are Invariants ? X0X0 Initial Values Arbitrary X 0 = true
0: i = 0; j = n–1; 1: while (0<=j){ 2: assert(i<n); i = i+1; j = j–1; } What are Invariants ? i=0 Æ j=n-1 ) X 1 true X1X1
0: i = 0; j = n–1; 1: while (0<=j){ 2: assert(i<n); i = i+1; j = j–1; } What are Invariants ? 0 · j Æ X 1 ) X 2 X1X1 X2X2
0: i = 0; j = n–1; 1: while (0<=j){ 2: assert(i<n); i = i+1; j = j–1; } What are Invariants ? X 2 ) i<n X2X2
0: i = 0; j = n–1; 1: while (0<=j){ 2: assert(i<n); i = i+1; j = j–1; } What are Invariants ? i=i o +1 Æ j=j o -1 Æ [ i o /i ][ j o /j ] X 2 ) X 1 X1X1 X2X2
What are Invariants ? … Æ [ i o /i ][ j o /j ] X 2 ) X 1 Predicates X 1, X 2 s.t. i=0 Æ j=n-1 ) X 1 0 · j Æ X 1 ) X 2 X 2 ) i<n
What are Invariants ? … Æ [ i o /i ][ j o /j ] X 2 ) X 1 Predicates X 1, X 2 s.t. i=0 Æ j=n-1 ) X 1 0 · j Æ X 1 ) X 2 X 2 ) i<n How to Infer Invariants? How to Solve for X 1, X 2 ? Idea: Lazy Abstraction
Tree of executions over atomic predicates i+j=n-1 0·j0·j Nodes: X 1, X 2 Edges: X 1 ) X 2
… [ i o /i ][ j o /j ] X 2 ) X 1 0 · j Æ X 1 ) X 2 X 2 ) i<n Lazy Predicate Abstraction X0X0 true Tree Root Root X (i.e. non-RHS) i=0 Æ j=n-1 Æ X 0 ) X 1 Atoms: i+j=n-1, 0 · j
Lazy Predicate Abstraction X0X0 true X1X1 Tree Edge “Unrolled” Implication … [ i o /i ][ j o /j ] X 2 ) X 1 0 · j Æ X 1 ) X 2 X 2 ) i<n i=0 Æ j=n-1 Æ X 0 ) X 1 Atoms: i+j=n-1, 0 · j
Lazy Predicate Abstraction X0X0 true X1X1 Theorem Prover i=0 Æ j=n-1 Æ X 0 ) X 1 Atoms: i+j=n-1, 0 · j ? i=0 Æ j=n-1 Æ true ) i+j=n-1 Valid
Lazy Predicate Abstraction X0X0 true X1X1 i+j=n-1 Theorem Prover i=0 Æ j=n-1 Æ X 0 ) X 1 Atoms: i+j=n-1, 0 · j i=0 Æ j=n-1 Æ true ) 0·j0·j Invalid … [ i o /i ][ j o /j ] X 2 ) X 1 0 · j Æ X 1 ) X 2 X 2 ) i<n ?
Lazy Predicate Abstraction X0X0 true X1X1 i+j=n-1 … [ i o /i ][ j o /j ] X 2 ) X 1 0 · j Æ X 1 ) X 2 X 2 ) i<n i=0 Æ j=n-1 Æ X 0 ) X 1 Atoms: i+j=n-1, 0 · j X2X2 i+j=n-1 Æ 0 · j ?
Lazy Predicate Abstraction X0X0 true X1X1 i+j=n-1 … [ i o /i ][ j o /j ] X 2 ) X 1 0 · j Æ X 1 ) X 2 X 2 ) i<n i=0 Æ j=n-1 Æ X 0 ) X 1 Atoms: i+j=n-1, 0 · j X2X2 i+j=n-1 Æ 0 · j i<ni<n Theorem Prover 0 · j Æ i+j=n-1 ) i<n Valid
Lazy Predicate Abstraction X0X0 true X1X1 i+j=n-1 X2X2 X1X1 i<ni<n ? i+j=n-1 Æ 0 · j … [ i o /i ][ j o /j ] X 2 ) X 1 0 · j Æ X 1 ) X 2 X 2 ) i<n i=0 Æ j=n-1 Æ X 0 ) X 1 Atoms: i+j=n-1, 0 · j i+j=n-1
Lazy Predicate Abstraction X0X0 true X1X1 i+j=n-1 X2X2 X1X1 i<ni<n i+j=n-1 Æ 0 · j … [ i o /i ][ j o /j ] X 2 ) X 1 0 · j Æ X 1 ) X 2 X 2 ) i<n i=0 Æ j=n-1 Æ X 0 ) X 1 Atoms: i+j=n-1, 0 · j i+j=n-1 Fixpoint Stop Unrolling Inferred Invariants Proved Asserts… Constraints Solved …not so fast!
C Program + Asserts Lazy Abstraction [popl 02] Atoms Safety Invariants How to get good atoms? e.g. i+j=n-1 If we have bad atoms... e.g. i=0, j=n-1, 0 · j
X2X2 i<ni<n X0X0 true X1X1 i=0 Æ j=n-1 X2X2 X1X1 i<ni<n i=0 Æ j=n-1 Æ 0 · j true …Yields Counterexample “Path” Abstraction With Bad Atoms... Assert Holds Not a fixpoint Assert Fails i:=0 j:=n–1 0<=j? i:=i+1 j:=j-1 0<=j?
Bad atoms yield counterexample paths
C Program + Asserts Lazy Abstraction [popl 02] AtomsPath Safety Invariants Counterexample Analysis Unsafe Paths “Counterexample Guided Abstraction Refinement” [Kurshan 94, Clarke et al. 00, Ball & Rajamani 00] PathAtoms
X2X2 i<ni<n X0X0 X1X1 X2X2 X1X1 i:=0 j:=n–1 0<=j? i:=i+1 j:=j-1 0<=j? PathAtomsFormulaProof Good Atoms Relationships from past Prove safety of future i:=0 j:=n–1 0<=j? i:=i+1 j:=j-1 0<=j? How to compute good atoms from paths?
PathAtomsFormulaProof Æ i 0 = 0 Æ j 0 = n–1 Æ 0 · j 0 Æ i 1 = i Æ j 1 = j Æ n · i 1 ¸ Æ 0 · j 1 Negate Assert Rename Variables (SSA) Formula Unsatisfiable iff Assert Holds X2X2 i<ni<n X0X0 X1X1 X2X2 X1X1 i:=0 j:=n–1 0<=j? i:=i+1 j:=j-1 0<=j?
PathAtomsFormulaProof Æ i 0 =0 Æ j 0 =n–1 Æ 0 · j 0 Æ i 1 =i 0 +1 Æ j 1 =j 0 -1 Æ n · i 1 Æ 0 · j 1 Æ i 0 = 0 Æ j 0 = n–1 Æ 0 · j 0 Æ i 1 = i Æ j 1 = j Æ i 1 ¸ n Æ 0 · j 1 0·j10·j1 j 1 =j 0 -1 j 0 =n-1 n·i1n·i1 i 1 =i 0 +1 i 0 =0 0 · j · n-2 0 · -1 n · i 0 +1 n·1n·1 False X2X2 i<ni<n X0X0 X1X1 X2X2 X1X1 i:=0 j:=n–1 0<=j? i:=i+1 j:=j-1 0<=j?
PathAtomsFormulaProof 0·j10·j1 j 1 =j 0 -1 j 0 =n-1 n·i1n·i1 i 1 =i 0 +1 i 0 =0 0 · j · n-2 n · i 0 +1 n·1n·1 False Good Atoms Relationships from past Prove safety of future + i+j=n-1
Atom = Craig Interpolant Of Past, Future Formulas Extracted from proof Of path unsatifiability Inferred Good Atom i+j=n-1 X2X2 i<ni<n X0X0 X1X1 X2X2 X1X1 0<=j? i:=0 j:=n–1 0<=j? i:=i+1 j:=j-1 0<=j?
Recap How to verify safety ? Compute invariants X 1, X 2... How to solve for X 1, X 2... ? Tree of executions over atoms How to find good atoms ? Interpolants of path formulas
Recap Safety Invariants Implications AI, PA, CEGAR,… X 0, X 1 X0 ) X1X0 ) X1
Part I First-Order (by Logic) Part II Higher-Order Verification
Key Problem: Invariants for… Collections? Closures? Polymorphism? Recursive Data?
Idea: Logically Qualified Types Factor Invariant to Logic x Type Idea: Liquid Types
Logic Describes Individual Data Type Quantifies over Structure
factored into 8 i: 0 · i < table.length ) -1 · table[i] table :: {v:int|-1 · v} array TypeLogic
factored into 8 x: next*(root,x) ) -1 · x.data root :: {v:int|-1 · v} list TypeLogic
Pre-Condition x:’a array -> {v:int|0 · v< len x} -> ’a Functions: Array.get Post-Condition ’a array -> int -> ’a
int -> int -> (int-> unit) -> unit Higher-Order: ffor lo:int -> hi:{int|lo · v} -> ({v:int|lo · v unit) -> unit
Logic Describes Individual Data Type Quantifies over Structure Theorem Prover Reasoning about Individual Data Type System Quantified Reasoning about Structure
Demo “Map-Reduce”
map :: (e -> (k, v) list) -> e list -> (k, v) list group :: (k, v) list -> (k, v list) table reduce :: (v -> v -> v) -> (k, v list) table -> (k, v) table
K-Means Clustering
0. Choose K Centers Arbitrarily
1. (Map) Points to Nearest Center
2. (Group) Points by Center
3. (Reduce) Centroids into New Centers
Repeat 1,2,3 Until Convergence
Demo K-Means via Map-Reduce
Base Types Collections Closures Polymorphism Recursive Data
let rec ffor l u f = if l < u then ( f l; ffor (l+1) u f ) Type of f int ! unit Template of f {v:int| X 1 } ! unit Liquid Type of f {v:int|l · v Æ v<u} ! unit l Flows Into Input of f {v:int | v=l} <: {v:int |X 1 } l<u |- l<u Æ v=l ) X 1 Solution X 1 = l · v Æ v<u Reduces to
Base Types Collections Closures Polymorphism Recursive Data
let nearest dist ctra x = let da = Array.map (dist x) ctra in [min_index da, (x, 1)] Type of Output int * ’b * int list Template of Output {v:int | X 1 } * ’b * {v:int | X 2 } list (’a ! ’b) ! x:’a array ! {v:’b array|len x = len v} Liquid Type of x:’a array ! {v:int| 0 · v Æ v < len x} min_index da {v:int| 0 · v Æ v < len da} da {v:’b array| len v = len ctra} len da = len ctra Æ 0 · v<len da ) X 1 len da = len ctra Æ v=1 ) X 2 da:{len v = len ctra} |-{ 0 · v<len da} * ’b * {v=1} list <: { X 1 } * ’b * { X 2 } list Reduces To Solution X 1 = 0 · v < len ctra X 2 = 0 < v Liquid Type of Output {v:int|0 · v<len ctra}*’b*{v:int|0<v} list
Base Types Collections Closures Polymorphism Recursive Data
let min_index a = let min = ref 0 in ffor 0 (Array.length a) (fun i -> if a.(i) < a.(!min) then min := i ); !min Liquid Type of ffor 0 (len a) ({v:int|0 · v < len a} ! unit) ! unit Template of (fun i ->...) {v:int| X i } ! unit { X i } ! unit <: {0 · v<len a} ! unit {0 · v<len a} unit {Xi}{Xi} {0 · v<len a} <: { X i } Reduces To unit <: unit 0 · v < len a ) X i Solution X i = 0 · v< len a Liquid Type of (fun i ->...) {v:int|0 · v<len a} ! unit Liquid Type of ffor l:int ! u:int ! ({v:int|l · v<u} ! unit) ! unit Liquid Type of ffor 0 u:int ! ({v:int|0 · v< u} ! unit) ! unit
Base Types Collections Closures Polymorphism Recursive Data
mapreduce (nearest dist ctra) (centroid plus) xs |> List.iter (fun (i,(x,sz)) -> ctra.(i)<- div x sz) Type of mapreduce (’a ! ’b * ’c list) !... ! ’b * ’c list Template of mapreduce (’a ! { X 1 } * ’a * { X 2 } list) !... ! { X 1 } * ’a * { X 2 } list Type Instantiation ’a with ’a ’b with int ’c with ’a * int Template Instantiation ’a with ’a ’b with {v:int| X 1 } ’c with ’a * {v:int| X 2 } Liquid Type of (nearest dist ya) ’a ! {0 · v < len ctra} * ’a * {0<v} list <: ’a ! { X 1 } * ’a * { X 2 } list Solution X 1 = 0 · v < len ctra X 2 = 0 < v Reduces To 0 · v < len ctra ) X 1 0 < v ) X 2 Liquid Type of mapreduce Output {0 · v < len ctra} * ’a * {0 < v} list
Polymorphism = “Meta” Invariants
Polymorphism = “Meta Invariants” foldl :: (a->b-> a)-> a-> b list-> a
Polymorphism = “Meta Invariants” foldl :: (a->b-> a)-> a-> b list-> a Initial Value Satisfies a
Polymorphism = “Meta Invariants” foldl :: (a->b-> a)-> a-> b list-> a Each “Iteration” Preserves a
Polymorphism = “Meta Invariants” foldl :: (a->b-> a)-> a-> b list-> a Hence, Output Satisfies a
Polymorphism = “Meta Invariants” foldl :: (a->b-> a)-> a-> b list-> a At callsite instantiate a for invariant Analysis oblivious to iterated structure
Base Types Collections Closures Polymorphism Recursive Data
Recursive Data Structures
Data (Structure) Invariants Piggyback Predicates On Types
[] :: {x:int|0<x} listint list 0<x Describes all elements x : int Representation
[] :: 0<x x : int Type Unfolding [] :: 0<h h : int [] :: 0<x x : int HeadTailEmpty PositiveProperty holds recursively List of positive integers
[] :: 0<x Describes all elements x : int x<v v Describes tail elements Representation
[] :: x<v x : int Type Unfolding [] :: h : int [] :: x<v x : int HeadTailEmpty Elements larger than head Property holds recursively List of sorted integers h<v Push Edge Predicate Into NodeRename Variable h<x
Piggyback Predicates On Types Data (Structure) Invariants
[] :: x : int Unfold :: h : int [] :: x : int l:sorted listh:intt:sorted list & {h<x} list Instantiate tl match l with h :: t x<V h<x Quantifier Instantiation
Piggyback Predicates On Types Data (Structure) Invariants
[] :: x : int Fold h : int [] :: x : int :: l:sorted listh:intt:sorted list & {h<x} list Generalize tl let l = h :: t in x<V h<x Quantifier Generalization
Demo isort
Recursive Data Structures
Piggyback Predicates On Types (Data) Structure Invariants
measure len = | [] -> 0 | x::xs -> 1 + len xs Representation: List Length
8 l,x,xs. len([]) = 0 len(x::xs) = 1+len(xs)
Piggyback Predicates On Types (Data) Structure Invariants
l:’a list h:’a t:’a list len(l)=1+len(t) Instantiate match l with h :: t Quantifier Instantiation 8 l,x,xs. len([]) = 0 len(x::xs) = 1+len(xs)
Piggyback Predicates On Types (Data) Structure Invariants
h:’a t:’a list Quantifier Generalization 8 l,x,xs. len([]) = 0 len(x::xs) = 1+len(xs) Generalize let l = h :: t in h:’a t:’a list l:’a list len(l)=1+len(t)
Demo msortb
Recursive Data Structures Piggyback Measures
Leaf l r l = Left subtree r = Right subtree treeHeight H l = Left subtree’s height H r = Right subtree’s height measure H = | Leaf = 0 | Node(x,l,r) = 1 + max (H l) (H r) Height Balanced Tree | Hl–Hr |< 2 Node Height difference bounded at each node
Demo eval
Automatic Liquid Type Inference By Predicate Abstraction
0<x [] :: x : int x<v Automatic Liquid Type Inference Predicates Determine Invariant Let X 1, X 2,... = Unknown Predicates Complex Subtyping Between data types X1X1 X2X2 Reduces To Simple Implications Between X 1, X 2,... Solved by Predicate Abstraction Over atoms 0<x, x<v,...
Part I First-Order (by Logic) Part II Higher-Order (by Types)
Take Home Lessons Why are HO Programs difficult? Complex “invariants” How to represent invariants? Factor into liquid type How to compute liquid type? AbsInt/Predicate Abstraction/…
“Back-End” Logic Constraint Solving Rich Decidable Logics Qualifier Discovery… Much Work Remains…
“Front-End” Types Destructive Update Concurrency Objects & Classes Dynamic Languages… Much Work Remains…
User Interface The smarter your analysis, the harder to tell why it fails! Much Work Remains…
source, papers, demo, etc.
Program (ML)Verified Safety Properties List-based SortingSorted, Outputs Permutation of Input Finite MapBalance, BST, Implements a Set Red-Black TreesBalance, BST, Color StablesortSorted Extensible VectorsBalance, Bounds Checking, … Binary HeapsHeap, Returns Min, Implements Set Splay HeapsBST, Returns Min, Implements Set MallocUsed and Free Lists Are Accurate BDDsVariable Order Union FindAcyclicity Bitvector UnificationAcyclicity
Finite Maps (ML) 5: ‘cat’ 3: ‘cow’ 8: ‘tic’ 1: ‘doc’ 4: ‘hog’ 7: ‘ant’ 9: ‘emu’ From Ocaml Standard Library Implemented as AVL Trees Rotate/Rebalance on Insert/Delete Verified Invariants Binary Search Ordered Height Balanced Keys Implement Set
Binary Decision Diagrams (ML) X1X1 X2X2 X2X2 X3X3 X4X4 X4X4 1 Graph-Based Boolean Formulas [Bryant 86] X 1 X 2 X 3 X 4 Efficient Formula Manipulation Memoizing Results on Subformulas Verified Invariant Variables Ordered Along Each Path
Vec: Extensible Arrays (317 LOC) “Python-style” extensible arrays for Ocaml find, insert, delete, join etc. Efficiency via balanced trees Balanced Height difference between siblings ≤ 2 Dsolve found balance violation
fatal off-by-one error Recursive Rebalance
Debugging via Inference Using Dsolve we found Where imbalance occurred (specific path conditions) How imbalance occurred (left tree off by up to 4) Leading to test and fix