Why3: Verification of Imperative Programs
Recall the insertion sort imperative algorithm, written for instance as a C function:
void insertionSort(int A[], int N) {
    int i, j, key;    
    for (j=1 ; j<N ; j++) {
        key = A[j];
        i = j-1;        
        while (i>=0 && A[i] > key) {
            A[i+1] = A[i];
            i--;
        }
        A[i+1] = key;
    }
}
Why3 allows for the verification of such imperative algorithms, abstracting away from program-level details such as machine integer types or memory allocation. WhyML contains imperative features, including mutable (reference) variables and arrays.
The Why3 array library, available at http://why3.lri.fr/stdlib/array.html, contains in particular the modules  array.IntArraySorted  and  array.ArrayPermut  that not only define the relevant notions for specifying the notion of sorting, but also contain lemmas that will facilitate automated proofs.
It is worth recalling the dichotomy between two different approaches to deductive program verification:
- 
    
On one hand we find verifiers that target specific real-world programming languages. Examples include Frama-C/WP, Verifast (for C and Java programs), KeY (for Java), or SPARK;
 - 
    
On the other hand, tools like Why3 and the Microsoft Research tools Dafny / Boogie offer their own programming languages targeting verification at the algorithmic level. Why3 is not a general-purpose programming language: it is designed for verification at the algorithmic level, rather than the program level.
Verified real-life programs can also be obtained with the latter tools, either
- by automatic extraction
        
- Why3 offers automatic generation of both C and OCaml code from (verified) WhyML developments.
 
 - by encoding programs into the language of the verifier, together with a memory model
        
- Boogie is designed specifically to be used in this way as an intermediate verifier
 - The SPARK toolset uses Why3 in this way
 - Micro-C and Python frontends are also available for Why3
 
 
 - by automatic extraction
        
 
In order to verify an algorithm with Why3 we create a module that starts by importing the required library modules, and write the algorithm in WhyML, the Why3 programming language. WhyML belongs to the ML family of languages, which also includes SML and OCaml. https://en.wikipedia.org/wiki/ML_(programming_language)
module InsertionSort
  use int.Int
  use ref.Ref
  use array.Array
  use array.IntArraySorted
  use array.ArrayPermut
  let insertion_sort (a: array int) 
    ensures { sorted a }
    ensures { permut_all (old a) a }
  = 
    . . . 
end
We will write the complete algorithm following step by step the C version, but adding also annotations that are required to prove its correctness: loop invariants and variants.
  let insertion_sort (a: array int) 
    ensures { sorted a }
    ensures { permut_all (old a) a }
  =
    for j = 1 to length a - 1 do
      invariant { sorted_sub a 0 j }
      invariant { permut_all (old a) a }
      let key = a[j] in
      let i = ref (j-1) in
      
      while !i >= 0 && a[!i] > key do
          invariant { -1 <= !i <= j-1 }
          invariant { sorted_sub a 0 j }
          invariant { !i = j-1 \/ (a[j-1] <= a[j] /\ key < a[!i+2]) }
          invariant { permut_all (old a) a[!i+1 <- key] }
          variant { !i }
          a[!i+1] <- a[!i];
          i := !i - 1
      done;
      a[!i+1] <- key
    done
We make the following remarks regarding the programming language:
- 
    
As in many other languages (but not C), arrays contain length information:
length ais the length of arraya. Array accesses are valid within their length - 
    
forloops are bounded iterations (as in Python). As such, there is no need to provide variants to establish termination, or to include trivial invariants concerning the control variables (jin the above example) - 
    
A distinction is made between normal variables like
keyandjabove (immutable, as in pure FP languages), and references, which offer mutability.forloop control variables are not references, and cannot be assigned - 
    
The instruction
let i = ref (j-1) in ...creates the referenceiand initializes its contents with the current value of the expressionj-1. In order to access the contents of the reference the symbol!is used - 
    
Three (!) different assignment operators are used:
- 
        
=is a binding, rather than an assignment. It is used for immutable variables, and also to initialize references (note that the reference variable itself is immutable; like a C pointer, it is its contents that can be modified) - 
        
:=is the reference assignment instruction. Think ofi := eas the instruction!i = e, similar to*i = ein C. Thusi := !i - 1increments the value ofi. - 
        
←is the array assignment operator: the instructiona[!i+1] <- keystores the value ofkeyin position!i+1of the arraya. 
 - 
        
 
And regarding the specification and annotations:
- 
    
The predicate
sortedconcerns the entirety of the array;sorted_subexpresses that a segment of an array is sorted:sorted_sub a x ymeans that the range between indicesxandy-1is sorted. - 
    
The expression
old arefers to the arrayain its initial state;a[k <- e]refers to the arrayaupdated by setting the value of indexktoe. This latter notation is used in the above example to express a loop invariant regarding the permutation property: as it is, the current array is not a permutation of the initial array because it does not contain thekeyelement; the invariant mentions the array obtained by writing it back. 
Try it Yourself: Selection sort
This lab is designed to illustrate the use of contract-based verification in Why3. You will verify a version of the selection sort algorithm relying on three different functions. Similarly to Frama-C/WP, the verification of a called function does not consider the code of the callees; instead it relies entirely on their contracts.
The selection sort algorithm **sorts an array by successively placing in each position the “next minimum” element, as follows:
[40, 20, 10, 30, 60, 0, 80] [0, 20, 10, 30, 60, 40, 80] [0, 10, 20, 30, 60, 40, 80] [0, 10, 20, 30, 60, 40, 80] [0, 10, 20, 30, 60, 40, 80] [0, 10, 20, 30, 40, 60, 80] [0, 10, 20, 30, 40, 60, 80] [0, 10, 20, 30, 40, 60, 80]
The array is modified by exchanging pairs of elements, using the following swap function. Note the use of the exchange library predicate to express the swapping property (we could also write this “by hand”):
  let swap (a: array int) (i: int) (j: int) =
    requires { 0 <= i < length a /\ 0 <= j < length a }
    ensures { exchange (old a) a i j }
   let v = a[i] in
    a[i] <- a[j];
    a[j] <- v
Now complete the specification and invariant in the following minimum function and verify both functions swap and select.
  let select (a: array int) (i: int) : int 
    requires { 0 <= i < length a }
    ensures  { i <= result < length a }
    ensures  { . . . }
    = 
    let min = ref i in
      for j = i + 1 to length a - 1 do
        invariant { . . . }
        if a[j] < a[!min] then min := j
      done;
    !min
Finally, complete the definition of the selection_sort algorithm, using the above select  function, and verify it.
 let selection_sort (a: array int) 
    ensures { sorted a }
    ensures { permut_all (old a) a }
 =
 (. . .)
Further exercises:
- 
    
Investigate wheteher the two behaviors are independent from each other. Is it possible to prove only the sorting or permutation behavior in an independent way?
 - 
    
Write an alternative version without a helper
selectfunction. - 
    
Replace the
swapspec usingexchangeby your own version, including all information required for the verification of the sorting algorithm to still succeed.