Last Modified:

This example extends the lesson about recursion with doubly-linked list example, also using model fields. Along with the extra reference manipulation for the back pointers, note the use of the goodLinksForward method to establish the well-formedness of the list pointers.

// ## openjml --esc --progress --timeout=300 ListDLL.java
/** This file contains a worked (specified and verified) example of a doubly-linked list using JML and OpenJML. 
 * It is intended to illustrate techniques that can be used for other simple recursive data structures. 
 * This example uses model fields.
 * 
 * A list of values of type T is an instance of the public class ListDLL<T>. The actual links are instances of the non-public
 * nested class Node<T>; both classes inherit from Link<T>, which contains common functionality. The linked-list is formed
 * by the typical chain of 'next' and 'prev' field references, with a null reference at the end of the list. The starting point, the List
 * object, is not a part of the abstract list.
 * 
 */

/** Parent class for ListDLL and Node containing the 'next' field and common functionality */
class Link<T> {

    //@ public model JMLDataGroup ownerFields;
    //@ ghost helper nullable public ListDLL<T> owner; //@ in ownerFields; maps next.ownerFields \into ownerFields;

    //@ model public seq<T> values; // sequence of values after the current Link, not including the current Link
    //@ public represents values = next == null ? seq.<T>empty() : next.values.prepend(next.value);

    //@ model public seq<Link<T>> links;
    //@ public represents links = next == null ? seq.<Link<T>>empty() : next.links.prepend(next);

    //@ model public seq<Link<T>> prevlinks;
    //@ public represents prevlinks = next == null ? seq.<Link<T>>empty() : next.links.prepend(next.prev);

    // True if my owner is the argument and all nodes after me have the argument as their owners.
    //@ public normal_behavior
    //@   reads this.owner, this.next.ownerFields, this.links;
    //@   ensures \result == (this.owner == owner && (next != null ==> next.allMine(owner)));
    //@ model pure helper public boolean allMine(ListDLL<T> owner);

    //@ public normal_behavior
    //@   reads this.links, this.prevlinks;
    //@   ensures \result <==> (this.next != null ==> (this.next.prev == this && this.next.goodLinksForward()));
    //@ model pure helper public boolean goodLinksForward();
    
    //@ model public \bigint size; 
    //@ public represents size = ((next == null) ? 0 : 1 + next.size);
    
    //@ public invariant this.owner != null && allMine(this.owner);
    //@ public invariant this.goodLinksForward();
    //@ public invariant values.size() == size;
    //@ public invariant links.size() == size;
    //@ public invariant !links.contains(this);

    //@ nullable spec_public
    protected ListDLL.Node<T> next; //@ in size, values, links, prevlinks, ownerFields; 
    //@ maps next.values \into values; maps next.next \into values; maps next.next.values \into values;
    //@ maps next.size \into size; maps next.next \into size; maps next.next.size \into size;
    //@ maps next.links \into links; maps next.next.links \into links; maps next.next.links \into links;
    //@ maps next.prev \into prevlinks; maps next.next.prev \into prevlinks; maps next.prevlinks \into prevlinks;
    
    //@ nullable spec_public helper
    protected Link<T> prev;

    //@ protected normal_behavior
    //@ pure helper
    protected Link() {
    }

    /** Returns the nth value in the list */
    //@ public normal_behavior
    //@   requires 0 <= n < this.values.size;
    //@   reads values, links;
    //@   ensures \result == values[n];
    //@ pure
    public T value(int n) {
        if (n == 0) {
            return next.value();
        } else {
            return next.value(n-1);
        }
    }

