Join point interfaces (JPIs) are contracts between aspects and advised code. Pieces of advice do not refer explicitly to the advised code anymore because of they are pointcut free. From now on, a piece of advice contains a bound JPI instead of a pointcut expression. Pointcut expressions are now defined as part of //exhibits clauses//. An exhibits clause must be defined as part of the advised code. Also, an exhibit clause contains a bound JPI
Due to our semantics a pointcut expression, defined as part of an exhibits clause, must select join points in a invariant way considering the bound JPI signature.
====== Motivation ======
We devise JPIs as a compiler extension of AspectBench compiler for AspectJ. In our current implementation we reuse some existing features such as pointcut matcher. That means that all pointcut expressions that are defined as part of exthibits clause will behave in the same way that in AspectJ. This decision looked good at first glance, but was later found to be faulty. The problem with the pointcut matcher is in certain situations join points are selected in a covariant way respect of type definitions which defeats our semantics (invariant selection join point selection).
The pointcut matcher elements which in certain situations select join points in a covariant way are //args//, //this// and //target//. //Args//, //this// and //target// are Pointcut Designators (PCDs) which expose the context of the selected join point. In AspectJ when an identifier is used with this PCDs, join points are selected in a covariant way (based on the type of the identifier). For example.
jpi void Call(Number n);
class C{
exhibits void Call(Number l) : //(1)
call(void *(..)) && args(l);
public static void doSomething(Integer x){...}
public static void doSomethingElse(Float x){...}
public static void main(string[] args){
doSomething(2); //(*)
doSomethingElse(2.3); //(*)
}
}
The pointcut expression defines as part of the exhibits clause (1) will capture the join points (*) in a covariant way. That means a) join points(*) will be captured by the pointcut expression, and b) the following advice will be able to manipulate the arguments of those join points as if they were Number.
aspect A{
void around Call(Number z){
proceed(new Integer(4));
}
}
You might see that this program will raise a ClassCastException when the second selected join point is executed.
====== Solution ======
In order to have a pointcut matcher that knows our semantics, we need to define three new PCDs: //argsInv//, //thisInv//, //targetInv//. Those PCDs will select join points only in an invariant way.
====== Implementation ======
The implementation of invariant versions of //arg//, //this// and //target// PCDs needs to modify our compiler extension in the following parts: //parser//, //AST//, //Front-end//, //Back-end// and //matcher//.
First, //argsInv//, //thisInv//, //targetInv// will be introduced as new AST nodes and also they will be added in the parser definition of our compiler extension. Second, we need to add the keywords //argsInv//, //thisInv//, //targetInv// to the lexer as pointcut keywords.
Finally, we will create //ArgVarInv//, //ThisVarInv// and //TargetVarInv// as Back-end nodes. Those nodes will implement in the //matchesAt// method the invariant type semantics. Apparently, we only need to specify the following expression to select join points in an invariant way:
CheckType.construct(cv,sootType) --> CheckTypeInv.construct(cs, sootType) //to check types in an invariant way
====== Appendix ======
In this section we show some examples which defeat the JPI's type system (also AspectJ) by using PCDs that use variant semantics in types (args, this, target).
jpi void MethodCall(AbstractCommand ac);
abstract class AbstractCommand{
public void exec(){}
}
class UndoableCommand extends AbstractCommand{}
class UndoCommand extends AbstractCommand{
exhibits void MethodCall(AbstractCommand ac) :
execution(void exec()) && this(ac); //variant PCD definition
@Override
public void exec(){}
}
public aspect TypeVariance{
void around MethodCall(AbstractCommand ac){
proceed(new UndoableCommand());
}
public static void main(String[] args){
UndoCommand uc = new UndoCommand();
uc.exec();
}
}
The output is:
Running test 1655: abctests/jpi/Type Variance
Commandline: abc -d abctests/jpi -warn-unused-advice:off -ext abc.ja.jpi abctests/jpi/TypeVariance.java
ac
InvocationTargetException while trying to run compiled class: java.lang.ClassCastException: UndoableCommand cannot be cast to UndoCommand
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at abc.testing.TestCase.runTest(TestCase.java:413)
at abc.testing.Main.doCase(Main.java:288)
at abc.testing.Main.main(Main.java:122)
Caused by: java.lang.ClassCastException: UndoableCommand cannot be cast to UndoCommand
at TypeVariance.inline$0$around$0(TypeVariance.java:23)
at UndoCommand.exec(TypeVariance.java)
at TypeVariance.main(TypeVariance.java:28)
... 7 more
FAIL: Test 1655: "abctests/jpi/Type Variance" failed in 7019ms, memory usage: 4512216.
Interestingly, we could replicate the same problem of ClassCastException by using Interface's types in pointcut definitions. We think that this fact is interestingly because //StrongAspectJ// supposed to solve this problem. But apparently they did not consider //interfaces// as part of type system. For example:
public aspect Interfaces {
public static void main(String[] args){
BasicThread bt = new BasicThread();
ComplexThread ct = new ComplexThread();
bt.run();
ct.run();
}
void around(Runnable r) : call(void run()) && target(r){
proceed(new DummyThread());
}
}
class BasicThread implements Runnable{
public void run() {}
}
class ComplexThread implements Runnable{
public void run() {}
}
class DummyThread implements Runnable{
public void run() {}
}
The execution output of this program is the following:
aspects.BasicThread
at aspects.Interfaces$AjcClosure1.run(Interfaces.aj:1)
at aspects.Interfaces.ajc$around$aspects_Interfaces$1$3b1f0542proceed(Interfaces.aj:1)
at aspects.Interfaces.ajc$around$aspects_Interfaces$1$3b1f0542(Interfaces.aj:13)
at aspects.Interfaces.main(Interfaces.aj:8)