static contract checking for haskell
DESCRIPTION
Static Contract Checking for Haskell. Dana N. Xu University of Cambridge Joint work with. Simon Peyton Jones Microsoft Research Cambridge. Koen Claessen Chalmers University of Technology. Module UserPgm where f :: [Int]->Int f xs = head xs `max` 0 : … f [] …. - PowerPoint PPT PresentationTRANSCRIPT
1
Static Contract Checking for Haskell
Dana N. Xu
University of Cambridge
Joint work with Simon Peyton Jones
Microsoft Research CambridgeKoen Claessen
Chalmers University of Technology
2
Module UserPgm where
f :: [Int]->Intf xs = head xs `max` 0
: … f [] …
Program Errors Give Headache!
Glasgow Haskell Compiler (GHC) gives at run-time
Exception: Prelude.head: empty list
Module Prelude where
head :: [a] -> ahead (x:xs) = xhead [] = error “empty list”
3
Types>> > > > > Contracts >> > > > >
head (x:xs) = x
head :: [Int] -> Int
…(head 1)…
head 2 {xs | not (null xs)} -> {r | True}
…(head [])…
Bug!
Bug!Contract
(original Haskell boolean expression)
Type not :: Bool -> Boolnot True = Falsenot False = True
null :: [a] -> Boolnull [] = Truenull (x:xs) = False
4
Contract Checkinghead 2 {xs | not (null xs)} -> {r | True}head (x:xs’) = x
f xs = head xs `max` 0
Warning: f [] calls head which may fail head’s precondition!
f_ok xs = if null xs then 0 else head xs `max` 0
No more warnings from compiler!
5
Satisfying a predicate contract
e 2 {x | p} if (1) p[e/x] gives True and
(2) e is crash-free.
Arbitrary boolean-valued Haskell expression
Recursive function, higher-order function,
partial functioncan be called!
6
Expressiveness of the Specification Language
data T = T1 Bool | T2 Int | T3 T T
sumT :: T -> Int sumT 2 {x | noT1 x} -> {r | True}sumT (T2 a) = asumT (T3 t1 t2) = sumT t1 + sumT t2
noT1 :: T -> BoolnoT1 (T1 _) = FalsenoT1 (T2 _) = TruenoT1 (T3 t1 t2) = noT1 t1 && noT1 t2
7
Expressiveness of the Specification LanguagesumT :: T -> IntsumT 2 {x | noT1 x} -> {r | True}sumT (T2 a) = asumT (T3 t1 t2) = sumT t1 + sumT t2
rmT1 :: T -> TrmT1 2 {x | True} -> {r | noT1 r}rmT1 (T1 a) = if a then T2 1 else T2 0rmT1 (T2 a) = T2 armT1 (T3 t1 t2) = T3 (rmT1 t1) (rmT1 t2)
For all crash-free t::T, sumT (rmT1 t) will not crash.
8
Higher Order Functions
all :: (a -> Bool) -> [a] -> Boolall f [] = Trueall f (x:xs) = f x && all f xs
filter :: (a -> Bool) -> [a] -> [a]filter 2 {f | True} -> {xs | True} -> {r | all f r}filter f [] = []filter f (x:xs’) = case (f x) of True -> x : filter f xs’ False -> filter f xs’
9
Contracts for higher-order function’s parameterf1 :: (Int -> Int) -> Int
f1 2 ({x | True} -> {y | y >= 0}) -> {r | r >= 0}f1 g = (g 1) - 1
f2:: {r | True}
f2 = f1 (\x -> x – 1)
Error: f1’s postcondition fails because (g 1) >= 0 does not imply (g 1) – 1 >= 0Error: f2 calls f1 which fails f1’s precondition
[Findler&Felleisen:ICFP’02, Blume&McAllester:ICFP’04]
10
Functions without Contractsdata T = T1 Bool | T2 Int | T3 T T
noT1 :: T -> BoolnoT1 (T1 _) = FalsenoT1 (T2 _) = TruenoT1 (T3 t1 t2) = noT1 t1 && noT1 t2
(&&) True x = x(&&) False x = False
No abstraction is more compact than the function definition itself!
11
Contract Synonymcontract Ok = {x | True}contract NonNull = {x | not (null x)}
head :: [Int] -> Inthead 2 NonNull -> Okhead (x:xs) = x
{-# contract Ok = {x | True} -#}{-# contract NonNull = {x | not (null x)} #-}{-# contract head :: NonNull -> Ok #-}
Actual Syntax
12
Questions on e 2 tbot = bot
x. x 2 {x | bot} ! {r | True} ?x. error “f” 2 {x | bot} ! {r | True} ?x. x 2 {x | True} ! {r | bot} ?x. head [] 2 {x | True} ! {r | bot} ?(True, 2) 2 {x | (snd x) > 0} ?(head [], 3) 2 {x | (snd x) > 0} ?error “f” 2 ? ? 2 {x | False} ? 2 {x | head []}
13
LanguageSyntax
following Haskell’slazy semantics
14
Two special constructors BAD is an expression that crashes.error :: String -> aerror s = BAD
head (x:xs) = xhead [] = BAD
UNR (short for “unreachable”) is an expression that gets stuck. This is not a crash, although execution comes to a halt without delivering a result. (identifiable infinite loop)
15
Syntax of Contracts(related to [Findler:ICFP02,Blume:ICFP04,Hinze:FLOPS06,Flanagan:POPL06])
Full version: x’:{x | x >0} -> {r | r > x’}Short hand: {x | x > 0} -> {r | r > x}
k:({x | x > 0} -> {y | y > 0}) -> {r | r > k 5}
t 2 Contractt ::= {x | p} Predicate Contract
| x:t1 ! t2 Dependent Function Contract
| (t1, t2) Tuple Contract
| Any Polymorphic Any Contract
16
Contract Satisfaction(related to [Findler:ICFP02,Blume:ICFP04,Hinze:FLOPS06])
e" means e diverges or e !* UNR
e 2 {x | p} , e" or (e is crash-free and p[e/x]!*{BAD, False} [A1]
e2 x:t1! t2 , e" or 8 e1 2 t1. (e e1) 2 t2[e1/x] [A2]
e 2 (t1, t2) , e" or (e1 2 t1 and e2 2 t2) [A3]
e 2 Any , True [A4]
Given ` e :: and `c t :: , we define e 2 t as follows:
17
Answers on e 2 tx. x 2 {x | bot} ! {r | True}x. BAD 2 {x | bot} ! {r | True} x. x 2 {x | True} ! {r | bot}x. BAD 2 {x | True} ! {r | bot} x. BAD 2 {x | True} ! Any(True, 2) 2 {x | (snd x) > 0} (BAD, 3) 2 {x | (snd x) > 0} BAD 2 AnyUNR, bot 2 {x | False}UNR, bot 2 {x | BAD} BAD 2 (Any, Any) BAD 2 Any ! Any
18
Lattice for Contracts
{x | True}
Any
{x | False}
({x | x>0}, Any)
({x | x>0}, {r | r>0})
{x | x>0}->{r | r>x}
Any -> {r | r>0}
19
Laziness
fst 2 ({x | True}, Any) -> {r | True}fst (a,b) = a …fst (5, error “f”)…
fstN :: (Int, Int) -> IntfstN 2 ({x | True}, Any) -> {r | True}fstN (a, b) n = if n > 0 then fstN (a+1, b) (n-1)
else a
g2 = fstN (5, error “fstN”) 100
Every expression satisfies Any
20
What to Check?Does function f satisfies its contract t (written f2 t)?
At the definition of each function f,
Check f 2 t assuming all functions called in f satisfy their contracts.
Goal: main 2 {x | True}
21
How to Check?Define e 2 t
Construct
e B t
(e “ensures” t)
Grand Theorem e 2 t , e B t is crash-free
(related to Blume&McAllester:ICFP’04)
Normal form e’
Simplify (e B t)
If e’ is syntactically safe,
then Done!
This Talk
ESC/Haskell (Xu:HW’06)
22
What we can’t do?g1, g2 2 Ok -> Okg1 x = case (prime x > square x) of True -> x False -> error “urk”
g2 xs ys = case (rev (xs ++ ys) == rev ys ++ rev xs) of True -> xs False -> error “urk”
Hence, three possible outcomes: (1) Definitely Safe (no crash, but may loop)(2) Definite Bug (definitely crashes)(3) Possible Bug
Crash!
Crash!
23
Crashing
Definition (Crash).
A closed term e crashes iff e !* BAD
Definition (Crash-free Expression)
An expression e is crash-free iff
8 C. BAD 2 C, ` C[[e]] :: (), C[[e]] !* BAD
24
Definition of B and C (B pronounced ensures C pronounced requires)
e B {x | p} = case p[e/x] of
True -> eFalse -> BAD
e C {x | p} = case p[e/x] of
True -> e
False -> UNR
Example:5 2 {x | x > 0} 5 B {x | x > 0}= case (5 > 0) of True -> 5 False -> BAD
related to [Findler:ICFP02,Blume:ICFP04,Hinze:FLOPS06]
25
e B x:t1 ! t2 = v. (e (vC t1)) B t2[vCt1/x]
e C x:t1 ! t2 = v. (e (vB t1)) C t2[vBt1/x]
e B (t1, t2) = case e of (e1, e2) -> (e1 B t1, e2 B t2)
e C (t1, t2) = case e of (e1, e2) -> (e1 C t1, e2 C t2)
Definition of B and C (cont.)
26
Higher-Order Function
f1 B ({x | True} -> {y | y >= 0}) -> {r | r >= 0}= … B C B= v1. case (v1 1) >= 0 of True -> case (v1 1) - 1 >= 0 of True -> (v1 1) -1 False -> BAD False -> UNR
f1 :: (Int -> Int) -> Intf1 2 ({x | True} -> {y | y >= 0}) -> {r | r >= 0}f1 g = (g 1) - 1
f2:: {r | True}f2 = f1 (\x -> x – 1)
27
Modified Definition of B and C
e B {x | p} = case p[e/x] of
True -> e
False -> BAD
e B {x | p} = e `seq` case fin (p[e/x]) of
True -> e
False -> BAD
old
new
e_1 `seq` e_2 = case e_1 of {DEFAULT -> e_2}
if e !* BAD,
then (fin e) ! False
else (fin e) ! e
28
Why need seq and fin?
(UNR B {x | False}) !* BAD
But UNR 2 {x | False}
x. x 2 {x | BAD} ! {x | True}
But x. x B {x | BAD} ! {x | True}
!* v. (v `seq` BAD) which is NOT crash-free.
e B {x | p} = e `seq` case fin (p[e/x]) of
True -> e
False -> BAD Without seq
Without fin
29
e B Any = UNRe C Any = BAD
f5 2 Any -> {r | True}f5 x = 5
error :: String -> a -- HM Typeerror 2 {x | True} -> Any -- Contract
Definition of B and C for Any
30
Properties of B and CKey Lemma:For all closed, crash-free e, and closed t,(e C t) 2 t
Projections: (related to Findler&Blume:FLOPS’06)For all e and t, if e 2 t, then (a) e ¹ e B t(b) e C t ¹ e
Definition (Crashes-More-Often):e1 ¹ e2 iff for all C, ` C[[ei]] :: () for i=1,2 and C[[e2]] !* BAD ) C[[e1]] !* BAD
31
About 30 Lemmas Lemma [Monotonicity of Satisfaction ]:If e1 2 t and e1 ¹ e2, then e22 tLemma [Congruence of ¹]:e1 ¹ e2 ) 8 C. C[[e1]] ¹ C[[e2]]Lemma [Idempotence of Projection]:8 e, t. e B t B t ≡ e B t 8 e, t. e C t C t ≡ eC t Lemma [A Projection Pair]:8 e, t. e B t C t ¹ eLemma [A Closure Pair]:8 e, t. e ¹ eC t B t
32
Lattice for Expressions (¹ )
UNR
BAD
(3,BAD)
5
x . x(3, 4)
x. 6
33
Lattice for Contracts
{x | True}
Any
{x | False}
({x | x>0}, Any)
({x | x>0}, {r | r>0})
{x | x>0}->{r | r>x}
Any -> {r | r>0}
34
Conclusion
Contract
Haskell Program
Glasgow Haskell Compiler (GHC)
Where the bug is
Why it is a bug
35
Contributions Automatic static contract checking instead of dynamic contract checking. Compared with ESC/Haskell
Allow pre/post specification for higher-order function’s parameter through contracts.
Reduce more false alarms caused by Laziness and in an efficient way. Allow user-defined data constructors to be used to define contracts Specifications are more type-like, so we can define contract synonym.
We develop a concise notation (B and C) for contract checking, which enjoys many properties. We give a new and relatively simpler proof of the soundness and completeness of dynamic contract checking, this proof is much trickier than it looks.
We implement the idea in GHC Accept full Haskell Support separate compilation as the verification is modular. Can check functions without contract through CEG unrolling.
36
Sortingsorted [] = Truesorted (x:[]) = Truesorted (x:y:xs) = x <= y && sorted (y : xs)
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
37
Various Exampleszip :: [a] -> [b] -> [(a,b)]zip :: {xs | True} -> {ys | sameLen xs ys} -> {rs | sameLen rs xs }
sameLen [] [] = TruesameLen (x:xs) (y:ys) = sameLen xs yssameLen _ _ = False
f91 :: Int -> Intf91 :: { n <= 101 } -> {r | r == 91 }f91 n = case (n <= 100) of
True -> f91 (f91 (n + 11)) False -> n – 10
38
f :: {x | x > 0} -> {r | True}f x = x
f B {x | x > 0} -> {r | True}=(x.x) B {x | x > 0} -> {r | True}= v. (x.x (v C {x | x > 0})) B {r | True}= v. (case v > 0 of True -> v False -> UNR)
g = … f…
f C {x | x > 0} -> {r | True}= v. (case v > 0 of True -> v False -> BAD)
39
Constructor Contracts{x | x > 0} : {xs | all (< 0) xs}
{x | x > 0} : Any
Any : Any
Support any user-defined data constructors!
(related to [Hinze&Jeuring&Löh:FLOPS’06])
40
How to Check e 2 t?Given e and t We construct a term (eBt) (pronounced e “ensures” t)
related to Findler&Felleisen:ICFP’02 (dynamic contract checking algorithm)
Prove (eBt) is crash-free. simplify the term (eBt) to e’ and e’ is syntactically safe,
then we are done.
Theorem 1 (related to Blume&McAllester:ICFP’04)
eBt is crash-free , e 2 t
Theorem 2simpl (eBt) is syntactically safe ) eBt is crash-free
This Talk
ESC/Haskell (HW’06)
41