presented by Juraj Mičko
for Research Topics in Software Engineering
on 2.11.2021

Motivation
-- type-safe, but throws exception
main = print ("Hello" !! 42)
-- total, but non-terminating
main = let z = z in z
-- total, terminating,
-- but probably not functional as expected
main = print (sum 1 2)
where
sum a b = a - b






1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Motivation

1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
can reject these programs during compile-time
Tradeoff: Some correct programs
are rejected, too

Refinement Types
-- Type specification (Haskell code)
foo :: Int
-- Type refinement (LiquidHaskell code)
{-@ foo :: { v:Int | v >= 42 } @-}
foo = 7 -- does not compile
foo = 42 -- OK
1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Goals of the paper
- Showcase LiquidHaskell
- Discuss properties that can be checked
- Show how real-world libraries can be verified using LH
1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
take :: t:Text -> i:Int -> Text
take t i =
if i < len t
then
Unsafe.take t i
else
error "Out of bounds"
UNSAFE
SLOW
(essentially an array of Unicode characters)
Example of using LiquidHaskell
1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
{-@ take :: t:Text -> { i:Int | i < len t } -> Text @-}
take t i =
if i < len t
then
Unsafe.take t i
else
error "Out of bounds"
Example of using LiquidHaskell
LiquidHaskell syntax
1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
{-@ take :: t:Text -> { i:Int | i < len t } -> Text @-}
take t i =
Unsafe.take t i
SAFE & FAST :)
(under-approximation)
Example of using LiquidHaskell
LiquidHaskell syntax
1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Workflow of LiquidHaskell

(under-approximation)
1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation

"refines Haskell's types with logical predicates that let us enforce critical properties at compile time"
- totality
- termination
- application-specific properties
LiquidHaskell can prove:
- memory safety
(buffer overflows, dangling pointers) - data structure correctness invariants
(e.g. red-black trees) -
functional properties
(e.g. sum)




1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Specifying correctness

{-@ append :: x:[a] -> y:[a] -> {z:[a] | len z = len x + len y} @-}
append [] ys = ys
append (x:xs) ys = x : append xs ys
1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Specifying totality
totality verified out of the box
(no extra annotation needed)

{-@ head :: { v:[_] | 0 < len v } -> _ @-}
head (x:xs) = x
1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Verifying totality
{-@ head :: { v:[_] | 0 < len v } -> _ @-}
head (x:xs) = x
-- translates to:
head l = case l of
(x:xs) ->
x
[] ->
error "..."

1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Verifying totality
{-@ head :: { v:[_] | 0 < len v } -> _ @-}
head (x:xs) = x
-- translates to:
head l = case l of
(x:xs) ->
-- l :: { 0 < len l }
x
[] ->
-- l :: { 0 < len l && len l = 0 }
error "..."

1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Specifying termination
-- automatic verification
{-@ last :: { v:[Int] | len v > 0 } -> Int @-}
last [x] = x
last (x:xs) = last xs
-- termination expression
{-@ range :: lo:Int -> hi:Int -> [Int] / [hi-lo] @-}
range lo hi
| lo < hi = lo : range (lo+1) hi
| otherwise = []

