|
jContractor Crash CourseAuthor: Parker AbercrombieLast updated: 1/26/03 ContentsWhat is jContractor?The goal of this tutorial Writing contracts Preconditions Postconditions Invariants What happens when a contract is violated? Separate contract classes Inheritance and interfaces Running jContractor What is jContractorjContractor is a 100% pure Java implementation of Design By Contract for the Java language. Contracts are written as methods that follow a simple naming convention. jContractor provides runtime contract checking by instrumenting the bytecode of classes that define contracts. jContractor can either add contract checking code to class files to be executed later, or it can instrument classes at runtime as they are loaded. All contracts are written in standard Java, so there is no need to learn a special contract specification language. Contracts can be written in the class that they apply too, or in a separate contract class. This allows developers to add contracts to classes for which they do not have source code (3rd party libraries, for example). jContractor understands preconditions, postconditions, and class invariants, with full support for inheritance.The Goal of this TutorialThis tutorial assumes familiarity with Design by Contract, but no knowledge of jContractor. Please read a Design by Contract tutorial if you lack this familiarity. Links are provided on the jContractor web page. This tutorial seeks to provide a very quick and very rough introduction to jContractor; just enough to get people started.Writing contractsContracts in jContractor are written as methods following a naming convention. Each contract method returns a boolean value. True indicates that the contract passed, false indicates that the contract was violated. The discussion below will refer to this example class:class Stack implements Cloneable { private Stack OLD; private Vector implementation; public Stack () { ... } public Stack (Object [] initialContents) { ... } public void push (Object o) { ... } public Object pop () { ... } public Object peek () { ... } public void clear () { ... } public int size () { ... } public Object clone () { ... } private int searchStack (Object o) { ... } } PreconditionsThe a method's precondition is checked when execution enters the method. Constructors are a small exception, because the call to the superclass constructor must be the first statement in a constructor. If such a call is present, it is executed before the precondition is checked. Precondition methods return boolean and take the same arguments as the non-contract method that they correspond to. The method name is given a "_Precondition" suffix.protected boolean push_Precondition (Object o) { return o != null; } private boolean searchStack_Precondition (Object o) { return o != null; }Preconditions for constructors follow the same convention: protected boolean Stack_Precondition (Object [] initialContents) { return (initialContents != null) && (initialContents.length > 0); }Rules about precondtions:
PostconditionsA method's postcondition is checked just before the method returns. Postcondition methods return boolean, and are named with a "_Postcondition" suffix. A postcondition method takes the same arguments as the method that it corresponds to, plus an additional argument of the method's return type (or java.lang.Void for methods that return void). This argument must be the last in the list, and holds the value returned by the method. The recommended name for this argument is "RESULT", but jContractor does not enforce this.protected boolean push_Postcondition (Object o, Void RESULT) { return implementation.contains(o) && (size() == OLD.size() + 1); } protected boolean size_Postcondition (int RESULT) { return RESULT >= 0; }Postconditions for constructors follow the same convention. The return type for constructors is Void. protected boolean Stack_Postcondition (Object [] initialContents, Void RESULT) { return size() == initialContents.length; }Postconditions may refer to the state of the object at method entry through the OLD instance variable. This variable must be declared private, and of the type of the class that contains it (Stack in this case). If a class defines an OLD variable, it must also implement Cloneable and provide a clone() method. When execution
enters a method, a clone of the object will be created and stored in
OLD.
Rules about postcondtions:
InvariantsClass invariants are checked at the entry and exit of every public method in the class. The invariant is defined in a method called "_Invariant" that takes no arguments and returns a boolean. This method must be declared protected.protected boolean _Invariant () { return size() >= 0; }Rules about invariants:
What happens when a contract is violated?When a contract is violated, an error is thrown, usually ending in program termination with an informative error message. The error classes are edu.ucsb.ccs.jcontractor.PreconditionViolationError, edu.ucsb.ccs.jcontractor.PostconditionViolationError, and edu.ucsb.ccs.jcontractor.InvariantViolationError. While nothing prevents you from catching and handling these errors, it is best to think twice before doing so. A contract violation usually points to a bug that should fixed before release, not handled at runtime.Separate contract classesInstead of writing contracts directly into your source code, you can write contract classes. Contract classes are given names with a "_CONTRACT" suffix, and do nothing but define contracts.class Stack_CONTRACT extends Stack { private Stack OLD; private Vector implementation; protected boolean Stack_Postcondition (Object [] initialContents, Void RESULT) { return size() == initialContents.length; } protected boolean Stack_Precondition (Object [] initialContents) { return (initialContents != null) && (initialContents.length > 0); } protected boolean push_Precondition (Object o) { return o != null; } protected boolean push_Postcondition (Object o, Void RESULT) { return implementation.contains(o) && (size() == OLD.size() + 1); } private int searchStack (Object o) { return 0; } private boolean searchStack_Precondition (Object o) { return o != null; } protected boolean _Invariant () { return size() >= 0; } }When jContractor instruments a class, the code for contracts defined in contract class is inserted into the non-contract class. Once there, it will have access to any members and variables in the of the non-contract class. However, to get the compiler to accept the code, it is sometimes necessary to provide fake variables and methods, such as implementation and searchStack(Object) in
the contract class above. These members are never actually used, but
they must be present so that the compiler accepts the code. A
convenient way to get access to non-private fields and methods in the
non-contract class is to make the contract class a subclass of the
non-contract class.
Using contract classes, it is possible to add contracts to classes for which you do not have source code. When a contract is specified in the non-contract class and in a contract class, the two are logical and-ed together. Inheritance and interfacesContracts are inherited, just like methods. When a method is overridden in a subclass, that class may specify its own contracts to modify those on the superclass method. The for rules combining contracts go like this:A subclass method must:
Interfaces may have contracts too (written in contract classes), so contracts can inherit multiply. Contracts from interfaces are logical or-ed with the superclass and subclass contracts in the case of preconditions, and logical-anded in the case of postconditions and invariants, just as you would expect. Using predicate logic expressionsjContractor supports predicate logic quantifiers such as forall and exists using JaQuaL, Java Quantification Library. See the JaQuaL manual for an introduction.Running jContractorUsage: jContractor [options] class
To run your program with contract checking enabled you need only
run jContractor and pass the name of the class to run as a command
line argument. Any command line arguments that appear after the class
name are passed to the $ java jContractor MyProgramIt is also possible to add contract checking code to class files so that they may be run with only a usual Java runtime environment. The jInstrument program is used for this. Invoke it, and pass the name of the class file as an argument. jInstrument will add contract checking code, and write the instrumented file to the directory specified by the "-d" option, or the current directory by default. To use jInstrument with the example above, do this: $ java jInstrument ./MyProgram.class $ java MyProgramjContractor and jInstrument can also be run by the Ant build tool produced by the Apache Jakarta project. The classes edu.ucsb.ccs.jcontractor.extras.jContractorTask and edu.ucsb.ccs.jcontractor.extras.jInstrumentTask provide this capability. Controlling the instrumentation leveljContractor allows the user to specify the level of instrumentation (preconditions only, preconditions and postconditions, or all contracts) for each class in the system. To execute the Foo program checking only preconditions, but preconditions and postconditions in thebar package, and all contracts in class
FooBar , One would enter
$ java jContractor -pre * -post bar.* -all bar.FooBar FoojContractor supports wildcards similar to those used in Java import statements, allowing the user to concisely specify the instrumentation level. The instrumentation levels may also be read from a file. The rational for this system is that for a method's postcondition to be satisfied, the precondition must have been satisfied. And the invariant can only be satisfied if both preconditions and postconditions have been met. So it is senseless to check the postcondition without checking the precondition, or the invariant without preconditions and postconditions. The supported instrumentation levels are:
$ cat Foo.instr * pre bar.* post bar.FooBar all $ java jContractor -f Foo.instr FooInstrumentation levels can be specified on the command line and in a file at the same time. Removing contract methods from compiled classesjContractor contracts have no runtime effect when the class is run without jContractor, but they do increase the size of the bytecode. jInstrument includes a "strip mode" which can automatically remove contract methods from bytecode in order to decrease the file size. The "-s" flag on the command line tells jInstrument to remove contract methods instead of adding calls to them. Obviously, you cannot later instrument the class to check contracts that have been removed. Likewise, you cannot strip contracts from a class that has been instrumented, because the class becomes dependent on its contract methods to run correctly.Here's a simple example: $ javac Test.java $ javap Test Compiled from Test.java public class Test extends java.lang.Object { public Test(); public void foo(); protected boolean foo_Precondition(); protected boolean foo_Postcondition(java.lang.Void); protected boolean _Invariant(); public static void main(java.lang.String[]); } $ java jInstrument -s Test.class $ javap Test Compiled from Test.java public class Test extends java.lang.Object { public Test(); public void foo(); public static void main(java.lang.String[]); }Note that the contract methods have been removed. Instrumentation filters have different meaning when it comes to removing contracts:
|