Set-up
On debian unstable, with opam and ocaml already installed.
Install system dependencies, Z3 and CVC4:
sudo apt install libcairo2-dev libexpat1-dev libgtk-3-dev libgtksourceview-3.0-dev zlib1g-dev libgmp-dev pkg-config autoconf z3 cvc4
Install opam dependencies and alt-ergo:
opam install ocamlfind menhir num zarith camlzip ocamlgraph lablgtk3 lablgtk3-sourceview3 re alt-ergo
Clone the repository:
git clone https://gitlab.inria.fr/why3/why3.git
Build Why3:
cd why3 && ./autogen.sh && ./configure && make
Install Why3:
sudo make install
Detect installed provers and generate configuration file:
why3 config detect
Test your installation:
cd examples && why3 ide -L . mergesort_array.mlw
Tutorial
Taken from
An introduction to deductive program verification.
Factorial recursive
Prove soundness and termination.
module FactRecursive
use int.Int
use int.Fact
let rec fact_rec (n: int) : int
requires { true }
ensures { result = fact n }
=
if n = 0 then 1
else n * fact_rec (n - 1)
end
mkdir fact && cd fact && why3 ide -L . factrecursive.mlw
Select the goal
fact_rec'vc
and press
S to split the goal.
Then select the new
split_vc
goal and press
0 to run provers on all its subgoals.
After making a change, press
Ctrl +
R to update the goals.
Solution
module FactRecursive
use int.Int
use int.Fact
let rec fact_rec (n: int) : int
requires { n >= 0 }
variant { n }
ensures { result = fact n }
=
if n = 0 then 1
else n * fact_rec (n - 1)
let test0 () = fact_rec 0
let test1 () = fact_rec 1
let test7 () = fact_rec 7
let test42 () = fact_rec 42
end
Factorial imperative
Prove soundness, termination and change the code to use a for loop instead of a while loop.
module FactImperative
use int.Int
use int.Fact
use ref.Ref
let fact_imp (n: int) : int
requires { true }
ensures { result = fact n }
=
let ref m = 0 in
let ref r = 1 in
while m < n do
invariant { true }
m := m + 1;
r := r * m
done;
r
end
You can use
let ref x = e
and avoid having to dereference it or
let x = ref e
and use the classical
!x
to get its value.
Solution
module FactImperative
use int.Int
use int.Fact
use ref.Ref
let fact_imp (n: int) : int
requires { n >= 0 }
ensures { result = fact n }
=
let ref m = 0 in
let ref r = 1 in
while m < n do
invariant { 0 <= m <= n }
invariant { r = fact m }
variant { n - m }
m := m + 1;
r := r * m
done;
r
let test0 () = fact_imp 0
let test1 () = fact_imp 1
let test7 () = fact_imp 7
let test42 () = fact_imp 42
end
Queue module
Taken from (don't look now, the solution is there)
Why3 gallery.
Write a complete specification, fill the implementation and prove it.
module Queue
use int.Int
use mach.peano.Peano
use list.List
use list.Length
use list.Reverse
use list.NthNoOpt
use list.Append
use seq.Seq
use seq.FreeMonoid
type t 'a = {
mutable front: list 'a; (* entry *)
mutable rear: list 'a; (* exit *)
ghost mutable seq: Seq.seq 'a;
}
invariant { true }
meta coercion function seq
let create () : t 'a
ensures { true }
= {
front = Nil;
rear = Nil;
seq = empty
}
let push (x: 'a) (q: t 'a) : unit
writes { }
ensures { true }
=
()
exception Empty
let pop (q: t 'a) : 'a
writes { }
ensures { true }
raises { Empty }
=
raise Empty
let peek (q: t 'a) : 'a
ensures { true }
raises { Empty }
=
raise Empty
let safe_pop (q: t 'a) : 'a
requires { true }
writes { }
ensures { true }
=
try pop q with Empty -> absurd end
let safe_peek (q: t 'a) : 'a
requires { true }
ensures { true }
=
try peek q with Empty -> absurd end
let clear (q: t 'a) : unit
writes { }
ensures { true }
=
()
let copy (q: t 'a) : t 'a
ensures { true }
=
q
let is_empty (q: t 'a)
ensures { true }
=
true
let length (q: t 'a) : Peano.t
ensures { true }
=
Peano.zero
let transfer (q1: t 'a) (q2: t 'a) : unit
writes { }
ensures { true }
=
()
end
Solution
module Queue
use int.Int
use mach.peano.Peano
use list.List
use list.Length
use list.Reverse
use list.NthNoOpt
use list.Append
use seq.Seq
use seq.FreeMonoid
type t 'a = {
mutable front: list 'a; (* entry *)
mutable rear: list 'a; (* exit *)
ghost mutable seq: Seq.seq 'a;
}
invariant { length seq = Length.length front + Length.length rear }
invariant { Length.length front > 0 -> Length.length rear > 0 }
invariant {
forall i. 0 <= i < length seq ->
seq[i] =
let n = Length.length rear in
if i < n then nth i rear
else nth (Length.length front - 1 - (i - n)) front }
meta coercion function seq
let create () : t 'a
ensures { result = empty }
= {
front = Nil;
rear = Nil;
seq = empty
}
let push (x: 'a) (q: t 'a) : unit
writes { q }
ensures { q = snoc (old q) x }
= match q.front, q.rear with
| Nil, Nil -> q.rear <- Cons x Nil
| _ -> q.front <- Cons x q.front
end;
q.seq <- snoc q.seq x
let rec lemma nth_append (i: int) (l1: list 'a) (l2: list 'a)
requires { 0 <= i < Length.length l1 + Length.length l2 }
ensures {
nth i (Append.(++) l1 l2) =
if i < Length.length l1 then nth i l1
else nth (i - Length.length l1) l2 }
variant { l1 }
=
match l1 with
| Nil -> ()
| Cons _ r1 ->
if i > 0 then nth_append (i - 1) r1 l2
end
let rec lemma nth_rev (i: int) (l: list 'a)
requires { 0 <= i < Length.length l }
ensures { nth i l = nth (Length.length l - 1 - i) (reverse l) }
variant { l }
=
match l with
| Nil -> absurd
| Cons _ s ->
if i > 0 then nth_rev (i - 1) s
end
exception Empty
let pop (q: t 'a) : 'a
writes { q }
ensures { (old q) <> empty }
ensures { result = (old q)[0] }
ensures { q = (old q)[1 ..] }
raises { Empty -> q = old q = empty }
=
let res = match q.rear with
| Nil -> raise Empty
| Cons x Nil -> q.front, q.rear <- Nil, reverse q.front; x
| Cons x s -> q.rear <- s; x
end in
q.seq <- q.seq [1 ..];
res
let peek (q: t 'a) : 'a
ensures { q <> empty }
ensures { result = q[0] }
raises { Empty -> q == empty }
=
match q.rear with
| Nil -> raise Empty
| Cons x _ -> x
end
let safe_pop (q: t 'a) : 'a
requires { q <> empty }
writes { q }
ensures { result = (old q)[0] }
ensures { q = (old q)[1 ..] }
= try pop q with Empty -> absurd end
let safe_peek (q: t 'a) : 'a
requires { q <> empty }
ensures { result = q[0] }
= try peek q with Empty -> absurd end
let clear (q: t 'a) : unit
writes { q }
ensures { q = empty }
=
q.seq <- empty;
q.rear <- Nil;
q.front <- Nil
let copy (q: t 'a) : t 'a
ensures { result == q }
= {
front = q.front;
rear = q.rear;
seq = q.seq
}
let is_empty (q: t 'a)
ensures { result <-> q == empty }
=
match q.front, q.rear with
| Nil, Nil -> true
| _ -> false
end
let length (q: t 'a) : Peano.t
ensures { result = Seq.length q.seq }
=
let rec length_aux (acc: Peano.t) (l: list 'a) : Peano.t
ensures { result = acc + Length.length l }
variant { l }
= match l with
| Nil -> acc
| Cons _ s -> length_aux (Peano.succ acc) s
end in
length_aux (length_aux Peano.zero q.front) q.rear
let transfer (q1: t 'a) (q2: t 'a) : unit
writes { q1, q2 }
ensures { q1 = empty }
ensures { q2 = (old q2) ++ (old q1) }
= match q2.rear with
| Nil ->
q2.front, q2.rear, q2.seq <- q1.front, q1.rear, q1.seq
| _ ->
q2.front <- Append.(++) q1.front (Append.(++) (reverse q1.rear) q2.front);
q2.seq <- q2.seq ++ q1.seq;
end;
clear q1
end