University of Virginia, Department of Computer Science
CS655: Programming Languages
Spring 2000

Final Report



Content

Content
Problem
Related Work
Solution
Evaluation
Conclusion
Appendix


Problem

Computer automation is present in almost every aspect of modern life. The heating system in your house is most likely microprocessor controlled. A computer controls the engine operations of your vehicle. Even the appliances in your kitchen contain some degree of embedded circuitry that control portions of their operation. At some time, one or more of these systems will encounter a software error, and may cease to work correctly. A failure of this kind is inconvenient, can also result in property damage, but is usually not life threatening. Now consider the software that runs air traffic control towers, military weapon systems, or nuclear power plants. An error that causes one of these systems to shut down can cause immense human casualties. Software designers, and society in general, cannot afford to have critical automated systems entrusted to programs that cannot reasonably and safely handle an unexpected error.

Exception mechanisms allow the programmer to manage run-time errors in an orderly fashion. Through exception handling, a function can automatically invoke an error-handling routine to systematically deal with errors that might otherwise crash the entire program. The principle enhancement afforded by exception handling mechanisms is that they eliminate and automate most of the error-handling code that has traditionally been hard coded in-line throughout programs. Some languages handle exceptions by returning a specific value from the function in which the error occurred. Other languages incorporate methods that pass errors to exception handling routines. The first goal of this project was to provide a survey of the exception structure of typical programming languages such as Ada, CLU, C++, Eiffel and Java and then to compare them.

Java has recently become a hot item in the programming language community. The portability of Java, due to its virtual machine being integrated into web browsers, has caused many programmers to embrace Java for intranet and Internet development. To solve the exception handling problem Java incorporates a system of "try" and "catch" blocks similar to the scheme implemented by C++. If a programmer incorporates error checking into a "try" block, then he can "throw" an exception in the case of an error. This method provides a disciplined process to shut down a function in the event an error is encountered, but it does not readily supply a means to fix the problem, and then fulfill the function's purpose. The second goal of this project was to study the possibilities of an improvement of the exception handling of Java.


Related Work

Importance of exception mechanisms

The importance of exception handling is well recognized by most software and application designers. This part of their design is critical and can lead to catastrophic failures if not properly considered. An example of such a consequence is the maiden flight of the European launcher, Ariane 5, which exploded 40 seconds into its flight sequence. The cause of this failure was traced to faulty exception handling mechanisms [1].

Published analyses of the cause of software defects frequently single out exception handling as a significant problem. For example, an analysis of software defects in Hewlett Packard's Scientific Instruments division identified "error checking" as the third most frequent cause of defects [2].

Exceptions are unexpected, unusual, or erroneous events that arise during program execution. Exceptions normally fall into two categories: synchronous exceptions and asynchronous exceptions. Asynchronous exceptions are caused by hardware, or system, events such as disk I/O completions, network message arrivals, mouse clicks, keystrokes, etc. These events are best handled through interrupt processing. Synchronous exceptions are caused by expression evaluation, statement execution, or are explicitly raised by the program (such as by a throw statement). Exception handling is a mechanism that provides a flexible framework within which programs may handle synchronous exceptions [3]. Exception mechanisms provide the ability to manage run-time errors in an orderly fashion. Through exception handling, a function can automatically invoke an error-handling routine to systematically deal with errors that might otherwise crash the entire program.

The first goal of this project is to identify the strengths and weaknesses of some typical exception handling mechanisms. The first papers entirely devoted to exception handling began to appear in the 70s [4, 5]. Goodenough introduced the idea of exceptions and exception handling in [4]. His landmark paper points out that exceptions are not necessarily errors; this implies that support for user-defined exception abstractions can expand the generality of an exception handling mechanism.

As our examples are going to illustrate it, different approaches to the nature of an exception exist. Besides, exception handling structures can be based on two alternative models [6]: the termination model and the resumption model. The first one considers that an exception should cause the program to terminate when the latter considers that a program should be able to recover from raised and "fixed" exception. The mechanisms intended to enforce a resumption model are called recovery mechanisms. Another orthogonal notion is the propagation. Different models deal with the way an exception should propagate through the hierarchy of procedures of a program.

The first task of our project was to analyze the exception structures of 5 languages: Ada, CLU, C++, Eiffel and Java. The 5 following sections are indented to provide the reader with some related work dealing with the exception mechanisms of each of these programming languages. These sections are sorted according to the historical order. Each section provides a brief historical background, a summary of the exception structure of the language and refers to some resources judged relevant by our project. The reader is invited to consult [26] for more details about the exception handling of a specific language. A short discussion follows and provides a comparison of these exception structures according to the orthogonal criteria introduced above. The last section deals with Design by Contract (trademark of ISE) which led us toward some interesting prospects for the end of the project.


CLU

Designed in 1973, CLU is one of the first programming languages intended to provide exception handling. [6] and [7] explain and justify the decisions about the design of CLU's exception handling mechanisms. CLU exception handling is based on the termination model and rejects the resumption model.

In CLU, exceptions are considered as optional attributes of procedures. So they are declared at procedure header by using the keyword signals The statement signal is used for raising of exceptions. CLU uses a single level model which means that only the caller can handle exceptions. [6] explains that an automatic propagation of errors was rejected because it did not fit with the concept of modularity. Exceptions which cannot be handled by the procedure where it occurs are turned into a generic exception called failure and propagated. Another way of propagating an exception is to use the resignal keyword.

As pointed out by [8], this model has the advantage of being both safe and easy to deal with for the programmer.


Ada

Ada is the result of a US Department of Defense project which took place during the late seventies. According to STEELMAN [9], Ada was designed with focus on reliability, so the exception handling mechanism was emphasized.

Ada has an exception handling mechanism for responding to unplanned error situations detected in declarations and statements during execution. As explained in [10], the exception situations include errors detected by hardware, software errors detected during execution, error situations in built-in operations, and user defined exceptions.

