Liquid Types

177
Liquid Types Pat Rondon Ming Kawaguchi Ranjit Jhala

description

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. - PowerPoint PPT Presentation

Transcript of Liquid Types

Page 1: Liquid Types

Liquid TypesPat Rondon

Ming Kawaguchi Ranjit Jhala

Page 2: Liquid Types

Goal: Software Verification

Verify absence of run-time errors• Buffer overflows• Deadlocks• Assertions

Page 3: Liquid Types

Progress: Path Sensitive Analyses

• SMT Solvers Path Predicates• Model Checking Loop Invariants,

Function Summaries

Page 4: Liquid Types

Progress: Path Sensitive Analyses

• SMT Solvers Path Predicates• Model Checking Loop Invariants,

Function Summaries

• ASTREE• SLAM • BLAST Device Drivers• SATURN Linux Kernel

Page 5: Liquid Types

Imprecise, Limited Applicability

Control-intensive Properties• Null-pointers • Double-locks …

• ASTREE• SLAM • BLAST Device Drivers• SATURN Linux Kernel

Page 6: Liquid Types

?Imprecise, Limited Applicability

Control-intensive Properties• Null-pointers • Double-locks …

Page 7: Liquid Types

• SLAM • BLAST Device Drivers• SATURN Linux Kernel?

The Sources of Imprecision

• Complex Data– Arrays – Lists – Hash Tables …

• Complex Control– Function Pointers– Closures– Callbacks …

Page 8: Liquid Types

• 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”

Page 9: Liquid Types

Types and Complex Data

• Complex Data– Arrays – Lists – Hash Tables …

Quantified Predicates• Forall x in array: …• Forall x in list: …• Hard to automate

Page 10: Liquid Types

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

Page 11: Liquid Types

Types and Complex Control

• Function Summaries• Pre/Post Conditions… are insufficient

• Complex Control– Function Pointers– Closures– Callbacks …

Page 12: Liquid Types

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

Page 13: Liquid Types

SMT and Model Checking

Path and value informationx>0, flag=1

Complex Data and Control

Page 14: Liquid Types

Type Systems

Complex Data and Control• int list• (’a!’b)!’a list!’b list

Path and value information

Page 15: Liquid Types

Combine Strengths

Data StructuresPath and value information

Precise Software Verification

Page 16: Liquid Types

Plan

• Motivation• Combining Types and Predicates

Page 17: Liquid Types

Combining Types and Predicates

Refinement Types Types refined with Predicates over values

Page 18: Liquid Types

Refinement Types

{V:int|0<V}

positive integers

Type Refinement

Page 19: Liquid Types

{V:int|i·V Æ V·j}

integers between i,j

Type Refinement

Refinement Types

Page 20: Liquid Types

{V:int|i<V Æ V<j} list

list of integers between i,j

Type Refinement

Refinement Types

Page 21: Liquid 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

Page 22: Liquid 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’)

Page 23: Liquid Types

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

Page 24: Liquid Types

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

Page 25: Liquid Types

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)

Page 26: Liquid Types

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

Page 27: Liquid Types

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

Page 28: Liquid Types

Verification using Refinement Types

Just one little problem…

How to compute Refinement Types?

Page 29: Liquid 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

Page 30: Liquid Types

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)

Page 31: Liquid Types

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

Page 32: Liquid Types

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

Page 33: Liquid Types

30% of code

Page 34: Liquid Types

Abstract MC

+ Type

Inference

Goal

Page 35: Liquid Types

How to compute Refinement Types?

1. Restrict space of types Liquid Types

2. Search space (efficiently)Liquid Type Inference

Page 36: Liquid Types

Plan

• Motivation• Combining Types and Predicates

Page 37: Liquid Types

Plan

• Motivation• Liquid Types

Page 38: 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

Page 39: Liquid Types

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 }

Page 40: Liquid Types

The Liquid Restriction

Liquid Refinements = conjunctions of qualifiers

Finite number of qualifiers ) Finite space of possible types

Inference= (efficiently) search finite space!

Page 41: Liquid Types

Plan

• Motivation• Liquid Types• Liquid Type Inference• Complex Control• Complex Data• Results

Page 42: Liquid Types

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

Page 43: Liquid Types

Liquid Type Inference

Step 1: Templates for unknowns

Step 2: Constraints on templates

Step 3: Solve constraints

Page 44: Liquid Types

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

Page 45: Liquid Types

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|?}

Page 46: Liquid Types

Step 1: TemplatesLiquid Type refines ML Type

Template n:{V:int|K1}!{V:int|K2}

Liquid Type Variablesfor unknown refinements

Page 47: Liquid Types

Liquid Type Inference

Step 1: Templates for unknowns

Step 2: Constraints on templates

Step 3: Solve constraints

Page 48: Liquid Types

Step 2: Constraints

Two kinds of constraints:• Scope• Value Flow

Page 49: Liquid Types

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)

