raphw / byte-buddy

Runtime code generation for the Java virtual machine.
https://bytebuddy.net
Apache License 2.0
6.27k stars 804 forks source link

Failed to install interceptor after intercepting constructor #1434

Closed kylixs closed 1 month ago

kylixs commented 1 year ago

We were going to support multiple JavaAgents with ByteBuddy, but we ran into some trouble. First, installing a constructor interceptor on one AgentBuilder and then installing a method interceptor on another AgentBuilder was failed.
We need help.

https://github.com/apache/skywalking-java/pull/521#issuecomment-1533332570

How about two skywalking agents for one target? I don't expect we would do two interceptors for one target inside skywalking. The other one is fork distribution. Will that work?

Unfortunately, there is a problem with intercepting constructors: Once a constructor is intercepted on one agent, other methods of the class that are intercepted on another agent will fail. The second agent could not find the constructor's dependent auxiliary class (created in the first agent) when parsing the target class. The auxiliary class actually exists in the classloader, but couldn't get the bytecode, even using ClassFileLocator. ForInstrumentation: agentBuilder.with(ClassFileLocator.ForInstrumentation.fromInstalledAgent(...) )

Reproduce:

  1. Checkout demo git clone git@github.com:kylixs/bytebuddy-intercept-demo.git
  2. Run test mvn clean test -Dtest=InterceptTest4

Test Code:

  ByteBuddyAgent.install();

  // install transformer
  installConstructorInterceptor(TARGET_CLASS_NAME, 1);
  installMethodInterceptor(TARGET_CLASS_NAME, SAY_HELLO_METHOD, 1);

  // load target class
  try {
      callBizFoo(1);
  } catch (Throwable e) {
      e.printStackTrace();
  } finally {
      // check interceptors
      checkConstructorInterceptor(1);
      checkMethodInterceptor(SAY_HELLO_METHOD, 1);
  }
  protected void installMethodInterceptor(String className, String methodName, int round, boolean deleteDuplicatedFields) {
      String interceptorClassName = METHOD_INTERCEPTOR_CLASS + "$" + methodName + "$" + round;
      String fieldName = "_sw_delegate$" + methodName + round;
      new AgentBuilder.Default()
              .with(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST)
              .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
              //.with(ClassFileLocator.ForInstrumentation.fromInstalledAgent(InterceptTest1.class.getClassLoader()))
              .type(ElementMatchers.named(className))
              .transform((builder, typeDescription, classLoader, module, protectionDomain) -> {
                          if (deleteDuplicatedFields) {
                              builder = builder.visit(new MyAsmVisitorWrapper());
                          }
                          return builder
                                  .method(ElementMatchers.nameContainsIgnoreCase(methodName))
                                  .intercept(MethodDelegation.withDefaultConfiguration()
                                          .to(new InstMethodsInter(interceptorClassName, classLoader), fieldName))
                                  ;
                      }
              )
              .with(new AgentBuilder.Listener.Adapter() {
                  @Override
                  public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
                      System.err.println(String.format("Transform Error: interceptorClassName: %s, typeName: %s, classLoader: %s, module: %s, loaded: %s", interceptorClassName, typeName, classLoader, module, loaded));
                      throwable.printStackTrace();
                  }
              })
              .installOn(ByteBuddyAgent.install());
  }

  protected void installConstructorInterceptor(String className, int round) {
      String interceptorClassName = CONSTRUCTOR_INTERCEPTOR_CLASS + "$" + round;
      String fieldName = "_sw_delegate$constructor" + round;
      new AgentBuilder.Default()
              .with(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST)
              .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
              //.with(ClassFileLocator.ForInstrumentation.fromInstalledAgent(InterceptTest1.class.getClassLoader()))
              .type(ElementMatchers.named(className))
              .transform((builder, typeDescription, classLoader, module, protectionDomain) -> {
                          return builder
                                  .constructor(ElementMatchers.any())
                                  .intercept(SuperMethodCall.INSTANCE.andThen(
                                          MethodDelegation.withDefaultConfiguration().to(
                                                  new ConstructorInter(interceptorClassName, classLoader), fieldName)
                                  ));
                      }
              )
              .with(new AgentBuilder.Listener.Adapter() {
                  @Override
                  public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
                      System.err.println(String.format("Transform Error: interceptorClass:%s, typeName: %s, classLoader: %s, module: %s, loaded: %s", interceptorClassName, typeName, classLoader, module, loaded));
                      throwable.printStackTrace();
                  }
              })
              .installOn(ByteBuddyAgent.install());
  }

Error:

