Sather - A Language Manual - Safety Features

[back] [Abstract] [Copyright Notice] [Contents] [next]

# Sather - A Language Manual - Chapter 11
Safety Features

Methods definitions may include optional pre- and post-conditions. Together with '`assert`' and these features allow the earnest programmer to annotate the intention of code. The Sather compiler provides facilities for turning on or off the runtime checking these safety features imply. Classes may also define a routine named '`invariant`', which is a post condition that applies to all public methods.

These safety features are associated with the notion of programming contracts. The precondition of a method is the contract that the method requires the caller to fulfill. It is a statement of the condition of the world that the method needs to find, in order to work correctly. The postcondition is a contract that the method guarantees, if its precondition has been met. It is a statement of the state the method will leave the world in, when it is finished executing. These programming contracts are very important in the creation of robust, reusable code.

In addition to providing a level of checking, these safety features are also an invaluable form of documentation. Since preconditions and postconditions must actually execute, they can be trusted to be accurate and up-to-date, unlike method comments which may easily fall out of sync with the code.

## 11.1 Preconditions

A precondition states the assumptions that a method makes. It is the contract that the caller must fullfil in order for the routine to work properly. Preconditions frequently include checks that an argument is non-zero or non-void.

The optional '`pre`' construct of method definitions contains a boolean expression which must evaluate to `true` whenever the method is called; it is a fatal error if it evaluates to `false`.The expression may refer to `self` and to the routine's arguments. For iterators, pre and post conditions are checked before and after every invocation of the iterator (not just the first or last time the iterator is called).

    class POSITIVE_INTERVAL is
       readonly attr start, finish:INT;
       create(start, finish:INT)
        -- Ensure that the interval is positive on positive numbers
       pre start > 0 and finish > 0 and finish-start >= 0
       is
          res ::= new;
          res.start := start;
          res.finish := finish;
          return res;
       end;
    end; -- class POSITIVE_INTERVAL

Note that it is usually not appropriate to place conditions on the internal state in the precondition. This is an inappropriate conduct, since it may be impossible for the caller to determine whether the conduct can be properly fulfilled.

    move_by(i:INT)
    pre start > 0
    is ...

The test on 'start' is actually verifying something about the internal state of the object, and has nothing to do with the caller of the routine. Tests such as the one above are more appropriately placed in assertions.

## 11.2 Postconditions

Post conditions state what a method guarantees to the caller. It is the method's end of the contract. Post conditions are also stated as an optional initial construct in a method.

The optional '`post`' construct of method definitions contains a boolean expression which must evaluate to true whenever the method returns; it is a fatal error if it evaluates to `false`. The expression may refer to `self` and to the method's arguments.

    class VECTOR is
       ...
       norm:FLT; -- norm of the vector

       normalize
       post norm = 1.0
       is ...
       -- Normalize the vector. The norm of the result must be 1.0

It is frequently useful to refer to the values of the arguments before the call, as well as the result of the method call. A problem arises because the initial argument values are no longer known by the time the method terminates, since they may have been arbitrarily modified. Also, since the post condition is outside the scope of the method body, it cannot easily refer to values which are computed before the method executes. The solution to this problem consists of using `result` expressions which provide the return value of the method and `initial` expressions which are evaluated at the time the method is invoked.

### 11.2.1 `initial` expressions

initial expressions may only appear in the `post` expressions of methods.

    add(a:INT):INT
    post initial(a)>result
    is ...

The argument to the initial expression must be an expression with a return value and must not itself contain `initial` expressions. When a routine is called or an iterator resumes, it evaluates each `initial` expression from left to right. When the postcondition is checked at the end, each `initial` expression returns its pre-computed value.

### 11.2.2 result expressions

Result expressions are essentially a way to refer to the return value of a method in a postcondition (the post condition is outside the scope of the routine and hence cannot access variables in the routine).

    sum:INT
    post result > 5
    is ...
    -- Means that the value return must be > 5

Result expressions may only appear within the postconditions of methods that have return values and may not appear within `initial` expressions. A `result` expression returns the value returned by the routine or yielded by the iterator. The type of a `result` expression is the return type of the method in which it appears (`INT`, in the above example).

### 11.2.3 Example

The above routine maintains an (always positive) running sum in 'sum'. Only positive numbers are added to the sum, and the result must always be bigger than the argument.

    class CALCULATOR is
       readonly attr sum:INT;  -- Always kept positive

       add_positive(x:INT):INT
       pre x > 0 post result >= initial(x)
       is
          return sum + x;
       end;
    end;

## 11.3 Assertions

Assertions are not part of the interface to a routine. Rather, they are an internal consistency check within a piece of code, to ensure that the computation is proceeding as expected.

### 11.3.1 assert statements

assert statements specify a boolean expression that must evaluate to `true`; otherwise it is a fatal error.

    private attr arr:ARRAY{INT};
    ...
    sum_of_elts is
       sum:INT := 0;
       loop e ::= arr.elt!;
          assert e > 0;
          sum := sum + e;
       end;
       return sum;
    end;

In the above piece of code, we expect the class to only be storing postive values in the array 'arr' . To double check this, when adding the elements together, we check whether each element is positive.

## 11.4 Invariants

A class invariant is a condition that should never be violated in any object, after it has been created. Invariants have not proven to be as widely used as pre- and post- conditions, which are quite ubiquitous in Sather code.

### 11.4.1 The invariant routine

If a routine with the signature '`invariant:BOOL`', appears in a class, it defines a class invariant. It is a fatal error for it to evaluate to `false` after any public method of the class returns, yields, or quits.

Consider a class with a list (we use the library class A_LIST) whose size must always be at least 1. Such a situtation could arise if the array usually contains the same sort of elements and we want to use the first element of the array as a prototypical element)

    class PROTO_LIST is
       private attr l:A_LIST{FOO};

       create(first_elt:FOO):SAME is
          res ::= new;
          res.l := #;
          res.l.append(first_elt);
          return res;
       end;

       invariant:BOOL is
          return l.size > 0
       end;

       delete_last:FOO is
          return l.delete_elt(l.size-1);
       end;
    end;

If the 'delete_last' operation is called on the last element, then the assertion will be violated and an error will result.

    proto:FOO := #;   -- Some FOO object
    a:PROTO_LIST := #(FOO);
    last:FOO := a.delete_last;
       -- At runtime, an invariant violation will occur
       -- for trying to remove the last element.

The invariant is checked at the end of every public method. However, the invariant is not checked after a private routine. If we have the additional routines

    delete_and_add is
       res ::= internal_delete_last;
       l.append(res);
       return res;
    end;

    private internal_delete_last:FOO is
       return l.delete_elt(l.size-1);
    end;

Now we can call 'delete_and_add'

    proto:FOO := #;
    a:PROTO_LIST := #(FOO);
    last:FOO := a.delete_and_add;  -- does not violate the class invariant

The private call to 'internal_delete_last' does violate the invariant, but it is not checked, since it is a private routine.

[back] [Abstract] [Copyright Notice] [Contents] [next]

Sather - A Language Manual