420 likes | 615 Views
Selective Open Recursion Modular Reasoning about Components and Inheritance. Jonathan Aldrich Carnegie Mellon University Kevin Donnelly Boston University. Outline. The Fragile Base Class Problem Selective Open Recursion Implementation & Analysis
E N D
Selective Open RecursionModular Reasoning aboutComponents and Inheritance Jonathan Aldrich Carnegie Mellon University Kevin Donnelly Boston University
Outline • The Fragile Base Class Problem • Selective Open Recursion • Implementation & Analysis • Discussion
Inheritance and Information Hiding • Parnas’ advice: • Modularize a system to hide information that may change
Inheritance and Information Hiding • Parnas’ advice: • Modularize a system to hide information that may change • Research question • How to hide information in component-based systems with inheritance?
A Challenge: Open Recursion • Recursion • Objects can make self-calls to their own methods
A Challenge: Open Recursion • Recursion • Objects can make self-calls to their own methods • Open Recursion • Self-calls are dynamically dispatched • I.e., target of call is “open” and can be overridden • Beneficial in “template method” design patterns
class Window { void draw() { // draw background this.drawForeground(); } void drawForeground() { … } } class MyWindow { void draw() { super.draw(); } void drawForeground() { super.drawForeground(); ... } ... } Open Recursion Window draw drawFore
class Window { void draw() { // draw background this.drawForeground(); } void drawForeground() { … } } class MyWindow { void draw() { super.draw(); } void drawForeground() { super.drawForeground(); ... } ... } Open Recursion Window draw drawFore Window MyWindow draw draw drawFore drawFore
Hook Methods • Hook methods • Called by this when some event occurs • Intended as extension point for subclasses • Open recursion is necessary
Hook Methods • Hook methods • Called by this when some event occurs • Intended as extension point for subclasses • Open recursion is necessary • Other methods • Perform some task • May be overridden, but has no “event” semantics • Open recursion unnecessary • At best, minor convenience • Has potential to cause the Fragile Base Class Problem
class CountingSet extends Set { private int count; void add(Object elem) { super.add(elem); count++; } void addAll(Collection c){ super.addAll(c); count+=c.size(); } int size() { return count; } // other functions } The Fragile Base Class Problem Set CountingSet addAll addAll add add
class CountingSet extends Set { private int count; void add(Object elem) { super.add(elem); count++; } void addAll(Collection c){ super.addAll(c); count+=c.size(); } int size() { return count; } // other functions } The Fragile Base Class Problem Set CountingSet addAll addAll add add Implicit assumption: Set.addAll does not call Set.add If this assumption is violated (or changed), CountingSet breaks
The Fragile Base Class Problem • Definition (for this instance of FBC) • A class may depend on the calling patterns of a superclass, and break if these are changed
Two Solutions to the FBC • Document open recursion [Kiczales & Lamping, Steyaert et al., Ruby & Leavens] • Exposes information, rather than hiding it • Prohibits natural changes to superclass Use forwarding to avoid open recursion [Bloch,Szyperski] Gives up benefits of open recursion Can we get the benefits of open recursion without the reasoning costs?
Two Solutions to the FBC • Document open recursion [Kiczales & Lamping, Steyaert et al., Ruby & Leavens] • Exposes information, rather than hiding it • Prohibits natural changes to superclass • Use forwarding to avoid open recursion [Bloch,Szyperski] • Gives up benefits of open recursion Can we get the benefits of open recursion without the reasoning costs?
Two Solutions to the FBC • Document open recursion [Kiczales & Lamping, Steyaert et al., Ruby & Leavens] • Exposes information, rather than hiding it • Prohibits natural changes to superclass • Use forwarding to avoid open recursion [Bloch,Szyperski] • Gives up benefits of open recursion • Can we get the benefits of open recursion without the reasoning costs?
Outline • The Fragile Base Class Problem • Selective Open Recursion • Implementation & Analysis • Discussion
Selective Open Recursion Use open recursion only where necessary • Hook methods • Use open modifier on method • Expresses “hook method” intent • All calls to open methods dispatched dynamically • Just as in Java today
Selective Open Recursion Use open recursion only where necessary • Hook methods • Use open modifier on method • Expresses “hook method” intent • All calls to open methods dispatched dynamically • Just as in Java today • Other methods • Calls to non-open methods on this dispatched statically • The only change vs. Java • Hides internal calling patterns from subclasses • Other calls dispatched dynamically • open≠ virtual
class Set { void add(Object o) { // adds an element } void addAll(Collection c){ foreach (Object o in c) this.add(o); } } add is not open, so subclass cannot intercept and depend on the call. Set can change without affecting subclasses. Selective Open Recursion Examples
class Set { void add(Object o) { // adds an element } void addAll(Collection c){ foreach (Object o in c) this.add(o); } } add is not open, so subclass cannot intercept and depend on the call. Set can change without affecting subclasses. class Set { // invoked for each add op. open void add(Object o) { // adds an element } void addAll(Collection c){ foreach (Object o in c) this.add(o); } } add is open, indicating the developer’s intent to expose this method as a semantic event to subclasses. Any changes to class Set must preserve these semantics. Selective Open Recursion Examples
Design Principles • Non-open as default • Only use open recursion when explicitly intended
Design Principles • Non-open as default • Only use open recursion when explicitly intended • Annotate the method, not the call • Design intent is attached to operation
Do you have to change the language? • Coding guidelines • Never call a public method on this • Hook methods should be protected • Non-hook, protected methods should be final • Not our idea • Suggested by Ruby & Leavens • Used extensively in JDK libraries
Do you have to change the language? • Coding guidelines • Never call a public method on this • Hook methods should be protected • Non-hook, protected methods should be final • Not our idea • Suggested by Ruby & Leavens • Used extensively in JDK libraries • However— • This solution relies on programmer discipline • Language integration provides automated checking
Outline • The Fragile Base Class Problem • Selective Open Recursion • Implementation & Analysis • Future work and Conclusion
Implementation in Java • Extension to Barat compiler • Bokowski & Spiegel • Rewrite calls to non-open methods on this • Call a final method with the implementation for the current class • Available at http://www.archjava.org/
Open Recursion Inference • Analysis to compute open annotations • Method m in class C must be open if: • Method m’ in class C’ <= C calls this.m • Class C’’ <= C’ overrides m • Class C’’ inherits or super-calls C’.m’
Open Recursion Inference • Analysis to compute open annotations • Method m in class C must be open if: • Method m’ in class C’ <= C calls this.m • Class C’’ <= C’ overrides m • Class C’’ inherits or super-calls C’.m’ • Results may be imperfect • Assumes whole program is available • Misses open methods that are not overridden • May mark methods open that should be refactored to be non-open
Experiments • Ran open recursion inference • java.* packages in JDK 1.4.2 • Except java.nio, java.sql (see paper)
Experiments • Ran open recursion inference • java.* packages in JDK 1.4.2 • Except java.nio, java.sql (see paper) • Hypotheses • Open recursion is rarely needed • Selective open recursion enables more optimization
Frequency of Open Recursion • 9897 methods in our portion of stdlib • 246 (2.5%) of methods were inferred open
Frequency of Open Recursion • 9897 methods in our portion of stdlib • 246 (2.5%) of methods were inferred open • Maybe the stdlib doesn’t use inheritance • 1394 of these methods are overridden • Only 18% of these are open Open recursion is rarely needed Thus making it selective may enable substantial information hiding
Frequency of Open Recursion • 9897 methods in our portion of stdlib • 246 (2.5%) of methods were inferred open • Maybe the stdlib doesn’t use inheritance • 1394 of these methods are overridden • Only 18% of these are open • Open recursion is rarely needed • Thus making it selective may enable substantial information hiding
Optimization Potential • 22339 calls in our portion of stdlib • 6852 self-calls • 716 to open methods • Must be dynamically dispatched • 6136 to non-open methods • Can be inlined in our proposal (27% of total calls) • Inlining in Java would require private, final, or whole-program analysis
Outline • The Fragile Base Class Problem • Selective Open Recursion • Implementation & Analysis • Future Work and Conclusion
Future Work: Application to Formal Reasoning • Formalizing as Featherweight Java + module system
Future Work: Application to Formal Reasoning • Formalizing as Featherweight Java + module system • Goal: bisimulation-based proof technique for showing contextual equivalence of modules in the presence of inheritance
Future Work: Application to Formal Reasoning • Formalizing as Featherweight Java + module system • Goal: bisimulation-based proof technique for showing contextual equivalence of modules in the presence of inheritance • Use selective open recursion and other techniques to provide more information hiding • i.e. prove more programs equivalent
Conclusion • Open Recursion complicates reasoning • rarely used in practice • Selective open recursion • retains benefits of open recursion where needed • avoids fragile base class problem • can be efficiently inferred • may allow more optimization and reasoning
class Set { void add(Object elem) { // adds an element } void addAll(Collection c) { foreach (Object o in c) this.add(o) } ... class CountingSet extends Set { private int count; void add(Object elem) { super.add(elem); count++; } void addAll(Collection c){ super.addAll(c); count+=c.size(); } int size() { return count; } // other functions } Open Recursion Calls to this are dispatched to subclass Thus subclass can tell exactly when each method is called