Static Contract Checking for Haskell
description
Transcript of Static Contract Checking for Haskell
Static Contract Checking for Haskell
Dana N. XuUniversity of Cambridge
Ph.D. Supervisor:Simon Peyton JonesMicrosoft Research Cambridge
From Types to Contractshead [] = BADhead (x:xs) = x
head :: [a] -> a
…(head 1)…
head {xs | not (null xs)} -> {r | True}
…(head [])…
Bug!
Bug!Contract(arbitrary
Haskell boolean expression)
Type null :: [a] -> Boolnull [] = Truenull (x:xs) = False
BAD means “should not
happen: crash”
What we want• Adapt Findler-Felleisen’s ideas for dynamic (high-order) contract checking.• Do static contract checking for a lazy language.
Contract Haskell function
Glasgow Haskell Compiler (GHC)
Where the bug is Why it is a bug
Three Outcomes(1) Definitely Safe (no crash, but may loop)(2) Definite Bug (definitely crashes)(3) Possible Bug
5
Sorting
sorted [] = Truesorted (x:[]) = Truesorted (x:y:xs) = x <= y && sorted (y : xs)
insert :: Int -> [Int] -> [Int]insert {i | True} -> {xs | sorted xs} -> {r | sorted r}
merge :: [Int] -> [Int] -> [Int]merge {xs | sorted xs}->{ys | sorted ys}->{r | sorted r}
bubbleHelper :: [Int] -> ([Int], Bool)bubbleHelper {xs | True} -> {r | not (snd r) ==> sorted (fst r)}
insertsort, mergesort, bubblesort {xs | True} -> {r | sorted r}
(==>) True x = x(==>) False x = True
AVL Tree
balanced :: AVL -> Boolbalanced L = Truebalanced (N t u) = balanced t && balanced u && abs (depth t - depth u) <= 1
data AVL = L | N Int AVL AVL insert, delete :: AVL -> Int -> AVLinsert {x | balanced x} -> {y | True} -> {r | notLeaf r && balanced r &&
0 <= depth r - depth x && depth r - depth x <= 1 }
delete {x | balanced x} -> {y | True} -> {r | balanced r && 0 <= depth x - depth r &&
depth x - depth r <= 1}
(&&) True x = x(&&) False x = False
The Contract Idea for Higher-Order Function[Findler/Felleisen]
f1 :: (Int -> Int) -> Intf1 ({x | x > 0} -> {y | y >= 0}) -> {r | r >= 0}f1 g = (g 0) - 1
f2 :: {r | True}f2 = f1 (\x -> x – 5)
Blame f1: f1 calls g with
wrong argument
f1 does not satisfy its post-condition
Blame f2: f2 calls f1 with
wrong argument
f3 :: {r | True}f3 = f1 (\x -> x – 1)
f3 is Ok.Can’t tell at
run-time
What is a Contract?
Contract t ::= {x | p} Predicate Contract
| x:t1 -> t2 Dependent Function Contract
| (t1, t2) Tuple Contract | Any Polymorphic Any Contract
3 { x | x > 0 } (3, []) Any3 { x | True } (3, []) (Ok, {ys | null ys})
Ok = {x | True} arbitrary Haskell boolean
expression
inc x:{x | x>0} -> {y | y == x + 1}
Precondition
Postcondition
Postcondition can mention
argument
arbitrary constructor
What we want?
Check f <contract_of_f>• If main Ok , then the whole program
cannot crash.• If not, show which function to blame and
why.
Beauty of Contract Checking
Define e t
Construct e t
(e “ensures” t)
Main Theorem e t iff e t is crash-free (related to Blume&McAllester:JFP’06)
some e’
Symbolically simplify (e t)
See if BAD is syntactically in e’.If yes, DONE;
else give BLAME
[POPL’10]
ESC/Haskell [Haskell’06]
e {x | p} = case p[e/x] ofTrue -> eFalse -> BAD
e x:t1 -> t2 = v. (e (v t1)) t2[(vt1)/x]
e (t1, t2) = case e of (e1, e2) -> (e1 t1, e2 t2)
e Any = UNR
Wrappers and ( pronounced ensures pronounced requires)
related to [Findler-Felleisen:ICFP02]
Wrappers and ( pronounced ensures pronounced requires)
related to [Findler-Felleisen:ICFP02]
e {x | p} = case p[e/x] ofTrue -> eFalse -> UNR
e x:t1 -> t2 = v. (e (v t1)) t2[v t1/x]
e (t1, t2) = case e of (e1, e2) -> (e1 t1, e2 t2)
e Any = BAD
Some Interesting Details
PracticeTheory
Adding tags, e.g. BAD “f”• Achieves precise blaming
More tags to trace functions to blame• Achieves the same goal of [Meunier:POPL06]
Using a theorem proverCounter-example guided unrolling
Contracts that loopContracts that crashLovely Lemmas
Lovely Lemmas
Summary• Static contract checking is a fertile and under-researched
area• Distinctive features of our approach– Full Haskell in contracts; absolutely crucial– Declarative specification of “satisfies”– Nice theory (with some very tricky corners)– Static proofs– Modular Checking– Compiler as theorem prover
After Ph.D.• Postdoc project in 2009: probabilistic contract for
component base design [ATVA’2010]• Current project at Xavier Leroy’s team (INRIA)
- a verifying compiler: 1. Apply the idea to OCaml compiler by allowing both static and dynamic contract checking 2. Connect with Coq to verify more programs statically. 3. Use Coq to prove the correctness of the framework. 4. Apply new ideas back to Haskell (e.g. GHC).
Static and Dynamic
Program with Specifications
Run time error attributes blame to
the right place
Compile time error attributes blame to
the right place
Dynamicchecking
Staticchecking
No blamingmeans
Program cannot crashs
Or, more plausibly: If you guarantee that f t,then the program cannot crash
What exactly does it mean to say that
e “satisfies” contract t?
e t
When does e satisfy a contract?
• Brief, declarative…
inc x:{x | x > 0} -> {y | y == x + 1}
Precondition
Postcondition
Postcondition can mention
argument
When does e satisfy a contract?
• The delicate one is the predicate contract.• Our decision: e {x | p} iff e is crash-free• Question: What expression is crash-free ? e is crash-free iff no blameless context can make e crash
e is crash-freeiff
C. BAD s C. C[e] * BAD
Crash-free Examples
Lemma: e is syntactically safe => e is crash-free.
Crash-free?
\x -> x YES(1, True) YES
(1, BAD) NO
\x -> if x > 0 then x else (BAD, x) NO
\x -> if x*x >= 0 then x + 1 else BAD Hmm.. YES
When does e satisfy a contract?
See the paper for …
• Why e must be crash-free to satisfy predicate contract?• Why divergent expression satisfies all contract?• What if contract diverges (i.e. p diverges)?• What if contract crashes (i.e. p crashes)?
How can we mechanically check that
e “satisfies” contract t?
e t ???
Example
head { xs | not (null xs) } -> Ok
head {xs | not (null xs)} -> Ok= \v. head (v {xs | not (null xs)}) Ok
e Ok = e= \v. head (v {xs | not (null xs)})= \v. head (case not (null v) of
True -> vFalse -> UNR)
head:: [a] -> ahead [] = BADhead (x:xs) = x
\v. head (case not (null v) ofTrue -> vFalse -> UNR)
null :: [a] -> Boolnull [] = Truenull (x:xs) = False
not :: Bool -> Boolnot True = Falsenot False = True
= \v. head (case v of [] -> UNR (p:ps) -> p)
Now inline ‘not’ and ‘null’
Now inline ‘head’= \v. case v of
[] -> UNR (p:ps) -> p
head:: [a] -> ahead [] = BADhead (x:xs) = x
So head [] fails with UNR, not
BAD, blaming the caller
Static and Dynamic
Program with Specifications
Run time error attributes blame to
the right place
Compile time error attributes blame to
the right place
Dynamicchecking
Staticchecking
[Findler, Felleisen,
Blume, Hinze,
Loh, Runciman, Chitil]
[Flanaghan, Mitchell, Pottier]