    /** Removes the nth value from the list */
    //@ public normal_behavior
    //@   requires 0 <= n < this.size;
    //@   {| requires next.next != null; assignable size, values, links, ownerFields, next.size, next.next.prev; also requires next.next == null; assignable size, values, links, ownerFields, next.size; |}
    //@   ensures this.size == \old(this.size) - 1;
    //@   ensures this.values.size() == \old(this.values.size()) - 1;
    //@   ensures this.links.size() == \old(this.links.size()) - 1;
    //@   ensures n == 0 ==> values == \old(values).tail(1);
    //@   ensures n == 0 ==> links == \old(links).tail(1);
    //@   ensures n == 0 ==> next == \old(next.next);
    //@   ensures n == 0 ==> values == \old(values).tail(1);
    //@   ensures n > 0 ==> next == \old(next);
    public void remove(int n) {
        //@ ghost \bigint newsize = next.size;
        if (n == 0) {
            //@ assert this.allMine(this.owner);
            //@ assert this.next.allMine(this.owner);
            //@ assert this.next.next != null ==> this.next.next.allMine(this.next.owner);
            //@ assert this.goodLinksForward();
            //@ assert this.next.goodLinksForward();
            //@ ghost seq<T> newvalues = this.next.values;
            //@ ghost seq<Link<T>> newlinks = this.next.links;
            if (this.next.next != null) {
                //@ assert this.next.next.goodLinksForward();
                //@ assert this.next.next.prev == this.next;
                //@ assert this.next.next.prev.next == this.next.next;
                //@ assert this.next.next.values == newvalues.tail(1);
                //@ assert this.next.next.links == newlinks.tail(1);
                //@ assert this.next.next.size == newsize - 1;
                //@ assert this.next.next.allMine(this.next.owner);
                this.next.next.prev = this;
                //@ assert this.next.next.goodLinksForward();
                //@ assert this.next.next.size == newsize - 1;
                //@ assert this.next.next.allMine(this.next.owner);
                //@ assert this.next.next.values == newvalues.tail(1);
                //@ assert this.next.next.links == newlinks.tail(1);
                this.next = this.next.next;
                //@ assert this.next.prev == this && this.next.goodLinksForward();
                //@ assert this.goodLinksForward();
                //@ assert this.next.size == newsize - 1;
                //@ assert this.size == newsize;
                //@ assert this.next.allMine(this.next.owner);
                //@ assert this.next.values == newvalues.tail(1);
                //@ assert this.values == newvalues;
                //@ assert this.next.links == newlinks.tail(1);
                //@ assert this.links == newlinks;
                //@ reachable;
            } else {
                this.next = this.next.next;
                //@ assert this.goodLinksForward();
                //@ assert this.size == newsize;
                //@ assert this.allMine(this.owner);
                //@ assert this.values == newvalues;
                //@ assert this.links == newlinks;
                //@ reachable;
            }
            //@ assert this.next != null ==> this.next.goodLinksForward();
            //@ assert this.goodLinksForward();
            //@ assert this.size == newsize;
            //@ assert this.allMine(this.owner);
            //@ assert this.values == newvalues;
            //@ assert this.links == newlinks;
        } else {
            //@ assert this.next.goodLinksForward();
            //@ assert this.next.size == newsize;
            this.next.remove(n-1);
            //@ reachable; 
            //@ assert this.next.goodLinksForward();
            //@ assert this.next.allMine(this.next.owner);
            //@ assert this.next.size == newsize - 1;
            //@ assert this.next.values.size() == newsize - 1;
            //@ assert this.next.links.size() == newsize - 1;
            //@ assert this.goodLinksForward();
            //@ assert this.size == newsize;
            //@ assert this.values.size() == newsize;
            //@ assert this.links.size() == newsize;
            //@ assert allMine(this.owner);
        }
        //@ assert this.goodLinksForward();
        //@ assert allMine(this.owner);
        //@ assert this.size == newsize;
        //@ assert this.values.size() == newsize;
        //@ assert this.links.size() == newsize;
    }
}

public class ListDLL<T> extends Link<T> {
    
    //@ public invariant this.owner == this;
    //@ public invariant this.prev == null;
    
    /** Creates an empty list -- just an anchor node with null 'next' field */
    //@ public normal_behavior
    //@   ensures \result.values == seq.<TT>empty();
    //@   ensures \result.size == 0;
    //@   ensures \result.links == seq.<Link<TT>>empty();
    //@   ensures \result.next == null;
    //@   ensures \result.owner == \result;
    //@ pure
    public static <TT> ListDLL<TT> empty() {
        return new ListDLL<TT>();
    }
    
    //@ private normal_behavior
    //@   ensures this.next == null;
    //@   ensures this.prev == null;
    //@   ensures this.owner == this;
    //@ pure
    private ListDLL() {
        this.next = null;
        this.prev = null;
        //@ set this.owner = this;
    }
    
    /** Pushes a new value onto the front of the list */
    //@ public normal_behavior
    //@   assignable size, values, links, ownerFields;
    //@   ensures this.size == \old(size) + 1;
    //@   ensures this.values == \old(values).prepend(t);
    //@   ensures this.links == \old(links).prepend(this.next);
    //@   ensures \fresh(this.next);
    //@   ensures this.next.value == t;
    //@   ensures this.next.next == \old(this.next);
    public void push(T t) {
        //@ assert allMine(this);
        //@ assert this.next != null ==> this.next.allMine(this);
        //@ assert next != null ==> next.goodLinksForward();
        var v = new ListDLL.Node<T>(t, next, this);
        //@ assert next != null ==> next.goodLinksForward();
        //@ assert v.owner == this;
        //@ assert v.next != null ==> v.next.allMine(this);
        //@ assert v.allMine(this);
        if (next != null) next.prev = v;
        //@ assert next != null ==> next.goodLinksForward();
        //@ assert v.goodLinksForward();
        //@ assert v.next == this.next;
        //@ assert (v.owner == this && (v.next != null ==> v.next.allMine(v.owner)));
        //@ assert v.allMine(this);
        this.next = v;
        //@ assert v.goodLinksForward();
        //@ assert this.goodLinksForward();
        //@ assert this.next.allMine(this); // a lemma regarding allMine
        //@ assert this.size == v.size + 1;
        //@ assert this.values == v.values.prepend(t);
        //@ assert this.links == v.links.prepend(v);
    }
    
