Is it possible to exclude field-sets in AspectJ

Tags: , ,



Is it possible to exclude fiel-sets in AspectJ pointcuts, so instrumentation does not stumble over final fields in Java 11?

When weaving the following aspect (the full example is here: https://github.com/DaGeRe/aspect-final-example):

@Pointcut("!within(de.aspectjtest..*)")
public void notWithinAspect() {
}

@Pointcut("!set(private * *)")
public void noSet() {
    
}

@Around("notWithinAspect() && noSet()")
public Object aroundStuff(final ProceedingJoinPoint thisJoinPoint, final EnclosingStaticPart thisEnclosingJoinPoint)
        throws Throwable {
    System.out.println("=== Call: " + thisJoinPoint.getSignature() + " " + thisJoinPoint.getKind());
    System.out.println(thisJoinPoint.getSourceLocation() + " " + thisJoinPoint.getStaticPart());
    System.out.println(thisJoinPoint.toLongString());
    return thisJoinPoint.proceed();
}

into

class FinalFieldConstructorExample {
    private final Integer parameters = 5;
    public Integer getParameters() {
        return parameters;
    }
}

public class MainWithError {
    public static void main(String[] args) {
        FinalFieldConstructorExample example = new FinalFieldConstructorExample();
        System.out.println(example.getParameters());
    }
}

by java -cp target/test-0.1-SNAPSHOT.jar -javaagent:../aspect/target/aspectjtest-0.1-SNAPSHOT.jar de.test.MainWithError I get

Exception in thread "main" java.lang.IllegalAccessError: Update to non-static final field de.test.FinalFieldConstructorExample.parameters attempted from a different method (init$_aroundBody2) than the initialize
r method <init>                                                                                                                                                                                                    
        at de.test.FinalFieldConstructorExample.init$_aroundBody2(MainWithError.java:5)                                                                                                                            
        at de.test.FinalFieldConstructorExample$AjcClosure3.run(MainWithError.java:1)                                                                                                                              
        at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:167)                                                                                                                               
        at de.aspectjtest.ExampleAspect.aroundStuff(ExampleAspect.java:27)                                                                                                                                         
        at de.test.FinalFieldConstructorExample.<init>(MainWithError.java:3)                                                                                                                                       
        at de.test.MainWithError.init$_aroundBody2(MainWithError.java:15)                                                                                                                                          
        at de.test.MainWithError$AjcClosure3.run(MainWithError.java:1)                                                                                                                                             
        at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:167)                     
        at de.aspectjtest.ExampleAspect.aroundStuff(ExampleAspect.java:27)                                                                                                                                         
        at de.test.MainWithError.main_aroundBody10(MainWithError.java)        
        at de.test.MainWithError$AjcClosure11.run(MainWithError.java:1)                                                                                                                                            
        at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:167)                     
        at de.aspectjtest.ExampleAspect.aroundStuff(ExampleAspect.java:27)     
        at de.test.MainWithError.main(MainWithError.java:15)

when I execute it with OpenJDK 11 (when setting everything to Java 8, it works fine). When removing the final modifier from FinalFieldConstructorExample and the && noSet() from the pointcut, it works fine and the output contains

=== Call: Integer java.lang.Integer.valueOf(int) method-call
MainWithoutError.java:5 call(Integer java.lang.Integer.valueOf(int))
call(public static java.lang.Integer java.lang.Integer.valueOf(int))
=== Call: Integer de.test.NonFinalFieldConstructorExample.parameters field-set
MainWithoutError.java:5 set(Integer de.test.NonFinalFieldConstructorExample.parameters)
set(private java.lang.Integer de.test.NonFinalFieldConstructorExample.parameters)