[INFO] Running com.demo.case1.InterceptTest4
Transform Error: interceptorClassName: methodInterceptorClass$sayHello$1, typeName: com.demo.BizFoo, classLoader: sun.misc.Launcher$AppClassLoader@75b84c92, module: null, loaded: false
net.bytebuddy.pool.TypePool$Resolution$NoSuchTypeException: Cannot resolve type description for com.demo.BizFoo$auxiliary$nSryTzXN      
        at net.bytebuddy.pool.TypePool$Resolution$Illegal.resolve(TypePool.java:191)
        at net.bytebuddy.pool.TypePool$Default$WithLazyResolution$LazyTypeDescription.delegate(TypePool.java:1112)
        at net.bytebuddy.description.type.TypeDescription$AbstractBase$OfSimpleType$WithDelegation.getModifiers(TypeDescription.java:8535)
        at net.bytebuddy.description.ModifierReviewable$AbstractBase.matchesMask(ModifierReviewable.java:618)
        at net.bytebuddy.description.ModifierReviewable$AbstractBase.isStatic(ModifierReviewable.java:329)
        at net.bytebuddy.description.type.TypeDescription$AbstractBase.getInnerClassCount(TypeDescription.java:8154)
        at net.bytebuddy.pool.TypePool$Default$LazyTypeDescription$GenericTypeToken$Resolution$Raw$RawAnnotatedType.getDeclaredAnnotations(TypePool.java:3818)
        at net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Validator$ForTypeAnnotations.isValid(TypeDescription.java:1671)
        at net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Validator$ForTypeAnnotations.onNonGenericType(TypeDescription.java:1660)
        at net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Validator$ForTypeAnnotations.onNonGenericType(TypeDescription.java:1575)
        at net.bytebuddy.description.type.TypeDescription$Generic$OfNonGenericType.accept(TypeDescription.java:3744)
        at net.bytebuddy.dynamic.scaffold.InstrumentedType$Default.validated(InstrumentedType.java:1756)
        at net.bytebuddy.dynamic.scaffold.MethodRegistry$Default.prepare(MethodRegistry.java:519)
        at net.bytebuddy.dynamic.scaffold.inline.RebaseDynamicTypeBuilder.toTypeWriter(RebaseDynamicTypeBuilder.java:220)
        at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$UsingTypeWriter.make(DynamicType.java:4057)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:12225)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:12160)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1800(AgentBuilder.java:11869)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:12567)        
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:12507)        
        at java.security.AccessController.doPrivileged(Native Method)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doPrivileged(AgentBuilder.java)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:12069)
        at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
        at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:473)
        at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
        at com.demo.case1.AbstractInterceptTest.callBizFoo(AbstractInterceptTest.java:24)
        at com.demo.case1.InterceptTest4.test4(InterceptTest4.java:20)
        ...

ConstructorInterceptorClass: constructorInterceptorClass$1, target: com.demo.BizFoo@20e3c449, args: [Tom]
ConstructorInterceptorClass: constructorInterceptorClass$1, target: com.demo.BizFoo@20e3c449, args: []
100
ConstructorInterceptorClass: constructorInterceptorClass$1, target: com.demo.BizFoo@2c58dcb1, args: [Smith]
Hello to Joe from Smith
org.opentest4j.AssertionFailedError: Int value is unexpected ==> expected: <101> but was: <100>
        at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
        at org.junit.jupiter.api.AssertionUtils.failNotEqual(AssertionUtils.java:62)
        at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:150)
        at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:542)
        at com.demo.case1.AbstractInterceptTest.callBizFoo(AbstractInterceptTest.java:30)
        at com.demo.case1.InterceptTest4.test4(InterceptTest4.java:20)
    ...

Found interceptor: constructorInterceptorClass$1
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.701 s <<< FAILURE! - in com.demo.case1.InterceptTest4
[ERROR] com.demo.case1.InterceptTest4.test4  Time elapsed: 0.69 s  <<< FAILURE!
org.opentest4j.AssertionFailedError: Not found interceptor: methodInterceptorClass$sayHello$1 ==> expected: <true> but was: <false>     
        at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
        at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:40)
        at org.junit.jupiter.api.Assertions.assertTrue(Assertions.java:193)
        at com.demo.case1.AbstractInterceptTest.checkMethodInterceptor(AbstractInterceptTest.java:37)
        at com.demo.case1.InterceptTest4.test4(InterceptTest4.java:26)
        ...
raphw commented 1 year ago

Can you try to disable validation? Create your validators as:

new AgentBuilder.Default(new ByteBuddy().with(Validation.DISABLED)))

