Search code examples
javabyte-buddyjavaagentsjava-security-manager

How to prevent Lambda expressions defined in a Java agent break the application it is attached to?


If I declare this advice in my agent:

public static class SequenceAdvice {
    @Advice.OnMethodEnter
    static void enter(@Advice.This Object thiz, 
                      @Advice.Origin Method method, 
                      @Advice.AllArguments Object... args) {
        StackWalker walker = 
            StackWalker.getInstance(RETAIN_CLASS_REFERENCE);

        walker.forEach(sf -> 
            System.out.println(sf.getClassName() + "." + sf.getMethodName())
        );
    }
}

as javac compiles the lambda expression into a private method (at least in OpenJDK 11):

public class SequenceAgent$SequenceAdvice {
  ...
  private static void lambda$enter$0(java.lang.StackWalker$StackFrame);
  ...
}

when the agent is attached to a program and the program is executed, it causes the program to crash:

Exception in thread "main" java.lang.IllegalAccessError: 
  class DemoController tried to access private method
    SequenceAgent$SequenceAdvice.lambda$enter$0(
       Ljava/lang/StackWalker$StackFrame;)V 
         (DemoController and SequenceAgent$SequenceAdvice 
          are in unnamed module of loader 'app')
    at DemoController.getDemos(DemoController.java)
    at DemoMain.main(DemoMain.java:13)

Ideally I prefer not to use objects instead of lambda expressions to workaround this:

public static class SequenceAdvice {

    public static Consumer<StackWalker.StackFrame> SF_CONSUMER = 
        new Consumer<>() {
            @Override
            public void accept(StackWalker.StackFrame sf) {
                System.out.println(sf.getClassName() + "." + sf.getMethodName());
            }
    };

    @Advice.OnMethodEnter
    static void enter(@Advice.This Object thiz, 
                      @Advice.Origin Method method, 
                      @Advice.AllArguments Object... args) {
        StackWalker walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);

        walker.forEach(SF_CONSUMER);
    }
}

A custom permissive security policy does not seem to resolve this error:

grant {
    permission java.security.AllPermission;
};

Is there a way to temporarily disable this category of security checks (e.g. "access to private method")?


Solution

  • You cannot use lambda expressions from an advice. The lambda expression will be a part of the advice class that is not exposed to the target class. Instead, you will need to define a utility class that defines the lambda expressions code in public methods and reference those methods as method references.

    You must then either:

    1. add this class to the bootstrap class loader via Instrumentation.
    2. add this class to the class loader of the instrumented class via Byte Buddy's Injector.

    This way, the references are available to the instrumented class and can be executed.