Therefore, I suppose the set-call (having a getKind of field-set, which seems not to be present in OpenJDK 8) to a static field is the reason of the problem. Is there any way of excluding it from AspectJ instrumentation (or working around the problem)? The documentation (https://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html#primitive-pointcuts) states that get can be used in a Pointcut, but I did not find a way to specify final, and even if I add noSet, it seems to be somehow touched and the error appears.

Answer

I think you are hitting AspectJ issue #563709. The error message is the same and so is the fact that it works on Java 8 but not 11 (probably 9+).

So as a workaround for now you want to avoid around-advising constructors. Either you exclude them via

@Around("notWithinAspect() && noSet() && !(execution(*.new(..)))")

or, considering the fact that your advice only does something before proceed(), just change the advice type:

@Before("notWithinAspect() && noSet()")
public void beforeStuff(final JoinPoint thisJoinPoint, final EnclosingStaticPart thisEnclosingJoinPoint) {
  System.out.println("=== Call: " + thisJoinPoint.getSignature() + " " + thisJoinPoint.getKind());
  System.out.println(thisJoinPoint.getSourceLocation() + " " + thisJoinPoint.getStaticPart());
  System.out.println(thisJoinPoint.toLongString());
}

If for whatever reason you need @Around and cannot refactor it into a @Before + @After pair generally, you can keep it with the above exclusion of constructor executions and add a separate @Before + @After advice pair just for the constructors.


Update:

Excluding constructors or using only @Before works, but is not usable for my use case (method execution duration monitoring)

Well, then how about this workaround, globally replacing @Around by pairs of @Before + @After? You may even notice that your log now also shows additional preinitialization and initialization pointcuts which formerly were not captured by the around advice because for those pointcut types around is not supported. Here is my MCVE:

package de.scrum_master.app;

public class FinalFieldConstructorExample {
  private final Integer parameters = 5;

  public Integer getParameters() {
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {}
    return parameters;
  }
}
package de.scrum_master.app;

public class MainWithError {
  public static void main(String[] args) {
    FinalFieldConstructorExample example = new FinalFieldConstructorExample();
    System.out.println(example.getParameters());
  }
}
package de.scrum_master.aspect;

import java.util.Stack;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MyAspect {
  private ThreadLocal<Stack<Long>> startTimeTL = ThreadLocal.withInitial(() -> new Stack<>());

  @Pointcut("within(de.scrum_master.aspect..*)")
  public void withinAspect() {}

  @Before("!withinAspect()")
  public void beforeStuff(final JoinPoint thisJoinPoint) {
    startTimeTL.get().push(System.currentTimeMillis());
  }

  @After("!withinAspect()")
  public void afterStuff(final JoinPoint thisJoinPoint) {
    System.out.println(thisJoinPoint + " -> " + (System.currentTimeMillis() - startTimeTL.get().pop()));
  }
}

The console log would look like this:

staticinitialization(de.scrum_master.app.MainWithError.<clinit>) -> 1
staticinitialization(de.scrum_master.app.FinalFieldConstructorExample.<clinit>) -> 0
preinitialization(de.scrum_master.app.FinalFieldConstructorExample()) -> 0
call(Integer java.lang.Integer.valueOf(int)) -> 0
set(Integer de.scrum_master.app.FinalFieldConstructorExample.parameters) -> 0
execution(de.scrum_master.app.FinalFieldConstructorExample()) -> 1
initialization(de.scrum_master.app.FinalFieldConstructorExample()) -> 1
call(de.scrum_master.app.FinalFieldConstructorExample()) -> 2
get(PrintStream java.lang.System.out) -> 0
call(void java.lang.Thread.sleep(long)) -> 100
get(Integer de.scrum_master.app.FinalFieldConstructorExample.parameters) -> 0
execution(Integer de.scrum_master.app.FinalFieldConstructorExample.getParameters()) -> 100
call(Integer de.scrum_master.app.FinalFieldConstructorExample.getParameters()) -> 100
5
call(void java.io.PrintStream.println(Object)) -> 1
execution(void de.scrum_master.app.MainWithError.main(String[])) -> 103

P.S.: Are you aware of the fact that for woven methods and constructors you are logging both call and execution for the same method/constructor?



Source: stackoverflow