Other than that, ideally you use Advice only and not delegation what avoids helper types which lead to these problems.

kylixs commented 1 year ago

Can you try to disable validation? Create your validators as:

new AgentBuilder.Default(new ByteBuddy().with(Validation.DISABLED)))
Transform Error: interceptorClassName: methodInterceptorClass$sayHello$1, typeName: com.demo.BizFoo, classLoader: jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dc, module: unnamed module @1937eaff, loaded: false
net.bytebuddy.pool.TypePool$Resolution$NoSuchTypeException: Cannot resolve type description for com.demo.BizFoo$auxiliary$uyBgM3WG
    at net.bytebuddy.pool.TypePool$Resolution$Illegal.resolve(TypePool.java:191)
    at net.bytebuddy.pool.TypePool$Default$LazyTypeDescription$TokenizedGenericType.toErasure(TypePool.java:6901)
    at net.bytebuddy.pool.TypePool$Default$LazyTypeDescription$GenericTypeToken$Resolution$Raw$RawAnnotatedType.of(TypePool.java:3781)
    at net.bytebuddy.pool.TypePool$Default$LazyTypeDescription$GenericTypeToken$Resolution$Raw$RawAnnotatedType$LazyRawAnnotatedTypeList.get(TypePool.java:3882)
    at net.bytebuddy.pool.TypePool$Default$LazyTypeDescription$GenericTypeToken$Resolution$Raw$RawAnnotatedType$LazyRawAnnotatedTypeList.get(TypePool.java:3827)
    at net.bytebuddy.pool.TypePool$Default$LazyTypeDescription$LazyMethodDescription$LazyParameterDescription.getType(TypePool.java:7663)
    at net.bytebuddy.description.method.ParameterDescription$AbstractBase.asToken(ParameterDescription.java:186)
    at net.bytebuddy.description.method.ParameterDescription$AbstractBase.asToken(ParameterDescription.java:135)
    at net.bytebuddy.description.method.ParameterList$AbstractBase.asTokenList(ParameterList.java:96)
    at net.bytebuddy.description.method.MethodDescription$AbstractBase.asToken(MethodDescription.java:892)
    at net.bytebuddy.description.method.MethodDescription$AbstractBase.asToken(MethodDescription.java:447)
    at net.bytebuddy.description.method.MethodList$AbstractBase.asSignatureTokenList(MethodList.java:112)
    at net.bytebuddy.dynamic.scaffold.inline.RebaseDynamicTypeBuilder.toTypeWriter(RebaseDynamicTypeBuilder.java:226)
    at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$UsingTypeWriter.make(DynamicType.java:4057)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:12225)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:12160)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1800(AgentBuilder.java:11869)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$Java9CapableVmDispatcher.run(AgentBuilder.java:12647)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$Java9CapableVmDispatcher.run(AgentBuilder.java:12579)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doPrivileged(AgentBuilder.java)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:12103)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport.transform(Unknown Source)
    at java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
    at java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:563)
    at java.base/java.lang.ClassLoader.defineClass1(Native Method)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
    at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:802)
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:700)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:623)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    at com.demo.case1.AbstractInterceptTest.callBizFoo(AbstractInterceptTest.java:26)
kylixs commented 1 year ago

Other than that, ideally you use Advice only and not delegation what avoids helper types which lead to these problems.

I want to bridge to origin ConstructorInter via Advice, how to pass context into ConstructorAdvice?

builder
  .constructor(ElementMatchers.any())
  .intercept(Advice.to(ConstructorAdvice.class))
builder
    .constructor(ElementMatchers.any())
    .intercept(SuperMethodCall.INSTANCE.andThen(
            MethodDelegation.withDefaultConfiguration().to(
                    new ConstructorInter(interceptorClassName, classLoader), fieldName)
    ));
kylixs commented 1 year ago