Ada defines a type EXCEPTION. Therefore, unlike CLU, exceptions are declared separately from procedures. As explained in [8], in contrast to CLU, Ada is based on a multi level model. This means that Ada allows any routine on the dynamic chain to handle exception. When an exception is raised, the handling mechanism searched the dynamic chain of blocks for a proper handler. If the exception is not caught, it is propagated as is up the call stack until it encounters an exception handler prepared to process it. If there are no exception handlers, it propagates to the highest level and causes the program to abort. As explained in [11], Ada does not provide recovery mechanisms and enforces a termination model.

Ada further standardizes behavior by pre-defining a number of exceptions for commonly encountered problems, such as constraint_error when an attempt is made to assign a value to a variable which is outside the permitted range of its type.

The advantage of this exception structure compared with CLU's is that it can handle exceptions that called subprogram does not know about. Because exceptions can be propagated to arbitrarily high levels of an Ada program, it is easy to arrange for them to be caught at some level where there is an appropriate interface for dealing with them. This gain of expressiveness is also a loss of safety since caller does not know about the entire interface of the callee and the handler does not know what caused the exception.


C++

C++ was designed and implemented by Bjarne Stroustrup and was first released in 1980 as "C with classes" defined as a better C supporting data abstraction and object-oriented programming. In 1998, C++ was standardized by a group of national standards bodies (ANSI, BSI, DIN, ISO ...).

[12], a reference about C++, tries to bring the maximum of useful information to the programmer whether his compiler supports C++ exception mechanisms or not. It appears that C++ exception handling structure (when supported) is quite complex.

C++ exceptions are treated as objects that functions can deal with. Exceptions in C++ can be thrown and caught through a try and catch structure. They can be caught higher up in the call chain, where the user can make the decision either to terminate or to continue the execution of the program. The language allows the user to design his own terminations to clean up parts of the application before exiting.

To put C++ into the context of the models offered by the two first languages, it provides a multilevel model much closer to Ada's than CLU's. Even though C++'s exception handling is based on the termination model and strongly suggests it by its default behavior, it allows the programmer to retry or resume the execution of the program.

C++ can also support preconditions and assertions (preconditions used for testing purposes) within procedures. These mechanisms can be seen as alternatives to try and catch blocks. When a procedure is called and if one of its preconditions is not satisfied, a generic failure exception (which can also be user-defined) is thrown. This feature can be considered as a rudimentary form of design by contract (cf. below).

Last but not least, though its mechanism of pointers, C++ is also capable of memory exhaustion handling. This possibility provides the programmer with a unique control ability.

C++ is probably the language offering the most functions and the most flexibility. It also gives a lot of responsibility to the programmer. The reader which would be interested in the possibilities offered by an exception structure in C++ is referred to [13]. However, even though this reference provides an interesting example, we do not think that describing its content would be in the scope of this short presentation.


Eiffel

Eiffel was designed in 1985 by Bertrand Meyer of ISE (Internet Software Engineering) as an object-oriented programming language. ISE says itself to be one of the first companies which had focused its activities entirely on Object Technology for enterprise development. However, our interest in studying this language was more in its exception handling mechanisms. The underlying theory of Eiffel is Design by Contract. This programming paradigm views software construction as based on contracts between clients (callers) and suppliers (routines), relying on mutual obligations and benefits made explicit by assertions. We found this concept particularly relevant to our problem and decided to focus on it for a possible improvement of the exception mechanisms of Java. A more thorough explanation of Design by Contract is given later.

In Eiffel, exceptions [14] are neither object nor variables of a certain type. They are interpreted as breaches in contract. The contract is defined by a set of routine preconditions and postconditions and class invariant. Exceptions are then considered contract violations and are generally raised via failed assertions.

Eiffel provides recovery mechanisms through the retry instruction of a rescue clauses. A rescue clause attempts to bring the current object to a stable state (one satisfying the class invariant), and then it terminates in one of two ways:


Java

For the sidelights on history, Java technology was created as a programming tool in a small, closed-door project initiated by Patrick Naughton, Mike Sheridan, and James Gosling of Sun in 1991 [15]. Released in 1995 in its current form, it is now a growing programming language used notably to develop web applications.

In Java [16], an exception is an object (all exception types must extend the Java language class throwable or one of its subclasses). Like C++, Java provides a system of try and catch. An exception can be thrown by using either a throw statement taking an object as parameter or a throws clause which takes a comma separated list of exception types.

When an exception is raised, it is caught by an encompassing clause further up the method stack. If the exception is not caught, a default exception handler takes effect, usually printing useful information about where the exception was thrown (such as a call stack). To summarize, Java enforces a multi level model. From [17], "the basic philosophy of Java is that badly-formed code will not be run". Therefore, Java does not provide recovery mechanisms and terminates the program if an exception is thrown.

Although exception structure in Java looks like C++'s ([17] even explains that Java's exception handling structure is based on C++'s which is based itself on Ada's), [18] argues that Java avoids some of the problems of C++. C++ unwinds the stack by calling destructors when an exception is thrown. If a user wants to clean up, he needs to put some code into these destructors. Java uses a more elegant solution by providing a finally clause which can be included in the try clause. This finally clause is executed whether the completion of the try clause is achieved normally or, through an exception or, through a control flow statement. [18] concludes that this system is much easier to use than the complex exception handling of C++.


Comparative summary of these studies

Ada and CLU were interesting in the sense that they were both among the first programming languages providing exception handling mechanisms. They illustrated a debate about single level model (CLU) versus multi level model (Ada). I recall that in CLU, only caller can handle exceptions and these exceptions are passed with the headers of the functions. Ada allows any routine on the dynamic chain to handle exceptions and, exceptions are declared separately from procedures. Even though more powerful, Ada's approach appears to be less secure for the reason that the caller does not know about the entire interface of the callee [8] and handler does not know what caused the exception.

The table below provides a short classification of the exception handling of the programming languages we studied:

Comparative summaries of the exception mechanisms of five languages
CLU
Ada
C++
Java
Eiffel
Nature of exceptions
attribute of procedure
specific type
object
object
breach in contract
Resumption
No
No
Mostly no
No
Yes
Model of propagation
single level
multi level
multi level
multi level
multi level

We can notice that the single level model of CLU is a peculiarity. More recent programming languages all provide the possibility to propagate exceptions outside the scope of the caller.