Page 50: Liquid Types

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)

Page 51: Liquid Types

Step 2: Constraints

Two kinds of constraints:• Scope• Value Flow

Page 52: Liquid Types

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.

Page 53: Liquid Types

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.

Page 54: Liquid Types

Step 2: Flow ConstraintsCapture Value-Flow in an Environment

Env `

Branch info + Type assumptions for variables

E2E1

Page 55: Liquid Types

Step 2: Flow ConstraintsCapture Value-Flow in an Environment

Env ` E2E1

Page 56: Liquid Types

Step 2: Flow ConstraintsCapture Value-Flow in an Environment

Vals(E1) µ Vals(E2)Env `

Page 57: Liquid Types

Step 2: Flow ConstraintsCapture Value-Flow in an Environment

Type(E1) <: Type(E2)

Subtype

Env `

Page 58: Liquid Types

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}

Page 59: Liquid Types

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

Page 60: Liquid Types

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)

Page 61: Liquid Types

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)

Page 62: Liquid Types

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

Page 63: Liquid Types

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

Page 64: Liquid Types

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

Page 65: Liquid Types

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)

Page 66: Liquid Types

Liquid Type Inference

Step 1: Templates for unknowns

Step 2: Constraints on templates

Step 3: Solve constraints

Page 67: Liquid Types

Step 3: Solve Constraints

Goal:Find map from K to qualifierswhich satisfies constraints

Page 68: Liquid Types

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.]

Page 69: Liquid Types

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

Page 70: Liquid Types

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

Page 71: Liquid Types

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

Page 72: Liquid Types

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

Page 73: Liquid Types

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

Page 74: Liquid Types

; ` 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

Page 75: Liquid Types

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

Page 76: Liquid Types

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

Page 77: Liquid Types

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

Page 78: Liquid Types

Step 3: Solve Scope ConstraintsK1 0·VK2

0·V , n·V , V·length FV·n ,

UNSAT: No array variables in scope

n:int ` K2

Page 79: Liquid Types

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

Page 80: Liquid Types

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

Page 81: Liquid Types

When does subtyping hold ?

Env ` {V:t|P1} <: {V:t|P2} if

(Env Æ P1) ) P2Valid:

Conservative Embedding To Decidable Logic (EUFA)

Page 82: Liquid Types

Base Subtyping = Implication

Env ` {V:t|P1} <: {V:t|P2} if

(Env Æ P1) ) P2Valid:

Conservative Embedding To Decidable Logic (EUFA)

Page 83: Liquid Types

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

Page 84: Liquid Types

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

Page 85: Liquid Types

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

Page 86: Liquid Types

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

Page 87: Liquid Types

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

Page 88: Liquid Types

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

Page 89: Liquid Types

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

Page 90: Liquid Types

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

Page 91: Liquid Types

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}

Page 92: Liquid Types

Liquid Type Inference

Step 1: Templates for unknowns

Step 2: Constraints on templates

Step 3: Solve constraints

Page 93: Liquid Types

Plan

• Motivation• Liquid Types• Liquid Type Inference• Complex Control• Complex Data• Results

Page 94: Liquid Types

Complex ControlValue-flow captured via subtyping

• Higher-order functions• Polymorphism

All reduced to simple implications!

Page 95: Liquid Types

Higher Order Functions

T1!T1’ <: T2!T2’ iff

Via Function Subtyping

Page 96: Liquid Types

T1T2

T1!T1’ <: T2!T2’ iff

<:

Via Function Subtyping

Higher Order Functions

Page 97: Liquid Types

T1 T1’T2 T2’

T1!T1’ <: T2!T2’ iff

<: and <:

Via Function Subtyping

Higher Order Functions

Func. subtyping reduces to implications

Page 98: Liquid Types

Complex ControlValue-flow captured via subtyping

• Higher-order functions• Polymorphism

All reduced to simple implications!

Page 99: Liquid Types

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

Page 100: Liquid Types

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

Page 101: Liquid Types

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

Page 102: Liquid Types

Plan

• Motivation• Liquid Types• Liquid Type Inference• Complex Control• Complex Data• Results

Page 103: Liquid Types

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)

Page 104: Liquid Types

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?

Page 105: Liquid Types

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?

Page 106: Liquid Types

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,…)

Page 107: Liquid Types

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”

Page 108: Liquid Types

[] + :: 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”

Page 109: Liquid Types

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”

Page 110: Liquid Types

Recursive Refinement Types

Predicates “Piggyback” on Rec Type

P(x0), P(x1),… 8x. P(x)

How to Generalize?

How to Instantiate?

Fold

Unfold

Page 111: Liquid Types

Recursive Refinements

[[],[P1,P2]] (¹t.Nil + Cons int * t)

Every “head” satisfies P1

Every “tail” satisfies P2

Page 112: Liquid Types

Positive Integer Lists

Every int satisfies 0<Vi.e. Every int is positive

