Adding Contracts to Java

Jan Newmarch
Information Science and Engineering
University of Canberra
[email protected]
Abstract
Design by contract is a technique for specifying the requirements and deliverables between clients and suppliers in an OO system. Contracts have been implemented as part of the Eiffel language. This paper examines the possible ways that contracts can be added to Java, and explores in detail one particular mechanism based on reflection. The advantages and disadvantages of this mechanism are discussed.

1. Introduction

Design by contract is a technique introduced by Meyer and implemented in Eiffel [Meyer]. It represents a way of specifying the requirements and deliverables between clients and suppliers of services in an OO system. A contract allows a method to specify what conditions need to be true before the method can function correctly, and in turn it specifies what will be true after it has executed. By ``publishing'' its contract, a method can then avoid often torturous validation code.

This promotes a programming style that is the opposite of the extremely defensive style seen in much systems programming. For example, instead of the C style of code such

 
if ((fp = fopen("file", "r")) == NULL) {
    fprintf(stderr, "cant open file\n");
    exit(1);
}
...

one might see (in informal notation)


require: "file" can be opened in mode "r"
fp = fopen("file", "r");
...

The runtime system is expected to check assertions, and if they fail then it will take appropriate action. This action may be to throw an exception (in languages that support exceptions) that may be caught by programmer code or left to the default runtime action.

Contracts may form documentation only or may be part of the runtime environment. Typical Eiffel systems allow this to be controlled by a compile-time switch, whereby code can be generated to enforce contract checking or to ignore it. Eiffel is unfortunately atypical among programming languages, in that it includes the option of runtime type checking. Most languages can only leave it to the documentation level, and even then, only in an informal manner.

Java has arisen as a major OO language, after a long gestation in private and a short period of visibility. Despite being developed over a period of years, when it was made public there were several unfinished features that were simply discarded. Amongst these, according to Gosling [Gosling], were assertions and a suitable contract mechanism. Now that Java is a standardised language with a formal mechanism for making changes, it may prove difficult or time consuming to incorporate these features into the language.

This paper discusses some of the possibilities that may be explored in adding contracts to Java, and reports on an experimental implementation using the ``reflection'' techniques available in Java. Reflection is a Java API that allows a program to examine classes and methods at runtime, and allows methods to be constructed and executed. It is shown that using the Reflection API is a feasible way of implementing contracts, but slow using current Java implementations. It also throws up several language design issues that cause difficulty in using the reflection technique, but which were probably unavoidable given the development environment and requirements.

2. Language Changes

It is not hard to devise syntax changes to Java with a corresponding semantic meaning. For example, a pop() method for a Stack could have its contract specified as

 
public Object pop()
              require(! empty())
              ensure(result == old getTOS() &&
                     getSize() == old getSize() - 1)
{
   ...
}

The semantics would be as in Eiffel - there is little need to rework this.

This would require changes to both the syntax and semantics of the Java language, and these are under the control of Sun as a standards organisation. For example, it would require introduction of keywords such as require, ensure and old, and a change in syntax to allow clauses to be specified for methods. This may occur in time, but if there is a desire to use contracts now, it may be too long to wait.

3. Programmer Responsibility

A mechanism that would work with no changes to language or involving any other techniques simply uses a style that each programmer conforms to. That is, each programmer includes the require clause on entry to a method, and calls the ensure code on exit.

Performing the require checking is easy. For example, the pop method would just make a call to the precondition code:

 
public Object pop() {
    if (empty())
        throw new RequireException();
    ...
}

Performing ensure checking is a little harder. Firstly, there is no single return point from a Java method. Fortunately, each method can have a finally section which is called as the method returns. Postcondition code can be placed in the finally section. Secondly, it is often necessary to refer back to values as they were on entry to the method. This can only be done by manual coding, saving the old values on entry to the method. Finally, the return value of a method may be needed in this checking. Whereas Eiffel returns values in the variable Result, there is no such variable in Java. This, or a variable playing a similar role, would need to be added as a convention.

This would lead to the pop method looking like


public Object pop() {
    // require
    if (empty())
        throw new RequireException();

    // capture values
    Object oldTOS = getTOS();
    int oldSize = getSize();
    Object result = null;

    try {
        // implementation
        result = s[--size];
        return result;
    } finally {
        // ensure
        if (! (result == oldTOS &&
            getSize() == oldSize - 1))
            throw new EnsureException();
    }
}

This has both advantages and disadvantages. The advantage is that it is an enforceable programming style that adds contract checking. Simple tools could be built to extract the contract from the code for documentation purposes.

