Jump to: navigation, search

OTCaveats

Caveats -- Common pitfalls with explanation and solution

Collection.remove seems broken

The following snippet produces an unexpected result:

team class T {
    protected class R playedBy B {}
    ArrayList<B> bases= new ArrayList<B>();
    void test(R r) {
        int oldSize= bases.size();
        bases.add(r);
        bases.remove(r);
        if (oldSize != bases.size())
                throw new RuntimeException("WRONG RESULT WILL OCCUR");
    }
}
  • Reason: The API of Collection and implementing classes is asymmetric:
    boolean  add(E e);
    boolean  remove(Object o);

    This causes the add invocation to lower (OTJLD §2.2) the argument thus adding the associated B instance to the list. However, remove does not know that the list contains Bs, so it tries to remove the role, which is not found in the list.

    This should be seen as a bug in the API of Collection, the OT/J compiler actually behaves as specified.
  • Update: Starting with release 1.3.0M3 the compiler will signal a warning regarding new rule OTJLD §2.2(f) to alert the developer of this situation:
        bases.remove(r);
                     ^
    Assigning a T.R<@tthis[T]> to java.lang.Object is ambiguous: will not apply lowering to B because direct upcasting is sufficient (OTJLD 2.2(f)).
  • Workaround: Before invoking Collection.remove on a collection containing base objects while the object to be removed might be passed as a role, it is necessary to assign the role to a variable of the base type to force lowering:
    ...
        void test(R r) {
            int oldSize= bases.size();
            bases.add(r); // implicitly lowered
            B b= r; // force lowering
            bases.remove(b);
            if (oldSize != bases.size())
                    throw new RuntimeException("WILL NOT HAPPEN");
        }
    ...

    Wicked super calls

    If a method of a base class contains a super call to a method other than the directly overridden method this can produce unexpected results in conjunction with callin method bindings.

    Here is an example of what I call a wicked super call:

    public class B0 {
        void bm() { }
    }
    public class B1 extends B0 {
        void bm2() {
            super.bm(); // wicked super call
        }
    }

    In the simplest case the following aspect will not be triggered:

    public team class T1 {
        protected class R playedBy B1 { // note that B0 is not directly affected
            void rm() { }
            rm <- after bm;
        }
        public static void main(String[] args) {
            new T1().activate();
            new B1().bm2();
        }
    }
    • Explanation: The wicked super call regards the instance as of type B0, thus the aspect binding for type B1 is not triggered.

    Here is a more involved example:

    public class B0 {
        void bm() { }
    }
    public class B1 extends B0 {
        void bm2() {
            super.bm(); // wicked super call
        }
        void bm() {
            bm2();
        }
    }
    public team class T {
        protected class R playedBy B0 {
            callin void rm() {
                base.rm();
            }
            rm <- replace bm;
        }
        public static void main(String[] args) {
            new T().activate();
            new B1().bm();
        }
    }

    Here the OT/J implementation had to choose between two undesirable effects:

    1. originally the wicked super call invoked aspect dispatch, which, however, includes dynamic dispatch back into B1 thus causing an infinite recursion / StackOverflowError along this circle: B1.bm -call-> B1.bm2 -super call-> B0.bm -dynamic dispatch-> B1.bm.
    2. as of r19093 this recursion is fixed (see also ticket #146), but now the wicked super call does no longer trigger the aspect although role R is in this case actually bound to the base class B0.

    The latter behavior is a bug, but within the current weaving strategy no simple solution is available for fixing the bug without the mentioned danger of a StackOverflowError.

    • Workaround: Should you encounter the situation (2.) please try to find another join point that is not a wicked super call.

    Intercept initialization

    When code in a static initialization block (indirectly) invokes a method that is callin-bound, it may happen that the callin binding does not fire.

    public team class MyTeam {
        protected class MyRole playedBy MyBase {
            void log() { System.out.println("here"); }
            log <- after init;
        }
    }
    ...
    public class Main {
        static MyBase aBase = new MyBase();
        static {
            aBase.init();
        }
        public static void main(String[] args) {
            // normal stuff here
        }
    }

    If MyTeam is instantiated and activated via the launch configuration, the method invocation aBase.init() still does not fire the callin log <- after init. The reason is simple: team activation via the launch configuration happens exactly at the start of the main method. The code in the static initializer, however, is executed before invoking main. The same effect would result, if the constructor of MyBase would invoke init (see the immediate initialization of field aBase), because technically both styles of initializing static fields are equivalent.

    • Workaround: Either avoid performing real action in static initializers — the time when they are executed is basically unpredictable any way — or look for other join points to intercept.
    • Variant: Using OT/Equinox a similar situation occurs when trying to intercept the start method of a bundle activator. Also such start method is invoked at an "unpredictable" time and the system is not yet fully initialized. Specifically, if a bundle is adapted by an aspect bundle, the aspect is activated immediately after the base bundle's start method has terminated.
    Similar to static initializers in plain Java, the code in an activator's start method should never perform "real work". This rule is valid independent of Object Teams.