/ [hi-lo]
1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Demo:
Verify QuickSort
- returns an ordered list
- is total
- terminates
(this still doesn't prove all about quicksort)
1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Evaluation

- found a bug in !
text
- Various properties of popular Haskell libraries (10K LoC, 56 modules)
including
containers, hscolour, bytestring, text, vector-algorithms, xmonad
~2 LoC required for specifying a function
# of specifications LoC
1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Limitations
- Limited expressivity
- Code modifications may be needed
- Ghost parameters
- Abstract specialization of functions
- Ghost parameters
- Error reporting
- Supports only linear arithmetic
appendSorted pivot [] ys = pivot : ys
appendSorted pivot (x:xs) ys = x : appendSorted pivot xs ys
/Users/jjurm/workspace/liquid-haskell/Main.hs:28:13-33: error:
• Illegal type specification for `Main.appendableSortedLists`
Main.appendableSortedLists :: forall a .
(Ord<[]> a) =>
lq_tmp$db##2:(Main.SortedList a) -> lq_tmp$db##3:(Main.SortedList a) -> {VV :
GHC.Types.Bool | VV == Main.appendableSortedLists lq_tmp$db##2 lq_tmp$db##3
&& VV == (if is$GHC.Maybe.Nothing (Main.highBound lq_tmp$db##2) then true else (if is$GHC.Maybe.Nothing
(Main.lowBound lq_tmp$db##3) then true else GHC.Maybe.Just##lqdc##$select##GHC.Maybe.Just##1 (Main.highBound lq_tmp$db##2)
<= GHC.Maybe.Just##lqdc##$select##GHC.Maybe.Just##1 (Main.lowBound lq_tmp$db##3)))}
Sort Error in Refinement: {VV : bool | (VV == Main.appendableSortedLists lq_tmp$db##2 lq_tmp$db##3
&& VV == (if is$GHC.Maybe.Nothing (Main.highBound lq_tmp$db##2) then true else
(if is$GHC.Maybe.Nothing (Main.lowBound lq_tmp$db##3) then true else GHC.Maybe.Just##lqdc##$select##GHC.Maybe.Just##1
(Main.highBound lq_tmp$db##2) <= GHC.Maybe.Just##lqdc##$select##GHC.Maybe.Just##1 (Main.lowBound lq_tmp$db##3))))}
Unbound symbol Main.lowBound --- perhaps you meant: Main.highBound ?
•
|
28 | {-@ reflect appendableSortedLists @-}
| ^^^^^^^^^^^^^^^^^^^^^
max :: forall <p :: Int -> Prop>. Int<p> -> Int<p> -> Int<p>
1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Alternatives
- Verification of Haskell code: Coq, Agda, Idris, ...
- Other Static Contract Checkers exist
LiquidHaskell reuses specs for: functional correctness, totality, termination
1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Summary


1. Motivation ❯ 2. Paper intro ❯ 3. Example ❯ 4. Verifying programs ❯ 5. Demo ❯ 6. Evaluation
Syntax:
Specifications for type refinements
-- contracts
{-@ div :: n:Nat -> d:Pos -> { v:Nat | v <= n } @-}
div n d = n / d
-- dependant contracts
{-@ zip :: x:[a] -> { y:[b] | len x = len y } -> { z:[(a, b)] | len x = len z } @-}
-- measures (uninterpreted function)
{-@ measure len :: [a] -> Int @-}
len [] = 0
len (x:xs) = 1 + len xs
-- [] :: { v:[a] | len v = 0 }
-- (:) :: _ -> xs:_ -> { v:[a] | len v = 1 + len xs }
{-@ append :: xs:[a] -> ys:[a] -> { v:[a] | len v = len xs + len ys } @-}
Syntax:
Specifications for type refinements (2)
-- abstract refinements
{-@ max :: forall <p :: Int -> Prop>. Int<p> -> Int<p> -> Int<p> @-}
{-@ evenNumber :: { v: Int | v mod 2 == 0 } @-}
evenNumber = max 4 (-2)
-- type aliases
{-@ predicate NonEmp X = 0 < len X @-}
{-@ type NonEmpList a = { v: [a] | NonEmp v } @-}
{-@ deduplicate :: l:_ -> { v:_ | NonEmp l => NonEmp v } @-}
-- or
{-@ head :: l:NonEmpList a -> a @-}
-- unchecked refinement
{-@ assume answerAnything :: _ -> 42 @-}
answerAnything x = ...
Specifying termination (3)
-- mutual recursion
{-@ isEven :: n:Nat -> Bool / [n, 0] @-}
isEven 0 = True
isEven n = isOdd $ n-1
{-@ isOdd :: n:Nat -> Bool / [n, 1] @-}
isOdd n = not $ isEven n
[1, 2]
-> [1, 1]
-> [1, 0]
-> [0, 2]
-> [0, 1]
-> [0, 0]
Verifying correctness

-- [] :: { v:[_] | len v = 0 }
-- (:) :: _ -> xs:_ -> { v:[_] | len v = 1 + len xs }
{-@ append :: a:[_] -> b:[_] -> {c:[_] | len c = len a + len b} @-}
append [] ys =
-- len [] = 0
ys
-- len ys = len ys + len []
append (x:xs) ys =
-- len xs = len (x:xs) - 1
x : append xs ys
-- len (x : append xs ys) = 1 + len xs + len ys
Specifying termination (2)
-- lexicographic termination
{-@ ack :: m:Nat -> n:Nat -> Nat / [m, n] @-}
ack m n
| m == 0 = n + 1
| n == 0 = ack (m-1) 1
| otherwise = ack (m-1) (ack m (n-1))
[1, 2]
-> [1, 1]
-> [1, 0]
-> [0, 2]
-> [0, 1]
-> [0, 0]

Links
- Project website: https://ucsd-progsys.github.io/liquidhaskell/
- Specifications: https://ucsd-progsys.github.io/liquidhaskell/specifications/
Liquid Haskell
By Juraj Mičko
Liquid Haskell
- 159