[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

8. Type Specifications

This chapter describes the way JML can be used to specify abstract data types (ADTs).

Overall the mechanisms used in JML to specify ADTs can be described as follows. First, the interface of a type is described using the Java syntax for such a type's declaration (see section 7. Class and Interface Member Declarations); this includes any required fields and methods, along with their types and visibilities, etc. Second, the behavior of a type is described by declaring model and ghost fields to be the client (or subtype) visible abstractions of the concrete state of the objects of that type, by writing method specifications using those fields, and by writing various jml-declarations to further refine the logical model defined by these fields. These jml-declarations can also be used to record various design and implementation decisions.

The syntax of these jml-declarations is as follows.

 
jml-declaration ::= modifiers invariant
        | modifiers history-constraint
        | modifiers represents-clause
        | modifiers initially-clause 
        | modifiers monitors-for-clause
        | modifiers readable-if-clause
        | modifiers writable-if-clause
        | axiom-clause

The semantics of each of kind of jml-declaration is discussed in the sections below. However, before getting to the details, we start with some introductory examples.

8.1 Introductory ADT Specification Examples  
8.2 Invariants  
8.3 Constraints  
8.4 Represents Clauses  
8.5 Initially Clauses  
8.6 Axioms  
8.7 Readable If Clauses  
8.8 Writable If Clauses  
8.9 Monitors For Clause  


8.1 Introductory ADT Specification Examples

[[[Need examples here, which should be first written into the org.jmlspecs.samples.jmlrefman package and then included and discussed here.]]]


8.2 Invariants

The syntax of an invariant declaration is as follows.

 
invariant ::= invariant-keyword predicate ;
invariant-keyword ::= invariant | invariant_redundantly

An example of an invariant is given below. The invariant in the example has default (package) visibility, and says that in every state that is a visible state for an object of type Invariant, the object's field b is not null and the array it refers to has exactly 6 elements. In this example, no postcondition is necessary for the constructor since the invariant is an implicit postcondition for it.

 
package org.jmlspecs.samples.jmlrefman;
 
public abstract class Invariant {

    boolean[] b; 

    //@ invariant b != null && b.length == 6;

    //@ assignable b;
    Invariant() {
        b = new boolean[6];
    }
}

Invariants are properties that have to hold in all visible states. The notion of visible state is of crucial importance in the explanation of the semantics of both invariants and constraints. A state is a visible state for an object o if it is the state that occurs at one of these moments in a program's execution:

Note that visible states for an object o do not include states at the beginning and end of invocations of helpers, which are constructors or methods declared with the helper modifier (see section 9.6.4 Semantics of helper methods and constructors). Thus the post-state of a helper constructor and the pre- and post-states of helper methods are not visible states.

A state is a visible state for a type T if it occurs after static initialization for T is complete and it is a visible state for some object that has type T. Note that objects of subtypes of type T also have type T.

JML distinguishes static and instance invariants. These are mutually exclusive and any invariant is either a static or instance invariant. An invariant may be explicitly declared to be static or instance by using one of the modifiers static or instance in the declaration of the invariant. An invariant declared in a class declaration is, by default, an instance invariant. An invariant declared in an interface declaration is, by default, a static invariant.

For example, the invariant declared in the class Invariant above is an instance invariant, because it occurs inside a class declaration. If Invariant had been an interface instead of a class, then this invariant would have been a static invariant.

A static invariant may only refer to static fields of an object. An instance invariant, on the other hand, may refer to both static and non-static fields.

The distinction between static and instance invariants also affects when the invariants are supposed to hold. A static invariant declared in a type T must hold in every state that is a visible state for type T. An instance invariant declared in a type T must hold for every object o of type T, for every state that is a visible state for o.

For reasoning about invariants we make a distinction between assuming, establishing, and preserving an invariant. A method (or constructor) assumes an invariant if the invariant must hold in its pre-state. A method or constructor establishes an invariant if the invariant must hold in its post-state. A method or constructor preserves an invariant if the invariant is both assumed and established.

JML's verification logic enforces invariants by making sure that each non-helper method, constructor, or finalizer:

This means that each non-helper constructor found in a class C preserves the static invariants of all types, including C, that have finished their static initialization, establishes the instance invariant of the object under construction, and, modulo creation and deletion of objects, preserves the instance invariants of all other objects. (Objects that are created by a constructor must have their instance invariant established; and objects that are deleted by the action of the constructor can be assumed to satisfy their instance invariant in the constructor's pre-state.) Note in particular that, at the beginning of a constructor invocation, the instance invariant of the object being initialized does not have to hold yet.

Furthermore, each non-helper non-static method found in a type T preserves the static invariants of all types that have finished their static initialization, including T, and, modulo creation and deletion of objects, preserves the instance invariants of all objects, in particular the receiver object. However, finalizers do only assume the instance invariant of the receiver object, and do not have to establish it on exit.

The semantics given above is highly non-modular, but is in general necessary for the enforcement of invariance when no mechanisms are available to prevent aliasing problems, or when constructs like (concrete) public fields are used [Poetzsch-Heffter97]. Of course, one would like to enforce invariants in a more modular way. By a modular enforcement of invariants, we mean that one could verify each type independently of the types that it does not use, and that a well-formed program put together from such verified types would still satisfy the semantics for invariants given above. That is, each type would be responsible for the enforcement of the invariants it declares and would be able to assume, without checking, the invariants of other types it uses.

To accomplish this ideal, it seems that some mechanism for object ownership and alias control [Noble-Vitek-Potter98] [Mueller-Poetzsch-Heffter00] [Mueller-Poetzsch-Heffter00a] [Mueller-Poetzsch-Heffter01a] [Mueller02] [Mueller-Poetzsch-Heffter-Leavens03] is necessary. However, this mechanism is still not a part of JML, although some design work in this direction has taken place [Mueller-Poetzsch-Heffter-Leavens06].

On the other hand, people generally assume that there are no object ownership alias problems; this is perhaps a reasonable strategy for some tools, like run-time assertion checkers, to take. The alternative, tracking which types and objects are in visible states, and checking every applicable invariant for every type and object in a visible state, is obviously impractical.

Therefore, assuming or ignoring the problems with object ownership and alias control, one obtains a simple and more modular way to check invariants. This is as follows.

When doing such proofs, one may assume the static invariant of any type (that is finished with its static initialization), and one may also assume the instance invariant of any other object.

In this, more modular, style of checking invariants, one can think of all the static invariants in a class as being implicitly conjoined to the pre- and postconditions of all non-helper constructors and methods, and the instance invariants in a class as being implicitly conjoined to the postcondition of all non-helper constructors, and to the pre- and postconditions of all non-helper methods.

As noted above, helper methods and constructors are exempt from the normal rules for checking invariants. That is because the beginning and end of invocations of these helper methods and constructors are not visible states, and therefore they do not have to preserve or establish invariants. Note that only private methods and constructors can be declared as helper. See section 7.1.1.4 Helper Methods and Constructors.

The following subsections discuss other points about the semantics of invariants:

It should be noted that the last two points above are not specific to Java or JML, but these are tricky issues that have to be considered for any notion of invariant in an object-oriented languages. Indeed, these two issues make the familiar notion of invariant a lot more complicated than one might guess at first sight!

8.2.1 Static vs. instance invariants  
8.2.2 Invariants and Exceptions  
8.2.3 Access Modifiers for Invariants  
8.2.4 Invariants and Inheritance  


8.2.1 Static vs. instance invariants

As discussed above (see section 8.2 Invariants), invariants can be declared static or instance. Just like a static method, a static invariant cannot refer to the current object this and thus cannot refer to instance fields of this or non-static methods of the type.

Instance invariants must be established by the constructors of an object, and must be preserved by all non-helper instance methods. If an object has fields that can be changed without calling methods (usually a bad idea), then any such changes must also preserve the invariants. For example, if an object has a public field, each assignment to that field must establish all invariants that might be affected.

Static methods do not have a receiver object for which they need to assume or establish an instance invariant, since they have no receiver object. However, a static method may assume instance invariants of other objects, such as argument objects passed to the method.(7)

Static invariants must be established by the static initialization of a class, and must be preserved by all non-helper constructors and methods, i.e., by both static and instance methods.

The table below summarizes this:
 
          | static          non-helper     non-helper    non-helper
          | initialization  static method  constructor   instance method
--------------------------------------------------------------------
static    | establish       preserve       preserve      preserve   
invariant |                                                         
          |                                                         
instance  | (irrelevant)    (irrelevant)   establish     preserve,   
invariant |                                              if not a   
                                                         finalizer

A word of warning about terminology. As stated above, we call an invariant about static properties "static invariants" and we call an invariant about the dynamic properties of objects an "instance invariant" or, equivalently, an "object invariant." This terminology is contrary to the literature but it is more accurate with respect to the nomenclature of Java.


8.2.2 Invariants and Exceptions

Methods and constructors should preserve and establish invariants both in the case of normal termination and in the case of abrupt termination (i.e., when an exception is thrown). In other words, invariants are implicitly included in both normal postconditions, i.e., ensures clauses, and in exceptional postconditions, i.e., signals clauses, of methods and constructors.

The requirement that invariants hold after abrupt termination of a method or constructor may seen excessively strong. However, it is the only sound option in the long run. After all, once an object's invariant is broken, no guarantees whatsoever can be made about subsequent method invocations on that object. When faced with a method or constructor that may violate an invariant in case it throws an exception, one will typically try to strengthen the precondition of the method to rule out this exceptional behavior or try to weaken the invariant. Note that a method that does not have any side effects when it throws an exception automatically preserves all invariants.


8.2.3 Access Modifiers for Invariants

Invariants can be declared with any one of the Java access modifiers private, protected, and public. Like class members, invariants declared in a class have package visibility if they do not have one of these keywords as modifier. Similarly, invariants declared in an interface implicitly have public visibility if they do not have one of these keywords as modifier.

The access modifier of an invariant affects which members, i.e. which fields and which (pure) methods, may be used in it, according to JML's usual visibility rules. See section 2.4 Privacy Modifiers and Visibility, for the details and an example using invariants.

The access modifiers of invariants do not affect the obligations of methods and constructors to maintain and establish them. That is, all non-helper methods are expected to preserve invariants irrespective of the access modifiers of the invariants and the methods. For example, a public method must preserve private invariants as well as public ones.

As noted in See section 2.4 Privacy Modifiers and Visibility, there are restrictions on the visibility of fields that can be referenced in invariants to prevent specifications that clients cannot understand and to prevent invariants that clients cannot preserve. Thus, for example, private invariants cannot mention public fields [Leavens-Mueller07].


8.2.4 Invariants and Inheritance

Each type inherits all the instance invariants specified in its superclasses and superinterfaces. [[[Erik wrote: "Static invariants are not inherited", but there seems to be some kind of static field inheritance in Java...]]] [[[ DRCok- but all the static invariants of a superclass have to be maintained by the subclass methods - isn't this equivalent to inheritance?]]]

The fact that (instance) invariants are inherited is one of the reasons why the use of the keyword super is not allowed in invariants. [[[ Is this true? - I don't understand this. DRCok ]]]


8.3 Constraints

History constraints [Liskov-Wing93b] [Liskov-Wing94], which we call constraints for short, are related to invariants. But whereas invariants are predicates that should hold in all visible states, history constraints are relationships that should hold for the combination of each visible state and any visible state that occurs later in the program's execution. Constraints can therefore be used to constrain the way that values change over time.

The syntax of history constraints in JML is as follows.

 
history-constraint ::= constraint-keyword predicate
              [ for constrained-list ] ;
constraint-keyword ::= constraint | constraint_redundantly
constrained-list ::= method-name-list | \everything
method-name-list ::= method-name [ , method-name ] ...
method-name ::= method-ref [ ( [ param-disambig-list ] ) ] | method-ref-start . * 
method-ref ::= method-ref-start [ . method-ref-rest ] ...
        | new reference-type
method-ref-start ::=  super | this | ident
method-ref-rest ::=  this | ident
param-disambig-list ::= param-disambig [ , param-disambig ] ...
param-disambig ::= type-spec [ ident [ dims ] ]

Because methods will not necessarily change the values referred to in a constraint, a constraint will generally describe reflexive and transitive relations.

For example, the constraints in the example below say that the value of field a and the length of the array b will never change, and that the length of the array c will only ever increase.
 
package org.jmlspecs.samples.jmlrefman;

public abstract class Constraint {

    int a;
    //@ constraint a == \old(a);
 
    boolean[] b; 

    //@ invariant b != null;
    //@ constraint b.length == \old(b.length) ;

    boolean[] c;

    //@ invariant c != null;
    //@ constraint c.length >= \old(c.length) ;

    //@ requires bLength >= 0 && cLength >= 0;
    Constraint(int bLength, int cLength) {
      b = new boolean[bLength];
      c = new boolean[cLength];
    }
}

Note that, unlike invariants, constraints can -- and typically do -- use the JML keyword \old.

A constraint declaration may optionally explicitly list one or more methods. It is the listed methods that must respect the constraint. If no methods are listed, then all non-helper methods of the class (and any subclasses) must respect the constraint. A method respects a history constraint iff the pre-state and the post-state of a non-static method invocation are in the relation specified by the history constraint. So one can think of history constraints as being implicitly included in the postcondition of relevant methods. However, history constraints do not apply to constructors and destructors, since constructors do not have a pre-state and destructors do not have a post-state.

Private methods declared as helper methods do not have to respect history constraints, just like these do not have to preserve invariants.

A few points to note about history constraints:

These aspects of constraints are discussed in more detail below.

8.3.1 Static vs. instance constraints  
8.3.2 Access Modifiers for Constraints  
8.3.3 Constraints and Inheritance  


8.3.1 Static vs. instance constraints

History constraints can be declared static. Non-static constraints are also called instance constraints. Like a static invariant, a static history constraint cannot refer to the current object this or to its fields.

Static constraints should be respected by all constructors and all methods, i.e., both static and instance methods.

Instance constraints must be respected by all instance methods.

The table below summarizes this:
 
          | static          non-helper     non-helper    non-helper
          | initialization  static method  constructor   instance method
--------------------------------------------------------------------
static    | (irrelevant)    respect        respect       respect   
constraint|                                                         
          |                                                         
instance  | (irrelevant)    (irrelevant)   (irrelevant)  respect    
constraint|                                                         

Instance constraints are irrelevant for constructors, in that here there is no pre-state for a constructor that can be related (or not) to the post-state. However, if a visible state arises during the execution of a constructor, then any instance constraints have to be respected.

In the same way, and for the same reason, static constraints are irrelevant for static initialization.


8.3.2 Access Modifiers for Constraints

The access modifiers public, private, and protected pose exactly the same restrictions on constraints as they do on invariants, see 8.2.3 Access Modifiers for Invariants.


8.3.3 Constraints and Inheritance

Any class inherits all the instance constraints specified in its superclasses and superinterfaces. [[[Static constraints are not inherited.]]] [[[ But they still apply to subclasses, no ? and it says they are above - David]]]

The fact that (instance) constraints are inherited is one of the reasons why the use of the keyword super is not allowed in constraints. [[[ Needs explanation - David ]]]


8.4 Represents Clauses

The following is the syntax for represents clauses.

 
represents-clause ::= represents-keyword store-ref-expression = spec-expression ;
        | represents-keyword store-ref-expression \such_that predicate ;
represents-keyword ::= represents | represents_redundantly

The first form of represents clauses is called a functional abstraction. This form defines the value of the store-ref-expression in a visible state as the value of the spec-expression that follows the =.

The second form (with \such_that) is called a relational abstraction. This form constrains the value of the store-ref-expression in a visible state to satisfy the given predicate.

For each type and model field, there can be at most one non-redundant represents-clause that in the type that has the given model field in its left-hand side. A represents clause is redundant if it is introduced using the keyword represents_redundantly.

A represents-clause can be declared as static (See section 6. Type Declarations, for static declarations). In a static represents clause, only static elements can be referenced both in the left-hand side and the right-hand side. In addition, the following restriction is enforced:

Unless explicitly declared as static, a represents-clause is non-static (for exceptions see see section 6. Type Declarations). A non-static represents clause can refer to both static and non-static elements on the right-hand side.

Note that represents clauses can be recursive. That is, a represents clause may name a field on its right hand side that is the same as the field being represented (named on the left hand side). It is the specifier's responsibility to make sure such definitions are well-defined. But such recursive represents clauses can be useful when dealing with recursive datatypes [Mueller-Poetzsch-Heffter-Leavens03].


8.5 Initially Clauses

The initially-clause has the following syntax.

 
initially-clause ::= initially predicate ;

The meaning of an initially-clause is that each non-helper (see section 6.2.9 Helper) constructor for each concrete subtype of the enclosing type (including that type itself, if it is concrete) must establish the predicate. Thus, the predicate can be thought of as implicitly conjoined to the postconditions of all non-helper constructors of such a type and all of its subtypes.


8.6 Axioms

An axiom-clause has the following syntax.

 
axiom-clause ::= axiom predicate ;

Such a clause specifies that a theorem prover should assume that the given predicate is true (whenever such an assumption is needed).

[[[ example needed ]]]


8.7 Readable If Clauses

The syntax of the readable-if-clause is as follows.

 
readable-if-clause ::= readable ident if predicate ;

Such a clause gives a condition that must be true before the field named by ident can be read. This field must be one declared in the type in which the declaration appears, or in a supertype of the class.


8.8 Writable If Clauses

The syntax of the writeable-if-clause is as follows.

 
writable-if-clause ::= writable ident if predicate ;

Such a clause gives a condition that must be true before the field named by ident can be written. This field must be one declared in the type in which the declaration appears, or in a supertype of the class.


8.9 Monitors For Clause

The monitors-for-clause is adapted from ESC/Java [Leino-Nelson-Saxe00] [Rodriguez-etal05]. It has the following syntax.

 
monitors-for-clause ::= monitors_for ident
                        = spec-expression-list ;

A monitors-for-clause such as monitors_for f <- e1, e2; specifies a relationship between the field, f and a set of objects, denoted by a specification expression list e1, e2. The meaning of this declaration is that all of the (non-null) objects in the list, in this example, the objects denoted by e1 and e2, must be locked to read the field (f in the example) in this object.

Note that the righthand-side of the monitors-for-clause is not just a store-ref-list, but is in fact a spec-expression-list, where each spec-expression evaluates to a reference to an object.


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by U-leavens-nd\leavens on May, 31 2013 using texi2html