Bug 135865 - [annotations][itds] declare @method is broken for call()
Summary: [annotations][itds] declare @method is broken for call()
Status: NEW
Alias: None
Product: AspectJ
Classification: Tools
Component: Compiler (show other bugs)
Version: DEVELOPMENT   Edit
Hardware: PC Windows XP
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: aspectj inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2006-04-10 09:19 EDT by Andrew Clement CLA
Modified: 2013-06-24 11:03 EDT (History)
1 user (show)

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-04-10 09:19:05 EDT
From a post on the user list, we might have a bug in this area (I wouldnt be too surprised...) :
===========

Hello,

I think I have found a bug on the weaving process, related to annotations
introduction. I don't know exactly how to explain the problem but it seems to
be something because of the order the compiler does the weaving.

The code is as follows:

Aspect:

public aspect Aspect {

       declare @method:
               !@Annotation * (@Annotation *..*).*(..) :
                       @Annotation("introduced");

       pointcut anotado(Annotation b) :
               call(@Annotation * *..*.*(..)) &&
               @annotation(b);

       pointcut anotadoTopo(Annotation b) :
               anotado(b) &&
               !cflowbelow(anotado(Annotation));

       pointcut anotadoNãoTopo(Annotation b, Annotation bTopo) :
               anotado(b) &&
               cflowbelow(anotadoTopo(bTopo));

       before(Annotation b, Annotation bTopo) :
               anotadoNãoTopo(b, bTopo) {
               out.println("Non-top:");
               out.println("\tJoin point: " + thisJoinPointStaticPart);
               out.println("\tEnclosing join point: " + thisEnclosingJoinPointStaticPart);
               out.println("\tAnnotation: " + b);
               out.println("\tTop annotation: " + bTopo);
       }

       before(Annotation b) :
               anotadoTopo(b) {
               out.println("Top:");
               out.println("\tJoin point: " + thisJoinPointStaticPart);
               out.println("\tEnclosing join point: " + thisEnclosingJoinPointStaticPart);
               out.println("\tAnnotation: " + b);
       }
}

Testing Classes:

public class A {

       @Annotation("A.foo") void foo() {
               new B().foo();
       }
       public static void main(String[] args) {
               new A().foo();
       }

}

@Annotation("B")
public class B {

       // The Annotation is injected here!
       void foo() {
       }
}

If we use the option "clean" in the menu project, the output is:

Top:
       Join point: call(void teste.A.foo())
       Enclosing join point: execution(void teste.A.main(String[]))
       Annotation: @teste.Annotation(value=A.foo)

Next, if we go to any class, change it and save it, the result is:

Top:
       Join point: call(void teste.A.foo())
       Enclosing join point: execution(void teste.A.main(String[]))
       Annotation: @teste.Annotation(value=A.foo)
Non-top:
       Join point: call(void teste.B.foo())
       Enclosing join point: execution(void teste.A.foo())
       Annotation: @teste.Annotation(value=introduced)
       Top annotation: @teste.Annotation(value=A.foo)

It seems that the before advices are weaved before the ITD of the annotation,
which means that the before never finds the method annotated (actually, it
hasn't been annotated before, because it will be so later). Am I right?

Best regards,

Paulo Zenida

P.S. If you haven't understood properly the example, you can find the full
source in
https://svn.ci.iscte.pt/aop-tests/strange-feature/WeavingBugs/trunk/
Comment 1 Andrew Clement CLA 2006-04-10 16:24:44 EDT
Initial triage...

Complete program:
===========8<==============
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) @interface Ann {String value();}

aspect Aspect {

  // Methods with out the Ann annotation but in an Ann annotated type get Ann
  declare @method: !@Ann * (@Ann *).*(..) : @Ann("introduced");

  // Call to an annotated method
  pointcut annotated(Ann b) : call(@Ann * *(..)) && @annotation(b);

  // Top level call to an annotated method
  pointcut annotatedTop(Ann b) : annotated(b) && !cflowbelow(annotated(Ann));

  // Non top level call
  pointcut annotatedNotTop(Ann b, Ann bTopo) : 
    annotated(b) && cflowbelow(annotatedTop(bTopo));

  before(Ann b, Ann bTopo) :
    annotatedNotTop(b, bTopo) {
    System.out.println("Non-top:");
    System.out.println("\tJoin point: " + thisJoinPointStaticPart);
    System.out.println("\tEnclosing join point: " + thisEnclosingJoinPointStaticPart);
    System.out.println("\tAnn: " + b);
    System.out.println("\tTop annotation: " + bTopo);
  }

  before(Ann b) :
    annotatedTop(b) {
    System.out.println("Top:");
    System.out.println("\tJoin point: " + thisJoinPointStaticPart);
    System.out.println("\tEnclosing join point: " + thisEnclosingJoinPointStaticPart);
    System.out.println("\tAnn: " + b);
  }
}

public class A {
  @Ann("A.foo") void foo() { new B().foo(); }
  public static void main(String[] args) { new A().foo(); }
}

@Ann("B") class B {
  // The Ann is injected here!
  void foo() { }
}
===========8<==============

Problem is:  if method foo in type B is annotated directly, rather than through dec @method then the program when executed shows two matches:

Top:
        Join point: call(void A.foo())
        Enclosing join point: execution(void A.main(String[]))
        Ann: @Ann(value=A.foo)
Non-top:
        Join point: call(void B.foo())
        Enclosing join point: execution(void A.foo())
        Ann: @Ann(value=foo)
        Top annotation: @Ann(value=A.foo)

If the program is compiled adding the annotation via dec @method then you only see one:
Top:
        Join point: call(void A.foo())
        Enclosing join point: execution(void A.main(String[]))
        Ann: @Ann(value=A.foo)
Comment 2 Andrew Clement CLA 2006-05-17 08:07:47 EDT
ok, I have created proper failing testcases with a much simplied set of source files (committed into CVS).  The problem is an ordering problem, suppose you are weaving advice into class A depending on whether it makes calls to annotated methods in B.  Suppose you have an aspect X that applies annotations to methods in B.

If you compile them in the order: X,B,A then it all works, the declare @method/field are applied to B before weaving A and so you match as expected.
If you compile them in the order: X,A,B then by the time you are weaving A we haven't yet added the annotations to the methods in B and your advice doesn't match.

Unlike other ordering problems, this can't be solved by ensuring some hierarchy is processed top-down - since these types are unrelated in that sense.

The problem arises because declare @method/ctor/field are treated as 'special' - kind of semi type/shadow mungers.  If they were proper type mungers they'd be getting done up front when the type hierarchy is fixed up.  If they were shadow mungers then they'd not have an affect on pointcut matching.

This seems to reveal a case where they need to be treated as full type mungers and done up front.
Comment 3 Andrew Clement CLA 2007-10-24 09:47:56 EDT
re-investigate for 1.5.4 but might get bumped to 1.6.0 when sized
Comment 4 Andrew Clement CLA 2013-06-24 11:03:38 EDT
unsetting the target field which is currently set for something already released