Another interesting point is that recovery mechanisms are almost never supported by either traditional exception models. The only language allowing the user to recover from an exception is C++. Remaining true to its reputation of "shopping list" language, C++ also provides preconditions which could be compared with a rudimentary form of design by contract. However that may be, adding a kind of resumption appeared to be an interesting track for the continuation of the project. This naturally leads us to Eiffel.

The language which attracted our attention the most was Eiffel and its underlying theory of Design by Contract. This paradigm offers an original interpretation of exceptions by defining them as breaches in contract. We considers that the solution consisting of providing this feature for Java is a valuable idea. This is precisely the topic of the next section.


Design by Contract

The second problem addressed by our project is to investigate the possibility of making improvements to the exception handling mechanism in Java. Our first idea to achieve this task has been to analyze the concept of Design by Contract.

Design by Contract is the most popular "non-bug" request in the Java Developer Connection Home Page [19] (bug number 4071460). Among the many applications of Design by Contract theory, one of the benefits is that it leads to a systematic approach to the problem of exception handling. Other benefits include an effective framework for building bug-free object oriented systems, quality assurance and a method for documenting software components. In an interview with James Gosling, the chief architect of Java, he was asked that if he could re-architect Java which language features would he change, include or omit [20]. His response was "If you go to my personal home page on the Java Web site, (http://java.sun.com/people/jag), I've got this really crusty, old copy of the Java Spec -- the last one that I wrote the entirety of. And it's got a collection of things in there that are about some testing -- to be able to do assertions and preconditions and post-conditions. I had been doing some work on that, but because of schedules, I had to rip that out, and I regret doing that today."

A software element is a way to fulfill a certain explicit or implicit contract. In this model, an exception is considered as the consequence of the inability of the element to fulfill that contract, for any reason, which may include a hardware failure or a software bug which makes it impossible to fulfill the contract. In such cases there may be three possible responses:

  1. Retrying: If an alternative strategy is available the routine will restore the invariant and make another attempt using the new strategy.
  2. Organized Panic: A viable strategy is not available. Restore the invariant, terminate and report failure to the caller by triggering a new exception.
  3. False Alarm: It may be possible to continue after taking some corrective measures.

The above concepts are included in the Eiffel language which has built in language and runtime support for design by contract [21]. Eiffel integrates preconditions (require-clause), postconditions (ensure-clause), class invariants, old and rescue/retry constructs into the definition of methods and classes. [21] further explains the natural connection between Design by Contract and exception handling: "Among the many other applications of the contract theory we may note that the theory leads naturally to a systematic approach to the thorny problem of exception handling: handling abnormal cases".

A number of researchers have been influenced by the desire to add Design by Contract to Java and have published papers on how it can be used to complement Java's exception handling mechanism [22,23,24,25].

iContract by Kramer [23] is implemented as Java preprocessor that instruments source code with checks for class invariants as well as pre- and post- conditions that may be associated with methods in classes and interfaces. Special comment tags are interpreted by iContract and converted into assertion checking code that is inserted into source code.

jContractor by Karaorman [24] is a library based approach to support Design by Contract with preconditions, postconditions, class invariants and recovery and exception handling in Java. jContractor supports the specification of general or specialized exception handling code for methods. The instrumented method contains wrapper code to catch exceptions thrown inside the original method body. If the contracts include an exception-handler method for the type of exception caught by the wrapper, the exception code gets executed. The jContractor approach appears to be more flexible than the Eiffel rescue mechanism since separate handlers can be written for different types of exceptions and more information can be made available to the handler code. However, jContractor does not support the retry construct of Eiffel. Karaorman et al. have not completed the implementation of jContractor libraries and plan to make it available in the "near future".

Jass (Java with assertions) by Bartetzko [25] is a precompiler which incorporates the theory of Design by Contract into Java and which supports assertions for Java. It also provides pre- and post- conditions for methods, invariants for classes, invariants and variants for loops, check statements as well as rescue and retry statements. It also proposes the inheritance of assertions as a refinement. This last notion is based on the fact that in Object Oriented language, a method of a subclass can be used where a superclass method is expected. Design by Contract needs to ensure that the precondition of the method of a subclass is weaker than the precondition of the superclass, and inversely that the subclass postcondition is stronger than the superclass postcondition.



Solution

One of the goals for our project is to explore the feasibility of enhancing the exception handling mechanism in Java. The work related to our problem convinced us that the idea of integrating Design by contract into Java was sound. As explained by the previous section, this idea is not really new. We showed that there already existed several tools which intended to provide Design by Contract to Java. iContract and Jass, for example, present similar approaches by using a preprocessor.

To add Design by Contract to Java, we chose Jass because it has the advantage of having open source code whereas iContract does not. As explained in the previous section, Jass also supports inheritance as a refinement. More precisely, Jass is a precompiler that takes a program written with Jass assertions (.jass), and compiles it into a Java file (.java) that can then be compiled into a Java class file (.class) for execution on the Java Virtual Machine (JVM). It is also interesting to notice that the enhancements of Jass are coded through a special kind of Java comment which is ignored by a normal Java compiler. So there is no need to modify a .Jass file to compile it with a standard Java compiler (thus discarding the enhancements).

However, Jass does not support a generic rescue-retry mechanism (which Eiffel does). It only allows rescue for failures of the preconditions, the postconditions and the invariants.

Our implementation adds a generic rescue-retry which is similar to Eiffel's. Our motivation to add a rescue-retry feature is based on the fact that whenever there is a contract, there is a risk that someone will break it. One of the main causes for an exception (contract violation) is the failure of a routine. A routine that fails triggers an exception in its caller. A routine may, however, handle an exception through a rescue clause. This optional clause attempts to "patch things up" by bringing the current object to a stable state (one satisfying the class invariant). In our solution, the rescue clause can terminate in either of two ways:


The principle is that a routine must either succeed or fail: either it fulfils its contract, or it does not; in the latter case it must notify its caller by triggering an exception.

