Download - Sound Haskell
Sound Haskell
Dana N. Xu
University of Cambridge
Joint work with Simon Peyton Jones
Microsoft Research CambridgeKoen Claessen
Chalmers University of Technology
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”
Types>> > > > > Contracts >> > > > >
head (x:xs) = x
head :: [Int] -> Int
…(head 1)…
head :: {x | 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
Preconditionshead :: {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!
Expressiveness of the Specification Language
data T = T1 Bool | T2 Int | T3 T T
sumT :: T -> IntsumT :: {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
Expressiveness of the Specification LanguagesumT :: T -> IntsumT :: {x | noT1 x} -> {r | True}sumT (T2 a) = asumT (T3 t1 t2) = sumT t1 + sumT t2
rmT1 :: T -> TrmT1 :: {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.
Functions without Annotationsdata 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!
Higher Order Functions
all :: (a -> Bool) -> [a] -> Boolall f [] = Trueall f (x:xs) = f x && all f xs
filter :: (a -> Bool) -> [a] -> [a]filter :: {p | True} -> {xs | True} -> {r | all p r}filter p [] = []filter p (x:xs’) = case (p x) of True -> x : filter p xs’ False -> filter p xs’
Contracts for higher-order function’s parameterf1 :: (Int -> Int) -> Int
f1 :: ({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
Lazinessfst (a,b) = a
Option 1 ()fst :: {x | True} -> {r | True}
Option 2 ()fst :: ({x | True}, Any) -> {r | True} …fst (5, error “f”)…
fstN :: (Int, Int) -> IntfstN :: ({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
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
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
Contract Synonymtype Ok = {x | True}
type NonNull = {x | not (null x)}
head :: [Int] -> Int
head :: NonNull -> Ok
head (x:xs) = x
{-# type Ok = {x | True} -#}
{-# type NonNull = {x | not (null x)} #-}
{-# contract head :: NonNull -> Ok #-}
Actual Syntax
What we can’t dog1, g2 :: 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!
LanguageSyntax
following Haskell’slazy semantics
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)
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
What to Check?Does function f satisfies its contract t (written f2 t)?
Goal: main 2 {x | True}
At the definition of each function f,assuming the given precondition holds,we check1. No pattern matching failure2. Precondition of all calls in the body of f holds3. Postcondition holds for f itself.
How to Check?Given e and t We construct a term (eBt) (pronounced e “ensures” t) Prove (eBt) is crash-free.
simplify the term (eBt) to e’ and e’ is syntactically safe, then we are done.
Theorem 1eBt is crash-free , e 2 t
Theorem 2simpl (eBt) is syntactically safe ) eBt is crash-free
This Talk
ESC/Haskell (HW’06)
Syntax of Contracts
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}
Contract Satisfaction
e" means e diverges or e !* UNR
B pronounced ensuresC 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
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)
Function Contract and Tuple Contract
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)
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 :: ({x | True} -> {y | y >= 0}) -> {r | r >= 0}f1 g = (g 1) - 1
f2:: {r | True}f2 = f1 (\x -> x – 1)
e B Any = UNRe C Any = BAD
f5 :: Any -> {r | True}f5 x = 5
error :: String -> a -- HM Typeerror :: {x | True} -> Any -- Contract
Any Contract
Properties of B and CLemma1:For all closed, crash-free e, and closed t,e C t 2 t
Lemma2:For all e and t, if e2 t, then (a) e v e B t(b) e C t v e
Definition (Crashes-More-Often):e1 v e2 iff for all C, ` C[[ei]] :: () for i=1,2 and(1) C[[e2]] !* BAD ) C[[e1]] !* BAD
About 30 Lemmas Lemma [Monotonicity of Satisfaction ]:If e1 2 t and e1 v e2, then e22 tLemma [Congruence of v]:e1 v e2 ) 8 C. C[[e1]] v 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 v eLemma [A Closure Pair]:8 e, t. e v eC t B t
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.