Tilburg University Three Types of Liquid Religion de Groot ...
Liquid Types
description
Transcript of Liquid Types
Liquid TypesPat 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
Control-intensive Properties• Null-pointers • Double-locks …
• ASTREE• SLAM • BLAST Device Drivers• SATURN Linux Kernel
?Imprecise, Limited Applicability
Control-intensive Properties• Null-pointers • Double-locks …
• SLAM • BLAST Device Drivers• SATURN Linux Kernel?
The Sources of Imprecision
• Complex Data– Arrays – Lists – Hash Tables …
• Complex Control– Function Pointers– Closures– Callbacks …
• Complex Data– Arrays – Lists – Hash Tables …
• Complex Control– Function Pointers– Closures– Callbacks …
Types, Data and Control
“Since the 70s, types have dealt with data and control”
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 …
int list Forall x in list: x is an int
Quantified Predicates• Forall x in array: …• Forall x in list: …• Hard to automate
Types and Complex Control
• Function Summaries• Pre/Post Conditions… are insufficient
• Complex Control– Function Pointers– Closures– Callbacks …
Types and Complex Control
• Complex Control– Function Pointers– Closures– Callbacks …
(’a!’b)!’a list!’b list
Higher-Order Summaries
• Function Summaries• Pre/Post Conditions… are insufficient
SMT and Model Checking
Path and value informationx>0, flag=1
Complex Data and Control
Type Systems
Complex Data and Control• int list• (’a!’b)!’a list!’b list
Path and value information
Combine Strengths
Data StructuresPath and value information
Precise Software Verification
Plan
• Motivation• Combining Types and Predicates
Combining Types and Predicates
Refinement Types Types refined with Predicates over values
Refinement Types
{V:int|0<V}
positive integers
Type Refinement
{V:int|i·V Æ V·j}
integers between i,j
Type Refinement
Refinement Types
{V:int|i<V Æ V<j} list
list of integers between i,j
Type Refinement
Refinement Types
i:{V:int|0<V}!{V:int|i<V}
function with positive input i output larger than input
“Post”“Pre”“Requires” “Ensures”
Refinement Types
Verification using Refinement Types
Divide by zero?
let abs x = if x>0 then x else -x
let trunc i n = let i’ = abs i in let n’ = abs n in if i’<=n’ then i else n’*(div i i’)
Verification using Refinement Types
let abs x = if x>0 then x else -x
let trunc i n = let i’ = abs i in let n’ = abs n in if i’<=n’ then i else n’*(div i i’)
div :: int!{V:int|V0}!int
Verification using Refinement Types
let abs x = if x>0 then x else -x
let trunc i n = let i’ = abs i in let n’ = abs n in if i’<=n’ then i else n’*(div i i’)
Typecheck implies i’is nonzero
div :: int!{V:int|V0}!int
Verification using Refinement Types
Array index within bounds?
let arraysum a = let rec loop m i n = if i >= n then m else let i’= i + 1 in let m’= m + (get a i) in loop m’ i’ n in loop 0 0 (length a)
Verification using Refinement Types
let arraysum a = let rec loop m i n = if i >= n then m else let i’= i + 1 in let m’= m + (get a i) in loop m’ i’ n in loop 0 0 (length a)
get:: x:’a array!{V:int|0 <= V < length x}!’a
Verification using Refinement Types
Typecheck implies i within bounds
let arraysum a = let rec loop m i n = if i >= n then m else let i’= i + 1 in let m’= m + (get a i) in loop m’ i’ n in loop 0 0 (length a)
get:: x:’a array!{V:int|0 <= V < length x}!’a
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) = if i = n then sum else helper (v1, v2, i+1, n, sum + (get v1 i) * (get v2 i))
let dotprod(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)
fun('a) nRows (A (_, m, _)) = mwithtype {m:nat,n:nat} <> => 'a array2D(m,n) -> int(m)
fun('a) nCols (A (_, _, n)) = nwithtype {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 falsewithtype {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 falsewithtype {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 truewithtype {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 jwithtype {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 iwithtype {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
fun norm (arr2, n, i, j) = let val c = sub2 (arr2, i, j) in norm_aux (arr2, n, i, c, 1) endwithtype {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 val cj = sub2 (arr2, i, j) val cj' = 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) endwithtype {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) endwithtype {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) val i = 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
... A Lot of Annotations
Simplex Algorithm
fun('a) nRows (A (_, m, _)) = mwithtype {m:nat,n:nat} <> => 'a array2D(m,n) -> int(m)
fun('a) nCols (A (_, _, n)) = nwithtype {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 falsewithtype {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 falsewithtype {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 truewithtype {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 jwithtype {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 iwithtype {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
fun norm (arr2, n, i, j) = let val c = sub2 (arr2, i, j) in norm_aux (arr2, n, i, c, 1) endwithtype {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 val cj = sub2 (arr2, i, j) val cj' = 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) endwithtype {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) endwithtype {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) val i = 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
… A Lot of Annotations
Simplex Algorithm
30% of code
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
Liquid Types:Types refined with conjunctions of qualifiers
F = “wildcard” instantiate with any program variable
Logical Qualifiers:0 · VF · V V · FV · length F
Liquid Type
Logical Qualifiers:0 · VF · V V · FV · length F
let rec sum n = if n <= 0 then 0 else let s = 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
Algorithm:Step 1: Templates for unknownsStep 2: Constraints on templatesStep 3: Solve constraints
2a = b – 10 b = 2008 -
1952
Alice’s age: a Bob’s age: b
= 23= 56
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 ?
Liquid Type Inference
Liquid Type Inference
Step 1: Templates for unknowns
Step 2: Constraints on templates
Step 3: Solve constraints
Step 1: TemplatesLiquid Type refines ML Type
ML Type
let rec sum n = if n < 0 then 0 else let s = sum (n-1) in s + n
n:int!int
via Hindley-Milner Type Inference
Step 1: TemplatesLiquid Type refines ML Type
ML TypeLiquid Type
Template
let rec sum n = if n < 0 then 0 else let s = sum (n-1) in s + n
n:int!int
n:{V:int|K1}!{V:int|K2}n:{V:int|?}!{V:int|?}
Step 1: TemplatesLiquid Type refines ML Type
Template n:{V:int|K1}!{V:int|K2}
Liquid Type Variablesfor unknown refinements
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 ConstraintsWhich variables can appear in type
Template
let rec sum n = if n < 0 then 0 else let s = sum (n-1) in s + n
sum:: n:{V:int|K1}!{V:int|K2}
; ` K1
No variables in scope, no variables in K1 (S1)
Step 2: Scope ConstraintsWhich variables can appear in type
Template
let rec sum n = if n < 0 then 0 else let s = sum (n-1) in s + n
sum:: n:{V:int|K1}!{V:int|K2}
n:int ` K2
Only n in scope, n can appear in K2 (S2)
Step 2: Constraints
Two kinds of constraints:• Scope• Value Flow
Step 2: Flow ConstraintsCapture Value-Flow in an Environment
if x>0 then x else -x
When x>0 value of then expr. flows into if expr.
Step 2: Flow ConstraintsCapture Value-Flow in an Environment
if x>0 then x else -x
When :x>0 value of else expr. flows into if expr.
Step 2: Flow ConstraintsCapture Value-Flow in an Environment
Env `
Branch info + Type assumptions for variables
E2E1
Step 2: Flow ConstraintsCapture Value-Flow in an Environment
Env ` E2E1
Step 2: Flow ConstraintsCapture Value-Flow in an Environment
Vals(E1) µ Vals(E2)Env `
Step 2: Flow ConstraintsCapture Value-Flow in an Environment
Type(E1) <: Type(E2)
Subtype
Env `
Step 2: Flow ConstraintsTemplate
let rec sum n = if n < 0 then 0 else let s = sum (n-1) in s + n
sum:: n:{V:int|K1}!{V:int|K2}
Step 2: Flow ConstraintsTemplate
let rec sum n = if n < 0 then 0 else let s = sum (n-1) in s + n
sum:: n:{V:int|K1}!{V:int|K2}
sum:…,n:{V:int|K1},n<0 ` {V:int|V=0}<:{V:int|K2}
then flows into output
Step 2: Flow ConstraintsTemplate
let rec sum n = if n < 0 then 0 else let s = sum (n-1) in s + n
sum:: n:{V:int|K1}!{V:int|K2}
then flows into output
n:K1 ,n<0 ` V=0 <: K2 (F1)
Step 2: Flow ConstraintsTemplate
let rec sum n = if n < 0 then 0 else let s = sum (n-1) in s + n
sum:: n:{V:int|K1}!{V:int|K2}
n:K1 ,:n<0 ` V=n-1 <: K1
parameter flows into input
(F2)
Step 2: Flow ConstraintsTemplate
let rec sum n = if n < 0 then 0 else let s = sum (n-1) in s + n
sum:: n:{V:int|K1}!{V:int|K2}
{V:int|K2 [n-1/n]}
In output substitute actual for formal
Step 2: Flow ConstraintsTemplate
let rec sum n = if n < 0 then 0 else let s = sum (n-1) in s + n
sum:: n:{V:int|K1}!{V:int|K2}
{V:int|K2 [n-1/n]}
result is bound to s
Step 2: Flow ConstraintsTemplate
let rec sum n = if n < 0 then 0 else let s = sum (n-1) in s + n
sum:: n:{V:int|K1}!{V:int|K2}
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
else flows into output
(F3)
result is bound to s
Step 2: ConstraintsScope
Flow
; ` K1 (S1)n:int ` K2 (S2)
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
(F3)n:K1 ,:n<0 ` V=n-1 <: K1 (F2)n:K1 ,n<0 ` V=0 <: K2
(F1)
Liquid Type Inference
Step 1: Templates for unknowns
Step 2: Constraints on templates
Step 3: Solve constraints
Step 3: Solve Constraints
Goal:Find map from K to qualifierswhich satisfies constraints
Step 3: Solve Constraints
Algorithm: Initial map: K all quals While 9unsatisfied constraint c:
remove quals. to satisfy c Finds least-fixpoint solution
[aka Houdini, Cartesian Pred. Abst.]
Step 3: Initial SolutionK1
K2
0·V , F·V , V·F , V·length F 0·V , F·V , V·F , V·length F
Logical Qualifiers:0 · VF · V V · FV · length F
Step 3: Solve Scope ConstraintsK1
K2
0·V , F·V , V·F , V·length F 0·V , F·V , V·F , V·length F
; ` K1n:int ` K2
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
n:K1 ,:n<0 ` V=n-1 <: K1
n:K1 ,n<0 ` V=0 <: K2
Step 3: Solve Scope ConstraintsK1 0·V , F·V , V·F , V·length F
SAT: No free variables in qualifier
K2 0·V , F·V , V·F , V·length F
; ` K1
Step 3: Solve Scope ConstraintsK1 0·V , F·V , V·F , V·length F
UNSAT: No free variables allowed
K2 0·V , F·V , V·F , V·length F
; ` K1
Step 3: Solve Scope ConstraintsK1 0·V , V·F , V·length FK2
0·V , F·V , V·F , V·length F
UNSAT: No free variables allowed
; ` K1
; ` K1
Step 3: Solve Scope ConstraintsK1 0·VK2
0·V , F·V , V·F , V·length F
n:int ` K2
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
n:K1 ,:n<0 ` V=n-1 <: K1
n:K1 ,n<0 ` V=0 <: K2
Step 3: Solve Scope ConstraintsK1 0·VK2
0·V , F·V , V·F , V·length F
SAT: No free variables in qualifier
n:int ` K2
Step 3: Solve Scope ConstraintsK1 0·VK2
0·V , F·V , V·F , V·length F
SAT: Instantiate F with n
n·V ,
n:int ` K2
Step 3: Solve Scope ConstraintsK1 0·VK2
0·V , n·V , V·F , V·length F
SAT: Instantiate F with n
V·n ,
n:int ` K2
Step 3: Solve Scope ConstraintsK1 0·VK2
0·V , n·V , V·length FV·n ,
UNSAT: No array variables in scope
n:int ` K2
Step 3: Scope Constraints SolvedK1 0·VK2
0·V , n·V , V·n
; ` K1n:int ` K2
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
n:K1 ,:n<0 ` V=n-1 <: K1
n:K1 ,n<0 ` V=0 <: K2
Step 3: Solve Flow ConstraintsK1 0·VK2
0·V , n·V , V·n
; ` K1n:int ` K2
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
n:K1 ,:n<0 ` V=n-1 <: K1
n:K1 ,n<0 ` V=0 <: K2
When does subtyping hold ?
Env ` {V:t|P1} <: {V:t|P2} if
(Env Æ P1) ) P2Valid:
Conservative Embedding To Decidable Logic (EUFA)
Base Subtyping = Implication
Env ` {V:t|P1} <: {V:t|P2} if
(Env Æ P1) ) P2Valid:
Conservative Embedding To Decidable Logic (EUFA)
Step 3: Solve Flow Constraints
Env ` {V:t|P} <: {V:t|K}
YES: Keep q(Env Æ P) ) qValid?SMT Query
NO: Drop q
K …,q,… Current Solution
Step 3: Solve Flow ConstraintsK1 0·VK2
0·V , n·V , V·n
; ` K1n:int ` K2
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
n:K1 ,:n<0 ` V=n-1 <: K1
n:K1 ,n<0 ` V=0 <: K2
Step 3: Solve Flow ConstraintsK1 0·VK2
0·V , n·V , V·n
n:K1 ,:n<0 ` V=n-1 <: K1
NOValid? (0·n Æ :n<0 Æ V=n-1)) 0·V
Current Solution
n:K1 ,:n<0 ` V=n-1 <: K1
Step 3: Solve Flow ConstraintsK1 K2
0·V , n·V , V·n
; ` K1n:int ` K2
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
n:K1 ,n<0 ` V=0 <: K2
Step 3: Solve Flow ConstraintsK1 K2
0·V , n·V , V·n
YES Valid? (n<0 Æ V=0))
n:K1 ,n<0 ` V=0 <: K2
0·V
Step 3: Solve Flow ConstraintsK1 K2
0·V , n·V , V·n
YES Valid? (n<0 Æ V=0))
n:K1 ,n<0 ` V=0 <: K2
n·V
Step 3: Solve Flow ConstraintsK1 K2
0·V , n·V , V·n
Valid? (n<0 Æ V=0))
n:K1 ,n<0 ` V=0 <: K2
NOV·n
Step 3: Solve Flow ConstraintsK1 K2
0·V , n·V
n:K1 ,:n<0 ` V=n-1 <: K1
; ` K1n:int ` K2
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
n:K1 ,n<0 ` V=0 <: K2
All Constraints Satisfied
Inferred Liquid TypeK1 K2
0·V , n·V
Template
let rec sum n = if n < 0 then 0 else let s = sum (n-1) in s + n
sum:: n:{V:int|K1}!{V:int|K2}
Liquid Type sum:: n:int !{V:int| 0·V Æ n·V}
Liquid Type Inference
Step 1: Templates for unknowns
Step 2: Constraints on templates
Step 3: Solve constraints
Plan
• Motivation• Liquid Types• Liquid Type Inference• Complex Control• Complex Data• Results
Complex ControlValue-flow captured via subtyping
• Higher-order functions• Polymorphism
All reduced to simple implications!
Higher Order Functions
T1!T1’ <: T2!T2’ iff
Via Function Subtyping
T1T2
T1!T1’ <: T2!T2’ iff
<:
Via Function Subtyping
Higher Order Functions
T1 T1’T2 T2’
T1!T1’ <: T2!T2’ iff
<: and <:
Via Function Subtyping
Higher Order Functions
Func. subtyping reduces to implications
Complex ControlValue-flow captured via subtyping
• Higher-order functions• Polymorphism
All reduced to simple implications!
Polymorphism
Instantiate ML: ’a with int’b with int
(’a!’b)!’a list!’b listList.map::
At a call-site: List.map abs xs
Instantiate Liquid: ’a with {V:int|K1}
’b with {V:int|K2}
(K1! K2)!K1 list!K2 listList.map::
K2 list
Polymorphism
Instantiate ML: ’a with int’b with int
(’a!’b)!’a list!’b listList.map::
At a call-site: List.map abs xs
Instantiate Liquid: ’a with {V:int|K1}
’b with {V:int|K2}
(K1! K2)!K1 list!K2 listList.map::
K1 K2
0·V
K2 list
K2 list
Polymorphism
Instantiate ML: ’a with int’b with int
(’a!’b)!’a list!’b listList.map::
At a call-site: List.map abs xs
Instantiate Liquid: ’a with {V:int|K1}
’b with {V:int|K2}
K1 K2
0·V
(K1! K2)!K1 list!K2 listList.map::
{0·V} list
Plan
• Motivation• Liquid Types• Liquid Type Inference• Complex Control• Complex Data• Results
Whats Complex about Data?1
2 5
3 4
1 2 53 4
1
2 43
5
Unbounded size: need Universally Quantified Invariants
``Every element has property P”
P(x0), P(x1),… 8x. P(x)
Whats Complex about Data?1
2 5
3 4
1 2 53 4
1
2 43
5
Unbounded size: need Universally Quantified Invariants
P(x0), P(x1),… 8x. P(x)
How to Generalize?
How to Instantiate?
Quantification using Types1
2 5
3 4
1 2 53 4
1
2 43
5
Unbounded size: need Universally Quantified Invariants
P(x0), P(x1),… 8x. P(x)
How to Generalize?
How to Instantiate?
Quantification using Types1
2 5
3 4
1 2 53 4
1
2 43
5
Unbounded size: need Universally Quantified Invariants
Two (orthogonal) mechanisms
• Recursion (e.g. Lists, Trees, …)• Maps (e.g. Links, Arrays, HashTables,…)
Recursive Types
1 2 3[1;2;3]1::(2::(3::[]))
type int list =
| []
| :: of int * int list
¹t.[] + :: of int * t
``Every element is an int”
[] + :: of int *
(¹t.T)
Recursive Types: Instantiation
let x::_= xs
¹t.[] + :: of int * t
``Every elt of xs is an int”
Unfold:: of int *
(¹t.T)
``x is an int”
Recursive Types: Generalization
[] + :: of int *
(¹t.T) x is an int, every elt of xs is an int
let y =
x::xs
:: of int * (¹t.T) Fold
¹t.[] + :: of int * t
``Every elt of y is an int”
Recursive Refinement Types
Predicates “Piggyback” on Rec Type
P(x0), P(x1),… 8x. P(x)
How to Generalize?
How to Instantiate?
Fold
Unfold
Recursive Refinements
[[],[P1,P2]] (¹t.Nil + Cons int * t)
Every “head” satisfies P1
Every “tail” satisfies P2
Positive Integer Lists
Every int satisfies 0<Vi.e. Every int is positive
[[],[0<V, True]] (¹t.Nil + Cons int * t)
Lower Bounded Integer Lists
Every int satisfies x·Vi.e. Every int greater than x
[[],[x·V, True]] (¹t.Nil + Cons int * t)
Nested Refinements
¹t.Nil+Cons x:int * [[],[P1,P2]] t
Every “inner list” refined by [[],[P1,P2]]
¹t.Nil + Cons x:int * [[],[x·V,True]] t
Tail is {x ·V} list• head x · every tail element• holds for every “inner list” • i.e. sorted (increasing) list
Sorted List
Duplicate-Free List
¹t.Nil + Cons x:int * [[],[x V,True]] t
Non-aliasing within collectionse.g. List of distinct addresses, handles etc.
Recursive Data Structures
Trees:type int tree=
| E
| N of int * int tree * int tree
Recursive type:¹t. E + N int * t * t
Balanced Trees
B (¹t.E + N x:int * l:t * r:t)
Where:B = [[],[True,True,|ht l - ht V|· 2]]
Binary Search Trees
L = [[],[V·x,True,True]
¹t.E + N x:int * L t * R
t
R = [[],[V¸x,True,True] elements of right tree ¸ x
elements of left tree · x
Inference ? As before!
1. Templates
2. Constraints 3. Solve
Quantification using Types1
2 5
3 4
1 2 53 4
1
2 43
5
Unbounded size: need Universally Quantified Invariants
Two (orthogonal) mechanisms
• Recursion (e.g. Lists, Trees, …)• Maps (e.g. Links, Arrays, HashTables,…)
(Finite) Maps1
2 43
5n1.succs=[n2;n3;n4]
(node, node list) Map
Field Read/Get
Field Write/Set
n.succs
n.succs :=
e
set succs n e
get succs n
DataKey
Refined Maps1
2 43
5
(node, node list) Map(n:node,{V:node|n<V} list) Map
P(x0), P(x1),… 8x. P(x)
How to Generalize?
How to Instantiate?
Refine poly-type for set
Refine poly-type for getWhen getting data from key
When setting key to data
Acyclic Graph!
Refined Maps
(n:’a,’b) Map
set :: (n:’a,’b) Map! k:’a!’b[k/n]! unit
’b can refer to n
get :: (n:’a,’b) Map! k:’a!’b[k/n]
Generalize-on-write
Instantiate-on-read
Quantification using Types1
2 5
3 4
1 2 53 4
1
2 43
5
Unbounded size: need Universally Quantified Invariants
Two (orthogonal) mechanisms
• Recursion (e.g. Lists, Trees, …)• Maps (e.g. Links, Arrays, HashTables,…)
Inference ? As before!
1. Templates
2. Constraints 3. Solve
Plan
• Motivation• Liquid Types• Liquid Type Inference• Complex Control• Complex Data• Results
Liquid Type Inference for Ocaml
Property
Unsafe
Safe
DsolveQualifiers
Safety Properties: 1. Type signatures (div,Array.get,
…)2. Assertions
Demo
• Array bounds checking
• Data structures
Results
Array Bounds Checking Qualifiers
V op F, V op length F,
op 2 {·, ¸, =, }
Array Bounds CheckingProgram Lines
dotprod 7
bsearch 24
queens 30
insersort 33
tower 36
matmult 43
heapsort 84
fft 107
simplex 118
gauss 142
Total 632
Array Bounds CheckingProgram Lines DML
Annot.dotprod 7 3
bsearch 24 3
queens 30 7
insersort 33 12
tower 36 8
matmult 43 10
heapsort 84 11
fft 107 13
simplex 118 33
gauss 142 22
Total 632 125
Array Bounds CheckingProgram Lines DML
Annot.DsolveAnnot.
dotprod 7 3 0
bsearch 24 3 0
queens 30 7 0
insersort 33 12 0
tower 36 8 1
matmult 43 10 0
heapsort 84 11 0
fft 107 13 1
simplex 118 33 0
gauss 142 22 1
Total 632 125 3
Array Bounds CheckingProgram Lines DML
Annot.DsolveAnnot.
DsolveTime(s)
dotprod 7 3 0 0.3bsearch 24 3 0 0.5queens 30 7 0 0.7
insersort 33 12 0 0.9tower 36 8 1 3.3
matmult 43 10 0 1.8heapsort 84 11 0 0.5
fft 107 13 1 9.1simplex 118 33 0 7.7
gauss 142 22 1 3.1Total 632 125 3 27.9
bitv: bitvector library (426 LOC)
15 lines interface specifications
14 annotations = 6 Qualifiers + 8 Assumes (mod. arith. axioms)
type t = {len:int; bits:int array}
0 ≤ Array.length bits ∧0 ≤ len ≤ 30 * Array.length bits
where
bitv bug
let unsafe_blit bits1 off1 bits2 off2 c = unsafely copy bits from bits1 to bits2
let blit v1 o1 v2 o2 c = if c<0 || o1 < 0 || o1 + c > v1.length || o2 < 0 || o2 + c > v2.length then invalid_arg "Bitv.blit"; unsafe_blit v1.bits o1 v2.bits o2 c
fatal off-by-one error
let unsafe_blit bits1 off1 bits2 off2 c = unsafely copy bits from bits1 to bits2
let blit v1 o1 v2 o2 c = if c<0 || o1 < 0 || o1 + c >= v1.length || o2 < 0 || o2 + c >= v2.length then invalid_arg "Bitv.blit"; unsafe_blit v1.bits o1 v2.bits o2 c
Fix
Results
• Array-bounds
• Data Structures
Data StructuresProgram
Lines
List-sort 111
Map 98
Redblack 106
Stablesort
124
Vec 343
BinHeap 122
SplayHeap 134
Malloc 71
Bdd 206
UnionFind 65
SubvSolve 264
Total 1736
Data StructuresProgram
Lines Property
List-sort 111 Sorted, Elts
Map 98 Balance, BstOrder, Set
Redblack 106 Balance, BstOrder, Color
Stablesort
124 Sorted
Vec 343 Balance, Len1, Len2
BinHeap 122 HeapOrder, Min, Set
SplayHeap 134 BstOrder, Min, Set
Malloc 71 Alloc
Bdd 206 VariableOrder
UnionFind 65 Acyclic
SubvSolve 264 Acyclic
Total 1736
Data StructuresProgram
Lines Property Annot
List-sort 111 Sorted, Elts 7
Map 98 Balance, BstOrder, Set 14
Redblack 106 Balance, BstOrder, Color 2
Stablesort
124 Sorted 1
Vec 343 Balance, Len1, Len2 9
BinHeap 122 HeapOrder, Min, Set 6
SplayHeap 134 BstOrder, Min, Set 3
Malloc 71 Alloc 2
Bdd 206 VariableOrder 3
UnionFind 65 Acyclic 2
SubvSolve 264 Acyclic 2
Total 1736 54
Data StructuresProgram
Lines Property Annot Time(s)
List-sort 111 Sorted, Elts 7 5
Map 98 Balance, BstOrder, Set 14 25
Redblack 106 Balance, BstOrder, Color 2 29
Stablesort
124 Sorted 1 4
Vec 343 Balance, Len1, Len2 9 87
BinHeap 122 HeapOrder, Min, Set 6 33
SplayHeap 134 BstOrder, Min, Set 3 6
Malloc 71 Alloc 2 2
Bdd 206 VariableOrder 3 80
UnionFind 65 Acyclic 2 5
SubvSolve 264 Acyclic 2 20
Total 1736 54 300
Vec: extensible arrays (317 LOC)“Python-style” arrays for Ocaml:
find, insert, delete, join etc.
efficiency via balanced trees
Balanced :
height diff. between siblings · 2Dsolve found violation:
in rebalance procedure
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 upto 4
Leading to test and fix
Plan
• Motivation• Liquid Types• Liquid Type Inference• Complex Control• Complex Data• Results
Precise VerificationLiquid Types
Summary
Complex data & control
Path & value predicatesAbstract MC
Types
=
http://pho.ucsd.edu/liquidsource, papers, demo, etc.
HERE
Demo1: (Dsolve)simple loop(arraymax), binsearch
Demo2: (HO Funs)fold-loop (arraymax) / splitting slide
Demo3: (Polymorph)fold-loop (arraymax) / result > 0 slide
Future Work
• Usability– Error messages, running time
• Mutable state– Liquid Types for C, Java, C#
• Qualifier Discovery– Counterexample-Guided
A satisfied customer reports…
The Sources of Imprecision
• Complex Data– Arrays – Lists – Hash Tables …
• Complex Control– Function Pointers– Closures– Callbacks …
Quantified Predicates• For all x in list: …• Hard to automate
Modular Analysis• Summarization fails
Dependent ML [Pfenning-Xi 1998]
let rec helper (v1, v2, i, n, sum) = if i = n then sum else helper (v1, v2, i+1, n, sum + (get v1 i) * (get v2 i))
let dotprod(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)
fun('a) nRows (A (_, m, _)) = mwithtype {m:nat,n:nat} <> => 'a array2D(m,n) -> int(m)
fun('a) nCols (A (_, _, n)) = nwithtype {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 falsewithtype {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 falsewithtype {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 truewithtype {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 jwithtype {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 iwithtype {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
fun norm (arr2, n, i, j) = let val c = sub2 (arr2, i, j) in norm_aux (arr2, n, i, c, 1) endwithtype {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 val cj = sub2 (arr2, i, j) val cj' = 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) endwithtype {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) endwithtype {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) val i = 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
... A Lot of Annotations
Simplex Algorithm
fun('a) nRows (A (_, m, _)) = mwithtype {m:nat,n:nat} <> => 'a array2D(m,n) -> int(m)
fun('a) nCols (A (_, _, n)) = nwithtype {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 falsewithtype {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 falsewithtype {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 truewithtype {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 jwithtype {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 iwithtype {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
fun norm (arr2, n, i, j) = let val c = sub2 (arr2, i, j) in norm_aux (arr2, n, i, c, 1) endwithtype {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 val cj = sub2 (arr2, i, j) val cj' = 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) endwithtype {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) endwithtype {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) val i = 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
… A Lot of Annotations
Simplex Algorithm
30% of code
“The more interesting your types get, the less fun it is to write them down”
- Benjamin Pierce
Abstract MC
+ Type
Inference
Goal
n:K1 ,:n<0 ` V=n-1 <: K1
Step 3: Solve Flow ConstraintsK1 K2
0·V , n·V
; ` K1n:int ` K2
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
n:K1 ,n<0 ` V=0 <: K2
Step 3: Solve Flow ConstraintsK1 K2
0·V , n·V
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
s:(0·V Æ n·V)[n-1/n] s:(0·V Æ n-1·V)
(:n<0 Æ 0·s Æ n-1·s Æ
V=s+n) )
Step 3: Solve Flow ConstraintsK1 K2
0·V , n·V
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
(:n<0 Æ 0·s Æ n-1·s Æ
V=s+n) )
Step 3: Solve Flow ConstraintsK1 K2
0·V , n·V
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
(:n<0 Æ 0·s Æ n-1·s Æ
V=s+n) )
Valid?
YES 0·V
Step 3: Solve Flow ConstraintsK1 K2
0·V , n·V
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
(:n<0 Æ 0·s Æ n-1·s Æ
V=s+n) )
Valid? n·V
YES
Step 3: Solve Flow ConstraintsK1 K2
0·V , n·V
n:K1 ,:n<0 ` V=n-1 <: K1
; ` K1n:int ` K2
n:K1 ,:n<0,s:K2[n-1/n] ` V=s+n <: K2
n:K1 ,n<0 ` V=0 <: K2
All Constraints Satisfied
Higher-Order Functions
let fold n b f = let rec loop i c = if i >= n then c else loop (i+1) (f i c) in loop 0 blet arraycat a = let cat i s = s^(get a i) in fold (length a) “” cat
Qualifiers: 0 · V , V <F, V · length F
Higher-Order Functions
ML Type n:int!b:’a!f:(int!’a!’a)!’an:{V:int|K3}!b:’a!f:({V:int|K4}!’a!’a)!’a
n:int!b:’a!f:({V:int|0·V Æ V<n}!’a!’a)!’aLiq. Type
TemplateSolution K3
K4 0·V , V<n
let fold n b f = let rec loop i c = if i >= n then c else loop (i+1) (f i c) in loop 0 b
Qualifiers: 0 · V , V <F, V · length F
Higher Order Functions
let fold n b f = ... let arraycat a = let cat i s = s^(get a i) in fold (length a) 0 cat
Template i: K5 !str!strML i:int!str!str
fold:: n:int!b:str!f:({0·V Æ V<n}!str!str)!str
[(length a)/n]
<: {0·V Æ V<length a}!str!strSolution K5 0·V , V<length a
Qualifiers: 0 · V , V <F, V · length F
Liq. Type i:{0·V Æ V<length a}!str!str
Higher Order Functions
Liq. Type i:{0·V Æ V<length a}!str!str
let fold n b f = let rec loop i c = if i >= n then c else loop (i+1) (f i c) in loop 0 blet arraycat a = let cat i s = s^(get a i) in fold (length a) “” cat
Array safe!
Type MagicValue-flow captured via subtyping
• Higher-order functions• Polymorphism• Recursive data structures
All reduced to simple implications!
Polymorphism
let fold n b f = ...let rec fac n = let mul i c = (i+1)*c in fold n 1 mul
Qualifiers: 0·V, 0<V
Instantiate ML:’a with int Instantiate Liquid:’a with K6
n:int!b:’a!f:({0·V }!’a!’a)!’an:int!b:K6!f:({0·V }!K6!K6)!K6fold::{0·V }!{0<V}!{0<V}mul::
Solution K6 0<V
int ! {V:int|0<V}fac:: Positive
n:int!b:{0<V} !f:({0·V }!{0<V}!{0<V})!{0<V}
Type MagicValue-flow captured via subtyping
• Higher-order functions• Polymorphism• Recursive data structures
All reduced to simple implications!
Structural Properties: Size
Refinements over size
measure size =
| Nil = 0| Cons(h,t) = 1 + size t
{V:int list | 0 < size V}Non-empty list of integers
Structural Propertiesmeasure size =
| Nil = 0| Cons(h,t) = 1 + size t
xs:int list ! {V:int list| size V · size xs}
Function w/ output smaller than input(e.g. List.filter)
! {V:int|0<V
Refinements over size
Balanced Trees via Height measure
B (¹t.E + N x:int * l:t * r:t)
measure ht =
| E = 0| N (x,l,r) = 1 + max (ht l) (ht r)
B = [[],[True,True,|ht l - ht V|· 2]]
Map Types
(n, n list) Map
set succs n e
get succs n
(’a, ’b) Map
(Finite) Maps1
2 43
5
n1.succs=[n2;n3;n4]
(n, n list) Map
Field Read/GetField Write/Set
n.succs
n.succs :=
e
set succs n e
get succs n