1.79k likes | 1.98k Views
Liquid Types. Pat Rondon Ming Kawaguchi Ranjit Jhala. Goal: Software Verification. Verify absence of run-time errors. Buffer overflows Deadlocks Assertions. Progress: Path Sensitive Analyses. SMT Solvers Path Predicates Model Checking Loop Invariants, Function Summaries.
E N D
Liquid Types Pat Rondon Ming Kawaguchi Ranjit Jhala
Goal: Software Verification Verify absence of run-time errors • Buffer overflows • Deadlocks • Assertions
Progress: Path Sensitive Analyses • SMT Solvers Path Predicates • Model Checking Loop Invariants, Function Summaries
Progress: Path Sensitive Analyses • SMT Solvers Path Predicates • Model Checking Loop Invariants, Function Summaries • ASTREE • SLAM • BLAST Device Drivers • SATURN Linux Kernel
Imprecise, Limited Applicability • ASTREE • SLAM • BLAST Device Drivers • SATURN Linux Kernel • Control-intensive • Properties • Null-pointers • Double-locks …
Imprecise, Limited Applicability ? • Control-intensive • Properties • Null-pointers • Double-locks …
The Sources of Imprecision • Complex Data • Arrays • Lists • Hash Tables … • Complex Control • Function Pointers • Closures • Callbacks … ? • SLAM • BLAST Device Drivers • SATURN Linux Kernel
Types, Data and Control • “Since the 70s, • typeshave dealt with • dataand control” • Complex Data • Arrays • Lists • Hash Tables … • Complex Control • Function Pointers • Closures • Callbacks …
Types and Complex Data • Complex Data • Arrays • Lists • Hash Tables … • Quantified Predicates • Forall x in array: … • Forall x in list: … • Hard to automate
Types and Complex Data • Complex Data • Arrays • Lists • Hash Tables … • Quantified Predicates • Forall x in array: … • Forall x in list: … • Hard to automate Forall x in list: x is an int int list
Types and Complex Control • Complex Control • Function Pointers • Closures • Callbacks … • Function Summaries • Pre/Post Conditions • … are insufficient
Types and Complex Control (’a!’b)!’alist!’blist Higher-Order Summaries • Complex Control • Function Pointers • Closures • Callbacks … • Function Summaries • Pre/Post Conditions • … are insufficient
SMT and Model Checking Path and value information x>0, flag=1 Complex Data and Control
Type Systems • Complex Data and Control • int list • (’a!’b)!’alist!’blist Path and value information
Combine Strengths Path and value information Data Structures • Precise Software Verification
Plan • Motivation • Combining Types and Predicates
Combining Types and Predicates Refinement Types Typesrefinedwith Predicates over values
RefinementTypes positive integers • {V:int|0<V} Type Refinement
RefinementTypes integers between i,j {V:int|i·VÆV·j} Type Refinement
RefinementTypes list of integers between i,j {V:int|i<VÆV<j}list Type Refinement
RefinementTypes function with positive input i output larger than input i:{V:int|0<V}!{V:int|i<V} “Pre” “Post” “Requires” “Ensures”
Verification using Refinement Types • let abs x = • ifx>0 thenx else -x • lettrunci n = • leti’ = abs iin • let n’ = abs n in • ifi’<=n’ thenielsen’*(divii’) Divide by zero?
Verification using Refinement Types • let abs x = • ifx>0 thenx else -x • lettrunci n = • leti’ = abs iin • let n’ = abs n in • ifi’<=n’ thenielsen’*(divii’) • div::int!{V:int|V0}!int
Verification using Refinement Types • let abs x = • ifx>0 thenx else -x • lettrunci n = • leti’ = abs iin • let n’ = abs n in • ifi’<=n’ thenielsen’*(divii’) • div::int!{V:int|V0}!int Typecheck implies i’is nonzero
Verification using Refinement Types • letarraysum a = • let recloop m i n = • ifi >= n then m else • leti’= i + 1 in • let m’= m + (get a i) in • loop m’ i’ n in • loop 0 0 (length a) Array index within bounds?
Verification using Refinement Types • letarraysum a = • let recloop m i n = • ifi >= n then m else • leti’= i + 1 in • let m’= m + (get a i) in • loop m’ i’ n in • loop 0 0 (length a) get::x:’aarray!{V:int|0<=V<lengthx}!’a
Verification using Refinement Types • letarraysum a = • let recloop m i n = • ifi >= n then m else • leti’= i + 1 in • let m’= m + (get a i) in • loop m’ i’ n in • loop 0 0 (length a) get:: x:’aarray!{V:int|0<=V<lengthx}!’a Typecheck implies iwithin bounds
Verification using Refinement Types Just one little problem… How to compute Refinement Types?
How to compute Refinement Types? • Automatic Generation? • undecidable: space of types is unbounded • Manual Specification? • “The more interesting your types get, the less fun it is to write them down.” • - Benjamin Pierce
Dependent ML [Pfenning-Xi 1998] let rec helper (v1, v2, i, n, sum) = ifi= n then sum else helper (v1, v2, i+1, n, sum + (get v1 i) * (get v2 i)) letdotprod(v1, v2) = helper (v1, v2, 0, length v1, 0) withtype{n:nat, i:nat | i <= n} => int array(n) * int array(n) * int(i) * int(n) * int -> int withtype{n:nat} => int array(n) * int array(n) -> int Programmer writes type annotations (like @requires, @ensures, @invariant)
... A Lot of Annotations fun norm (arr2, n, i, j) = let val c = sub2 (arr2, i, j) in norm_aux (arr2, n, i, c, 1) end withtype {m:pos,n:pos,i:pos,j:pos | i < m, j < n} <> => (float array(n)) array(m) * int(n) * int(i) * int(j) -> unit fun row_op_aux1 (arr2, n, i, i', c, j) = if j < n then let valcj = sub2 (arr2, i, j) valcj' = sub2 (arr2, i', j) val _ = update2 (arr2, i', j, cj' -. cj *. c) in row_op_aux1 (arr2, n, i, i', c, j+1) end else () withtype {m:pos,n:pos,i:pos,i':nat, j:pos | i < m, i' < m, j <= n} <n-j> => (float array(n)) array(m) * int(n) * int(i) * int(i') * float * int(j) -> unit fun row_op_aux2 (arr2, n, i, i', j) = let val c' = sub2 (arr2, i', j) in row_op_aux1 (arr2, n, i, i', c', 1) end withtype {m:pos,n:pos,i:pos,i':nat, j:pos | i < m, i' < m, j < n} <> => (float array(n)) array(m) * int(n) * int(i) * int(i') * int(j) -> unit fun row_op_aux3 (arr2, m, n, i, j, i') = if i' < m then if i' <> i then let val _ = row_op_aux2(arr2, n, i, i', j) in row_op_aux3 (arr2, m, n, i, j, i'+1) end else row_op_aux3 (arr2, m, n, i, j, i'+1) else () withtype {m:pos,n:pos,i:pos,j:pos,i':nat | i < m, j < n, i' <= m} <m-i'> => (float array(n)) array(m) * int(m) * int(n) * int(i) * int(j) * int(i') -> unit fun row_op (arr2, m, n, i, j) = let val _ = norm (arr2, n, i, j) in row_op_aux3 (arr2, m, n, i, j, 0) end withtype {m:pos,n:pos,i:pos,j:pos| i < m, j < n} <> => (float array(n)) array(m) * int(m) * int(n) * int(i) * int(j) -> unit fun simplex (arr2, m, n) = if is_neg (arr2, n) then if unb1 (arr2, m, n, 0, 1) then abort ("simplex: unbound solution!") else let val j = enter_var (arr2, n, 1, sub2 (arr2, 0, 1), 2) val (i, r) = init_ratio (arr2, m, n, j, 1) vali = depart_var (arr2, m, n, j, i, r, i+1) val _ = row_op (arr2, m, n, i, j) in simplex (arr2, m, n) end else () withtype {m:int,n:int | m > 1, n > 2} (float array(n)) array(m) * int(m) * int(n) -> unit fun main (A (arr2, m, n)) = if m > 1 then if n > 2 then simplex (arr2, m, n) else abort ("too few columns") else abort ("too few rows") withtype float array2D -> unit fun('a) nRows (A (_, m, _)) = m withtype {m:nat,n:nat} <> => 'a array2D(m,n) -> int(m) fun('a) nCols (A (_, _, n)) = n withtype {m:nat,n:nat} <> => 'a array2D(m,n) -> int(n) fun is_neg_aux (arr2, n, j) = if j < n - 1 then if sub2 (arr2, 0, j) <. 0.0 then true else is_neg_aux (arr2, n, j+1) else false withtype {m:pos,n:pos,j:nat | j <= n} <n-j> => (float array(n)) array(m) * int(n) * int(j) -> bool fun is_neg (arr2, n) = is_neg_aux (arr2, n, 1) withtype {m:pos,n:pos} <> => (float array(n)) array(m) * int(n) -> bool fun unb1 (arr2, m, n, i, j) = if j < n-1 then if sub2 (arr2, 0, j) <. 0.0 then unb2 (arr2, m, n, i+1, j) else unb1 (arr2, m, n, 0, j+1) else false withtype {m:pos,n:pos,i:nat,j:nat | i < m, j <= n} <n-j, m-i> => (float array(n)) array(m) * int (m) * int(n) * int(i) * int(j) -> bool and unb2 (arr2, m, n, i, j) = if i < m then if sub2 (arr2, i, j) <. 0.0 then unb2 (arr2, m, n, i+1, j) else unb1 (arr2, m, n, 0, j+1) else true withtype {m:pos,n:pos,i:nat,j:nat | i <= m, j < n} <n-j,m-i> => (float array(n)) array(m) * int (m) * int(n) * int(i) * int(j) -> bool fun enter_var (arr2, n, j, c, j') = if j' < n-1 then let val c' = sub2 (arr2, 0, j') in if c' <. c then enter_var (arr2, n, j', c', j'+1) else enter_var (arr2, n, j, c, j'+1) end else j withtype {m:pos,n:pos,j:pos,j':pos | j+1 < n, j' < n} <n-j'> => (float array(n)) array(m) * int(n) * int(j) * float * int(j') -> [j:pos | j+1 < n] int(j) fun depart_var (arr2, m, n, j, i, r, i') = if i' < m then let val c' = sub2 (arr2, i', j) in if c' >. 0.0 then let val r' = sub2(arr2, i', n-1) /. c' in if r' <. r then depart_var(arr2, m, n, j, i', r', i'+1) else depart_var (arr2, m, n, j, i, r, i'+1) end else depart_var (arr2, m, n, j, i, r, i'+1) end else i withtype {m:pos,n:pos,i:pos,i':pos,j:pos | i < m, i' <= m, j < n} <m-i'> => (float array(n)) array(m) * int(m) * int(n) * int(j) * int(i) * float * int(i') -> [i:pos | i < m] int(i) fun init_ratio (arr2, m, n, j, i) = if i < m then let val c = sub2 (arr2, i, j) in if c >. 0.0 then (i, sub2 (arr2, i, n-1) /. c) else init_ratio (arr2, m, n, j, i+1) end else abort ("init_ratio: negative coefficients!") withtype {m:pos,n:pos,j:pos,i:pos | j < n, i <= m} <m-i> => (float array(n)) array(m) * int(m) * int(n) * int(j) * int(i) -> [i:pos | i < m] int(i) * float Simplex Algorithm
… A Lot of Annotations fun norm (arr2, n, i, j) = let val c = sub2 (arr2, i, j) in norm_aux (arr2, n, i, c, 1) end withtype {m:pos,n:pos,i:pos,j:pos | i < m, j < n} <> => (float array(n)) array(m) * int(n) * int(i) * int(j) -> unit fun row_op_aux1 (arr2, n, i, i', c, j) = if j < n then let valcj = sub2 (arr2, i, j) valcj' = sub2 (arr2, i', j) val _ = update2 (arr2, i', j, cj' -. cj *. c) in row_op_aux1 (arr2, n, i, i', c, j+1) end else () withtype {m:pos,n:pos,i:pos,i':nat, j:pos | i < m, i' < m, j <= n} <n-j> => (float array(n)) array(m) * int(n) * int(i) * int(i') * float * int(j) -> unit fun row_op_aux2 (arr2, n, i, i', j) = let val c' = sub2 (arr2, i', j) in row_op_aux1 (arr2, n, i, i', c', 1) end withtype {m:pos,n:pos,i:pos,i':nat, j:pos | i < m, i' < m, j < n} <> => (float array(n)) array(m) * int(n) * int(i) * int(i') * int(j) -> unit fun row_op_aux3 (arr2, m, n, i, j, i') = if i' < m then if i' <> i then let val _ = row_op_aux2(arr2, n, i, i', j) in row_op_aux3 (arr2, m, n, i, j, i'+1) end else row_op_aux3 (arr2, m, n, i, j, i'+1) else () withtype {m:pos,n:pos,i:pos,j:pos,i':nat | i < m, j < n, i' <= m} <m-i'> => (float array(n)) array(m) * int(m) * int(n) * int(i) * int(j) * int(i') -> unit fun row_op (arr2, m, n, i, j) = let val _ = norm (arr2, n, i, j) in row_op_aux3 (arr2, m, n, i, j, 0) end withtype {m:pos,n:pos,i:pos,j:pos| i < m, j < n} <> => (float array(n)) array(m) * int(m) * int(n) * int(i) * int(j) -> unit fun simplex (arr2, m, n) = if is_neg (arr2, n) then if unb1 (arr2, m, n, 0, 1) then abort ("simplex: unbound solution!") else let val j = enter_var (arr2, n, 1, sub2 (arr2, 0, 1), 2) val (i, r) = init_ratio (arr2, m, n, j, 1) vali = depart_var (arr2, m, n, j, i, r, i+1) val _ = row_op (arr2, m, n, i, j) in simplex (arr2, m, n) end else () withtype {m:int,n:int | m > 1, n > 2} (float array(n)) array(m) * int(m) * int(n) -> unit fun main (A (arr2, m, n)) = if m > 1 then if n > 2 then simplex (arr2, m, n) else abort ("too few columns") else abort ("too few rows") withtype float array2D -> unit fun('a) nRows (A (_, m, _)) = m withtype {m:nat,n:nat} <> => 'a array2D(m,n) -> int(m) fun('a) nCols (A (_, _, n)) = n withtype {m:nat,n:nat} <> => 'a array2D(m,n) -> int(n) fun is_neg_aux (arr2, n, j) = if j < n - 1 then if sub2 (arr2, 0, j) <. 0.0 then true else is_neg_aux (arr2, n, j+1) else false withtype {m:pos,n:pos,j:nat | j <= n} <n-j> => (float array(n)) array(m) * int(n) * int(j) -> bool fun is_neg (arr2, n) = is_neg_aux (arr2, n, 1) withtype {m:pos,n:pos} <> => (float array(n)) array(m) * int(n) -> bool fun unb1 (arr2, m, n, i, j) = if j < n-1 then if sub2 (arr2, 0, j) <. 0.0 then unb2 (arr2, m, n, i+1, j) else unb1 (arr2, m, n, 0, j+1) else false withtype {m:pos,n:pos,i:nat,j:nat | i < m, j <= n} <n-j, m-i> => (float array(n)) array(m) * int (m) * int(n) * int(i) * int(j) -> bool and unb2 (arr2, m, n, i, j) = if i < m then if sub2 (arr2, i, j) <. 0.0 then unb2 (arr2, m, n, i+1, j) else unb1 (arr2, m, n, 0, j+1) else true withtype {m:pos,n:pos,i:nat,j:nat | i <= m, j < n} <n-j,m-i> => (float array(n)) array(m) * int (m) * int(n) * int(i) * int(j) -> bool fun enter_var (arr2, n, j, c, j') = if j' < n-1 then let val c' = sub2 (arr2, 0, j') in if c' <. c then enter_var (arr2, n, j', c', j'+1) else enter_var (arr2, n, j, c, j'+1) end else j withtype {m:pos,n:pos,j:pos,j':pos | j+1 < n, j' < n} <n-j'> => (float array(n)) array(m) * int(n) * int(j) * float * int(j') -> [j:pos | j+1 < n] int(j) fun depart_var (arr2, m, n, j, i, r, i') = if i' < m then let val c' = sub2 (arr2, i', j) in if c' >. 0.0 then let val r' = sub2(arr2, i', n-1) /. c' in if r' <. r then depart_var(arr2, m, n, j, i', r', i'+1) else depart_var (arr2, m, n, j, i, r, i'+1) end else depart_var (arr2, m, n, j, i, r, i'+1) end else i withtype {m:pos,n:pos,i:pos,i':pos,j:pos | i < m, i' <= m, j < n} <m-i'> => (float array(n)) array(m) * int(m) * int(n) * int(j) * int(i) * float * int(i') -> [i:pos | i < m] int(i) fun init_ratio (arr2, m, n, j, i) = if i < m then let val c = sub2 (arr2, i, j) in if c >. 0.0 then (i, sub2 (arr2, i, n-1) /. c) else init_ratio (arr2, m, n, j, i+1) end else abort ("init_ratio: negative coefficients!") withtype {m:pos,n:pos,j:pos,i:pos | j < n, i <= m} <m-i> => (float array(n)) array(m) * int(m) * int(n) * int(j) * int(i) -> [i:pos | i < m] int(i) * float Simplex Algorithm
Abstract MC + Type Inference Goal
How to compute Refinement Types? • 1. Restrict space of types • Liquid Types • 2. Search space (efficiently) • Liquid Type Inference
Plan • Motivation • Combining Types and Predicates
Plan • Motivation • Liquid Types
Logically Qualified Types Logical Qualifiers: 0 ·V F·V V·F V ·lengthF F= “wildcard” instantiate with any program variable Liquid Types: Types refined with conjunctions of qualifiers
Liquid Type Logical Qualifiers: 0 ·V F·V V·F V ·lengthF • letrec sum n = • if n <= 0 then 0 else • lets= sum (n-1) in • s + n sum:: n:int!{V:int|0·VÆn·V}
The Liquid Restriction Liquid Refinements = conjunctions of qualifiers Finite number of qualifiers )Finite space of possible types Inference = (efficiently) search finite space!
Plan • Motivation • Liquid Types • Liquid Type Inference • Complex Control • Complex Data • Results
Liquid Type Inference Remember these: If Alice doubles her age, she would still be 10 years younger than Bob, who was born in 1952. How old are Alice and Bob ? Algorithm: Step 1: Templates for unknowns Step 2: Constraints on templates Step 3: Solve constraints Alice’s age: a Bob’sage:b = 23 = 56 2a = b– 10 b = 2008 - 1952
Liquid Type Inference Step 1: Templates for unknowns Step 2: Constraints on templates Step 3: Solve constraints
Step 1: Templates Liquid Type refines ML Type • letrec sum n = • if n < 0 then 0 else • lets= sum (n-1) in • s + n ML Type n:int!int via Hindley-Milner Type Inference
Step 1: Templates Liquid Type refines ML Type • letrec sum n = • if n < 0 then 0 else • lets= sum (n-1) in • s + n ML Type Liquid Type Template n:int!int n:{V:int|?}!{V:int|?} n:{V:int|K1}!{V:int|K2}
Step 1: Templates Liquid Type refines ML Type Liquid Type Variables for unknown refinements Template n:{V:int|K1}!{V:int|K2}
Liquid Type Inference Step 1: Templates for unknowns Step 2: Constraints on templates Step 3: Solve constraints
Step 2: Constraints Two kinds of constraints: • Scope • Value Flow
Step 2: Scope Constraints Which variables can appear in type Template sum:: n:{V:int|K1}!{V:int|K2} • letrec sum n = • if n < 0 then 0 else • lets= sum (n-1) in • s + n (S1) ;`K1 No variables in scope, no variables in K1
Step 2: Scope Constraints Which variables can appear in type Template sum:: n:{V:int|K1}!{V:int|K2} • letrec sum n = • if n < 0 then 0 else • lets= sum (n-1) in • s + n (S2) n:int`K2 Only n in scope, n can appear in K2