The disadvantages are firstly that it is a little cumbersome, and coding standards may be observed in the breach unless rigorously maintained. Secondly, it is not possible to turn contract checking ``on'' and ``off' like it is with Eiffel at compile time. It is always on if present, off if absent.

A more serious problem is that it does not address inheritance. The semantics of contracts is that they are also inherited. The preconditions are or-ed together, and the postconditions are and-ed together. To perform this by hand in a large system with a lot of inheritance would be extremely difficult and time consuming.

4. Possibilities

A Java program exists in two static formats and is subject to a small number of operations. The two formats are source code and byte code as class files. The operations that can be performed include interpretation, transformation, compilation and reflection. (While other formats such as native code format are possible via native code compilers, they violate the ``write once, run anywhere'' paradigm and are not yet common except in transient form by some ``Just In Time'' compilers.) Contract checking may be introduced during any of the operations:

Interpretation
  • The source code could be interpreted directly. An interpreter could introduce contract checking as part of method evaluation. No source level interpreters seem to be available as yet.
  • The class files are usually interpreted on each platform. It would be possible to modify these interpreters but to do so would be at variance with the published Java Virtual Machine semantics.
Transformation
  • Source code can be transformed into source code for the same language. Along the way can be added contract checking, even if the target language does not have such a feature. Kramer [Kramer] has developed such a transformation system, which is considered later.
  • Class files can be transformed into other class files. This does not seem to have been explored yet.
Compilation
Compilation is a translation from source code to byte code. Eiffel compilers often have a switch to generate vanilla code or contract-checking code. No Java compilers do this as yet, probably due to a lack of the feature in the language.
Reflection
Reflection is a method whereby the runtime state of a program can be made available to the program, and conversely, the program can generate - at runtime - code to be executed at runtime. This has typically been used in two situations:
  • a debugger allows execution steps to be mapped onto source code, and also allows runtime evaluation of expressions.
  • Artificial Intelligence programs allow generation of code such as database queries from user input that can be dynamically interpreted.

A method of using reflection is considered in this paper, and contrasted against these other possibilities.

5. Preprocessing the Source Code

The iContract system by Kramer [Kramer] uses a preprocessor to generate Java code similar to that in section three which supplies contracts. The contract is given using extra comment keywords, similar in format to those used by the documentation tool JavaDoc:


/**
 * @pre !empty()
 * @post result == old getTOS() && \
         getSize() == old getSize() - 1)
*/
Object pop()
{ ... }

If the preprocessor is not run, contracts are not inserted into the code, and it runs like a standard Java program. If the preprocessor is run, it generates new Java code with the additional checks in place.

iContract is a fully functional mechanism to implement assertions, but it relies on a preprocessor external to any current Java compilation and runtime environment. It certainly provides a way of implementing contracts. Adding contracts using this system requires access to the source code of the classes. It cannot be added post-hoc to class files.

6. Other Mechanisms

There is little else reported in the Java literature on assertions and contracts. The JDC Technical Tips column for February 1998 [JDCTips] carries a short article on an Assert class that can be used in programs. It does not address the full complexities of contracts, though.

Payne et al [Payne] discuss the problem of contracts in Java at some length. Their final proposal only covers the most basic of contract systems. It does not deal with ``old'' values, nor does it handle inheritance.

7. Reflection API

This paper considers a further mechanism for handling contracts. In this, each method call is passed through a suitable interpreter at runtime. This interpreter can perform precondition checking before calling the method, and can perform postcondition checking on completion. This is discussed in detail in the following sections.

Java does support a measure of interpretive execution, but this needs to be called specifically by the programmer rather than being accessible in some ``magic'' way. Java has a ``reflection'' mechanism [ReflectionAPI] that allows a program to examine classes and objects and find out information about them. For example, one can query a class about all of its methods and find out the method names and parameters. In addition, one can also construct a method call for an object and then invoke that method.

8. Interpreting method calls

8.1 Contract class

Let us develop how this can be used to supply an interpretive layer to method calls. Instead of say, doing


Stack s = new Stack();
s.push(obj);

we want to write something like


Stack s = new Stack();
execute(s.push(obj));

Now execute() will not be a method of any of the application classes: it may need to be called for any method of any class. So we need a class, called say Contract with a static method execute(). This is the only public method needed for this class. It may throw exceptions, say a RequireException and an EnsureException.

The parameters to execute() are a little messy. We cannot simply write s.push(obj) because this will simply evaluate the method before we get a chance to interpret it. We need to pass the object as one parameter and the name of the method as another. This will allow the introspection/reflection mechanism to find the method and execute it. The parameters to the method call, on the other hand, must be evaluated at the time of the call. We need a list of method parameters, evaluated immediately. The execute() method will then have three parameters: the object with the method, the method as a string, and an array of evaluated parameters of the method. The call will look like