And intercept constructor by Advice also create a private constructor with helper class, same problem as MethodDelegation:

 public class BizFoo {
     private String name;

     public BizFoo() {
         BizFoo bizFoo = this;
         bizFoo((auxiliary.WTloNYVS)null);
         System.out.println(String.format("ConstructorAdvice: method: %s, args: %s", "public com.demo.BizFoo()", Arrays.asList(new Object[0])));
     }

     private /* synthetic */ BizFoo(auxiliary.WTloNYVS wTloNYVS) {
/* 8*/         this("Tom");
     }

     public BizFoo(String string) {
         BizFoo bizFoo = this;
         String string2 = string;
         bizFoo(string2, null);
         System.out.println(String.format("ConstructorAdvice: method: %s, args: %s", "public com.demo.BizFoo(java.lang.String)",
Arrays.asList(string)));
     }
new AgentBuilder.Default()
        .with(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST)
        .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
        //.with(ClassFileLocator.ForInstrumentation.fromInstalledAgent(InterceptTest1.class.getClassLoader()))
        .type(ElementMatchers.named(className))
        .transform((builder, typeDescription, classLoader, module, protectionDomain) -> {
                    return builder
                            .constructor(ElementMatchers.any())
                            .intercept(Advice.to(ConstructorAdvice.class));
                }
        )
raphw commented 1 year ago

The second agent with Advice should declare disableClassFormatChanges(), I think that should fix it.

kylixs commented 1 year ago

The second agent with Advice should declare disableClassFormatChanges(), I think that should fix it.

Something not expected:

Intercept constructor with Advice in first agent, and then intercept method with Advice and disableClassFormatChanges() in second agent will fail.

  new AgentBuilder.Default()
          .with(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST)
          .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
          .type(ElementMatchers.named(className))
          .transform((builder, typeDescription, classLoader, module, protectionDomain) -> {
                      return builder
                              .constructor(ElementMatchers.any())
                              .intercept(Advice.to(ConstructorAdvice.class));
                  }
          )
          .installOn(ByteBuddyAgent.install());
  new AgentBuilder.Default()
          .disableClassFormatChanges()
          .with(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST)
          .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
          .type(ElementMatchers.named(className))
          .transform((builder, typeDescription, classLoader, module, protectionDomain) -> {
                      return builder
                              .method(ElementMatchers.nameContainsIgnoreCase(methodName))
                              .intercept(Advice.to(InstMethodAdvice.class));
                  }
          )
          .installOn(ByteBuddyAgent.install());
Transform Error: interceptorClassName: methodInterceptorClass$sayHello$2, typeName: com.demo.BizFoo, classLoader: jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dc, module: unnamed module @2c88a3e8, loaded: false
java.lang.IllegalStateException: Cannot call super (or default) method for public java.lang.String com.demo.BizFoo.sayHello(java.lang.String)
    at net.bytebuddy.implementation.SuperMethodCall$Appender.apply(SuperMethodCall.java:133)
    at net.bytebuddy.asm.Advice$Appender$EmulatingMethodVisitor.resolve(Advice.java:12120)
    at net.bytebuddy.asm.Advice$Appender.apply(Advice.java:12073)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyCode(TypeWriter.java:730)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyBody(TypeWriter.java:715)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor$CodePreservingMethodVisitor.visitCode(TypeWriter.java:5502)
    at net.bytebuddy.jar.asm.ClassReader.readMethod(ClassReader.java:1513)
    at net.bytebuddy.jar.asm.ClassReader.accept(ClassReader.java:744)
    at net.bytebuddy.jar.asm.ClassReader.accept(ClassReader.java:424)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:4014)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:2224)
    at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$UsingTypeWriter.make(DynamicType.java:4057)
    at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Delegator.make(DynamicType.java:4007)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:12225)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:12160)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1800(AgentBuilder.java:11869)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$Java9CapableVmDispatcher.run(AgentBuilder.java:12647)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$Java9CapableVmDispatcher.run(AgentBuilder.java:12579)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doPrivileged(AgentBuilder.java)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:12103)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport.transform(Unknown Source)
    at java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
    at java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:563)
    at java.base/java.lang.ClassLoader.defineClass1(Native Method)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
    at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:802)
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:700)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:623)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    at com.demo.case1.AbstractInterceptTest.callBizFoo(AbstractInterceptTest.java:24)
    at com.demo.case1.InterceptTest4.test4(InterceptTest4.java:20)
dogourd commented 1 year ago

The top of the exception stack trace shows that the error comes from MethodRegistry#prepare. This commit c66e3086eb075e attempted to fix this issue. Have you tried validating it with the latest version of ByteBuddy?

kylixs commented 1 year ago

The above tests already use latest version:

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>net.bytebuddy</groupId>
                <artifactId>byte-buddy</artifactId>
                <version>1.14.4</version>
            </dependency>
            <dependency>
                <groupId>net.bytebuddy</groupId>
                <artifactId>byte-buddy-agent</artifactId>
                <version>1.14.4</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
kylixs commented 1 year ago

@raphw I found one way! The bytecode of the helper class can be successfully obtained in an asynchronous thread, avoiding the nested call the transformer. The reason is not clear. Is it related to the JVM retransform class logic?

public class MyClassFileLocator implements ClassFileLocator {

