Browse Source

Faster type matching (#5724)

* Faster type matching

* make findLoadedClass accessible on java17

* enable jaxrs instrumentation for quarkus test

* fix websphere

* fix muzzle

* javadoc formating

* ignore classes that are know to fail to load for virtual field transforms

* add back jaxrs and jaxws annotation instrumentations

* Apply suggestions from code review

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>

* fix compile error

* comments

* replace deprecated method usage

* add comment

* add an spi to get access to bootstrap proxy from muzzle module

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
Lauri Tulmin 2 years ago
parent
commit
4ad44909ca
18 changed files with 741 additions and 125 deletions
  1. 1 2
      instrumentation/external-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/extannotations/ExternalAnnotationInstrumentation.java
  2. 2 1
      instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/ClassLoaderInstrumentationModule.java
  3. 76 0
      instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/DefineClassInstrumentation.java
  4. 68 0
      javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/DefineClassHelper.java
  5. 6 0
      javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java
  6. 102 0
      javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/DefineClassHandler.java
  7. 19 0
      javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/bootstrap/BootstrapProxyProviderImpl.java
  8. 10 1
      javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/field/FieldBackedImplementationInstaller.java
  9. 19 6
      javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java
  10. 33 0
      javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/util/IgnoreFailedTypeMatcher.java
  11. 32 0
      javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/util/NamedMatcher.java
  12. 5 15
      javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/tooling/CacheProviderTest.groovy
  13. 320 90
      muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java
  14. 5 4
      muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentLocationStrategy.java
  15. 18 2
      muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentTooling.java
  16. 13 0
      muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/BootstrapProxyProvider.java
  17. 3 1
      muzzle/src/test/java/io/opentelemetry/javaagent/tooling/muzzle/AgentLocationStrategyTest.java
  18. 9 3
      testing-common/integration-tests/build.gradle.kts

+ 1 - 2
instrumentation/external-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/extannotations/ExternalAnnotationInstrumentation.java

@@ -6,7 +6,6 @@
 package io.opentelemetry.javaagent.instrumentation.extannotations;
 
 import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
-import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
 import static io.opentelemetry.javaagent.instrumentation.extannotations.ExternalAnnotationSingletons.instrumenter;
 import static java.util.logging.Level.WARNING;
 import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
@@ -100,7 +99,7 @@ public class ExternalAnnotationInstrumentation implements TypeInstrumentation {
 
   @Override
   public ElementMatcher<TypeDescription> typeMatcher() {
-    return hasSuperType(declaresMethod(isAnnotatedWith(traceAnnotationMatcher)));
+    return declaresMethod(isAnnotatedWith(traceAnnotationMatcher));
   }
 
   @Override

+ 2 - 1
instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/ClassLoaderInstrumentationModule.java

@@ -34,6 +34,7 @@ public class ClassLoaderInstrumentationModule extends InstrumentationModule {
     return asList(
         new BootDelegationInstrumentation(),
         new LoadInjectedClassInstrumentation(),
-        new ResourceInjectionInstrumentation());
+        new ResourceInjectionInstrumentation(),
+        new DefineClassInstrumentation());
   }
 }

+ 76 - 0
instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/DefineClassInstrumentation.java

@@ -0,0 +1,76 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.internal.classloader;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+import io.opentelemetry.javaagent.bootstrap.DefineClassHelper;
+import io.opentelemetry.javaagent.bootstrap.DefineClassHelper.Handler.DefineClassContext;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import java.nio.ByteBuffer;
+import java.security.ProtectionDomain;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class DefineClassInstrumentation implements TypeInstrumentation {
+
+  @Override
+  public ElementMatcher<TypeDescription> typeMatcher() {
+    return named("java.lang.ClassLoader");
+  }
+
+  @Override
+  public void transform(TypeTransformer transformer) {
+    transformer.applyAdviceToMethod(
+        named("defineClass")
+            .and(
+                takesArguments(
+                    String.class, byte[].class, int.class, int.class, ProtectionDomain.class)),
+        DefineClassInstrumentation.class.getName() + "$DefineClassAdvice");
+    transformer.applyAdviceToMethod(
+        named("defineClass")
+            .and(takesArguments(String.class, ByteBuffer.class, ProtectionDomain.class)),
+        DefineClassInstrumentation.class.getName() + "$DefineClassAdvice2");
+  }
+
+  @SuppressWarnings("unused")
+  public static class DefineClassAdvice {
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static DefineClassContext onEnter(
+        @Advice.This ClassLoader classLoader,
+        @Advice.Argument(0) String className,
+        @Advice.Argument(1) byte[] classBytes,
+        @Advice.Argument(2) int offset,
+        @Advice.Argument(3) int length) {
+      return DefineClassHelper.beforeDefineClass(
+          classLoader, className, classBytes, offset, length);
+    }
+
+    @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+    public static void onExit(@Advice.Enter DefineClassContext context) {
+      DefineClassHelper.afterDefineClass(context);
+    }
+  }
+
+  @SuppressWarnings("unused")
+  public static class DefineClassAdvice2 {
+    @Advice.OnMethodEnter(suppress = Throwable.class)
+    public static DefineClassContext onEnter(
+        @Advice.This ClassLoader classLoader,
+        @Advice.Argument(0) String className,
+        @Advice.Argument(1) ByteBuffer classBytes) {
+      return DefineClassHelper.beforeDefineClass(classLoader, className, classBytes);
+    }
+
+    @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+    public static void onExit(@Advice.Enter DefineClassContext context) {
+      DefineClassHelper.afterDefineClass(context);
+    }
+  }
+}

+ 68 - 0
javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/DefineClassHelper.java

