JML Tutorial - Frame Conditions
The previous lesson described the verification process when there are multiple methods that call each other. But that lesson left out an important consideration: how to specify side-effects of methods.
Consider this example:
// openjml --esc T_frame1.java
public class T_frame1 {
public int counter1;
public int counter2;
//@ requires counter1 < Integer.MAX_VALUE;
//@ ensures counter1 == 1 + \old(counter1);
public void increment1() {
counter1 += 1;
}
//@ requires counter2 < Integer.MAX_VALUE;
//@ ensures counter2 == 1 + \old(counter2);
public void increment2() {
counter2 += 1;
}
public void test() {
//@ assume counter1 == 0 && counter2 == 0;
increment1();
//@ assert counter1 == 1;
//@ assert counter2 == 0;
}
}
which produces
T_frame1.java:23: verify: The prover cannot establish an assertion (Assert) in method test
//@ assert counter2 == 0;
^
1 verification failure
Note first a new bit of syntax: the \old
designator. The increment
methods make a change in state: the value of counter1
or counter2
is different after
the method than before and we need a way to refer to their values before and after. The \old
syntax means to determine the value of the enclosed expression
in the pre-state, that is, the state at the beginning of the method’s execution.
counter1
without the \old
designator means the value of counter1
in the
post-state, the state after the method has completed.
Also, why the comparison to Integer.MAX_VALUE
? That is to avoid warnings about arithmetic overflow. We’ll get to that topic later.
Now to the point of this lesson. The two increment methods verify, but
what is happening in the test method?
First we assume some values for counter1
and counter2
. This is just to give
a concrete starting point.
After calling increment1
, the value of counter1
has increased by 1;
the postcondition of increment1
says just that and the first assert
statement is readily proved.
But the second assert statement is not verified. Why not? increment1()
does not change
counter2
. The problem is that the specification of increment1()
does not say
that counter2
is unchanged. One solution would be to add an additional
ensures clause that states that counter2 == \old(counter2)
. This specification
verifies as correct.
But this is not a practical solution. We can’t add to increment1()
’s specification a clause stating that every visible variable is unchanged.
Instead we use a frame condition whose purpose is to state what memory
locations a method might have modified. There are a variety of names for
the frame clause: traditionally it is assignable
, but assigns
is also permitted.
Note that modifies
is also an
(implemented) synonym, but in some tools it has a slightly different meaning,
so its use is not recommended.
The frame condition states which memory locations might be changed by the method at hand. Anything not mentioned is assumed to be unchanged. In fact, a method is not allowed to assign to a memory location (even with the same value) unless it is listed in the frame condition — this makes the check for violations of the frame condition, whether by tool or by eye, independent of the values computed.
So now our example looks like this:
// openjml --esc T_frame3.java
public class T_frame3 {
public int counter1;
public int counter2;
//@ requires counter1 < Integer.MAX_VALUE;
//@ assigns counter1;
//@ ensures counter1 == 1 + \old(counter1);
public void increment1() {
counter1 += 1;
}
//@ requires counter2 < Integer.MAX_VALUE;
//@ assigns counter2;
//@ ensures counter2 == 1 + \old(counter2);
public void increment2() {
counter2 += 1;
}
public void test() {
//@ assume counter1 == 0 && counter2 == 0;
increment1();
//@ assert counter1 == 1;
//@ assert counter2 == 0;
increment2();
//@ assert counter1 == 1;
//@ assert counter2 == 1;
}
}
which successfully verifies.
A few more details about the memory locations in a frame condition:
- One does not need to list variables that are local to the body of a method; those are not visible in the specification and they are not part of the program state outside of the method.
- The formal arguments of the method are in scope for the frame condition,
just like for the
requires
andensures
clauses. The formal arguments
themselves cannot be changed by a method, but if they are references to objects, their fields might be written to by a method. So a methodm(MyType q)
might have a frame conditionassigns q.f;
iff
is a field ofMyType
that is written to in the body ofm
. - If a method has no external effects other than its return value, you can specify a frame condition
assigns \nothing;
q.*
for an expressionq
means all fields of qa[i]
for expressiona
andi
means that particular array elementa[*]
for array expressiona
means all elements of that arraya[i..j]
for expressionsa
,i
, andj
means the stated range of array elements, fromi
toj
inclusive.
If there is no frame condition at all, then a default is used, namely assigns \everything;
— which means exactly that: after a call of this method, any memory location in the state might have been written to and might be changed. It is very difficult to prove anything about a program that includes a call to a method with such a frame condition. Thus
you must include a frame condition for any method that is called within a program.
A shorthand way to say that a method assigns \nothing;
is to designate it pure
, as in
//@ requires ...
//@ ensures ...
//@ pure
public void m() { ... }
though there are a few other details to purity — see the lesson on pure.
There are two other points to know about frame conditions. First, where a frame condition clause includes expressions, such as the indices of array expressions, those expressions are evaluated in the pre-state, not the post-state.
Second, a frame condition is a method specification clause like requires
and ensures
. A method specification may contain more than one such clause.
However, note that each cluase is considered individually. That is, each clause
by itself lists the memory locations that may be written to by the method.
As each frame condition clause must be valid on its own, the effect of multiple clauses is the same as one clause with the intersection of the sets of locations given by the separate clauses.
For example,
assigns i,j;
assigns i,k;
is the same as
assigns i;
and
assigns i;
assigns j;
is the same as
assigns \nothing;
Admittedly, it would be much more convenient and perhaps intuitive if the result of mutiple assigns clauses was the union of their contents, but that is not the case, for historical reasons. The advice is then to have only one frame condition clause per specification (case), even if that means the clause has a long list. (All the method specifications in the tutorial lessons so far have just one specification case; an advanced lesson presents multiple specification cases.)