Capucine Example: Sparse ArraysOur solution for one of the VACID challenges. |
This example is an implementation of sparse arrays, which are one of the VACID challenges. It features interesting invariants and a main program which handles two arrays, which Capucine is able to separate easily. To compile it, go into the capucine directory and type:
ocamlbuild bench/sparse.gwhy
(* VACID Challenge: Sparse Arrays *)
(* version RTE: we want to check for Run Time Errors, that is the absence
of out-of-bounds array acces *)
(* Logic (Infinite) Arrays *)
logic type array (alpha)
logic function store (array (alpha), int, alpha): array (alpha)
logic function select (array (alpha), int): alpha
axiom select_eq:
forall a: array (alpha).
forall i: int.
forall v: alpha.
[select(store(a, i, v), i)] = [v]
axiom select_neq:
forall a: array (alpha).
forall i: int.
forall j: int.
forall v: alpha.
[i] <> [j] ==>
[select(store(a, i, v), j)] = [select(a, j)]
(* Array Reference *)
selector (length, cell)
class Array (alpha) =
(int * array (alpha))
invariant(a) = [0] <= [a.length]
end
val array_get(a: Array (alpha) [R], i:int) : alpha
requires [0] <= [i] and [i] < [!a.length]
ensures [result] = [select(!a.cell,i)] =
select(!a.cell,i)
val array_set(a: Array (alpha) [R], i:int, v:a) : unit
consumes R^c
produces R^c
requires [0] <= [i] and [i] < [!a.length]
ensures [!a.length] = [old(!a.length)] and
[!a.cell] = [store(old(!a.cell),i,v)]
=
(a := (!a.length, store(!a.cell,i,v)))
val array_create(size:int): Array (alpha) [R]
consumes R^e
produces R^c
requires [0] <= [size]
ensures [!result.length] = [size] =
let tmp = new Array (alpha) in
tmp := (size, !tmp.cell);
tmp
(* Sparse Arrays *)
predicate interval(a:int, x:int, b:int) = [a] <= [x] and [x] < [b]
selector (value, idx, back, n, default, size)
(* value: actual values at their actual indexes
idx: corresponding indexes in back
back: corresponding indexes in idx, makes sense between 0 and n-1
n: see back
size: allocated size
*)
class Sparse (alpha) =
own Rval, Ridx, Rback;
(Array (alpha) [Rval] * Array (int) [Ridx] * Array (int) [Rback] *
int * alpha * int)
invariant (x) =
[0] <= [x.n] and [x.n] <= [x.size] and
[!(x.value).length] = [x.size] and
[!(x.idx).length] = [x.size] and
[!(x.back).length] = [x.size] and
forall i: int. interval([0],[i],[x.n]) ==>
interval([0],[select (!(x.back).cell, i)],[x.size]) and
[select (!(x.idx).cell, select (!(x.back).cell, i))] = [i]
end
(* is_elt(a,i) tells whether index i points to a existing element. It
can be checked as follows:
(1) check that array idx maps i to a index j between 0 and n (excluded)
(2) check that back(j) is i
*)
predicate is_elt(a: Sparse (alpha) [R], i: int) =
[0] <= [select (!(!a.idx).cell, i)]
and [select (!(!a.idx).cell, i)] < [!a.n]
and [select (!(!a.back).cell, select (!(!a.idx).cell, i))] = [i]
logic function model (Sparse (alpha) [R], int): alpha
axiom model_in:
forall a: Sparse (alpha) [R].
forall i: int.
is_elt([a], [i]) ==> [model(a, i)] = [select(!(!a.value).cell, i)]
axiom model_out:
forall a: Sparse (alpha) [R].
forall i: int.
not is_elt([a], [i]) ==> [model(a, i)] = [!a.default]
val create(sz:int, def: alpha): Sparse (alpha) [R]
consumes R^e
produces R^c
requires
[0] <= [sz]
ensures
[!result.size] = [sz] and
forall i: int. [model (result, i)] = [def]
=
let arr = new Sparse (alpha) in
arr := (array_create (sz), array_create (sz),
array_create (sz), 0, def, sz);
arr
val get(a: Sparse (alpha) [R], i: int): alpha
consumes R^c
produces R^c
requires [0] <= [i] and [i] < [!a.size]
ensures [result] = [model(a, i)] =
let index = array_get(!a.idx, i) in
if 0 <= index and index < !a.n and array_get(!a.back, index) = i
then
array_get(!a.value, i)
else
!a.default
(* array maps [0;n[ into [0;k[ *)
predicate into(n:int, k:int, t:array(int)) =
forall i:int. interval(0,i,n) ==> interval(0,[select(t,i)],k)
predicate injective(n:int, t:array(int)) =
(forall i:int. forall j:int. interval(0,i,n) ==> interval(0,j,n) ==>
[select(t,i)] = [select(t,j)] ==> i = j)
predicate surjective(n:int, t:array(int)) =
forall k:int. interval(0,k,n) ==>
exists j:int. interval(0,j,n) and [select(t,j)] = k
lemma array_injective_is_surjective:
forall n:int. forall a:array(int).
into(n,n,a) and injective(n,a) ==> surjective(n,a)
val set(a: Sparse (alpha) [R], i: int, v: alpha): unit
consumes R^c
produces R^c
requires [0] <= [i] and [i] < [!a.size]
ensures
[!a.size] = [old(!a.size)] and
(forall j: int. (* [0] <= [j] and [j] < [!a.size] and *)
[j] <> [i] ==> [model(a, j)] = [old(model(a, j))])
and ([model(a, i)] = [v])
=
array_set((focus !a.value), i, v);
let index = array_get(!a.idx, i) in
if not (0 <= index and index < !a.n and array_get(!a.back, index) = i)
then (
(* array back maps [0;n[ into [0;size[ *)
assert into([!a.n],[!a.size],[!(!a.back).cell]);
(* array back is injective *)
assert injective([!a.n],[!(!a.back).cell]);
(* if n=size then array back is also surjective *)
assert [!a.n] = [!a.size] ==> surjective([!a.n],[!(!a.back).cell]);
assert [!a.n] = [!a.size] ==>
interval(0,i,[!a.n]) ==>
exists j:int.
interval(0,j,[!a.n]) and [select(!(!a.back).cell,j)] = i;
(* the hard assertion! *)
assert [!a.n] < [!a.size];
array_set((focus !a.idx), i, !a.n);
array_set((focus !a.back), !a.n, i);
a := (!a.value, !a.idx, !a.back, !a.n + 1, !a.default, !a.size)
)
val main(): unit =
region Ra: Sparse (int) in
region Rb: Sparse (int) in
let default = 0 in
let a = (create(10,default): Sparse (int) [Ra]) in
let b = (create(20,default): Sparse (int) [Rb]) in
let x = get(a, 5) in
let y = get(b, 7) in
assert ([x] = [default] and [y] = [default]);
set(a, 5, 1);
set(b, 7, 2);
let x = get(a, 5) in
let y = get(b, 7) in
assert ([x] = [1] and [y] = [2]);
let x = get(a, 7) in
let y = get(b, 5) in
assert ([x] = [default] and [y] = [default]);
let x = get(a, 0) in
let y = get(b, 0) in
assert ([x] = [default] and [y] = [default]);
let x = get(a, 9) in
let y = get(b, 9) in
assert ([x] = [default] and [y] = [default])
(*
;
assert false (* poorman's check for inconsistency *)
*)