@@ -0,0 +1,68 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.bootstrap;
+
+import java.nio.ByteBuffer;
+
+public class DefineClassHelper {
+
+  /** Helper class for {@code ClassLoader.defineClass} callbacks. */
+  public interface Handler {
+    DefineClassContext beforeDefineClass(
+        ClassLoader classLoader, String className, byte[] classBytes, int offset, int length);
+
+    void afterDefineClass(DefineClassContext context);
+
+    /** Context returned from {@code beforeDefineClass} and passed to {@code afterDefineClass}. */
+    interface DefineClassContext {
+      void exit();
+    }
+  }
+
+  private static volatile Handler handler;
+
+  public static Handler.DefineClassContext beforeDefineClass(
+      ClassLoader classLoader, String className, byte[] classBytes, int offset, int length) {
+    return handler.beforeDefineClass(classLoader, className, classBytes, offset, length);
+  }
+
+  public static Handler.DefineClassContext beforeDefineClass(
+      ClassLoader classLoader, String className, ByteBuffer byteBuffer) {
+    // see how ClassLoader handles ByteBuffer
+    // https://github.com/openjdk/jdk11u/blob/487c3344fee3502b4843e7e11acceb77ad16100c/src/java.base/share/classes/java/lang/ClassLoader.java#L1095
+    int length = byteBuffer.remaining();
+    if (byteBuffer.hasArray()) {
+      return beforeDefineClass(
+          classLoader,
+          className,
+          byteBuffer.array(),
+          byteBuffer.position() + byteBuffer.arrayOffset(),
+          length);
+    } else {
+      byte[] classBytes = new byte[length];
+      byteBuffer.duplicate().get(classBytes);
+      return beforeDefineClass(classLoader, className, classBytes, 0, length);
+    }
+  }
+
+  public static void afterDefineClass(Handler.DefineClassContext context) {
+    handler.afterDefineClass(context);
+  }
+
+  /**
+   * Sets the {@link Handler} with callbacks to execute when {@code ClassLoader.defineClass} is
+   * called.
+   */
+  public static void internalSetHandler(Handler handler) {
+    if (DefineClassHelper.handler != null) {
+      // Only possible by misuse of this API, just ignore.
+      return;
+    }
+    DefineClassHelper.handler = handler;
+  }
+
+  private DefineClassHelper() {}
+}

+ 6 - 0
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java

@@ -24,6 +24,7 @@ import io.opentelemetry.javaagent.bootstrap.AgentClassLoader;
 import io.opentelemetry.javaagent.bootstrap.AgentInitializer;
 import io.opentelemetry.javaagent.bootstrap.BootstrapPackagePrefixesHolder;
 import io.opentelemetry.javaagent.bootstrap.ClassFileTransformerHolder;
+import io.opentelemetry.javaagent.bootstrap.DefineClassHelper;
 import io.opentelemetry.javaagent.extension.AgentListener;
 import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesConfigurer;
 import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
