470 likes | 710 Views
Contracts for Java (work in progress). Matthias Felleisen Rice University Houston, Texas. Components and Contracts. Components, Java, and Contracts Problems with Contracts The Meaning of Contracts Compiling Contracts. The Component Utopia. Programming in a Component World (1).
E N D
Contracts for Java (work in progress) Matthias Felleisen Rice University Houston, Texas
Components and Contracts • Components, Java, and Contracts • Problems with Contracts • The Meaning of Contracts • Compiling Contracts
Programming in a Component World (1) The market delivers interchangeable components with external connectors, ready to be hooked up.
Programming in a Component World (2) Programmers wire together components.
Programming in a Component World (3) Sometimes they add some adapter code.
Programming in a Component World (4) They build new components from old ones and a few adapters for distribution.
What’s Needed for a Component World? • need flexible means of programming and wiring components together • need tools for finding components • need quality assurance and “sanctions” against “black sheep”
Specifying and Wiring Components • Classes are bad: • Mixins: connectors are external • Modules are bad: • Functors: connectors are external • Units: connectors are external, graph-based • Industry’s wiring standards • Objects • COM, DCOM, and/or CORBA
Finding Components • Industry: “in documentation we trust” and “good luck” • Academia: use types and module signatures to find the missing link • DiCosmo (1996 …) • Felleisen (1983)
“Warranties” for Components • Industry: “We didn’t do anything.” • Academia: Types Uber Alles • Contracts: From Eiffel to iJava
Contracts for “Commercial” Components 8. NO WARRANTIES. Microsoft expressly disclaims any warranty for the SOFTWARE PRODUCT. THE SOFTWARE PRODUCT AND ANY RELATED DOCUMENTATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OR MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NONINFRINGEMENT. THE ENTIRE RISK ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE PRODUCT REMAINS WITH YOU.
“Warranties” for Contracts • syntactic level: types • semantic level 1: functionality • semantic level 2: concurrency • quality of service level: unexplored
Eiffel: Programming by Contract • Components are classes • Contracts are pre- and post-conditions (assertions) on methods • Contract enforcement attempts to pinpoint the culprit for error signals iContract: Eiffel for Java
A Simple Example class Q implements Queue { Queue enq(Object X) { ... // post: !this.empty() … Queue deq() { … // pre: !this.empty() … boolean empty() { … } … } // Good Client: Queue q = new Q(); q.enq(X).deq() … // Bad Client: Queue q = new Q; q.deq().enq(X) … Eiffel blames this call
Problems with Call-backs some_a.m2() A B some_b.m(some_a) “Java is higher-order”
A Complex Example (1) class Q implements Queue { Queue enq(Object X) { ... // post: !this.empty() // effect: o.onEnq(this) Queue deq() { … // pre: !this.empty() // effect: o.onDeq(this) void register(Observer o) { ... // effect: o.init() // please: a “good” Observer … class GoodO implements Observer { void init() { … } Queue onEnq(Queue q){ ... // post: !q.empty() Queue onDeq(Queue q){ ... // pre: q.empty() … }
A Complex Example (2) class Q implements Queue { Queue enq(Object X) { ... // post: !this.empty() // effect: o.onEnq(this) Queue deq() { … // pre: !this.empty() // effect: o.onDeq(this) void register(Observer o) { ... // effect: o.init() // please: a “good” Observer … class BadO implements Observer { void init() { … display … } Queue onEnq(Queue q) { q.deq() … } Queue onDeq(Queue q) { q.enq(X) … } … } No Warranty on state of q after call !
A Complex Example (3) puts bad observer together with queue Client BadObserver Who’s to blame? Queue … but BadObserver violates the “informal” contract iContract blames Q
What We Learned • Contracts should be a part of the class interface • Contracts should have a semantics so that we can determine whether a run-time system blames the appropriate component
Contract Java • extend Java interfaces • keep Java semantics • inter-operate with existing Java (easily)
The Language of Contracts: Pre/Post Conditions interface DoubleRoot { double getValue(); double sqrt(); @pre { this.getValue () >= 0.0 } @post { abs(sqrt * sqrt - this.getValue()) < 0.01 } contracts are boolean Java expressions thisrefers to the current object sqrtrefers to the result of the method call
Incompleteness of Assertion Language interface Stack { void push(Object o); @post “o is at top of stack” Object pop(); @pre { !this.empty() } boolean empty(); } interface Stack { void push(Object o); @post: { this.top() == o } Object pop(); @pre { !this.empty() } boolean empty(); Object top(); @pre { !this.empty() } }
Completeness: • … neither possible nor necessary • some properties aren’t computable • for some, the interfaces are too poor • state important invariants (safety) [see Rosenblum1995]
What are the Design Problems? • classes implement multiple interfaces • substitutability • weakening & strengthening conditions • implied interface inheritance
Multiple Interfaces: Meet one Pre class Cimplements I, J {} interface I { void out(int in); @pre { in < 0 } } interfaceJ { void out(int in); @pre { in > 0 } } class Client{ C c = new C(); ……... … ((I)c).m(xxx); … ……… … ((J)c).m(yyy); … }
Multiple Interfaces: Meet one Post class Cimplements I, J {} interface I { int out(); @post { out < 0 } } interfaceJ { int out(); @post { out > 0 } } class Client{ C c = new C(); ……... … ((I)c).m(); … ……… … ((J)c).m(); … } deal with objects on a per interface basis -- principle of least astonishment!
Substitutability (1) interface SimpleCounter { int getValue(); void dec(); void inc(); } interface PositiveCounter { int getValue(); void dec(); @pre { this.getValue() > 0 } void inc(); } class Counter implements SimpleCounter, PositiveCounter
Substitutability (2) class Factory { SimpleCounter make() { return new Counter(); } } class Client { void m(SimpleCounter sc) { sc.dec(); } } class Main { … Client c = new Client(); Factory f = new Factory(); c.m((PositiveCounter) c.make() ); … } undetected violation
Substitutability (3) • Who is guilty? • How do we detect it? class Proxy_Counter extends Counter { PositiveCounter c; Proxy_Counter(PositiveCounter x) { c = x; } void dec() { if (theValue < 0) Violation.error(“Main violated PositiveCounter”); super.dec(); } }
Substitutability (4) • proxy classes violate object equality (==) of (explicit and implicit) casts • fixing this turns object equality into an expensive method • a true fix requires class loader modifications in addition (legacy libraries)
Weakening & Strengthening “Post-s” factory pattern interface I { I make(); } class C implements I { … } interface J extends I { I make(); @post { make instanceof J } } class D extends C implements J { … } • it’s desirable • it’s okay • weakening could be done, too
Weakening & Strengthening “Pre-s” extensible visitor pattern interface I { Object visit(Visitor_I t); } interface J extends I { Object visit(Visitor_I t); @pre { t instanceofVisitor_J } } • it’s desirable • it causes trouble: • require all implementations of J to satisfy pre • use proxies as before • weakening should be done; also causes problems
Implied Interface Inheritance (1) class C implements I { … } class D extends C implements J , I { … } class C implements I { … } class D extends C implements J { … }
Implied Interface Inheritance (2) interface Array { … void update(int ix, Object o) @pre { x < this.getSize() } } interface Resize_Array { … void update(int x, Object o) } class AC implements Array class Resize_AC extends AC implements Resize_Array Java forces a precondition on Resize_AC’s update if viewed as an Array --- better: eliminate implied interface inheritance from Java
The Idea • elaborate contracts away • check interface hierarchy • contracts in interfaces become methods in implementations • method calls are re-directed
Source with Contracts interface Queue { boolean empty(); Queue enq(Object X); @post { !this.empty() } Queue deq(); @pre { !this.empty() } void register(Observer o); } class Q implements Queue { boolean empty(); Queue enq(Object X) { … } Queue deq() { … } void register(Observer o) { … } }
Elaborated Target (1) interface Queue { boolean empty(); Queue enq(Object X); @post { !this.empty() } Queue deq(); @pre { !this.empty() } void register(Observer o); } class Q implements Queue { boolean empty(); Queue enq_Queue(Object X) { Object o = this.enq(X); if (this.empty()) error(“Queue violated”); return o; } Queue enq(Object X) { … } … <to be continued>
Elaborated Target (2) interface Queue { boolean empty(); Queue enq(Object X); @post { !this.empty() } Queue deq(); @pre { !this.empty() } void register(Observer o); } … Queue deq_Queue(String source) { if (this.empty()) error(source); this.deq(); } … }
Elaborated Target (3) class O implements Observer { void onEnq(Queue q) { q.deq_Queue (“O violates Queue”); } … } class O implements Observer { void onEnq(Queue q) { q.deq(); } … }
Elaboration with Multiple Interfaces interface I { S m(T x); @pre … @post … } interface J { S m(T x); @pre… @post … } class C implements I, J { … } class C implements I, J { S m_I(T x, String source) { … } S m_J(T x, String source) { … } S m(T x) { … } … }
Combining Multiple “Post-s” interface I { S m(T x); @post post_I } interface J extends I { S m(T x); @post post_J } class C implements J { … } class C implements J { S m_J(T x, String source) { … test post_I and post J … } S m(T x) { … } … }
Summary and Conclusion • Adding contracts to Java is a compromise. • It brings out weaknesses in Java design. • Future work is to design a variant of Java that is more accommodating to contracts.
The End Joint work with Robby Findler Thanks to Matthew Flatt and Clemens Szyperski for discussions.