Object parameters[] = new Object [] {obj};
Contract.execute(s, "push", parameters);

This constructs a parameter array with one element, obj, and then calls the method execute() with the object, the string name of the method and the parameter array. This syntax is not appealing, but unavoidable using this mechanism.

8.2 Checking contracts

The method execute() must perform require and ensure checking, as well as executing the method. In structured English, the algorithm is


execute(obj, method, args)
    find the class of obj
    find the requires clause, the ensures clause
        and the formal parameter list of the method
        for this class
    substitute the actual parameters for the formal
        parameters in the requires clause
    evaluate the requires clause, and throw an exception
        if false

    for each "old" component of the ensures clause, evaluate
        its initial value (after substitution of parameters)

    find the method for this class
    execute the method after substitution of parameters
        and save the result

    evaluate the ensures clause after substitution of
        parameters and result, and throw an exception
        if false

   return the result

The reflection API is used to retrieve the method for the class, and to evaluate it with the actual parameters substituted. There is a nuisance implementation detail here. The Reflection API has two relevant methods: Class.getDeclaredMethod() and Class.getDeclaredMethods(). The first of these requires the classes of actual parameters to exactly match those of the formal parameters. So an actual parameter of type Integer will fail to find a method with formal parameter of type Object, although it is perfectly valid to make such a call. The second, getDeclaredMethods() returns a list of all methods (including inherited methods), and a slower search must be made through all methods using this, trying each class and its superclasses to find a suitable method. This is slower than the technique actually used by the JVM interpreter.

8.3 Locating contracts

In order to check a contract for a method call of a class, it must be located. This location will need to be separate from the class file, since we are making no changes to the Java source files, and hence no changes to the compiled files. It could be stored in some central registry, in jar files or in directories. The particular method is not particularly important.

It is important to note that this method of checking contracts does not require access to source code, unlike transformation methods. In particular, contracts can be added to library classes if desired.

9. Problems

9.1 Primitive values

Java has a set of primitive types such as int and boolean. These types are not classes. It is easy to see why not: Java shares many features with C, and a simple and efficient system for these common types would be preferable to a heavier class system. If a class is required, there are the classes Integer, Boolean, etc which act as ``wrappers'' around the primitive types. This would not be a problem in other ``purer'' languages such as SmallTalk or Eiffel.

Methods can have a mix of primitive types and classes for parameters. However, if one wants to form a list of the parameters, such as an array of parameters, this causes a problem: an array can be an array of objects, or of one particular primitive type, or of another primitive type, and so on. This can only be resolved by wrapping the primitive types in an object of their corresponding class and using this object.

This can potentially lead to ambiguities. For example, suppose a method can take an int parameter or an Integer parameter:


void f(int n);
void f(Integer N);

A list of parameter values would be a one-element list of Integer objects in both cases.

The Reflection API handles this in a number of ways:

  • Primitive types are wrapped and unwrapped as needed
  • When a list of class types is required, the primitive types have a special set of class objects distinct from the wrapper class

For example, the list of argument classes for the first method, with an int parameter, contains the class object java.lang.Integer.TYPE, whereas for the second method with an Integer parameter contains Integer. Thus by looking at the class types, the Reflection API can determine whether or not to unwrap an object.

This wrapping and unwrapping, and the introduction of special class objects, is clearly cumbersome and inelegant. It is too late to change to change this fundamental language decision now though - it is part of Java's inheritance that is not the best for some purposes.

9.2 Speed

Interpretation usually carries penalties of execution time overheads. Typically an interpreted system is an order of magnitude slower than a compiled system. The mechanism used here is not full interpretation, but only interpretation of method calls. Thus one may hope that the speed penalty is not so severe. It turns out not to be the case.

A Stack class was implemented with the following contracts:


/**
 * ensure: obj == getTOS() && getSize() == old getSize() + 1
 */
public void push(Integer obj);

/**
 * require: ! empty()
 * ensure: result == old getTOS() &&  \
                     getSize() == old getSize() - 1
 */
public Integer pop();

Pushing a number of elements onto the stack and then popping them all off was performed. This was done on 150Mhz Pentium running Linux, using the port of Sun's JDK 1.1.3. The computer had 32M RAM and 64M of swap-space. For each number of elements, three tests were performed:

  • no interpretation and no contract checking
  • interpretation of calls to push() and pop() but no contract checking
  • interpretation of calls to push() and pop() with the contracts also interpreted as a boolean expression (involving interpretive calls to empty(), getTOS() and getSize())