    /** Removes the first value from the list */
    //@ public normal_behavior
    //@   requires this.size > 0;
    //@   requires this.values.size() > 0;
    //@   {| requires next.next == null; assignable ownerFields, size, values, links, prevlinks; also requires next.next != null; assignable ownerFields, size, values, links, prevlinks, next.next.prev; |}
    //@   ensures this.size == \old(size) - 1;
    //@   ensures this.values == \old(values).tail(1);
    //@   ensures this.links == \old(links).tail(1);
    //@   ensures next == \old(next.next);
    public void pop() {
        //@ assert this.owner == this && this.allMine(this.owner);
        //@ assert this.next.owner == this && this.next.allMine(this.owner);
        //@ assert this.next.next != null ==> this.next.next.allMine(this.next.owner);
        //@ assert this.goodLinksForward();
        //@ assert this.next.goodLinksForward();
        //@ assert this.next.next != null ==> this.next.next.goodLinksForward();
        //@ ghost var n = this.next;
        //@ assert (n.next != null) ==> n.next.prev == n;
        //@ assert (n.next != null) ==> n.next.prev.next == n.next;
        //@ ghost \bigint newsize = next.size;
        //@ assert this.next.next != null ==> next.next.size == newsize - 1;
        //@ ghost seq<T> newvalues = this.next.values;
        //@ assert this.next.next != null ==> this.next.next.values == newvalues.tail(1);
        //@ ghost seq<Link<T>> newlinks = this.next.links;
        //@ assert this.next.next != null ==> this.next.next.links == newlinks.tail(1);
        //@ reachable;
        if (this.next.next != null) {
            //@ assert this.next.next.allMine(this.next.owner);
            //@ assert this.next.next.goodLinksForward();
            //@ assert this.next.next.next != null ==> this.next.next.next.goodLinksForward();
            this.next.next.prev = this;
            //@ assert this.next.next.next != null ==> this.next.next.next.goodLinksForward();
            //@ assert this.next.next.goodLinksForward();
            //@ assert this.next.next.size == newsize - 1;
            //@ assert this.next.next.allMine(this.next.owner);
            //@ assert this.next.next.values == newvalues.tail(1);
            //@ assert this.next.next.links == newlinks.tail(1);
            this.next = this.next.next;
            //@ assert this.next.prev == this && this.next.goodLinksForward();
            //@ assert this.goodLinksForward();
            //@ assert this.next.size == newsize - 1;
            //@ assert this.size == newsize;
            //@ assert this.next.allMine(this.next.owner);
            //@ assert this.next.values == newvalues.tail(1);
            //@ assert this.values == newvalues;
            //@ assert this.next.links == newlinks.tail(1);
            //@ assert this.links == newlinks;
            //@ reachable;
        } else {
            this.next = this.next.next;
            //@ assert this.goodLinksForward();
            //@ assert this.size == newsize;
            //@ assert this.allMine(this.owner);
            //@ assert this.values == newvalues;
            //@ assert this.links == newlinks;
            //@ reachable;
        }
        //@ assert this.next != null ==> this.next.goodLinksForward();
        //@ assert this.goodLinksForward();
        //@ assert this.size == newsize;
        //@ assert this.allMine(this.owner);
        //@ assert this.values == newvalues;
        //@ assert this.links == newlinks;
    }
    
    static class Node<T> extends Link<T> {

        /** Constructs a new link with given value and next field; for internal use only */
        //@ private normal_behavior
        //@   requires next != prev;
        //@   ensures this.next == next;
        //@   ensures this.prev == prev;
        //@   ensures this.value == value;
        //@   ensures this.owner == prev.owner;
        //@ pure helper
        private Node(T value, /*@ helper nullable */ Node<T> next, /*@ helper */ Link<T> prev) {
            this.value = value;
            this.next = next;
            this.prev = prev;
            //@ set this.owner = prev.owner;
        }
        
        //@ spec_public
        private T value; //@ in values;
        
        /** Returns the value from this link */
        //@ public normal_behavior
        //@   reads value;
        //@   ensures \result == value;
        //@ pure helper
        public T value() {
            return value;
        }
    }
}

class Test {
    