[[],[0<V, True]] (¹t.Nil + Cons int * t)

Page 113: Liquid Types

Lower Bounded Integer Lists

Every int satisfies x·Vi.e. Every int greater than x

[[],[x·V, True]] (¹t.Nil + Cons int * t)

Page 114: Liquid Types

Nested Refinements

¹t.Nil+Cons x:int * [[],[P1,P2]] t

Every “inner list” refined by [[],[P1,P2]]

Page 115: Liquid Types

¹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

Page 116: Liquid Types

Duplicate-Free List

¹t.Nil + Cons x:int * [[],[x V,True]] t

Non-aliasing within collectionse.g. List of distinct addresses, handles etc.

Page 117: Liquid Types

Recursive Data Structures

Trees:type int tree=

| E

| N of int * int tree * int tree

Recursive type:¹t. E + N int * t * t

Page 118: Liquid Types

Balanced Trees

B (¹t.E + N x:int * l:t * r:t)

Where:B = [[],[True,True,|ht l - ht V|· 2]]

Page 119: Liquid Types

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

Page 120: Liquid Types

Inference ? As before!

1. Templates

2. Constraints 3. Solve

Page 121: Liquid Types

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,…)

Page 122: Liquid Types

(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

Page 123: Liquid Types

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!

Page 124: Liquid Types

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

Page 125: Liquid Types

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,…)

Page 126: Liquid Types

Inference ? As before!

1. Templates

2. Constraints 3. Solve

Page 127: Liquid Types

Plan

• Motivation• Liquid Types• Liquid Type Inference• Complex Control• Complex Data• Results

Page 128: Liquid Types

Liquid Type Inference for Ocaml

Property

Unsafe

Safe

DsolveQualifiers

Safety Properties: 1. Type signatures (div,Array.get,

…)2. Assertions

Page 129: Liquid Types

Demo

Page 130: Liquid Types

• Array bounds checking

• Data structures

Results

Page 131: Liquid Types

Array Bounds Checking Qualifiers

V op F, V op length F,

op 2 {·, ¸, =, }

Page 132: Liquid Types

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

Page 133: Liquid Types

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

Page 134: Liquid Types

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

Page 135: Liquid Types

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

Page 136: Liquid Types

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

Page 137: Liquid Types

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

Page 138: Liquid Types

Results

• Array-bounds

• Data Structures

Page 139: Liquid Types

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

Page 140: Liquid Types

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

Page 141: Liquid Types

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

Page 142: Liquid Types

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

Page 143: Liquid Types

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

Page 144: Liquid Types

fatal off-by-one error

Recursive Rebalance

Page 145: Liquid Types

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

Page 146: Liquid Types

Plan

• Motivation• Liquid Types• Liquid Type Inference• Complex Control• Complex Data• Results

Page 147: Liquid Types

Precise VerificationLiquid Types

Summary

Complex data & control

Path & value predicatesAbstract MC

Types

=

Page 148: Liquid Types

http://pho.ucsd.edu/liquidsource, papers, demo, etc.

Page 149: Liquid Types

HERE

Page 150: Liquid Types

Demo1: (Dsolve)simple loop(arraymax), binsearch

Demo2: (HO Funs)fold-loop (arraymax) / splitting slide

Demo3: (Polymorph)fold-loop (arraymax) / result > 0 slide

Page 151: Liquid Types

Future Work

• Usability– Error messages, running time

• Mutable state– Liquid Types for C, Java, C#

• Qualifier Discovery– Counterexample-Guided

Page 152: Liquid Types

A satisfied customer reports…

Page 153: Liquid Types

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

Page 154: Liquid Types

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)

Page 155: Liquid Types

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

Page 156: Liquid Types

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

Page 157: Liquid Types

30% of code

Page 158: Liquid Types

“The more interesting your types get, the less fun it is to write them down”

- Benjamin Pierce

Page 159: Liquid Types

Abstract MC

+ Type

Inference

Goal

Page 160: Liquid Types

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

Page 161: Liquid Types

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) )

Page 162: Liquid Types

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) )

Page 163: Liquid Types

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

Page 164: Liquid Types

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

Page 165: Liquid Types

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

Page 166: Liquid Types

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

Page 167: Liquid Types

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

Page 168: Liquid Types

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

Page 169: Liquid Types

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!

Page 170: Liquid Types

Type MagicValue-flow captured via subtyping

• Higher-order functions• Polymorphism• Recursive data structures

All reduced to simple implications!

Page 171: Liquid Types

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}

Page 172: Liquid Types

Type MagicValue-flow captured via subtyping

• Higher-order functions• Polymorphism• Recursive data structures

All reduced to simple implications!

Page 173: Liquid Types

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

Page 174: Liquid Types

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

Page 175: Liquid Types

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]]

Page 176: Liquid Types

Map Types

(n, n list) Map

set succs n e

get succs n

(’a, ’b) Map

Page 177: Liquid Types

(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