Search code examples
javastack-overflowaopaspectj

AspectJ stackoverflowerror due to infinite loop


I am instrumenting a 3rd-party application - and have defined the following pointcut

@Aspect
public class MyAspect {

    @Pointcut("execution(some.app.Application.new(..))")
    public void appCreation() {
    }

    @After("appCreation()")
    public void afterCreation() {
        MyUtil.doSomething();
    }  
}

Now the problem comes from the fact that MyUtil.doSomething() eventually calls the constructor of some.app.Application - this is of course then "detected" by my aspect and MyUtil.doSomething() is called again and it calls .... you got the point.

I tried to put a && !within(MyAspect) in the Pointcut definition but it did not help. Is there any way to suppress the detection of the pointcut in the cases where MyUtil is further up the call stack?

If it is relevant: MyUtil.doSomething() is not calling the application constructor directly but after a couple of intermediate calls


Solution

  • Okay, first let us reproduce your problem:

    Java classes:

    package de.scrum_master.app;
    
    public class Application {
        public Application() {
            System.out.println("Creating application");
        }
    
        public static void main(String[] args) {
            new Application();
        }
    }
    
    package de.scrum_master.app;
    
    public class MyUtil {
        public static void doSomething() {
            System.out.println("Doing something");
            new Application();
        }
    }
    

    Problematic aspect causing recursion:

    package de.scrum_master.aspect;
    
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    
    import de.scrum_master.app.MyUtil;
    
    @Aspect
    public class MyAspect {
        @Pointcut("execution(*..Application.new(..))")
        public void appCreation() {}
    
        @After("appCreation()")
        public void afterCreation() {
            MyUtil.doSomething();
        }
    }
    

    Console log:

    Creating application
    Doing something
    Creating application
    Doing something
    (...)
    Exception in thread "main" java.lang.StackOverflowError
        at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source)
        at java.nio.charset.CharsetEncoder.encode(Unknown Source)
        at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)
        at sun.nio.cs.StreamEncoder.write(Unknown Source)
        at java.io.OutputStreamWriter.write(Unknown Source)
        at java.io.BufferedWriter.flushBuffer(Unknown Source)
        at java.io.PrintStream.write(Unknown Source)
        at java.io.PrintStream.print(Unknown Source)
        at java.io.PrintStream.println(Unknown Source)
        at de.scrum_master.app.MyUtil.doSomething(MyUtil.java:5)
        at de.scrum_master.aspect.MyAspect.afterCreation(MyAspect.aj:16)
        at de.scrum_master.app.Application.<init>(Application.java:6)
        at de.scrum_master.app.MyUtil.doSomething(MyUtil.java:6)
        (...)
    

    Now how do we avoid the problem? We need to avoid executing the advice if it is already being executed, i.e. if it is in the current control flow or cflow(). For advice execution there even is a special pointcut adviceexecution().

    Improved aspect avoiding infinite recursion:

    package de.scrum_master.aspect;
    
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    
    import de.scrum_master.app.MyUtil;
    
    @Aspect
    public class MyAspect {
        @Pointcut("adviceexecution() && within(MyAspect)")
        public void myAdvice() {}
    
        @Pointcut("execution(*..Application.new(..))")
        public void appCreation() {}
    
        @After("appCreation() && !cflow(myAdvice())")
        public void afterCreation() {
            MyUtil.doSomething();
        }
    }
    

    Console log after correction:

    Creating application
    Doing something
    Creating application
    

    One final remark: So far I was not questioning your application logic. Now I am: Does it really make sense to create another application from within a utility method if one was already created? I guess that even though this was a fun AOP exercise for me, the real problem is in the Java code, not in the AspectJ code.