    /** properties of an empty list */
    public static <Y> void testEmpty(Y y) {
        var in = ListDLL.<Y>empty();
        //@ assert in.size == 0;
        //@ assert in.values.size() == 0;
        //@ assert in.values == seq.<Y>empty();
    }

    /** pushing a value and then retrieving it */
    //@ requires in.next.next != null;
    public static <Y> void testPushValue(ListDLL<Y> in, Y y, Y yy) {
        in.push(y);
        //@ reachable;
        //@ assert in.value(0) == y;
        //@ assert in.size == \old(in.size) + 1;
        //@ assert in.values != \old(in.values);
        in.push(yy);
        //@ reachable;
        //@ assert in.value(1) == y;
        //@ assert in.value(0) == yy;
    }
    
    /** popping a list */
    //@ requires in.size >= 2;
    public static <Y> void testPopValue(ListDLL<Y> in) {
        Y y = in.value(1);
        in.pop();
        //@ reachable;
        Y yy = in.value(0);
        //@ assert y == yy;
    }
    
    /** pushing and popping leaves the list unchanged */
    //@ requires in.next.next != null;
    public static <Y> void testPushPop(ListDLL<Y> in, Y y) {
        in.push(y);
        //@ reachable;
        //@ assert in.size == \old(in.size) + 1;
        //@ assert in.values != \old(in.values);
        in.pop();
        //@ reachable;
        //@ assert in.size == \old(in.size);
        //@ assert in.values == \old(in.values);
    }
    
    /** pushing and removing the zeroth element leaves the list unchanged */
    //@ requires in.next.next != null;
    public static <Y> void testPushRemove(ListDLL<Y> in, Y y) {
        in.push(y);
        //@ reachable;
        //@ assert in.size == \old(in.size) + 1;
        //@ assert in.values != \old(in.values);
        in.remove(0);
        //@ reachable;
        //@ assert in.size == \old(in.size);
        //@ assert in.values == \old(in.values);
    }
    
    /** pushing a value on one list does not change the other */
    //@ requires in != other;
    //@ requires in.values == other.values;
    //@ requires in.size > 0;
    public static <Y> void testNI1(Y y, ListDLL<Y> in, ListDLL<Y> other) {
        in.push(y);
        //@ reachable;
        //@ assert in.values.tail(1) == other.values;
    }
    
    /** popping a value from one list does not change the other */
    //@ requires in != other;
    //@ requires in.values == other.values;
    //@ requires in.size > 0;
    public static <Y> void testNI2(ListDLL<Y> in, ListDLL<Y> other) {
        //@ assert in.size == other.size;
        //@ ghost seq<Y> oldvalues = in.values;
        in.pop();
        //@ reachable;
        //@ assert in.values == oldvalues.tail(1);
        //@ assert other.values == oldvalues;
    }
    
    /** removing a value (other than the 0th) from one list might change the other,
     * except when the owner invariant is used */
    //@ requires in != other;
    //@ requires in.values == other.values;
    //@ requires in.size > 1;
    public static <Y> void testNI3(ListDLL<Y> in, ListDLL<Y> other) {
        //@ ghost seq<Y> oldvalues = in.values;
        in.remove(1);
        //@ reachable;
        //@ assert oldvalues.size - 1 == in.values.size;
        //@ assert other.values.size - 1 == in.values.size;   // Should not be provable without the owner invariant
    }
    
    /** two different lists may have the same first element, except when the owner invariant is used */
    //@ requires in != other;
    //@ requires in.size > 0;
    //@ requires other.size > 0;
    public static <Y> void testNI4(ListDLL<Y> in, ListDLL<Y> other) {
        //@ assert in.next.owner == in;
        //@ assert other.next.owner == other;
        //@ assert in.next != other.next;    // Should not be provable without the owner invariant
    }
    
    /** using rep-exposure to change a value still does not change the other list if the owner invariant is used */
    //@ requires in != other;
    //@ requires in.values == other.values;
    //@ requires in.size > 1;
    //@ requires in.next.value != y;
    /*@ model public static <Y> void testNI5(ListDLL<Y> in, ListDLL<Y> other, Y y) {
        reachable;
        ghost \bigint n = in.size;
        assert in.values.size() == n;
        assert in.values.size() == other.values.size();
        assert in.size == other.size;
        assert in.size == n;
        assert in.next.values == other.next.values;
        assert in.values[0] == other.values[0];
        in.next.value = y;
        assert in.next.values == other.next.values;
        assert in.values[0] != other.values[0];
        assert in.size == n;
        assert in.size == other.size;
        reachable;
        assert in.values != other.values;    // Should not be provable without the owner invariant
        reachable;
        assert in.values.tail(1) == other.values.tail(1);
        reachable;
    }
    @*/
}