The results are shown in the table (times are in seconds):

Table 1: Time to execute push() and pop() of n elements (times in seconds)
Number of elements, n no interpretation method call interpretation contract interpretation
1 0.98 1.00 1.38
10 0.98 1.00
100 1.15 1.13 1.76
1000 0.99 2.39 8.06
2000 1.00 3.83 15.70
3000 1.01 5.14 22.56
4000 1.02 6.96 30.27
5000 1.02 8.27 37.32
6000 1.04 9.77 45.96
7000 1.42 11.07 52.44
8000 1.06 12.68 60.26
9000 1.07 14.20 69.17
10000 1.07 15.45 76.45
20000 1.18 36.31 176.00
30000 1.28 59.27 310.72
40000 1.48 98.77 512.21
50000 2.56 126.70 668.29
60000 4.64 153.83 748.67
70000 6.20 183.66 976.09
80000 8.64 214.05 1130.75
90000 10.89 244.37 1273.59
100000 13.32 270.52 1429.75

There is a fixed startup time of about one second. Changing ratios are probably caused by the garbage collector being invoked for the interpretive method at smaller stack sizes than for the compiled version, and even earlier for the contract checking version. Allowing for this, interpretation using the Reflection API is about 20-30 times slower than compiled code for the Linux JVM runtime system. Adding contract checking multiplies this by a further factor of five.

The programs were also run under the JBuilder development environment on Windows95 on the same computer. Only two tests were run, shown in the following table:

Table 2: Time to execute push() and pop() of n elements (times in seconds)
Number of elements, n no interpretation method call interpretation contract interpretation
10000 0.22 14.29 73.33
100000 43.78 226.74 1187.20

These figures are sufficiently similar to those of the previous table to leave the conclusions unaltered by use of a different JVM interpreter.

Measurements were also made of the search on parameter types caused by use of getDeclaredMethods() instead of getDeclaredMethod() (which assumes as exact match of parameter types). By jigging the code to match up types in call and declaration, the getDeclaredMethod() could be used in special cases. This effectively places the search for matching method in the JVM interpreter, which is written in C, rather than producing a list which has to be searched by Java. This did speed things up, but only by a factor of two. Of course, here the inheritance hierarchy is short and the number of methods is small, so this could be more of a factor for more complex classes.

An attempt was made to run the iContract system on the Stack to compare the interpretive versus the transformation method. Unfortunately, iContract threw an exception and died during transformation. The transformation was however performed by hand: when tested it showed little difference from the non-interpreted version, showing that the transformation method had far less overhead than interpreted methods.

10. Current status

The interpretive system discussed here has been built as ``proof of concept''. It lacks a full expression parser, but one is being built using the Java CC compiler-compiler [JavaCC]. Issues such as location and retrieval of contracts have not been finalised. Inheritance has not been dealt with, although it should not pose any problems. Once these issues are resolved it will be possible to determine if execution speed is a problem for larger scale applications.

Future work will continue on this project if appropriately funded. This may be conditional on factors such as a timeline for introduction of contracts as core components of Java.

11. Conclusion

A range of alternative mechanisms for the introduction of contracts into Java has been discussed. A method using the Java Reflection API has been implemented to ``proof of concept'' level, and initial timings produced.

This showed that this technique is feasible, but slow. To achieve an acceptable speed may require internal work on the Reflection implementation within the Java Virtual Machine interpreter.

On the other hand, this technique does not require access to source code in order to add contracts to methods, and the contracts may be stored in a variety of ways.

12. References

[Kramer]
R. Kramer, iContract - the Java Design by Contract Tool http://www.promigos.ch/kramer
[Meyer]
B. Meyer, object-Oriented Software Construction Prentice-Hall, 1997
[Gosling]
J. Gosling, Panel session at WWW7, Brisbane, 1998
[JavaCC] The Java Compiler-Compiler. http://suntest.sun.com/JavaCC/
[JDCTips]
JDC Technical Tips, February 1998, http://developer.javasoft.com/developer/javaInDepth/TechTips/techtips7.html
[Payne]
J. E. Payne, M. A. Schatz and M. N. Schmid, Implementing Assertions for Java, Dr Dobbs Journal, January 1998
[ReflectionAPI]
D. Green, The Reflection API, http://java.sun.com/docs/books/tutorial/reflect/index.html

Jan Newmarch (http://jan.newmarch.name)
[email protected]
Last modified: Tue Jul 21 21:45:19 EST 1998
Copyright ©Jan Newmarch