static contract checking for haskell

41
1 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

Upload: kiana

Post on 15-Jan-2016

45 views

Category:

Documents


3 download

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 Presentation

TRANSCRIPT

Page 1: Static Contract Checking for Haskell

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

Page 2: Static Contract Checking for Haskell

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”

Page 3: Static Contract Checking for Haskell

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

Page 4: Static Contract Checking for Haskell

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!

Page 5: Static Contract Checking for Haskell

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!

Page 6: Static Contract Checking for Haskell

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

Page 7: Static Contract Checking for Haskell

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.

Page 8: Static Contract Checking for Haskell

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’

Page 9: Static Contract Checking for Haskell

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]

Page 10: Static Contract Checking for Haskell

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!

Page 11: Static Contract Checking for Haskell

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

Page 12: Static Contract Checking for Haskell

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

Page 13: Static Contract Checking for Haskell

13

LanguageSyntax

following Haskell’slazy semantics

Page 14: Static Contract Checking for Haskell

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)

Page 15: Static Contract Checking for Haskell

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

Page 16: Static Contract Checking for Haskell

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:

Page 17: Static Contract Checking for Haskell

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

Page 18: Static Contract Checking for Haskell

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}

Page 19: Static Contract Checking for Haskell

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

Page 20: Static Contract Checking for Haskell

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}

Page 21: Static Contract Checking for Haskell

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)

Page 22: Static Contract Checking for Haskell

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!

Page 23: Static Contract Checking for Haskell

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

Page 24: Static Contract Checking for Haskell

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]

Page 25: Static Contract Checking for Haskell

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

Page 26: Static Contract Checking for Haskell

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)

Page 27: Static Contract Checking for Haskell

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

Page 28: Static Contract Checking for Haskell

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

Page 29: Static Contract Checking for Haskell

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

Page 30: Static Contract Checking for Haskell

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

Page 31: Static Contract Checking for Haskell

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

Page 32: Static Contract Checking for Haskell

32

Lattice for Expressions (¹ )

UNR

BAD

(3,BAD)

5

x . x(3, 4)

x. 6

Page 33: Static Contract Checking for Haskell

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}

Page 34: Static Contract Checking for Haskell

34

Conclusion

Contract

Haskell Program

Glasgow Haskell Compiler (GHC)

Where the bug is

Why it is a bug

Page 35: Static Contract Checking for Haskell

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.

Page 36: Static Contract Checking for Haskell

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

Page 37: Static Contract Checking for Haskell

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

Page 38: Static Contract Checking for Haskell

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)

Page 39: Static Contract Checking for Haskell

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

Page 40: Static Contract Checking for Haskell

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)

Page 41: Static Contract Checking for Haskell

41