@@ -120,6 +121,7 @@ public class AgentInstaller {
     Config config = Config.get();
 
     setBootstrapPackages(config);
+    setDefineClassHandler();
 
     // If noop OpenTelemetry is enabled, autoConfiguredSdk will be null and AgentListeners are not
     // called
@@ -209,6 +211,10 @@ public class AgentInstaller {
     BootstrapPackagePrefixesHolder.setBoostrapPackagePrefixes(builder.build());
   }
 
+  private static void setDefineClassHandler() {
+    DefineClassHelper.internalSetHandler(DefineClassHandler.INSTANCE);
+  }
+
   private static void runBeforeAgentListeners(
       Iterable<AgentListener> agentListeners,
       Config config,

+ 102 - 0
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/DefineClassHandler.java

@@ -0,0 +1,102 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.tooling;
+
+import io.opentelemetry.javaagent.bootstrap.DefineClassHelper.Handler;
+import java.nio.charset.StandardCharsets;
+import org.objectweb.asm.ClassReader;
+
+public class DefineClassHandler implements Handler {
+  public static final DefineClassHandler INSTANCE = new DefineClassHandler();
+  private static final ThreadLocal<DefineClassContextImpl> defineClassContext =
+      ThreadLocal.withInitial(() -> DefineClassContextImpl.NOP);
+
+  private DefineClassHandler() {}
+
+  @Override
+  public DefineClassContext beforeDefineClass(
+      ClassLoader classLoader, String className, byte[] classBytes, int offset, int length) {
+    // with OpenJ9 class data sharing we don't get real class bytes
+    if (classBytes == null
+        || (classBytes.length == 40
+            && new String(classBytes, StandardCharsets.ISO_8859_1)
+                .startsWith("J9ROMCLASSCOOKIE"))) {
+      return null;
+    }
+
+    DefineClassContextImpl context = null;
+    // attempt to load super types of currently loaded class
+    // for a class to be loaded all of its super types must be loaded, here we just change the order
+    // of operations and load super types before transforming the bytes for current class so that
+    // we could use these super types for resolving the advice that needs to be applied to current
+    // class
+    try {
+      ClassReader cr = new ClassReader(classBytes, offset, length);
+      String superName = cr.getSuperName();
+      if (superName != null) {
+        Class.forName(superName.replace('/', '.'), false, classLoader);
+      }
+      String[] interfaces = cr.getInterfaces();
+      for (String interfaceName : interfaces) {
+        Class.forName(interfaceName.replace('/', '.'), false, classLoader);
+      }
+    } catch (Throwable throwable) {
+      // loading of super class or interface failed
+      // mark current class as failed to skip matching and transforming it
+      // we'll let defining the class proceed as usual so that it would throw the same exception as
+      // it does when running without the agent
+      context = DefineClassContextImpl.enter(className);
+    }
+    return context;
+  }
+
+  @Override
+  public void afterDefineClass(DefineClassContext context) {
+    if (context != null) {
+      context.exit();
+    }
+  }
+
+  /**
+   * Detect whether loading the specified class is known to fail.
+   *
+   * @param className class being loaded
+   * @return true if it is known that loading class with given name will fail
+   */
+  public static boolean isFailedClass(String className) {
+    DefineClassContextImpl context = defineClassContext.get();
+    return context.failedClassName != null && context.failedClassName.equals(className);
+  }
+
+  private static class DefineClassContextImpl implements DefineClassContext {
+    private static final DefineClassContextImpl NOP = new DefineClassContextImpl();
+
+    private final DefineClassContextImpl previous;
+    private final String failedClassName;
+
+    private DefineClassContextImpl() {
+      previous = null;
+      failedClassName = null;
+    }
+
+    private DefineClassContextImpl(DefineClassContextImpl previous, String failedClassName) {
+      this.previous = previous;
+      this.failedClassName = failedClassName;
+    }
+
+    static DefineClassContextImpl enter(String failedClassName) {
+      DefineClassContextImpl previous = defineClassContext.get();
+      DefineClassContextImpl context = new DefineClassContextImpl(previous, failedClassName);
+      defineClassContext.set(context);
+      return context;
+    }
+
+    @Override
+    public void exit() {
+      defineClassContext.set(previous);
+    }
+  }
+}

+ 19 - 0
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/bootstrap/BootstrapProxyProviderImpl.java

@@ -0,0 +1,19 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.tooling.bootstrap;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.javaagent.tooling.Utils;
+import io.opentelemetry.javaagent.tooling.muzzle.BootstrapProxyProvider;
+
+@AutoService(BootstrapProxyProvider.class)
+public class BootstrapProxyProviderImpl implements BootstrapProxyProvider {
+
+  @Override
+  public ClassLoader getBootstrapProxy() {
+    return Utils.getBootstrapProxy();
+  }
+}

+ 10 - 1
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/field/FieldBackedImplementationInstaller.java

@@ -18,6 +18,8 @@ import io.opentelemetry.javaagent.tooling.HelperInjector;
 import io.opentelemetry.javaagent.tooling.TransformSafeLogger;
 import io.opentelemetry.javaagent.tooling.instrumentation.InstrumentationModuleInstaller;
 import io.opentelemetry.javaagent.tooling.muzzle.VirtualFieldMappings;
+import io.opentelemetry.javaagent.tooling.util.IgnoreFailedTypeMatcher;
+import io.opentelemetry.javaagent.tooling.util.NamedMatcher;
 import java.lang.instrument.Instrumentation;
 import java.util.Collection;
 import java.util.HashSet;
@@ -28,6 +30,7 @@ import net.bytebuddy.agent.builder.AgentBuilder;
 import net.bytebuddy.asm.AsmVisitorWrapper;
 import net.bytebuddy.description.type.TypeDescription;
 import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.matcher.ElementMatcher;
 import net.bytebuddy.utility.JavaModule;
 
 /**
@@ -196,9 +199,15 @@ final class FieldBackedImplementationInstaller implements VirtualFieldImplementa
            * For each virtual field defined in a current instrumentation we create an agent builder
            * that injects necessary fields.
            */
+          ElementMatcher<TypeDescription> typeMatcher =
+              new NamedMatcher<>(
+                  "VirtualField",
+                  new IgnoreFailedTypeMatcher(
+                      not(isAbstract()).and(hasSuperType(named(entry.getKey())))));
+
           builder =
               builder
-                  .type(not(isAbstract()).and(hasSuperType(named(entry.getKey()))))
+                  .type(typeMatcher)
                   .and(safeToInjectFieldsMatcher())
                   .and(InstrumentationModuleInstaller.NOT_DECORATOR_MATCHER)
                   .transform(NoOpTransformer.INSTANCE);

+ 19 - 6
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java

@@ -26,6 +26,8 @@ import io.opentelemetry.javaagent.tooling.muzzle.HelperResourceBuilderImpl;
 import io.opentelemetry.javaagent.tooling.muzzle.InstrumentationModuleMuzzle;
 import io.opentelemetry.javaagent.tooling.muzzle.Mismatch;
 import io.opentelemetry.javaagent.tooling.muzzle.ReferenceMatcher;
+import io.opentelemetry.javaagent.tooling.util.IgnoreFailedTypeMatcher;
+import io.opentelemetry.javaagent.tooling.util.NamedMatcher;
 import java.lang.instrument.Instrumentation;
 import java.security.ProtectionDomain;
 import java.util.List;
@@ -95,18 +97,29 @@ public final class InstrumentationModuleInstaller {
 
     AgentBuilder agentBuilder = parentAgentBuilder;
     for (TypeInstrumentation typeInstrumentation : typeInstrumentations) {
+      ElementMatcher<TypeDescription> typeMatcher =
+          new NamedMatcher<>(
+              instrumentationModule.getClass().getSimpleName()
+                  + "#"
+                  + typeInstrumentation.getClass().getSimpleName(),
+              new IgnoreFailedTypeMatcher(typeInstrumentation.typeMatcher()));
+      ElementMatcher<ClassLoader> classLoaderMatcher =
+          new NamedMatcher<>(
+              instrumentationModule.getClass().getSimpleName()
+                  + "#"
+                  + typeInstrumentation.getClass().getSimpleName(),
+              moduleClassLoaderMatcher.and(typeInstrumentation.classLoaderOptimization()));
+
       AgentBuilder.Identified.Extendable extendableAgentBuilder =
           agentBuilder
               .type(
                   new LoggingFailSafeMatcher<>(
-                      typeInstrumentation.typeMatcher(),
-                      "Instrumentation type matcher unexpected exception: "
-                          + typeInstrumentation.typeMatcher()),
+                      typeMatcher,
+                      "Instrumentation type matcher unexpected exception: " + typeMatcher),
                   new LoggingFailSafeMatcher<>(
-                      moduleClassLoaderMatcher.and(typeInstrumentation.classLoaderOptimization()),
+                      classLoaderMatcher,
                       "Instrumentation class loader matcher unexpected exception: "
-                          + moduleClassLoaderMatcher.and(
-                              typeInstrumentation.classLoaderOptimization())))
+                          + classLoaderMatcher))
               .and(NOT_DECORATOR_MATCHER)
               .and(muzzleMatcher)
               .transform(ConstantAdjuster.instance())

+ 33 - 0
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/util/IgnoreFailedTypeMatcher.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.tooling.util;
+
+import io.opentelemetry.javaagent.tooling.DefineClassHandler;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+/**
+ * A matcher wrapper that skips matching and returns {@code false} when it is known that loading the
+ * matched type will fail. If we know that the class that is currently loading can't be loaded
+ * successfully we can skip transforming it.
+ */
+public class IgnoreFailedTypeMatcher implements ElementMatcher<TypeDescription> {
+  private final ElementMatcher<TypeDescription> delegate;
+
+  public IgnoreFailedTypeMatcher(ElementMatcher<TypeDescription> delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public boolean matches(TypeDescription target) {
+    return !DefineClassHandler.isFailedClass(target.getTypeName()) && delegate.matches(target);
+  }
+
+  @Override
+  public String toString() {
+    return delegate.toString();
+  }
+}

+ 32 - 0
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/util/NamedMatcher.java

@@ -0,0 +1,32 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.tooling.util;
+
+import net.bytebuddy.matcher.ElementMatcher;
+
+/**
+ * A matcher wrapper that adds specified name to the output of {@code toString} to allow easy
+ * identification of where the given matcher originates from.
+ */
+public class NamedMatcher<T> implements ElementMatcher<T> {
+  private final String name;
+  private final ElementMatcher<T> delegate;
+
+  public NamedMatcher(String name, ElementMatcher<T> delegate) {
+    this.name = name;
+    this.delegate = delegate;
+  }
+
+  @Override
+  public boolean matches(T target) {
+    return delegate.matches(target);
+  }
+
+  @Override
+  public String toString() {
+    return name + "(" + delegate.toString() + ")";
+  }
+}

+ 5 - 15
javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/tooling/CacheProviderTest.groovy

@@ -96,10 +96,8 @@ class CacheProviderTest extends Specification {
     def poolStrat = new AgentCachingPoolStrategy()
 
     def loader = newClassLoader()
-    def loaderHash = loader.hashCode()
-    def loaderRef = new WeakReference<ClassLoader>(loader)
 
-    def cacheProvider = poolStrat.createCacheProvider(loaderHash, loaderRef)
+    def cacheProvider = poolStrat.getCacheProvider(loader)
 
     when:
     cacheProvider.register("foo", new TypePool.Resolution.Simple(TypeDescription.VOID))
@@ -114,12 +112,9 @@ class CacheProviderTest extends Specification {
     def poolStrat = new AgentCachingPoolStrategy()
 
     def loader1 = newClassLoader()
-    def loaderHash1 = loader1.hashCode()
-    def loaderRef1A = new WeakReference<ClassLoader>(loader1)
-    def loaderRef1B = new WeakReference<ClassLoader>(loader1)
 
-    def cacheProvider1A = poolStrat.createCacheProvider(loaderHash1, loaderRef1A)
-    def cacheProvider1B = poolStrat.createCacheProvider(loaderHash1, loaderRef1B)
+    def cacheProvider1A = poolStrat.getCacheProvider(loader1)
+    def cacheProvider1B = poolStrat.getCacheProvider(loader1)
 
     when:
     cacheProvider1A.register("foo", newVoid())
@@ -137,15 +132,10 @@ class CacheProviderTest extends Specification {
     def poolStrat = new AgentCachingPoolStrategy()
 
     def loader1 = newClassLoader()
-    def loaderHash1 = loader1.hashCode()
-    def loaderRef1 = new WeakReference<ClassLoader>(loader1)
-
     def loader2 = newClassLoader()
-    def loaderHash2 = loader2.hashCode()
-    def loaderRef2 = new WeakReference<ClassLoader>(loader2)
 
-    def cacheProvider1 = poolStrat.createCacheProvider(loaderHash1, loaderRef1)
-    def cacheProvider2 = poolStrat.createCacheProvider(loaderHash2, loaderRef2)
+    def cacheProvider1 = poolStrat.getCacheProvider(loader1)
+    def cacheProvider2 = poolStrat.getCacheProvider(loader2)
 
     when:
     cacheProvider1.register("foo", newVoid())

+ 320 - 90
muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java

@@ -5,8 +5,16 @@
 
 package io.opentelemetry.javaagent.tooling.muzzle;
 
+import io.opentelemetry.instrumentation.api.config.Config;
 import io.opentelemetry.instrumentation.api.internal.cache.Cache;
+import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder;
+import io.opentelemetry.javaagent.bootstrap.VirtualFieldAccessorMarker;
+import java.lang.instrument.Instrumentation;
 import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import javax.annotation.Nullable;
 import net.bytebuddy.agent.builder.AgentBuilder;
 import net.bytebuddy.description.annotation.AnnotationList;
@@ -15,7 +23,9 @@ import net.bytebuddy.description.method.MethodList;
 import net.bytebuddy.description.type.TypeDescription;
 import net.bytebuddy.description.type.TypeList;
 import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.loading.ClassInjector;
 import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.utility.JavaModule;
 
 /**
  *
@@ -37,6 +47,10 @@ public class AgentCachingPoolStrategy implements AgentBuilder.PoolStrategy {
   // Many things are package visible for testing purposes --
   // others to avoid creation of synthetic accessors
 
+  private static final boolean REFLECTION_ENABLED =
+      Config.get().getBoolean("otel.instrumentation.internal-reflection.enabled", true);
+  private static final Method findLoadedClassMethod = getFindLoadedClassMethod();
+
   static final int TYPE_CAPACITY = 64;
 
   static final int BOOTSTRAP_HASH = 7236344; // Just a random number
@@ -61,42 +75,81 @@ public class AgentCachingPoolStrategy implements AgentBuilder.PoolStrategy {
   final SharedResolutionCacheAdapter bootstrapCacheProvider =
       new SharedResolutionCacheAdapter(BOOTSTRAP_HASH, null, sharedResolutionCache);
 
-  @Override
-  public final TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) {
-    if (classLoader == null) {
-      return createCachingTypePool(bootstrapCacheProvider, classFileLocator);
-    }
+  private final AgentLocationStrategy locationStrategy;
 
-    WeakReference<ClassLoader> loaderRef =
-        loaderRefCache.computeIfAbsent(classLoader, WeakReference::new);
+  public AgentCachingPoolStrategy(AgentLocationStrategy locationStrategy) {
+    this.locationStrategy = locationStrategy;
+  }
 
-    int loaderHash = classLoader.hashCode();
-    return createCachingTypePool(loaderHash, loaderRef, classFileLocator);
+  private static Method getFindLoadedClassMethod() {
+    // instrumentation is null when this code is called from muzzle
+    Instrumentation instrumentation = InstrumentationHolder.getInstrumentation();
+    if (instrumentation == null) {
+      return null;
+    }
+    if (JavaModule.isSupported()) {
+      // ensure that we have access to ClassLoader.findLoadedClass
+      // if modular runtime is used export java.lang package from java.base module to the
+      // module of this class
+      JavaModule currentModule = JavaModule.ofType(AgentCachingPoolStrategy.class);
+      JavaModule javaBase = JavaModule.ofType(ClassLoader.class);
+      if (javaBase != null && javaBase.isNamed() && currentModule != null) {
+        ClassInjector.UsingInstrumentation.redefineModule(
+            instrumentation,
+            javaBase,
+            Collections.emptySet(),
+            Collections.emptyMap(),
+            Collections.singletonMap("java.lang", Collections.singleton(currentModule)),
+            Collections.emptySet(),
+            Collections.emptyMap());
+      }
+    }
+    try {
+      Method method = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
+      method.setAccessible(true);
+      return method;
+    } catch (NoSuchMethodException exception) {
+      throw new IllegalStateException(exception);
+    }
   }
 
-  @Override
-  public final TypePool typePool(
-      ClassFileLocator classFileLocator, ClassLoader classLoader, String name) {
-    return typePool(classFileLocator, classLoader);
+  private static boolean canUseFindLoadedClass() {
+    return findLoadedClassMethod != null;
   }
 
-  private TypePool.CacheProvider createCacheProvider(
-      int loaderHash, WeakReference<ClassLoader> loaderRef) {
-    return new SharedResolutionCacheAdapter(loaderHash, loaderRef, sharedResolutionCache);
+  private static Class<?> findLoadedClass(ClassLoader classLoader, String className) {
+    try {
+      return (Class<?>) findLoadedClassMethod.invoke(classLoader, className);
+    } catch (Exception exception) {
+      throw new IllegalStateException(exception);
+    }
   }
 
-  private TypePool createCachingTypePool(
-      int loaderHash, WeakReference<ClassLoader> loaderRef, ClassFileLocator classFileLocator) {
-    return new TypePool.Default.WithLazyResolution(
-        createCacheProvider(loaderHash, loaderRef),
+  @Override
+  public AgentTypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) {
+    return new AgentTypePool(
+        getCacheProvider(classLoader),
         classFileLocator,
+        classLoader,
         TypePool.Default.ReaderMode.FAST);
   }
 
-  private static TypePool createCachingTypePool(
-      TypePool.CacheProvider cacheProvider, ClassFileLocator classFileLocator) {
-    return new TypePool.Default.WithLazyResolution(
-        cacheProvider, classFileLocator, TypePool.Default.ReaderMode.FAST);
+  @Override
+  public AgentTypePool typePool(
+      ClassFileLocator classFileLocator, ClassLoader classLoader, String name) {
+    return typePool(classFileLocator, classLoader);
+  }
+
+  private TypePool.CacheProvider getCacheProvider(ClassLoader classLoader) {
+    if (classLoader == null) {
+      return bootstrapCacheProvider;
+    }
+
+    WeakReference<ClassLoader> loaderRef =
+        loaderRefCache.computeIfAbsent(classLoader, WeakReference::new);
+
+    int loaderHash = classLoader.hashCode();
+    return new SharedResolutionCacheAdapter(loaderHash, loaderRef, sharedResolutionCache);
   }
 
   /**
@@ -108,7 +161,7 @@ public class AgentCachingPoolStrategy implements AgentBuilder.PoolStrategy {
    *
    * <p>The loaderHash exists to avoid calling get & strengthening the Reference.
    */
-  static final class TypeCacheKey {
+  private static final class TypeCacheKey {
     private final int loaderHash;
     private final WeakReference<ClassLoader> loaderRef;
     private final String className;
@@ -172,7 +225,7 @@ public class AgentCachingPoolStrategy implements AgentBuilder.PoolStrategy {
     }
 
     @Override
-    public final int hashCode() {
+    public int hashCode() {
       return hashCode;
     }
 
@@ -190,10 +243,10 @@ public class AgentCachingPoolStrategy implements AgentBuilder.PoolStrategy {
     }
   }
 
-  static final class SharedResolutionCacheAdapter implements TypePool.CacheProvider {
+  private static final class SharedResolutionCacheAdapter implements TypePool.CacheProvider {
     private static final String OBJECT_NAME = "java.lang.Object";
     private static final TypePool.Resolution OBJECT_RESOLUTION =
-        new TypePool.Resolution.Simple(new CachingTypeDescription(TypeDescription.OBJECT));
+        new TypePool.Resolution.Simple(TypeDescription.OBJECT);
 
     private final int loaderHash;
     private final WeakReference<ClassLoader> loaderRef;
@@ -229,8 +282,6 @@ public class AgentCachingPoolStrategy implements AgentBuilder.PoolStrategy {
         return resolution;
       }
 
-      resolution = new CachingResolution(resolution);
-
       sharedResolutionCache.put(new TypeCacheKey(loaderHash, loaderRef, className), resolution);
       return resolution;
     }
@@ -241,89 +292,268 @@ public class AgentCachingPoolStrategy implements AgentBuilder.PoolStrategy {
     }
   }
 
-  private static class CachingResolution implements TypePool.Resolution {
-    private final TypePool.Resolution delegate;
-    private TypeDescription cachedResolution;
-
-    public CachingResolution(TypePool.Resolution delegate) {
-
-      this.delegate = delegate;
+  /** Based on TypePool.Default.WithLazyResolution */
+  private class AgentTypePool extends TypePool.Default {
+    private final WeakReference<ClassLoader> classLoaderRef;
+
+    public AgentTypePool(
+        TypePool.CacheProvider cacheProvider,
+        ClassFileLocator classFileLocator,
+        ClassLoader classLoader,
+        TypePool.Default.ReaderMode readerMode) {
+      super(cacheProvider, classFileLocator, readerMode);
+      this.classLoaderRef = new WeakReference<>(classLoader);
     }
 
     @Override
-    public boolean isResolved() {
-      return delegate.isResolved();
+    protected TypePool.Resolution doDescribe(String name) {
+      return new AgentTypePool.LazyResolution(classLoaderRef, name);
     }
 
     @Override
-    public TypeDescription resolve() {
-      // Intentionally not "thread safe". Duplicate work deemed an acceptable trade-off.
-      if (cachedResolution == null) {
-        cachedResolution = new CachingTypeDescription(delegate.resolve());
-      }
-      return cachedResolution;
+    protected TypePool.Resolution doCache(String name, TypePool.Resolution resolution) {
+      return resolution;
     }
-  }
 
-  /**
-   * TypeDescription implementation that delegates and caches the results for the expensive calls
-   * commonly used by our instrumentation.
-   */
-  private static class CachingTypeDescription
-      extends TypeDescription.AbstractBase.OfSimpleType.WithDelegation {
-    private final TypeDescription delegate;
-
-    // These fields are intentionally not "thread safe".
-    // Duplicate work deemed an acceptable trade-off.
-    private Generic superClass;
-    private TypeList.Generic interfaces;
-    private AnnotationList annotations;
-    private MethodList<MethodDescription.InDefinedShape> methods;
-
-    public CachingTypeDescription(TypeDescription delegate) {
-      this.delegate = delegate;
+    /**
+     * Non-lazily resolves a type name.
+     *
+     * @param name The name of the type to resolve.
+     * @return The resolution for the type of this name.
+     */
+    protected TypePool.Resolution doResolve(String name) {
+      TypePool.Resolution resolution = cacheProvider.find(name);
+      if (resolution == null) {
+        // calling super.doDescribe that will locate the class bytes and parse them unlike
+        // doDescribe in this class that returns a lazy resolution without parsing the class bytes
+        resolution = cacheProvider.register(name, super.doDescribe(name));
+      }
+      return resolution;
     }
 
-    @Override
-    protected TypeDescription delegate() {
-      return delegate;
-    }
+    /** Based on TypePool.Default.WithLazyResolution.LazyResolution */
+    private class LazyResolution implements TypePool.Resolution {
+      private final WeakReference<ClassLoader> classLoaderRef;
+      private final String name;
 
-    @Override
-    public Generic getSuperClass() {
-      if (superClass == null) {
-        superClass = delegate.getSuperClass();
+      LazyResolution(WeakReference<ClassLoader> classLoaderRef, String name) {
+        this.classLoaderRef = classLoaderRef;
+        this.name = name;
+      }
+
+      @Override
+      public boolean isResolved() {
+        return doResolve(name).isResolved();
+      }
+
+      private volatile TypeDescription cached;
+
+      @Override
+      public TypeDescription resolve() {
+        // unlike byte-buddy implementation we cache the descriptor to avoid having to find
+        // super class and interfaces multiple times
+        if (cached == null) {
+          cached = new AgentTypePool.LazyTypeDescription(classLoaderRef, name);
+        }
+        return cached;
       }
-      return superClass;
     }
 
-    @Override
-    public TypeList.Generic getInterfaces() {
-      if (interfaces == null) {
-        interfaces = delegate.getInterfaces();
+    private abstract class CachingTypeDescription
+        extends TypeDescription.AbstractBase.OfSimpleType.WithDelegation {
+      private volatile TypeDescription delegate;
+
+      @Override
+      protected TypeDescription delegate() {
+        if (delegate == null) {
+          delegate = doResolve(getName()).resolve();
+        }
+        return delegate;
+      }
+
+      private volatile AnnotationList annotations;
+
+      @Override
+      public AnnotationList getDeclaredAnnotations() {
+        if (annotations == null) {
+          annotations = delegate().getDeclaredAnnotations();
+        }
+        return annotations;
+      }
+
+      private volatile MethodList<MethodDescription.InDefinedShape> methods;
+
+      @Override
+      public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
+        if (methods == null) {
+          methods = delegate().getDeclaredMethods();
+        }
+        return methods;
       }
-      return interfaces;
     }
 
-    @Override
-    public AnnotationList getDeclaredAnnotations() {
-      if (annotations == null) {
-        annotations = delegate.getDeclaredAnnotations();
+    /**
+     * Based on TypePool.Default.WithLazyResolution.LazyTypeDescription
+     *
+     * <p>Class description that attempts to use already loaded super classes for navigating class
+     * hierarchy.
+     */
+    private class LazyTypeDescription extends AgentTypePool.CachingTypeDescription {
+      // using WeakReference to ensure that caching this descriptor won't keep class loader alive
+      private final WeakReference<ClassLoader> classLoaderRef;
+      private final String name;
+
+      LazyTypeDescription(WeakReference<ClassLoader> classLoaderRef, String name) {
+        this.classLoaderRef = classLoaderRef;
+        this.name = name;
+      }
+
+      @Override
+      public String getName() {
+        return name;
+      }
+
+      private volatile Generic cachedSuperClass;
+
+      @Override
+      public Generic getSuperClass() {
+        if (cachedSuperClass == null) {
+          Generic superClassDescription = delegate().getSuperClass();
+          ClassLoader classLoader = classLoaderRef.get();
+          if (canUseFindLoadedClass() && classLoader != null && superClassDescription != null) {
+            String superName = superClassDescription.getTypeName();
+            Class<?> superClass = findLoadedClass(classLoader, superName);
+            if (superClass != null) {
+              // here we use raw type and loose generic info
+              // we don't expect to have matchers that would use the generic info
+              superClassDescription = newTypeDescription(superClass).asGenericType();
+            }
+          }
+          cachedSuperClass = superClassDescription;
+        }
+        return cachedSuperClass;
+      }
+
+      private volatile TypeList.Generic cachedInterfaces;
+
+      @Override
+      public TypeList.Generic getInterfaces() {
+        if (cachedInterfaces == null) {
+          TypeList.Generic interfaces = delegate().getInterfaces();
+          ClassLoader classLoader = classLoaderRef.get();
+          if (canUseFindLoadedClass() && classLoader != null && !interfaces.isEmpty()) {
+            // here we use raw types and loose generic info
+            // we don't expect to have matchers that would use the generic info
+            List<TypeDescription> result = new ArrayList<>();
+            for (Generic interfaceDescription : interfaces) {
+              String interfaceName = interfaceDescription.getTypeName();
+              Class<?> interfaceClass = findLoadedClass(classLoader, interfaceName);
+              if (interfaceClass != null) {
+                result.add(newTypeDescription(interfaceClass));
+              } else {
+                result.add(interfaceDescription.asErasure());
+              }
+            }
+            interfaces = new TypeList.Generic.Explicit(result);
+          }
+
+          cachedInterfaces = interfaces;
+        }
+        return cachedInterfaces;
       }
-      return annotations;
     }
 
-    @Override
-    public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
-      if (methods == null) {
-        methods = delegate.getDeclaredMethods();
+    private AgentTypePool.LazyTypeDescriptionWithClass newTypeDescription(Class<?> clazz) {
+      return newLazyTypeDescriptionWithClass(
+          AgentTypePool.this, AgentCachingPoolStrategy.this, clazz);
+    }
+
+    /**
+     * Based on TypePool.Default.WithLazyResolution.LazyTypeDescription
+     *
+     * <p>Class description that uses an existing class instance for navigating super class
+     * hierarchy. This should be much more efficient than finding super types through resource
+     * lookups and parsing bytecode. We are not using TypeDescription.ForLoadedType as it can cause
+     * additional classes to be loaded.
+     */
+    private class LazyTypeDescriptionWithClass extends AgentTypePool.CachingTypeDescription {
+      // using WeakReference to ensure that caching this descriptor won't keep class loader alive
+      private final WeakReference<Class<?>> classRef;
+      private final String name;
+      private final int modifiers;
+
+      LazyTypeDescriptionWithClass(Class<?> clazz) {
+        this.name = clazz.getName();
+        this.modifiers = clazz.getModifiers();
+        this.classRef = new WeakReference<>(clazz);
+      }
+
+      @Override
+      public String getName() {
+        return name;
+      }
+
+      private volatile Generic cachedSuperClass;
+
+      @Override
+      public Generic getSuperClass() {
+        if (cachedSuperClass == null) {
+          Class<?> clazz = classRef.get();
+          if (clazz == null) {
+            return null;
+          }
+          Class<?> superClass = clazz.getSuperclass();
+          if (superClass == null) {
+            return null;
+          }
+          // using raw type
+          cachedSuperClass = newTypeDescription(superClass).asGenericType();
+        }
+
+        return cachedSuperClass;
+      }
+
+      private volatile TypeList.Generic cachedInterfaces;
+
+      @Override
+      public TypeList.Generic getInterfaces() {
+        if (cachedInterfaces == null) {
+          List<TypeDescription> result = new ArrayList<>();
+          Class<?> clazz = classRef.get();
+          if (clazz != null) {
+            for (Class<?> interfaceClass : clazz.getInterfaces()) {
+              // virtual field accessors are removed by internal-reflection instrumentation
+              // we do this extra check for tests run with internal-reflection disabled
+              if (!REFLECTION_ENABLED
+                  && VirtualFieldAccessorMarker.class.isAssignableFrom(interfaceClass)) {
+                continue;
+              }
+              // using raw type
+              result.add(newTypeDescription(interfaceClass));
+            }
+          }
+          cachedInterfaces = new TypeList.Generic.Explicit(result);
+        }
+
+        return cachedInterfaces;
+      }
+
+      @Override
+      public int getModifiers() {
+        return modifiers;
       }
-      return methods;
     }
+  }
 
-    @Override
-    public String getName() {
-      return delegate.getName();
+  private static AgentTypePool.LazyTypeDescriptionWithClass newLazyTypeDescriptionWithClass(
+      AgentTypePool pool, AgentCachingPoolStrategy poolStrategy, Class<?> clazz) {
+    // if class and existing pool use different class loaders create a new pool with correct class
+    // loader
+    if (pool.classLoaderRef.get() != clazz.getClassLoader()) {
+      ClassFileLocator classFileLocator =
+          poolStrategy.locationStrategy.classFileLocator(clazz.getClassLoader());
+      pool = poolStrategy.typePool(classFileLocator, clazz.getClassLoader());
     }
+    return pool.new LazyTypeDescriptionWithClass(clazz);
   }
 }

+ 5 - 4
muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentLocationStrategy.java

@@ -31,13 +31,14 @@ public class AgentLocationStrategy implements AgentBuilder.LocationStrategy {
   @Override
   public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule javaModule) {
     List<ClassFileLocator> locators = new ArrayList<>();
+
+    if (classLoader != null) {
+      locators.add(ClassFileLocator.ForClassLoader.WeaklyReferenced.of(classLoader));
+    }
+    // can be null in unit tests
     if (bootstrapProxy != null) {
       locators.add(ClassFileLocator.ForClassLoader.of(bootstrapProxy));
     }
-    while (classLoader != null) {
-      locators.add(ClassFileLocator.ForClassLoader.WeaklyReferenced.of(classLoader));
-      classLoader = classLoader.getParent();
-    }
 
     return new ClassFileLocator.Compound(locators);
   }

+ 18 - 2
muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentTooling.java

@@ -5,6 +5,10 @@
 
 package io.opentelemetry.javaagent.tooling.muzzle;
 
+import java.util.Iterator;
+import java.util.ServiceLoader;
+import net.bytebuddy.agent.builder.AgentBuilder;
+
 /**
  * This class contains class references for objects shared by the agent installer as well as muzzle
  * (both compile and runtime). Extracted out from AgentInstaller to begin separating some of the
@@ -12,7 +16,8 @@ package io.opentelemetry.javaagent.tooling.muzzle;
  */
 public final class AgentTooling {
 
-  private static final AgentCachingPoolStrategy POOL_STRATEGY = new AgentCachingPoolStrategy();
+  private static final AgentBuilder.PoolStrategy POOL_STRATEGY =
+      new AgentCachingPoolStrategy(locationStrategy(getBootstrapProxy()));
 
   public static AgentLocationStrategy locationStrategy() {
     return locationStrategy(null);
@@ -22,9 +27,20 @@ public final class AgentTooling {
     return new AgentLocationStrategy(bootstrapProxy);
   }
 
-  public static AgentCachingPoolStrategy poolStrategy() {
+  public static AgentBuilder.PoolStrategy poolStrategy() {
     return POOL_STRATEGY;
   }
 
+  private static ClassLoader getBootstrapProxy() {
+    Iterator<BootstrapProxyProvider> iterator =
+        ServiceLoader.load(BootstrapProxyProvider.class).iterator();
+    if (iterator.hasNext()) {
+      BootstrapProxyProvider bootstrapProxyProvider = iterator.next();
+      return bootstrapProxyProvider.getBootstrapProxy();
+    }
+
+    return null;
+  }
+
   private AgentTooling() {}
 }

+ 13 - 0
muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/BootstrapProxyProvider.java

@@ -0,0 +1,13 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.tooling.muzzle;
+
+/** This SPI can be used to find a class loader which can be used to look up bootstrap resources. */
+public interface BootstrapProxyProvider {
+
+  /** Provide a class loader which can be used to look up bootstrap resources. */
+  ClassLoader getBootstrapProxy();
+}

+ 3 - 1
muzzle/src/test/java/io/opentelemetry/javaagent/tooling/muzzle/AgentLocationStrategyTest.java

@@ -34,7 +34,9 @@ class AgentLocationStrategyTest {
 
   @Test
   void findsResourcesFromParentClassloader() throws Exception {
-    ClassFileLocator locator = new AgentLocationStrategy(null).classFileLocator(childLoader, null);
+    ClassFileLocator locator =
+        new AgentLocationStrategy(ClassLoader.getSystemClassLoader())
+            .classFileLocator(childLoader, null);
     assertThat(locator.locate("java/lang/Object").isResolved()).isTrue();
     assertThat(lastLookup).hasValue("java/lang/Object.class");
 

+ 9 - 3
testing-common/integration-tests/build.gradle.kts

@@ -31,6 +31,13 @@ tasks {
     }
     include("**/FieldInjectionDisabledTest.*")
     jvmArgs("-Dotel.javaagent.experimental.field-injection.enabled=false")
+  }
+
+  val testFieldBackedImplementation by registering(Test::class) {
+    filter {
+      includeTestsMatching("context.FieldBackedImplementationTest")
+    }
+    include("**/FieldBackedImplementationTest.*")
     // this test uses reflection to access fields generated by FieldBackedProvider
     // internal-reflection needs to be disabled because it removes these fields from reflection results.
     jvmArgs("-Dotel.instrumentation.internal-reflection.enabled=false")
@@ -39,15 +46,14 @@ tasks {
   test {
     filter {
       excludeTestsMatching("context.FieldInjectionDisabledTest")
+      excludeTestsMatching("context.FieldBackedImplementationTest")
     }
     // this is needed for AgentInstrumentationSpecificationTest
     jvmArgs("-Dotel.javaagent.exclude-classes=config.exclude.packagename.*,config.exclude.SomeClass,config.exclude.SomeClass\$NestedClass")
-    // this test uses reflection to access fields generated by FieldBackedProvider
-    // internal-reflection needs to be disabled because it removes these fields from reflection results.
-    jvmArgs("-Dotel.instrumentation.internal-reflection.enabled=false")
   }
 
   check {
     dependsOn(testFieldInjectionDisabled)
+    dependsOn(testFieldBackedImplementation)
   }
 }