    private final ForInstrumentation.ClassLoadingDelegate classLoadingDelegate;
    private Instrumentation instrumentation;
    private ClassLoader classLoader;
    private ExecutorService executorService = Executors.newFixedThreadPool(1);

    public MyClassFileLocator(Instrumentation instrumentation, ClassLoader classLoader) {
        this.instrumentation = instrumentation;
        this.classLoader = classLoader;
        classLoadingDelegate = ForInstrumentation.ClassLoadingDelegate.ForDelegatingClassLoader.of(classLoader);
    }

    @Override
    public Resolution locate(String name) throws IOException {
        Future<Resolution> future = executorService.submit(() -> getResolution(name));
        try {
            return future.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private Resolution getResolution(String name) {
        ExtractionClassFileTransformer classFileTransformer = new ExtractionClassFileTransformer(classLoader, name);
        try {
            instrumentation.addTransformer(classFileTransformer, true);
            try {
                instrumentation.retransformClasses(new Class[]{classLoadingDelegate.locate(name)});
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } finally {
            instrumentation.removeTransformer(classFileTransformer);
        }

        return classFileTransformer.getBinaryRepresentation() != null ?
                new Resolution.Explicit(classFileTransformer.getBinaryRepresentation()) :
                new Resolution.Illegal(name);
    }

    @Override
    public void close() throws IOException {
    }
}
    AgentBuilder agentBuilder = new AgentBuilder.Default();
    agentBuilder.with(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST)
        .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
        .type(ElementMatchers.named(className))
        .transform((builder, typeDescription, classLoader, module, protectionDomain) -> {
                    return builder
                            .constructor(ElementMatchers.any())
                            .intercept(SuperMethodCall.INSTANCE.andThen(
                                    MethodDelegation.withDefaultConfiguration().to(
                                            new ConstructorInter(interceptorClassName, classLoader), fieldName)
                            ));
                }
        )
        .installOn(ByteBuddyAgent.install());
AgentBuilder agentBuilder = new AgentBuilder.Default();
    agentBuilder.with(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST)
        .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
        .with(new MyClassFileLocator(ByteBuddyAgent.install(), AbstractInterceptTest.class.getClassLoader()))
        .type(ElementMatchers.named(className))
        .transform((builder, typeDescription, classLoader, module, protectionDomain) -> {
                    return builder
                            .method(ElementMatchers.nameContainsIgnoreCase(methodName))
                            .intercept(MethodDelegation.withDefaultConfiguration()
                                    .to(new InstMethodsInter(interceptorClassName, classLoader), fieldName))
                            ;
                }
        )
        .installOn(ByteBuddyAgent.install());
ConstructorInterceptorClass: constructorInterceptorClass$1, target: com.demo.BizFoo@196ae579, args: [Tom]
ConstructorInterceptorClass: constructorInterceptorClass$1, target: com.demo.BizFoo@196ae579, args: []
InstMethodInterceptorClass: methodInterceptorClass$sayHello$1, target: com.demo.BizFoo@196ae579, args: [100], SuperCall: com.demo.BizFoo$auxiliary$Sv6rR26r@41853299, method: public int com.demo.BizFoo.sayHello(int), originResult: 100, finalResult: 101
101
ConstructorInterceptorClass: constructorInterceptorClass$1, target: com.demo.BizFoo@60d40ff4, args: [Smith]
InstMethodInterceptorClass: methodInterceptorClass$sayHello$1, target: com.demo.BizFoo@60d40ff4, args: [Joe], SuperCall: com.demo.BizFoo$auxiliary$xoRUjdbx@27755487, method: public java.lang.String com.demo.BizFoo.sayHello(java.lang.String), originResult: Hello to Joe from Smith, finalResult: Hello to John from Smith
Hello to John from Smith
Found interceptor: constructorInterceptorClass$1
Found interceptor: methodInterceptorClass$sayHello$1
raphw commented 1 year ago

Glad you solved it. Can we close this question then?

kylixs commented 1 year ago

Getting bytecode in asynchronous threads doesn't seem to be perfect. Is there a better way?

raphw commented 1 year ago

What you mean by asynchronous threads and getting bytecode?

kylixs commented 1 year ago
    public Resolution locate(String name) throws IOException {
        Future<Resolution> future = executorService.submit(() -> getResolution(name));
        try {
            return future.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

I worry that locate bytecode of class in single thread from multiple threads may blocking or deadlocking.

raphw commented 1 year ago

Well, this is unfortunately up to the class loader, but as it's a single monitor lookup, it should not cause a dead lock.