diff --git a/instrumentation/dropwizard/dropwizard-testing/build.gradle.kts b/instrumentation/dropwizard/dropwizard-testing/build.gradle.kts index 8af679443afd..df202760a4aa 100644 --- a/instrumentation/dropwizard/dropwizard-testing/build.gradle.kts +++ b/instrumentation/dropwizard/dropwizard-testing/build.gradle.kts @@ -14,3 +14,7 @@ dependencies { // Requires old Guava. Can't use enforcedPlatform since predates BOM configurations.testRuntimeClasspath.resolutionStrategy.force("com.google.guava:guava:19.0") + +tasks.withType().configureEach { + jvmArgs("-Dotel.instrumentation.jaxrs.enabled=true") +} diff --git a/instrumentation/external-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/extannotations/ExternalAnnotationInstrumentation.java b/instrumentation/external-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/extannotations/ExternalAnnotationInstrumentation.java index c0f12df4fbf9..d0d30b53265b 100644 --- a/instrumentation/external-annotations/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/extannotations/ExternalAnnotationInstrumentation.java +++ b/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 ElementMatcher classLoaderOptimization() { @Override public ElementMatcher typeMatcher() { - return hasSuperType(declaresMethod(isAnnotatedWith(traceAnnotationMatcher))); + return declaresMethod(isAnnotatedWith(traceAnnotationMatcher)); } @Override diff --git a/instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/ClassLoaderInstrumentationModule.java b/instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/ClassLoaderInstrumentationModule.java index e3084047b93f..014511692931 100644 --- a/instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/ClassLoaderInstrumentationModule.java +++ b/instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/ClassLoaderInstrumentationModule.java @@ -34,6 +34,7 @@ public List typeInstrumentations() { return asList( new BootDelegationInstrumentation(), new LoadInjectedClassInstrumentation(), - new ResourceInjectionInstrumentation()); + new ResourceInjectionInstrumentation(), + new DefineClassInstrumentation()); } } diff --git a/instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/DefineClassInstrumentation.java b/instrumentation/internal/internal-class-loader/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/DefineClassInstrumentation.java new file mode 100644 index 000000000000..ff38e05a417f --- /dev/null +++ b/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 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); + } + } +} diff --git a/instrumentation/jaxrs/jaxrs-1.0/javaagent/build.gradle.kts b/instrumentation/jaxrs/jaxrs-1.0/javaagent/build.gradle.kts index a71653ffc643..4846072c1cb9 100644 --- a/instrumentation/jaxrs/jaxrs-1.0/javaagent/build.gradle.kts +++ b/instrumentation/jaxrs/jaxrs-1.0/javaagent/build.gradle.kts @@ -25,3 +25,7 @@ dependencies { testImplementation("io.dropwizard:dropwizard-testing:0.7.1") testImplementation("javax.xml.bind:jaxb-api:2.2.3") } + +tasks.withType().configureEach { + jvmArgs("-Dotel.instrumentation.jaxrs.enabled=true") +} diff --git a/instrumentation/jaxrs/jaxrs-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v1_0/JaxrsInstrumentationModule.java b/instrumentation/jaxrs/jaxrs-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v1_0/JaxrsInstrumentationModule.java index 2cdfe46bbe0f..d3a11b543453 100644 --- a/instrumentation/jaxrs/jaxrs-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v1_0/JaxrsInstrumentationModule.java +++ b/instrumentation/jaxrs/jaxrs-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v1_0/JaxrsInstrumentationModule.java @@ -31,4 +31,9 @@ public ElementMatcher.Junction classLoaderMatcher() { public List typeInstrumentations() { return singletonList(new JaxrsAnnotationsInstrumentation()); } + + @Override + protected boolean defaultEnabled() { + return false; + } } diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsInstrumentationModule.java b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsInstrumentationModule.java index 83d90eeff176..86ba7064346e 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsInstrumentationModule.java +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JaxrsInstrumentationModule.java @@ -34,4 +34,9 @@ public List typeInstrumentations() { new JaxrsAnnotationsInstrumentation(), new JaxrsAsyncResponseInstrumentation()); } + + @Override + protected boolean defaultEnabled() { + return false; + } } diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/build.gradle.kts b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/build.gradle.kts index 01c1fa17e9d7..26a873ee4d13 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/build.gradle.kts +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2/javaagent/build.gradle.kts @@ -45,4 +45,5 @@ dependencies { tasks.withType().configureEach { // TODO run tests both with and without experimental span attributes jvmArgs("-Dotel.instrumentation.jaxrs.experimental-span-attributes=true") + jvmArgs("-Dotel.instrumentation.jaxrs.enabled=true") } diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/build.gradle.kts b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/build.gradle.kts index 00769a94ae0b..a6d2dc2045e9 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/build.gradle.kts +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/build.gradle.kts @@ -54,5 +54,6 @@ tasks { withType().configureEach { // TODO run tests both with and without experimental span attributes jvmArgs("-Dotel.instrumentation.jaxrs.experimental-span-attributes=true") + jvmArgs("-Dotel.instrumentation.jaxrs.enabled=true") } } diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/build.gradle.kts b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/build.gradle.kts index 3089309830be..f70f94219b28 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/build.gradle.kts +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/javaagent/build.gradle.kts @@ -58,5 +58,6 @@ tasks { withType().configureEach { // TODO run tests both with and without experimental span attributes jvmArgs("-Dotel.instrumentation.jaxrs.experimental-span-attributes=true") + jvmArgs("-Dotel.instrumentation.jaxrs.enabled=true") } } diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/build.gradle.kts b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/build.gradle.kts index 33ab7befca51..44ce5bd10fc9 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/build.gradle.kts +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/javaagent/build.gradle.kts @@ -56,6 +56,7 @@ tasks { withType().configureEach { // TODO run tests both with and without experimental span attributes jvmArgs("-Dotel.instrumentation.jaxrs.experimental-span-attributes=true") + jvmArgs("-Dotel.instrumentation.jaxrs.enabled=true") } } diff --git a/instrumentation/jaxws/jaxws-2.0-axis2-1.6/javaagent/build.gradle.kts b/instrumentation/jaxws/jaxws-2.0-axis2-1.6/javaagent/build.gradle.kts index f3c15a989d2e..414861d54a8a 100644 --- a/instrumentation/jaxws/jaxws-2.0-axis2-1.6/javaagent/build.gradle.kts +++ b/instrumentation/jaxws/jaxws-2.0-axis2-1.6/javaagent/build.gradle.kts @@ -47,3 +47,7 @@ dependencies { testImplementation("com.sun.xml.ws:jaxws-rt:2.2.8") testImplementation("com.sun.xml.ws:jaxws-tools:2.2.8") } + +tasks.withType().configureEach { + jvmArgs("-Dotel.instrumentation.jaxws.enabled=true") +} diff --git a/instrumentation/jaxws/jaxws-2.0-cxf-3.0/javaagent/build.gradle.kts b/instrumentation/jaxws/jaxws-2.0-cxf-3.0/javaagent/build.gradle.kts index 2d6adb34089b..1eed826801dc 100644 --- a/instrumentation/jaxws/jaxws-2.0-cxf-3.0/javaagent/build.gradle.kts +++ b/instrumentation/jaxws/jaxws-2.0-cxf-3.0/javaagent/build.gradle.kts @@ -36,3 +36,7 @@ dependencies { testImplementation("javax.annotation:javax.annotation-api:1.2") testImplementation("com.sun.xml.messaging.saaj:saaj-impl:1.5.2") } + +tasks.withType().configureEach { + jvmArgs("-Dotel.instrumentation.jaxws.enabled=true") +} diff --git a/instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/build.gradle.kts b/instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/build.gradle.kts index ab5dbdba4e3a..c1e064687fe1 100644 --- a/instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/build.gradle.kts +++ b/instrumentation/jaxws/jaxws-2.0-metro-2.2/javaagent/build.gradle.kts @@ -37,4 +37,6 @@ tasks.withType().configureEach { jvmArgs("--add-exports=java.xml/com.sun.org.apache.xerces.internal.dom=ALL-UNNAMED") jvmArgs("--add-exports=java.xml/com.sun.org.apache.xerces.internal.jaxp=ALL-UNNAMED") jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") + + jvmArgs("-Dotel.instrumentation.jaxws.enabled=true") } diff --git a/instrumentation/jaxws/jaxws-jws-api-1.1/javaagent/build.gradle.kts b/instrumentation/jaxws/jaxws-jws-api-1.1/javaagent/build.gradle.kts index f1fadacbefa1..61d5c8090fd0 100644 --- a/instrumentation/jaxws/jaxws-jws-api-1.1/javaagent/build.gradle.kts +++ b/instrumentation/jaxws/jaxws-jws-api-1.1/javaagent/build.gradle.kts @@ -14,3 +14,7 @@ dependencies { library("javax.jws:javax.jws-api:1.1") implementation(project(":instrumentation:jaxws:jaxws-common:library")) } + +tasks.withType().configureEach { + jvmArgs("-Dotel.instrumentation.jaxws.enabled=true") +} diff --git a/instrumentation/jaxws/jaxws-jws-api-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxws/jws/v1_1/JwsInstrumentationModule.java b/instrumentation/jaxws/jaxws-jws-api-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxws/jws/v1_1/JwsInstrumentationModule.java index b603410e49fe..708617078b82 100644 --- a/instrumentation/jaxws/jaxws-jws-api-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxws/jws/v1_1/JwsInstrumentationModule.java +++ b/instrumentation/jaxws/jaxws-jws-api-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxws/jws/v1_1/JwsInstrumentationModule.java @@ -22,4 +22,9 @@ public JwsInstrumentationModule() { public List typeInstrumentations() { return Collections.singletonList(new JwsAnnotationsInstrumentation()); } + + @Override + protected boolean defaultEnabled() { + return false; + } } diff --git a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/DefineClassHelper.java b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/DefineClassHelper.java new file mode 100644 index 000000000000..0105a5b95a32 --- /dev/null +++ b/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() {} +} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java index 0ca30d2e51c4..0f639c141909 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java @@ -22,6 +22,7 @@ import io.opentelemetry.javaagent.bootstrap.AgentClassLoader; 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.bootstrap.BootstrapPackagesConfigurer; import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesConfigurer; @@ -114,6 +115,7 @@ public static ResettableClassFileTransformer installBytebuddyAgent( Config config = Config.get(); setBootstrapPackages(config); + setDefineClassHandler(); // If noop OpenTelemetry is enabled, autoConfiguredSdk will be null and AgentListeners are not // called @@ -129,6 +131,7 @@ public static ResettableClassFileTransformer installBytebuddyAgent( runBeforeAgentListeners(agentListeners, config, autoConfiguredSdk); } + AgentTooling.init(Utils.getBootstrapProxy()); AgentBuilder agentBuilder = new AgentBuilder.Default() .disableClassFormatChanges() @@ -203,6 +206,10 @@ private static void setBootstrapPackages(Config config) { BootstrapPackagePrefixesHolder.setBoostrapPackagePrefixes(builder.build()); } + private static void setDefineClassHandler() { + DefineClassHelper.internalSetHandler(DefineClassHandler.INSTANCE); + } + private static void runBeforeAgentListeners( Iterable agentListeners, Config config, diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/DefineClassHandler.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/DefineClassHandler.java new file mode 100644 index 000000000000..93efee07017a --- /dev/null +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/DefineClassHandler.java @@ -0,0 +1,90 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling; + +import io.opentelemetry.javaagent.bootstrap.DefineClassHelper.Handler; +import org.objectweb.asm.ClassReader; + +public class DefineClassHandler implements Handler { + public static final DefineClassHandler INSTANCE = new DefineClassHandler(); + private static final ThreadLocal defineClassContext = + ThreadLocal.withInitial(() -> DefineClassContextImpl.NOP); + + private DefineClassHandler() {} + + @Override + public DefineClassContext beforeDefineClass( + ClassLoader classLoader, String className, byte[] classBytes, int offset, int length) { + 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 know 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 DefineClassContextImpl previous; + private String failedClassName; + + private DefineClassContextImpl() {} + + 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); + } + } +} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/IgnoreFailedTypeMatcher.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/IgnoreFailedTypeMatcher.java new file mode 100644 index 000000000000..6abcdce90baf --- /dev/null +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/IgnoreFailedTypeMatcher.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling.instrumentation; + +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 know 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. + */ +class IgnoreFailedTypeMatcher implements ElementMatcher { + private final ElementMatcher delegate; + + IgnoreFailedTypeMatcher(ElementMatcher 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(); + } +} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java index d84d34c21c7e..272235dad676 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java @@ -92,18 +92,29 @@ AgentBuilder install( AgentBuilder agentBuilder = parentAgentBuilder; for (TypeInstrumentation typeInstrumentation : typeInstrumentations) { + ElementMatcher typeMatcher = + new NamedMatcher<>( + instrumentationModule.getClass().getSimpleName() + + "#" + + typeInstrumentation.getClass().getSimpleName(), + new IgnoreFailedTypeMatcher(typeInstrumentation.typeMatcher())); + ElementMatcher 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()) diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/NamedMatcher.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/NamedMatcher.java new file mode 100644 index 000000000000..18c6bee6d19d --- /dev/null +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/NamedMatcher.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling.instrumentation; + +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. + */ +class NamedMatcher implements ElementMatcher { + private final String name; + private final ElementMatcher delegate; + + NamedMatcher(String name, ElementMatcher 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() + ")"; + } +} diff --git a/javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/tooling/CacheProviderTest.groovy b/javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/tooling/CacheProviderTest.groovy index e63d508a2bec..1fce13b6ecb7 100644 --- a/javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/tooling/CacheProviderTest.groovy +++ b/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(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(loader1) - def loaderRef1B = new WeakReference(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(loader1) - def loader2 = newClassLoader() - def loaderHash2 = loader2.hashCode() - def loaderRef2 = new WeakReference(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()) diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java index f7e109fdae6d..831f41b82574 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java @@ -6,7 +6,13 @@ package io.opentelemetry.javaagent.tooling.muzzle; import io.opentelemetry.instrumentation.api.cache.Cache; +import io.opentelemetry.instrumentation.api.config.Config; +import io.opentelemetry.javaagent.bootstrap.VirtualFieldAccessorMarker; 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; @@ -37,6 +43,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().isInstrumentationEnabled(Collections.singleton("internal-reflection"), 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 +71,55 @@ 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; + + public AgentCachingPoolStrategy(AgentLocationStrategy locationStrategy) { + this.locationStrategy = locationStrategy; + } + + private static Method getFindLoadedClassMethod() { + try { + Method method = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); + method.setAccessible(true); + return method; + } catch (NoSuchMethodException exception) { + throw new IllegalStateException(exception); } + } - WeakReference loaderRef = - loaderRefCache.computeIfAbsent(classLoader, WeakReference::new); + private static Class findLoadedClass(ClassLoader classLoader, String className) { + try { + return (Class) findLoadedClassMethod.invoke(classLoader, className); + } catch (Exception exception) { + throw new IllegalStateException(exception); + } + } - int loaderHash = classLoader.hashCode(); - return createCachingTypePool(loaderHash, loaderRef, classFileLocator); + @Override + public AgentTypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) { + return new AgentTypePool( + getCacheProvider(classLoader), + classFileLocator, + classLoader, + TypePool.Default.ReaderMode.FAST); } @Override - public final TypePool typePool( + public AgentTypePool typePool( ClassFileLocator classFileLocator, ClassLoader classLoader, String name) { return typePool(classFileLocator, classLoader); } - private TypePool.CacheProvider createCacheProvider( - int loaderHash, WeakReference loaderRef) { - return new SharedResolutionCacheAdapter(loaderHash, loaderRef, sharedResolutionCache); - } + private TypePool.CacheProvider getCacheProvider(ClassLoader classLoader) { + if (classLoader == null) { + return bootstrapCacheProvider; + } - private TypePool createCachingTypePool( - int loaderHash, WeakReference loaderRef, ClassFileLocator classFileLocator) { - return new TypePool.Default.WithLazyResolution( - createCacheProvider(loaderHash, loaderRef), - classFileLocator, - TypePool.Default.ReaderMode.FAST); - } + WeakReference loaderRef = + loaderRefCache.computeIfAbsent(classLoader, WeakReference::new); - private static TypePool createCachingTypePool( - TypePool.CacheProvider cacheProvider, ClassFileLocator classFileLocator) { - return new TypePool.Default.WithLazyResolution( - cacheProvider, classFileLocator, TypePool.Default.ReaderMode.FAST); + int loaderHash = classLoader.hashCode(); + return new SharedResolutionCacheAdapter(loaderHash, loaderRef, sharedResolutionCache); } /** @@ -108,7 +131,7 @@ private static TypePool createCachingTypePool( * *

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 loaderRef; private final String className; @@ -172,7 +195,7 @@ public boolean equals(@Nullable Object obj) { } @Override - public final int hashCode() { + public int hashCode() { return hashCode; } @@ -190,10 +213,10 @@ public String toString() { } } - 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 loaderRef; @@ -229,8 +252,6 @@ public TypePool.Resolution register(String className, TypePool.Resolution resolu return resolution; } - resolution = new CachingResolution(resolution); - sharedResolutionCache.put(new TypeCacheKey(loaderHash, loaderRef, className), resolution); return resolution; } @@ -241,89 +262,260 @@ public void clear() { } } - 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 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 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) { + 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 classLoaderRef; + private final String name; - @Override - public Generic getSuperClass() { - if (superClass == null) { - superClass = delegate.getSuperClass(); + LazyResolution(WeakReference 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 methods; + + @Override + public MethodList 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 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 classLoaderRef; + private final String name; + + LazyTypeDescription(WeakReference 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 (classLoader != null && superClassDescription != null) { + String superName = superClassDescription.getTypeName(); + Class superClass = findLoadedClass(classLoader, superName); + if (superClass != null) { + superClassDescription = newTypeDescription(superClass).asGenericType(); + } + } + // using raw type + 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 (classLoader != null && !interfaces.isEmpty()) { + List result = new ArrayList<>(); + for (Generic interfaceDescription : interfaces) { + String interfaceName = interfaceDescription.getTypeName(); + Class interfaceClass = findLoadedClass(classLoader, interfaceName); + if (interfaceClass != null) { + // using raw type + result.add(newTypeDescription(interfaceClass)); + } else { + result.add(interfaceDescription.asErasure()); + } + } + interfaces = new TypeList.Generic.Explicit(result); + } + + cachedInterfaces = interfaces; + } + return cachedInterfaces; } - return annotations; } - @Override - public MethodList 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 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> 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 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); } } diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentLocationStrategy.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentLocationStrategy.java index e0e3fefef569..48315b98d8d0 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentLocationStrategy.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentLocationStrategy.java @@ -31,13 +31,14 @@ public ClassFileLocator classFileLocator(ClassLoader classLoader) { @Override public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule javaModule) { List 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); } diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentTooling.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentTooling.java index 74ea060bdb0c..7f69c138368e 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentTooling.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentTooling.java @@ -5,6 +5,8 @@ package io.opentelemetry.javaagent.tooling.muzzle; +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 +14,7 @@ */ public final class AgentTooling { - private static final AgentCachingPoolStrategy POOL_STRATEGY = new AgentCachingPoolStrategy(); + private static volatile ClassLoader bootstrapProxy; public static AgentLocationStrategy locationStrategy() { return locationStrategy(null); @@ -22,9 +24,18 @@ public static AgentLocationStrategy locationStrategy(ClassLoader bootstrapProxy) return new AgentLocationStrategy(bootstrapProxy); } - public static AgentCachingPoolStrategy poolStrategy() { - return POOL_STRATEGY; + public static AgentBuilder.PoolStrategy poolStrategy() { + return PoolStrategyHolder.POOL_STRATEGY; + } + + public static void init(ClassLoader classLoader) { + bootstrapProxy = classLoader; } private AgentTooling() {} + + private static class PoolStrategyHolder { + private static final AgentBuilder.PoolStrategy POOL_STRATEGY = + new AgentCachingPoolStrategy(locationStrategy(bootstrapProxy)); + } } diff --git a/muzzle/src/test/java/io/opentelemetry/javaagent/tooling/muzzle/AgentLocationStrategyTest.java b/muzzle/src/test/java/io/opentelemetry/javaagent/tooling/muzzle/AgentLocationStrategyTest.java index af02dc821e44..85c5b65ee7e1 100644 --- a/muzzle/src/test/java/io/opentelemetry/javaagent/tooling/muzzle/AgentLocationStrategyTest.java +++ b/muzzle/src/test/java/io/opentelemetry/javaagent/tooling/muzzle/AgentLocationStrategyTest.java @@ -34,7 +34,9 @@ void cleanup() { @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"); diff --git a/testing-common/integration-tests/build.gradle.kts b/testing-common/integration-tests/build.gradle.kts index 2c8ad8a682ef..64b68913b829 100644 --- a/testing-common/integration-tests/build.gradle.kts +++ b/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) } }