Bug 126167 - [@AspectJ] @AJ around advice does not behave like code style
Summary: [@AspectJ] @AJ around advice does not behave like code style
Status: RESOLVED FIXED
Alias: None
Product: AspectJ
Classification: Tools
Component: Compiler (show other bugs)
Version: DEVELOPMENT   Edit
Hardware: PC Windows XP
: P2 critical (vote)
Target Milestone: 1.5.2   Edit
Assignee: aspectj inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2006-02-02 06:29 EST by Andrew Clement CLA
Modified: 2006-06-26 04:23 EDT (History)
0 users

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Andrew Clement CLA 2006-02-02 06:29:17 EST
Note: This could be just a doc thing, where we need to document the limitations that annotation style around advice has compared to code style.

A user raised an issue on the list, here is 

He looked at this documentation:

http://www.eclipse.org/aspectj/doc/released/adk15notebook/ataspectj-pcadvice.html

and was looking how to use around advice and ProceedingJoinPoint.  I just tried the same aspect too:

import org.aspectj.lang.*;
import org.aspectj.lang.annotation.*;

@Aspect
     public class ProceedAspect {

       @Pointcut("call(* setAge(..)) && args(i)")
       void setAge(int i) {}

       @Around("setAge(i)")
       public Object twiceAsOld(ProceedingJoinPoint thisJoinPoint, int i) {
         System.err.println("advice running");
         return thisJoinPoint.proceed(new Object[]{i*2}); 
       }

}


public class Foo {
  int a;
  public void setAge(int i) {
     System.err.println("Setting age to "+i);
     a=i;
  }

  public static void main(String[]argv) {
    new Foo().setAge(5);
  }
}

> java Foo
advice running
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer
        at Foo.setAge_aroundBody1$advice(Foo.java:112)
        at Foo.main(Foo.java:8)

Before it will work, you need to supply the target in the proceed call array in the advice:

@Around("setAge(i)")
       public Object twiceAsOld(ProceedingJoinPoint thisJoinPoint, int i) {
         return thisJoinPoint.proceed(new Object[]{thisJoinPoint.getTarget(),i*2}); //using Java 5 autoboxing
       }

>java Foo
advice running
Setting age to 10

Maybe thats just something to document... but then let's change the pointcut from call to execution:

@Aspect
public class ProceedAspect {

  @Pointcut("execution(* setAge(..)) && args(i)")
  void setAge(int i) {}

 @Around("setAge(i)")
 public Object twiceAsOld(ProceedingJoinPoint thisJoinPoint, int i) {
   System.err.println("advice running");
   return thisJoinPoint.proceed(new Object[]{thisJoinPoint.getTarget(),i*2});
 }
}

>java Foo
Exception in thread "main" java.lang.ClassCastException: Foo can not be converted to int
        at org.aspectj.runtime.internal.Conversions.intValue(Conversions.java:57)
        at Foo.setAge_aroundBody1$advice(Foo.java:113)
        at Foo.setAge(Foo.java:1)
        at Foo.main(Foo.java:11)

oh ... let's now remove that getTarget() call we put in the advice:

>java Foo
advice running
Setting age to 10


I know many of the problems here are that we can't do compile time type checking of the object array passed to proceed in the source - we would have to generate sophisticated code to handle it at runtime.  There are numerous combinations, all failing in different ways so I tried to rationalise it and coded 11 testcases in code style and @AJ style to see what happens.  It seems in all but the most basic cases, we get into trouble.  I'm particularly worried about cases like those above where the code in the advice depends directly on the matched join point kind...

Anyway, the tests are all checked in, see AtAroundTests.
Comment 1 Adrian Colyer CLA 2006-05-04 03:41:37 EDT
I've also run into a number of situations where proceeding with arguments in @AJ style does not behave correctly.

This needs to be addressed for 1.5.2.
Comment 2 Andrew Clement CLA 2006-05-04 11:00:17 EDT
just a crazy thought (must be the heat stroke I got on my run...) - if replacing the target type for the proceed, perhaps we should have a second proceed method on PJP, so we have

proceed(Object[])
and
proceed(Object newtarget,Object[])

... just wondering if that might make processing easier - it will be more explicit in the advice what the programmer is trying to achieve...
Comment 3 Andrew Clement CLA 2006-06-21 09:48:07 EDT
I'm trying to decide the best thing to do here.  At the moment it looks like you have to supply the target if the joinpoint you match on has a 'this' regardless of whether the pointcut related to your advice uses 'this()'.  

The explosion in the example code in the first example in this bug are (i believe) consequences of this/target being equivalent at the execution join point.

It would seem to make sense that the new this/target have to be supplied if they are used in the pointcut (i.e. you might be wanting to replace them).

I thought I liked the idea of the two argument proceed() for these kinds of cases as it will allow us some rudimentary checking in the compiler - but is it unnecessarily coupling the pointcut and the advice?  For example (pseudocode... half code style, have @AJ style...)

pointcut p(Object o,int i): call(* something(..)) && this(o) && args(i);

void around(Object o,int i): p(o,i) {
  proceed(o,37);
}

the two arg variant is only in use there because p() uses this() - if p() was changed to:

pointcut p(Object o,int i): call(* something(..)) && args(o,i);

then proceed() taking two arguments is an error - we have tightly coupled the pointcut and the advice.

This suggests the single Object array form of proceed is still the best thing to do.

The decision then is whether the arguments to the proceed are based on what is available at the joinpoint or based on what was bound in the pointcut.

From the developers notebook, the intent seems to have been the latter, but based on the current implementation it is the former.
Comment 4 Andrew Clement CLA 2006-06-22 12:02:07 EDT
Adrian - what particular use case do you think causes problems?  I have fixed all the problems I've highlighted in this bug and examples in the developers notebook, is there something else?

As I mentioned, sensible rules are:

1. if you bind 'this()' you must supply it (the same or unchanged) as the first entry in the object array
2. if you bind 'target()' you must supply it (the same or unchanged) as the next entry in the object array
3. you need to then supply all the arguments expected at the join point.

Subsetting the arguments (like with code style) can be 'banned' (it may work, it may not but its unreliable) and reordering their sequence because you reordered them in the advice signature can be 'banned' (bunch of missing infrastructure).

I think that gives us a sensible definition of how to use it... any more elaborate and we may end up being forced to generate all the tedious bytecode that verifies the object array at runtime and produces nice errors...
Comment 5 Andrew Clement CLA 2006-06-23 09:39:14 EDT
all committed.  Doc changes are in, 30 new testcases.  I think all the original confusion and complexity arose because of this:

- whether you had to supply this/target in the object array to proceed was based on whether the matched join point had a this/target and whether this/target were the same (they are for execution jps for example).  It made no difference whether you bound them or not in your pointcut...

If you didn't know these rules, you might be failing to supply the extra parameter when it was needed or supplying an extra one when it wasnt required, causing the rest of the arguments you supply to be out of step with what is expected at the join point...   even worse, if you matched a 'selection' of join points that did or did not have a this/target, it'd be tough to get the proceed call correct

It's much more predictable now!
Comment 6 Andrew Clement CLA 2006-06-26 04:23:39 EDT
done all I plan to do with this for 1.5.2 - future problems we can cover with new bugs.