Title: Extended Static Checking for Haskell ESCHaskell
1Extended Static Checking for Haskell(ESC/Haskell)
- Dana N. Xu
- University of Cambridge
- advised by Simon Peyton Jones
- Microsoft Research, Cambridge
2Program Errors Give Headache!
- Module UserPgm where
- f Int-gtInt
- f xs head xs max 0
-
- f
Module Prelude where head a -gt a head
(xxs) x head error empty list
Glasgow Haskell Compiler (GHC) gives at
run-time Exception Prelude.head empty list
3Preconditions
- head xs _at_ requires not (null xs)
- head (xxs) x
- f xs head xs max 0
- Warning f calls head
- which may fail heads precondition!
- f_ok xs if null xs then 0
- else head xs max 0
A precondition (ordinary Haskell)
not Bool -gt Bool not True False not False
True
null a -gt Bool null True null (xxs)
False
4Postconditions
- rev xs _at_ ensures null res gt null xs
- rev
- rev (xxs) rev xs x
- case (rev xs) of
- -gt head xs
- (xxs) -gt
A postcondition (ordinary Haskell)
Crash!
(gt) Bool -gt Bool -gt Bool (gt) True x
x (gt) False x True
5Expressiveness of the Specification Language
data T T1 Bool T2 Int T3 T T sumT T -gt
Int sumT x _at_ requires noT1 x sumT (T2 a)
a sumT (T3 t1 t2) sumT t1 sumT t2 noT1 T
-gt Bool noT1 (T1 _) False noT1 (T2 _)
True noT1 (T3 t1 t2) noT1 t1 noT1 t2
6Expressiveness of the Specification Language
- sumT T -gt Int
- sumT x _at_ requires noT1 x
- sumT (T2 a) a
- sumT (T3 t1 t2) sumT t1 sumT t2
- rmT1 T -gt T
- rmT1 x _at_ ensures noT1 res
- rmT1 (T1 a) if a then T2 1 else T2 0
- rmT1 (T2 a) T2 a
- rmT1 (T3 t1 t2) T3 (rmT1 t1) (rmT1 t2)
- For all crash-free tT, sumT (rmT1 t) will not
crash.
7Functions without Annotations
- data T T1 Bool T2 Int T3 T T
- noT1 T -gt Bool
- noT1 (T1 _) False
- noT1 (T2 _) True
- noT1 (T3 t1 t2) noT1 t1 noT1 t2
- () True x x
- () False x False
No abstraction is more compact than the function
definition itself!
8Higher Order Functions
- all (a -gt Bool) -gt a -gt Bool
- all f True
- all f (xxs) f x all f xs
- filter p xs _at_ ensures all p res
- filter p
- filter p (xxs) case (p x) of
- True -gt x filter p xs
- False -gt filter p xs
9Various Examples
- zip xs ys _at_ requires sameLen xs ys
- zip xs ys _at_ ensures sameLen res xs
- sameLen True
- sameLen (xxs) (yys) sameLen xs ys
- sameLen _ _ False
- f91 n _at_ requires n lt 101
- f91 n _at_ ensures res 91
- f91 n case (n lt 100) of
- True -gt f91 (f91 (n 11))
- False -gt n 10
10Sorting
- sorted True
- sorted (x) True
- sorted (xyxs) x lt y sorted (y xs)
- insert i xs _at_ ensures sorted xs gt sorted res
- insertsort xs _at_ ensures sorted res
- merge xs ys _at_ ensures sorted xs sorted ys
- gt sorted res
- mergesort xs _at_ ensures sorted res
- bubbleHelper Int -gt (Int, Bool)
- bubbleHelper xs _at_ ensures not (snd res) gt
- sorted (fst res)
- bubblesort xs _at_ ensures sorted res
11What we cant do
- g1 x _at_ requires True
- g1 x case (prime x gt square x) of
- True -gt x
- False -gt error urk
- g2 xs ys
- case (rev (xs ys) rev ys rev xs) of
- True -gt xs
- False -gt error urk
Crash!
Crash!
Hence, three possible outcomes (1) Definitely
Safe (no crash, but may loop) (2) Definite Bug
(definitely crashes) (3) Possible Bug
12LanguageSyntax
following Haskells lazy semantics
13Preprocessing
1. Filling in missing pattern matchings.
2. Type checking the pre/postconditions.
head xs _at_ requires xs / head a -gt
a head (xxs) x
head Eq a gt a -gt a
14Symbolic Pre/Post Checking
- At the definition of each function f,
- assuming the given precondition holds,
- we check
- No pattern matching failure
- Precondition of all calls in the body of f holds
- Postcondition holds for f itself.
15Given f x e, f.pre and f.post
Goal show fchk is crash-free!
Theorem if so, then given precondition of f
holds 1. No pattern matching failure 2.
Precondition of all calls in the body of f
holds 3. Postcondition holds for f itself
16The Representative Function
No need to look inside OK calls
All crashes in f are exposed in f
17Simplifier
18Expressive specification does not increase the
complication of checking
- filter f xs _at_ ensures all f res
- filterchk f xs
- case xs of
- -gt True
- (xxs) -gt case (all f (filter f xs)) of
- True -gt all f (filter f xs)
19Arithmetic via External Theorem Prover
- foo Int -gt Int -gt Int
- foo i j _at_ requires i gt j
- foo i j case i gt j of
- False -gt BAD foo
- True -gt
- goo i foo (i8) i
- goochk i case (i8 gt i) of
- False -gt BAD foo
- True -gt
gtgtThmProver i8gti gtgtValid!
gtgtThmProver push(igtj) push(not (jlt0)) (igt0) gtgtVali
d!
case i gt j of True -gt case j lt 0 of False -gt
case i gt 0 of False -gt BAD f
20Counter-Example Guided Unrolling
sumT T -gt Int sumT x _at_ requires noT1 x
sumT (T2 a) a sumT (T3 t1 t2) sumT t1
sumT t2
After simplifying sumTchk, we may have
case ((OK noT1) x) of True -gt case x of
T1 a -gt BAD sumT T2 a -gt a T3
t1 t2 -gt case ((OK noT1) t1) of
False -gt BAD sumT True -gt
case ((OK noT1) t2) of
False -gt BAD sumT
True -gt (OK sumT) t1
(OK sumT) t2
21Step 1Program Slicing Focus on the BAD Paths
case ((OK noT1) x) of True -gt case x of
T1 a -gt BAD sumT T3 t1 t2 -gt case ((OK noT1)
t1) of False -gt BAD sumT
True -gt case ((OK noT1) t2) of
False -gt BAD sumT
22Step 2 Unrolling
case (case x of T1 a -gt False
T2 a -gt True T3 t1 t2 -gt (OK
noT1) t1 (OK noT1)
t2)) of True -gt case x of T1 a -gt BAD
sumT T3 t1 t2 -gt case ((OK noT1) t1) of
False -gt BAD sumT
True -gt case ((OK noT1) t2) of
False -gt BAD sumT
(OK noT1) t1
(OK noT1) t2
((OK noT1) t1)
((OK noT1) t2)
23Keeping Known Information
case (case (NoInline ((OK noT1) x)) of True
-gt case x of T1 a -gt False
T2 a -gt True T3 t1 t2 -gt (OK
noT1) t1 (OK noT1)
t2)) of True -gt case x of T1 a -gt BAD
sumT T3 t1 t2 -gt case ((OK noT1) t1) of
False -gt BAD sumT
True -gt case ((OK noT1) t2) of
False -gt BAD sumT
(OK noT1) t1
(OK noT1) t2
((OK noT1) t1)
((OK noT1) t2)
case (NoInline ((OK noT1) t1)) of
case (NoInline ((OK noT1) t2)) of
24Counter-Example Guided Unrolling The Algorithm
25Tracing
26Counter-Example Generation
f3 z 0 f3 (xxs) z case x gt z of
True -gt f2 x z False -gt ...
f1 x z _at_ requires x lt z f2 x z 1 f1 x z
- f3chk xs z
- case xs of
- -gt 0
- (xy) -gt case x gt z of
- True -gt Inside f2 ltl2gt
- (Inside f1 ltl1gt (BAD f1))
- False -gt
- Warning ltl3gt f3 (xy) z where xgtz
- calls f2
- which calls f1
- which may fail f1s precondition!
27Contributions
- Checks each program in a modular fashion on a per
function basis. The checking is sound. - Pre/postcondition annotations are in Haskell.
- Allow recursive function and higher-order
function - Unlike VC generation, we treat pre/postcondition
as boolean-valued functions and use symbolic
simplification. - Handle user-defined data types
- Better control of the verification process
- First time that Counter-Example Guided approach
is applied to unrolling. - Produce a trace of calls that may lead to crash
at compile-time. - Our prototype works on small but significant
examples - Sorting insertion-sort, merge-sort, bubble-sort
- Nested recursion
28Future Work
- Allowing pre/post declaration for data types.
- data A where
- A1 Int -gt A
- A2 Int -gt Bool -gt A
- A2 x y _at_ requires length x length y
- Allowing pre/post declaration for parameters in
higher-order function. - map f xs _at_ requires all f.pre xs
- map f
- map f (xxs) f x map f xs
- Allowing polymorphism and support full Haskell.