Dr. David J. Pearce

Verifying Bubble Sort in Whiley


Bubble sort is a classic sorting algorithm with lots of well-known issues. It’s been a long time since I thought much about this algorithm. But, it turns out to be an interesting verification example for Whiley, as it has some interesting loop invariants. The algorithm is pretty simple: it loops through an array swapping adjacent items which are incorrectly ordered, and repeats until nothing changes. For example, in one iteration the following swaps would occur:

We can see that, after this iteration, the resulting array is not yet in sorted order and, thus, we must repeat the process.

Specification

Right, we want to try and verify the bubble sort algorithm.  But, what does this mean? Well, let’s think about it in terms of types.  We want our sort() function to take a parameter of array type and return a result of sorted array type.  In Whiley we have a primitive integer array type already (i.e. int[]), but we need to define a sorted integer array type.  We do this as follows:

type sorted_ints is (int[] arr)
where all { i in 1..|arr| | arr[i-1] <= arr[i] }

This roughly says that every element in the array must be larger than the one below it (if it exists). Using this, we can give the signature of our sort() function as follows:

function sort(int[] items) -> (sorted_ints result)

Whilst this is a pretty good start, it is definitely not a complete specification. For example, we haven’t said that the resulting array must have the same size as the original. Or, for that matter, that it should contain the same elements as the original (though perhaps in a different order). Still, for now, that will do.

Implementation

Right, the next job is to actually implement the algorithm. My starting point is this:

function sort(int[] items) -> (sorted_ints result):
    //
    bool clean
    //
    do:
        // reset clean flag
        clean = true
        int i = 1
        // look for unsorted pairs
        while i < |items|: 
            if items[i-1] > items[i]:
               // found unsorted pair
               clean = false
               int tmp = items[i-1]
               items[i-1] = items[i]
               items[i] = tmp
            i = i + 1
    while !clean
    // Done
    return items

This implementation is pretty straightforward using a do-while loop to ensure we go through at least one iteration, and then continue until we no longer find unsorted pairs. You can punch this into whileylabs.com and you should find it compiles (though it won’t verify).

Verification

At this point, we’ve specified and implemented our sort() function. Now, we want to verify that the implementation meets its specification. That is, get a compile-time guarantee that the resulting array is sorted. You can think of this is as being similar to type checking our program, though it’s a bit more involved. If we try and compile the program on whileylabs.com with verification enabled, we’ll see the following:

This is telling us that the verifier believes the expression i-1 could be negative at this point, thus potentially leading to an index-out-of-bounds error at runtime. As programmers we can deduce that, in fact, since i starts at 1 and is always incremented this can never happen (you also need to know that integers in Whiley are unbounded and don’t wrap around). But, the verifier needs help here and, to give it the necessary clue, we need to provide a loop invariant:

        ...
        while i < |items| where i >= 1: 
            ...

The loop invariant is necessary because the verifier cannot reason about variables modified within a loop. For such variables, the only information available to the verifier is that given in the loop condition or the loop invariant or the variable’s type invariant.

Having resolved the verification problem around items[i-1], the verifier now reports the error “type invariant not satisfied”:

Again, this error is being reported because the verifier cannot reason precisely about loops. We need to provide another loop invariant, but what should it be? By the time control reaches the return statement, we need the array to be sorted. But, clearly, at an arbitrary point during the loop nest this is not necessarily true. What we can say is that, for a given index i, we know all elements below it are correctly sorted when the clean flag holds. We can update the code as follows:

property sorted(int[] arr, int n)
where all { i in 1..n | arr[i-1] <= arr[i] }

...

function sort(int[] items) -> (sorted_ints result):
    ...
        while i < |items| 
        where i >= 1
        where clean ==> sorted(items,i): 
            ...
    while !clean
    clean ==> sorted(items,|items|)
    // Done
    return items

Here, we’ve added a property sorted(int[] arr, int n) which describes the concept of an array arr being in sorted order up to (but not including) a bound n. Thus, when the entire array is sorted we have n == |arr|. In addition, we’ve now split the loop invariant for the inner loop across two lines using repeated where clauses, and these are just conjuncted together by the verifier.

At this point, the sort() implementation now passes verification after a short while. The complete implementation looks like this:

// define the type of natural numbers
type nat is (int n) where n >= 0

// define concept of partially sorted array
property sorted(int[] arr, int n)
where all { i in 1..n | arr[i-1] <= arr[i] }

// define type of sorted array
type sorted_ints is (int[] arr) where sorted(arr,|arr|)

function sort(int[] items) -> (sorted_ints result):
    //
    bool clean
    //
    do:
        // reset clean flag
        clean = true
        int i = 1
        // look for unsorted pairs
        while i < |items|
        where i >= 1
        where clean ==> sorted(items,i): 
            if items[i-1] > items[i]:
               // found unsorted pair
               clean = false
               int tmp = items[i-1]
               items[i-1] = items[i]
               items[i] = tmp
            i = i + 1
    while !clean
    where clean ==> sorted(items,|items|)
    // Done
    return items

Conclusion

We’ve seen here how to verify an implementation of bubble sort in Whiley. Whilst there are some subtle details, the process is not really that painfull. Certainly, making good use of the various features provided in Whiley (e.g. type invariants, properties, etc) makes things easier. Also, we need to be careful in interpreting what “verified” means here. It does not mean, for example, that the program cannot run out of memory! Likewise, the Whiley verifier does not check termination of loops and, hence, a verified program may still go into an infinite loop (though, by manual inspection, we can see our implementation does not). Finally, our specification is not as complete as it could be since, for example, it does even not require that the resulting array is the same size as the original. But, despite these limitations, there is still a lot of value to be gained from verifying our code.