Our implementation consists of enhancing the BNF (which stands for Backus-Noir Formalism) notations of Java. The BNF notation is used to describe a context-free grammar. It is used to write the specification for a language. Then "Compiler compilers" are used to generate the language compiler by using the BNF notation as an input. In our example, the language syntax for Java can be written in BNF notation. Then two tools - the lexical analyzer JJTree and the parser generator JavaCC are used to generate the java compiler. By enhancing the core BNF notation for Java we can add functionality to it. You can find the BNF notation for a generic Rescue-Retry in the
Appendix.

JJTree is a preprocessor for JavaCC that inserts parse tree building actions at various places in the JavaCC source. The output of JJTree is run through JavaCC to create the parser. By default JJTree generates code to construct parse tree nodes for each nonterminal in the language. This behavior can be modified so that some nonterminals do not have nodes generated, or so that a node is generated for a part of a production's expansion.

Although JavaCC is a top-down parser, JJTree constructs the parse tree from the bottom up. The JavaCC input grammar is of the form:
    javacc_input ::= javacc_options
    "PARSER_BEGIN" "("  ")"
    java_compilation_unit
    "PARSER_END" "("  ")"
    ( production )*
    
The grammar file starts with a list of options (which is optional). This is then followed by a Java compilation unit enclosed between "PARSER_BEGIN(name)" and "PARSER_END(name)". After this is a list of grammar productions which consist of the listing of the BNF notation for a generic Rescue-Retry. The name that follows "PARSER_BEGIN" and "PARSER_END" must be the same and this identifies the name of the generated parser. For example, if name is MyParser, then the following files are generated:
    MyParser.java: The generate parser. 
    MyParserTokenManager.java: The generated token manager (or scanner/lexical analyzer)
    MyParserConstants.java: A bunch of useful constants
To illustrate these explanations, an example for the use of the Rescue-Retry construct follows:
    Protected int tx_failures;
        /* Initialize to zero in an init function */

    Public boolean tx_succesful;
        /* Attempt to transmit the message 100 times and set "tx_succesful" accordingly */

    Public void transmit_message(String message ) {
        If tx_failures < 100
        {
            attempt_transmission(message); 
            tx_succesful = true;
        }
        else tx_succesful = false;
        rescue 
        {
            tx_failures = tx_failures + 1; 
            retry;
        }
In the Appendix, we also listed part of the code for the back-end which generates the Java output file.


Evaluation

The goal of the evaluation was to compare the current mechanisms of Java with Jass as well as with our own version of Jass supporting generic rescue-retry structures. For this purpose, different criteria have been selected: the size of the executable files, the execution time and last but not least the reliability that we based on the error messages generated by our tests. To be able to measure the performance of Java enhanced by Jass in comparison with the regular Java, we have designed several tests. These tests use seven different classes:


The source code of these tests can be found in the Appendix. We recall that the source code of a .jass file can be directly compiled by a regular Java compiler. So when we decided to discard the Jass enhancements for evaluation purposes, we did not modify the code of the .jass files. Besides, the classes used for our evaluation do not all include Jass enhancements. In the Appendix and this section, when a class has Jass enhancements, its name ends with .jass whereas a class without enhancement has a name logically ending with .java.

Our tests can be grouped into four evaluations.

Evaluation 1

We carried out two tests using Buffer.jass, UnlimitedBuffer.jass and BufferTest1.java. The program pushes 1000 integers into a buffer, and then tries to pop 1001 out of it. During the first test, eccentrically called Test 1, we compiled and ran the program through a normal Java compiler which means that we did not use the Jass enhancements and we did not do anything to handle the exception. For the second test - that we creatively named Test 2 - Buffer.jass and UnlimitedBuffer.jass are precompiled with Jass before being compiled by the Java compiler with BufferTest1.java. This time, we used the enhancements made possible by Jass.

We used the UNIX command time which can record the execution time of a program. This provided three different values corresponding with the elapse time, the user time and the system time.

The results can be summarized by the following table:
.. ..
Evaluation table
Scenario
Size
(in Bytes)
Real Execution Time
(in second)
User Execution Time
(in second)
System Execution Time
(in second)
Test 1
(without Jass)
Buffer.class 1,081
UnlimitedBuffer.class 1,282
1.33
0.27
0.33
Test 2
(with Jass)
Buffer.class 4,366
UnlimitedBuffer.class 3,854
22.33
21.03
0.33

For each test, we also considered the error message generated:

Test 1

java.lang.ArrayIndexOutOfBoundsException: 0 >= 0
         at java.util.Vector.elementAt(Vector.java)
         at jass.purejava.UnlimitedBuffer.remove(UnlimitedBuffer.java:25)
         at jass.purejava.BufferTest.main(BufferTest.java:20)
... which means the exception is logically not detected by our program, but is caught by the handler of java.util.Vector class.

Test 2
jass.runtime.LoopInvariantException: jass.purejava.BufferTest.main([Ljava.lang.String;):17
         at jass.purejava.BufferTest.main(BufferTest.java:28)

... which means that the exception is successfully caught in the main function of the class BufferTest.

This first set of tests carried out showed that the Jass implementation created code that was approximately 3 times larger and considerably slower than the implementation without Jass. However, from the point of view of the reliability, this program shows an instance when Java's standard exception handling does not catch the exception at the level of the failing method which Jass does. Therefore, if speed and disk space are not a concern, we can say that Jass improves the exception mechanisms of Java.

Evaluation 2

In this second set of two tests, we used Buffer.jass, UnlimitedBuffer.jass and BufferTest2.java . We intended to evaluate the inheritance of assertions proposed by Jass as a refinement. The idea was that the superclass Buffer.jass could ensure that all the items in the buffer were not 0, so for example after having popped out A from the buffer, 10/A would not result in a "divided by zero" exception. However, the subclass UnlimitedBuffer.jass allows the items to be 0. Therefore, without the refinement proposed by Jass and after having popped out A from this unbounded buffer, 10/A would probably result in a "divided by zero" exception.

In the first test, we examined what happened when our three files were directly compiled by the Java compiler. Here is the error message:

    java.lang.ArithmeticException: / by zero
        at jass.examples.BufferTest.foo(BufferTest.java:24)
        at jass.examples.BufferTest.main(BufferTest.java:32)

... which means that the exception is detected at line 24 of BufferTest.foo() when the program tries to use the "0" popped out from the buffer.

In the second test, we examined what happened when Buffer.jass and UnlimitedBuffer.jass are precompiled with Jass before the Java compilation (we recall that BufferTest2.java does not contain any Jass enhancement). Here is the error message:

    jass.runtime.RefinementException: jass.examples.UnlimitedBuffer 
        at jass.examples.UnlimitedBuffer.jassCheckInvariant(UnlimitedBuffer.java:119)
        at jass.examples.UnlimitedBuffer.add(UnlimitedBuffer.java:45)
        at jass.examples.BufferTest.foo(BufferTest.java:12)
        at jass.examples.BufferTest.main(BufferTest.java:32)

... which means that the exception is detected at line 12 of BufferTest.foo() when the program tries to add a "null" into the buffer.

This second set of tests showed that the Jass inheritance of assertions allowed us to detect a design inconsistency on the relation between a subclass and its superclass. In normal java, it is very unlikely that a programmer will use a try and catch block to ensure that the precondition of the method of a subclass is weaker than the precondition of the same method of the superclass. Even if he does so, it is not trivial. With Jass, this tedious work is replaced by the implementation of a simple jassGetSuperState() function (cf. Appendix for further details) which systematically ensures the inheritance of assertions from a class to its subclasses.

Evaluation 3

This test used Buffer.jass, Consumer.java, Producer.java and ProducerConsumer.java. Without assertions it is possible for the Consumer to access an object in the buffer and make changes to it while the Producer is still in the subroutine it uses to place data in the buffer. In this case, the Producer would believe that the data it had placed in the buffer was still valid, when it might have been modified. Adding assertions (in this case a post-condition) ensures that if one of the threads attempts to access the same element of memory at the same time as another thread, an exception is raised. In this instance Jass provided run-time checks that added security to the program.

Our tests showed that the generated exception is displayed differently each time. This is explained by the fact that the failure is timing dependent. The two threads are started asynchronously, and there is no way to predict when the Consuming thread will erroneously attempt to access an area of the buffer that the Producing thread is currently operating on. Below are three examples of the messages that are returned when the failure occurs:

    jass.runtime.PostconditionException: jass.examples.Buffer.full() 
        at jass.examples.Buffer.full(Compiled Code)
        at jass.examples.Producer.run(Compiled Code)

    jass.runtime.PostconditionException:
        jass.examples.Buffer.add(java.lang.Object) 
        at jass.examples.Buffer.add(Buffer.java:61)
        at jass.examples.Producer.run(Compiled Code)

    jass.runtime.PostconditionException: jass.examples.Buffer.empty() 
        at jass.examples.Buffer.empty(Compiled Code)
        at jass.examples.Producer.run(Compiled Code) 

Not only is it possible to get different exceptions, but if the program is run 20 times, it will probably execute without error at least 6 times. What makes this such a good example is that we really are not doing anything to cause the exceptions. The timing of the threads randomly causes the exceptions in a manner that could actually occur in a real system.

This evaluation showed an instance when Java's standard exception handling did not raise an error at all whereas Jass raised exceptions adapted to the specific breach of contract. We can conclude again that Jass greatly improves the exception handling of Java.

Evaluation 4

So far the three evaluations that we carried out compared Java with Java enhanced by Jass. However, we have not yet evaluated the improvement of Jass brought by our own implementation. This is the purpose of this last evaluation. We used four classes:


In this evaluation, the exception is raised when the method Receive_Ack() of Server.jass tries to read an acknowledgment message from the receive buffer and that this buffer is empty. Without the recovery system, Java would have to raise an exception and then the application would have to decide what to do with it. A handler could institute a retry through a method of hard coding, but that would be very inefficient, and would carry extra complexity of propagating the method's parameters with the exception in order for the hard-coded retry to work. It would terminate the program (or at least most programs we use now do). For instance, when we are downloading files from the Internet and encounter a missed transmission, the download terminates and signals the failure to the user through an error message box. In our example, it would just raise a Java runtime exception and terminate the program.

For our test, we precompiled Buffer.jass and Server.jass with Jass. After the Java compilation, the program is run. When a transmission attempt is not acknowledged, an exception is thrown. This exception is reported to the user via the terminal, and then the server resends the transmission. Ideally, the server would only resend when an ack was not received, but in our program it actually sends all ten transmissions again.

This last evaluation showed that our enhanced Jass allows a Java program to attempt to handle an exception by a method other than termination. We can even claim that our implementation provides recovery mechanisms for Java.

Conclusion of the evaluation

Each evaluation carried out provides its own interesting result. We showed that even though Jass causes an important performance degradation both in term of execution time and disk space, it allows programmers to trace the routine raising an exception more accurately than Java. We also showed that the inheritance of assertions enforced by Jass as a refinement is valuable by helping programmers to detect design inconsistencies related to class inheritance.

We showed an example where Jass is able to detect exceptions that Java would ignore thus misleading the rest of the program. We consider this improvement as a major gain for programmers who can thus trace tricky bugs generated by a non-trivial chain of errors.

Finally, we showed an example where our version of Jass is able to recover from an exception, which Java is not able to do. We think that our implementation brings a valuable enhancement to Jass by replacing the timid retry statement of the original Jass by the same recovery mechanisms as Eiffel's. A few more weeks could have allowed us to definitely prove it.


Conclusion

Debugging code is tedious, time consuming, and translates into high costs for software producers. Everyday companies wrestle with the dilemma of how to reduce the time spent in debugging and rework. The most effect method of reducing debug is error prevention. Using Design by Contract prevents errors from occurring. By forcing the programmer to declare a set of specifications that must be met in order to interface with a class, a more robust class is produced. These specifications are especially useful in object-oriented systems where inheritance and reuse are key design principles. The Liskov Substitution Principle states that "objects of the subtype ought to behave the same as those of the supertype as far as anyone or any program using supertype objects can tell" [Liskov]. Given a function F that uses type T, and given that S is a subtype of T, then an object of type S should be able to replace an object of type T, without F being able to tell the difference. Now if this principle is applied to classes, then the enhancement to design supplied by Design by Contract becomes apparent. If a class T has a contract C, and S, a subclass of T, has the same contract, then S can be used in place of T. Ensuring that derived classes are type safe can be done by merely reusing the contract from the base class.

Java with Design by Contract (in this instance Jass) produces code that is much larger and much slower than Java. Before we began evaluating Jass we already knew that the code size would be larger because we were adding code. We also knew that it was going to be slower, because Java is slow, and adding more Java is going to make it slower. Our argument for adding Design by Contract to Java is not for performance, but more for correct code production. Design by Contract promotes more correct initial code due to the programmer having to state assertions as pre- and post- conditions, and a class invariant. Having to provide the interface specifications for classes forces the programmer to more carefully design the code and catch errors that otherwise might have been missed. Design by Contract for Java may not be an affordable solution for all programming projects and environments, but is a powerful tool for designing reliable, bug-free code in projects were execution speed and compact code size are not the essential design.


References

  1. Ariane 5- Flight 501 Failure, European Space Agency (1996), Report by the Inquiry Board http://www.esrin.esa.it/htdocs/tidc/Press/Press96/ariane5rep.html
  2. Error Handling: When Bad Things Happen to Good Infrastructures, Howell C. (1998), Position Paper for Information Survivability Workshop
  3. Analysis of Programs With Exception-Handling Constructs, Sinha S. and Harrold M. J. (1998), Department of Computer and Information Science, Ohio State University, Columbus, OH
  4. Exception Handling: Issues and a Proposed Notation, Goodenough J. (1975), Second ACM Symposium on Principles of Programming Languages
  5. A Program structure for Error Detection and Recovery, Horner J., Lauer H. et al (1974), Lecture Notes in Computer Science, Springer-Verlag
  6. A History of CLU, Liskov B. (1992), Laboratory of Computer Science, MIT, Cambridge, MA, http://www.pmg.lcs.mit.edu/CLU.html
  7. Exception Handling in CLU, Liskov B. and Snyder A. (1979), IEEE Software Engineering
  8. Principles of Programming Languages (CS342), Leavens G. T. (1989), lecture notes, Department of Computer Science, Iowa State University, http://www.cs.iastate.edu/~leavens/ComS342-MacLennan/lectures/clu/exception-handling.txt http://www.cs.iastate.edu/~leavens/ComS342-MacLennan/lectures/ada/exceptions.txt
  9. Ada, C, C++, and Java vs. The Steelman, Wheeler D. A. (1996), http://www.adahome.com/History/Steelman/steeltab.htm
  10. Data Structures and Algorithms - Ada exceptions, Morris J. (1998), University of West Alabama, Course outline, http://www.ee.uwa.edu.au/~plsd210/ds/ada_exceptions.html
  11. Introduction to programming languages - Imperative programming, Aaby A. A. (1996), Computer Science Department, Walla Walla College, Washington, http://cs.wwc.edu/~aabyan/221_2/PLBOOK/Imperative.html
  12. C++ Library User Guide (1997), Dumpleton Software Consulting Pty Limited, OSE, http://www.dscpl.com.au/ose-5.2/lib_1.htm
  13. Integrating Structured Exception Handling in the C++ exception mechanism, Pels R. (2000), http://www.drbob42.com/cbuilder/structur.htm
  14. Eiffel: The Language, Meyer B. (1992), Prentice Hall
  15. Java Technology: An Early History, Byous J. (1998), Sun Microsystems, http://java.sun.com/features/1998/05/birthday.html
  16. The Java Language Specification, Gosling J., Joy B. and Steele G. (1996), http://java.sun.com/docs/books/jls/
  17. Thinking in Java - Error Handling with Exceptions, Eckel B. (2nd edition to be released in June 2000), Prentice-Hall http://www.star.cz/tij2/TIJ2R1-9_.html
  18. Java and C++ - A critical comparison, Robert Martin (1996), http://idm.internet.com/opinion/java_v_cpp.shtml
  19. The Bug Parade- Java Technical Support, Sun Microsystems, http://developer.java.sun.com/developer/bugParade/bugs/4071460.html
  20. James Gosling talks about the Java Platform Here and Now, Steinberg J. (1998), JavaOne, http://www.javaworld.com/javaworld/javaone98/j1-98-interview.gosling.html
  21. An introduction to Design By Contract, Interactive Software Technology (1999), http://www.eiffel.com/doc/manuals/technology/contract/
  22. Adding Contracts to Java with Handshake, Duncan A., Hölzle U. (1998), Technical Report TRCS98-32, Computer Science Department, University of California, Santa Barbara, http://www.cs.ucsb.edu/oocsb/papers/handshake98.html
  23. iContract- the Java Design-By-Contract Tool, Kramer R. (1998), Cambridge Technology Partners, http://www.reliable-systems.com/tools/iContract/documentation/icontract-tools98usa.pdf
  24. jContractor: A Reflective Java Library to Support Design by Contract, Karaorman M., Hölzle U. and Bruno J. (1998), Department of Computer Science, University of California Santa Barbara, http://www.cs.ucsb.edu/oocsb/papers/TRCS98-31.html
  25. Java with Assertions, the Jass Page (1999), Department of Informatics, University of Oldenburg, Germany, http://semantik.informatik.uni-oldenburg.de/~jass/
  26. Exception mechanisms, De A., Harry J., Lepouchard Y., Li P. and Lu P. (2000), Department of Computer Science, University of Virginia, Project Report, http://www.cs.virginia.edu/~yel4j/cs655/


Appendix


BNF Notation for Rescue-Retry

     void MethodBodyBlock() :
     {}
     {
         [ RequireClause() ] ( BlockStatement() )* [ EnsureClause() ] [ RescueClause() ]
     }

     void AssertionClause() :
     {}
     {
         [ InvariantClause() ] [ VariantClause() ] Statement()
     }

     void RequireClause() :
     {}
     {
         <REQUIRE> ( BooleanAssertion() ";" )+ <ASS_END>
     }

     void EnsureClause() :
     {}
     {
         <ENSURE> ( BooleanChangeAssertion() ";" )+ <ASS_END>
     }

     void BooleanAssertion() :
     {}
     {
         LOOKAHEAD ( AssertionLabel() ) AssertionLabel() AssertionExpression() | AssertionExpression()
         }

     void BooleanChangeAssertion() :
     {}
     {
         LOOKAHEAD ( <CHANGEONLY> ) <CHANGEONLY> "{" [ ChangeList() ] "}" | BooleanAssertion()
     }

     void ChangeList() :
     {}
     {
          FieldReference() ( "," FieldReference() )*
     }

     void FieldReference() :
     {}
     {
         [ "this" "." ] <IDENTIFIER>
     }

     void AssertionLabel() :
     {}
     {
         "[" <IDENTIFIER> [ ":" AssertionExpression() ] "]"
     }

     void ClassInvariantClause() :
     {}
     {
         <INVARIANT> ( BooleanAssertion() ";" )+ <ASS_END>
     }

     void InvariantClause() :
     {}
     {
         <INVARIANT> (  BooleanAssertion() ";" )+ <ASS_END>
     }

     void CheckClause() :
     {}
     {
         <CHECK> ( BooleanAssertion() ";" )+ <ASS_END>
     }

     void RescueClause() :
     {}
     {
         <RESCUE>  [ ( RescueCatch() ";" )+ ] | [ RescueRetry() ] <ASS_END>
     }

     void RescueCatch() :
     {}
     {
         "catch" "(" FormalParameter() ")" "{" ( BlockStatement() | RetryStatement() )* "}"
     }

     void  RescueRetry() :
     {}
     {
         RetryStatement() ";"
     }

Code Generation Functions for Rescue-Retry

Node for Rescue-Retry

    public Object visit(RescueCatch node, Object vi) {

        /* RescueCatch ::= ("catch" "(" FormalParameter ")")? "{" ( BlockStatement | RetryStatement )* "}" */
        boolean retry = false;

        if (node.getFirstToken().image == "catch") {
            FormalParameter p = (FormalParameter)node.jjtGetChild(0).jjtAccept(this,vi);
            ((VInfo)vi).rescues.addElement(p);
            // look for a retry (important info for code generation !)
            for (int i = 1; i < node.jjtGetNumChildren(); i++)
            if (node.jjtGetChild(i).hasId(JJTRETRYSTATEMENT)) {
                retry = true;
            }
        } else
        {
            Class type = ClassPool.getClass("RuntimeException");
            String name = new String();
            name = "e";
            FormalParameter p = new FormalParameter();
            p.setType(type);
            p.setName(name);
            p.setModifier(0);
            System.out.println(p.toString());
            ((VInfo)vi).rescues.addElement(p);
            // look for a retry (important info for code generation !)
            for (int i = 0; i < node.jjtGetNumChildren(); i++)
            if (node.jjtGetChild(i).hasId(JJTRETRYSTATEMENT)) {
                retry = true;
            }
        }

        if (retry) ((VInfo)vi).retries.addElement(Boolean.TRUE); 
         else ((VInfo)vi).retries.addElement(Boolean.FALSE); 
        return null;
    }

Output Functions for Rescue-Retry

    public Object visit(RescueCatch node, Object vinfo) {

        VInfo vi = (VInfo)vinfo;
        out.println();
        out.print(vi.method_indent+"\t");
        // print "catch" "("
        printComments(node.getFirstToken(),vi);
        out.print("catch");

        if (node.getFirstToken().image != "catch") {
            out.print("( RuntimeException e )");
            // print "{"
            print(node.getFirstToken(),vi);
            for (int i = 0; i < node.jjtGetNumChildren(); i++) {
                node.jjtGetChild(i).jjtAccept(this,vi);
            }
        } else
        {
            print(node.getFirstToken().next,vi);
            print((SimpleNode)node.jjtGetChild(0),vi);
            // print ")"
            print(node.jjtGetChild(0).getLastToken().next,vi);
            // print "{"
            print(node.jjtGetChild(0).getLastToken().next.next,vi);
            for (int i = 1; i < node.jjtGetNumChildren(); i++) {
                node.jjtGetChild(i).jjtAccept(this,vi);
            }
        }

        if (!vi.m.getRetries()[vi.rescueCounter].equals(Boolean.TRUE)) {
            out.println();
            out.print(vi.method_indent+"\t\tthrow "+vi.m.getRescues()[vi.rescueCounter].getName()+";"); 
            print(node.getLastToken(),vi);
        } else
        {
            print(node.getLastToken(),vi);
        }

        vi.rescueCounter++;
        return null;
    } 

    public Object visit(RetryStatement node, Object vi) {

        printSpecials(node.getFirstToken(),vi);
        out.print("flagRetry = true; continue Retry;");
        return null;
    }

Buffer.jass

    package jass.examples;

    public class Buffer implements Cloneable {

        protected int in,out;

        protected Object[] buffer;

        public Buffer() {
            buffer = new Object[0];
        }

        public Buffer (int anzahl) {
            /** require anzahl > 0; **/
            buffer = new Object[anzahl];
            /** ensure buffer.length == anzahl; **/
        }

        public boolean empty() {
            return in - out == 0;
            /** ensure changeonly{}; **/
        }

        public boolean full() {
            return in - out == buffer.length;
            /** ensure changeonly{}; **/    
        }

        public void add(Object o) {
            /** require [valid_object] o != null; [buffer_not_full] !full(); **/
            buffer[in % buffer.length] = o;
            in++;
            /** ensure changeonly{in,buffer}; Old.in == in - 1; **/
        }

        public Object remove() {
            /** require [buffer_not_empty] !empty(); **/
            Object o = buffer[out % buffer.length];
            out++;
            return o;
            /** ensure changeonly{out}; [valid_object] Result != null; **/
        }

        public boolean contains(Object o) {
            /** require o != null; **/
            boolean found = false;
            for (int i = 0; i < buffer.length; i++) 
            /** invariant 0 <= i && i <= buffer.length; **/
            /** variant buffer.length - i **/
            if (buffer[i].equals(o)) found = true;
            return found;
            /** ensure changeonly{}; **/    
        }

        protected Object clone() {
            /* Use the Objects clone method. This is enough cause only in and out are refered with Old expressions. */
            Object b = null;
            try {
                b = super.clone();
            }
            catch (CloneNotSupportedException e){;}
            return b;
        }

        /** invariant [range] 0 <= in - out && in - out <= buffer.length; 
        [valid_content] buffer.length == 0 || (forall i : {out%buffer.length .. in%buffer.length-1} # buffer[i] != null); **/

    }

UnlimitedBuffer.jass

    package jass.examples;

    public class UnlimitedBuffer extends Buffer implements jass.runtime.Refinement {

        protected java.util.Vector v = new java.util.Vector();

        public boolean empty() {
            return v.size() == 0;
            /** ensure changeonly{}; **/
        }

        public boolean full() {
            return false;
            /** ensure changeonly{}; **/
        }

        public synchronized void add(Object o) {
            /** require [valid_object] o != null; **/
            v.addElement(o);
            /** ensure Old.v.size() == v.size()-1;**/
        }

        public synchronized Object remove() {
            /** require [buffer_not_empty] !empty(); **/
            Object o = v.elementAt(0);
            v.removeElementAt(0);
            return o;
            /** ensure [valid_object] Result != null; **/
        }

        public synchronized boolean contains(Object o) {
            /** require o != null; **/
            return v.contains(o);
            /** ensure changeonly{}; **/    
        }

        protected Object clone() {
            java.util.Vector h = new java.util.Vector();
            for (int i = 0; i < v.size(); i++) h.addElement(v.elementAt(i));
            UnlimitedBuffer ub = new UnlimitedBuffer();
            ub.v = h;
            return ub;
        }

        private Buffer jassGetSuperState() {
            Buffer b = new Buffer(v.size()+1);
            b.in = v.size();
            b.out = 0;
            for (int i = 0; i < b.buffer.length-1; i++)
            b.buffer[i] = v.elementAt(i);
            return b;
        }

        /** invariant forall i : { 0 .. v.size()-1 } # v.elementAt(i) != null; **/

    }

BufferTest1.java

    package jass.examples;

    public class BufferTest {

        public static void main (String[] as) {
            UnlimitedBuffer b = new UnlimitedBuffer();
            int i;
            for ( i = 1; i< 1000; i ++ ){
                b.add(new Integer(i));
            }
            for ( i = 1; i<=1000; i++ ) {
                System.out.println(b.remove());
            }
        }

    }

BufferTest2.java

    package jass.examples;

    public class BufferTest {

        static void foo ( Buffer b ) {
            int i;
            for ( i = 1; i< 100; i ++ ){
                b.add(new Integer(i));
            }
            b.add( null );
            for ( i = 1; i<=100; i++ ) 
            {
                Object kk;
                String dds;
                int ii;
                kk = b.remove();
                dds = kk.toString();
                ii = Integer.parseInt( dds.trim() );
                System.out.println(100/ii);
            }
        }

        public static void main (String[] as) {
            UnlimitedBuffer b = new UnlimitedBuffer();
            foo( b );
        }

    }

Consumer.java

    package jass.examples;

    public class Consumer extends Thread {
        protected Buffer cbuffer;
        public Consumer (Buffer buffer) {
            this.cbuffer = buffer;
        }
        public synchronized void run () {
            for (int i = 0;i < 100;) {
                if (!cbuffer.empty()) {
                    System.out.println(cbuffer.remove()+" <--");
                    i++;
                }
            }
        }
    }

Producer.java

    package jass.examples;

    public class Producer extends Thread {
        protected Buffer pbuffer;
        public Producer (Buffer buffer) {
            this.pbuffer = buffer;
        }
        public synchronized void run () { 
            for (int i = 0;i<100;) {
                if (!pbuffer.full()) {
                    System.out.println("--> "+i);       
                    pbuffer.add(new Integer(i++));
                }
            }
        }
    }

ProducerConsumer.java

    package jass.examples;

    public class ProducerConsumer {
        protected static Producer  producer;
        protected static Consumer  consumer;
        protected static Buffer    buffer;
        public static void main (String[] args) {
            buffer = new Buffer(7);
            producer = new Producer(buffer);
            consumer = new Consumer(buffer);
            producer.start();
            consumer.start();
        }
    }

Server.jass

    package jass.examples;

    public class Server {
        protected Buffer TxBuffer;
        protected Buffer RxBuffer;
        protected int j;

        public Server (Buffer buffer1, Buffer buffer2) {
            this.TxBuffer = buffer1;
            this.RxBuffer = buffer2;
        }

        public void Transmit () { 
            for (int i = 0;i<10;) {
                Attempt_Transmission("TestMessage");
                i++;
            }
            Check_Ack();
            /** rescue 
            {
                System.out.println("Transmit Messages Lost- Attempting Retransmit");
                retry;
            }; **/
        }

        public void Check_Ack () {
            for (int i = 0;i<10;) {
                Receive_Ack();
                i++;
            }
        }

        private void Attempt_Transmission(String TxString) {
            if (!TxBuffer.full()) {
                System.out.println("--> TestMessage");
                TxBuffer.add(new String("TestMessage"));
            }
        }

        private void Receive_Ack() {
         // if(!RxBuffer.empty()) {
                System.out.println("<-- Ack");
                RxBuffer.remove();
         // }
         // else throw(java.lang.RuntimeException);
        }
    }

Client.java

    package jass.examples;

    public class Client {
        protected Buffer cbuffer;
        protected int j;

        public Client (Buffer buffer) {
            this.cbuffer = buffer;
        }

        public void Init () {
            for (int i = 0;i < 5;) {
                cbuffer.add(new String("ack") );
                i++;
            }
        }
    }

ClientServer.java

    package jass.examples;

    public class ClientServer {
        protected static Client client;
        protected static Server server;
        protected static Buffer TxBuffer;
        protected static Buffer AckBuffer;

        public static void main (String[] args) {
            TxBuffer = new Buffer(10);
            AckBuffer = new Buffer(10);
            server = new Server(TxBuffer, AckBuffer);
            client = new Client(AckBuffer);
            client.Init();
            server.Transmit();
        }
    }




CS 655 University of Virginia
CS 655: Programming Languages
uvacs655@egroups.com