From 1597e6af3fe617fd13d832cc2ca8adcb69619b9b Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 22 May 2026 12:05:35 +0200 Subject: [PATCH 1/4] add annotation for CL target --- .../internal/ClassLoadingStrategy.java | 32 ++++++++ .../internal/ClassLoadingTarget.java | 33 ++++++++ .../indy/ClassLoadingTargetUtil.java | 55 +++++++++++++ .../indy/ClassLoadingTargetUtilTest.java | 78 +++++++++++++++++++ .../indy/dummies/targetcl/package-info.java | 5 ++ 5 files changed, 203 insertions(+) create mode 100644 javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/ClassLoadingStrategy.java create mode 100644 javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/ClassLoadingTarget.java create mode 100644 javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java create mode 100644 javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtilTest.java create mode 100644 javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/dummies/targetcl/package-info.java diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/ClassLoadingStrategy.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/ClassLoadingStrategy.java new file mode 100644 index 000000000000..7daca65232fc --- /dev/null +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/ClassLoadingStrategy.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.extension.instrumentation.internal; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time.
+ * Explicitly defines the classloader that needs to be used to load classes. + * + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.PACKAGE}) +public @interface ClassLoadingStrategy { + + ClassLoadingTarget value() default ClassLoadingTarget.INSTRUMENTATION_ISOLATED; +} diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/ClassLoadingTarget.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/ClassLoadingTarget.java new file mode 100644 index 000000000000..ed63a24b155e --- /dev/null +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/ClassLoadingTarget.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.extension.instrumentation.internal; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time.
+ */ +public enum ClassLoadingTarget { + /** + * Class or package will be injected into the instrumented classloader, as if the class was + * present in the application classpath. When referenced directly from instrumentation advice or + * helper classes, it should never be loaded in the instrumentation module or agent classloader. + */ + INSTRUMENTATION_TARGET, + /** + * Class or package will be injected into an isolated instrumentation module classloader when + * using InvokeDynamic instrumentation. This is the default for most instrumentation classes as + * they need to be isolated from the instrumented application.
+ * When using inlined instrumentation, this is equivalent to {@link #INSTRUMENTATION_TARGET}. + */ + INSTRUMENTATION_ISOLATED, + /** + * Class or package will be injected into a shared instrumentation module classloader when using + * InvokeDynamic instrumentation. This should be used for shared libraries classes and classes + * that are used across multiple instrumentation modules.
+ * When using inlined instrumentation, this is equivalent to {@link #INSTRUMENTATION_TARGET}. + */ + INSTRUMENTATION_SHARED +} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java new file mode 100644 index 000000000000..6c2e1e8b263e --- /dev/null +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java @@ -0,0 +1,55 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling.instrumentation.indy; + +import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingStrategy; +import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingTarget; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; + +/** Utility to help with class-loading indy modules */ +public class ClassLoadingTargetUtil { + + private static final Type STRATEGY_ANNOTATION = Type.getType(ClassLoadingStrategy.class); + private static final Type TARGET_ENUM = Type.getType(ClassLoadingTarget.class); + + private ClassLoadingTargetUtil() {} + + /** + * Reads the class class-loading strategy from class (or package) bytecode + * + * @param bytecode class or package bytecode + * @return class loading strategy, defaults to {@link ClassLoadingTarget#INSTRUMENTATION_ISOLATED} + * if annotation is not present. + */ + // package-protected for testing + static ClassLoadingTarget getTarget(byte[] bytecode) { + ClassReader cr = new ClassReader(bytecode); + ClassNode classNode = new ClassNode(); + cr.accept(classNode, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG); + if (classNode.visibleAnnotations != null) { + for (AnnotationNode annotation : classNode.visibleAnnotations) { + if (Type.getType(annotation.desc).equals(STRATEGY_ANNOTATION)) { + for (Object value : annotation.values) { + if (value instanceof String[]) { + String[] array = (String[]) value; + if (array.length == 2 && Type.getType(array[0]).equals(TARGET_ENUM)) { + return ClassLoadingTarget.valueOf(array[1]); + } + } + } + } + } + } + return ClassLoadingTarget.INSTRUMENTATION_ISOLATED; + } + + public static ClassLoadingTarget getTarget(String className, ClassLoader classLoader) { + return null; + } +} diff --git a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtilTest.java b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtilTest.java new file mode 100644 index 000000000000..81ee354efa4c --- /dev/null +++ b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtilTest.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling.instrumentation.indy; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingStrategy; +import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingTarget; +import java.io.IOException; +import java.io.InputStream; +import net.bytebuddy.utility.StreamDrainer; +import org.junit.jupiter.api.Test; + +public class ClassLoadingTargetUtilTest { + + @Test + void checkTarget() { + // isolated by default when not explicitly set + testStrategy(AClass.class, ClassLoadingTarget.INSTRUMENTATION_ISOLATED); + + // explicitly set at class level + testExplicitAnnotation(BClass.class, ClassLoadingTarget.INSTRUMENTATION_ISOLATED); + testExplicitAnnotation(CClass.class, ClassLoadingTarget.INSTRUMENTATION_SHARED); + testExplicitAnnotation(DClass.class, ClassLoadingTarget.INSTRUMENTATION_TARGET); + + // explicitly set at package level, we test only once as the implementation is same as class + byte[] packageByteCode = + getPackageByteCode( + "io.opentelemetry.javaagent.tooling.instrumentation.indy.dummies.targetcl"); + assertThat(ClassLoadingTargetUtil.getTarget(packageByteCode)) + .isEqualTo(ClassLoadingTarget.INSTRUMENTATION_SHARED); + } + + private void testStrategy(Class type, ClassLoadingTarget expected) { + byte[] bytecode = getClassByteCode(type); + assertThat(ClassLoadingTargetUtil.getTarget(bytecode)).isEqualTo(expected); + } + + private void testExplicitAnnotation(Class type, ClassLoadingTarget expected) { + ClassLoadingStrategy annotation = type.getAnnotation(ClassLoadingStrategy.class); + assertThat(annotation).isNotNull(); + assertThat(annotation.value()).isEqualTo(expected); + testStrategy(type, expected); + } + + private byte[] getClassByteCode(Class type) { + String classFileName = type.getName().replace('.', '/') + ".class"; + return getByteCode(classFileName); + } + + private byte[] getByteCode(String resourcePath) { + try (InputStream input = getClass().getClassLoader().getResourceAsStream(resourcePath)) { + assertThat(input).isNotNull(); + return StreamDrainer.DEFAULT.drain(input); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private byte[] getPackageByteCode(String packageName) { + String packageFileName = packageName.replace('.', '/') + "/package-info.class"; + return getByteCode(packageFileName); + } + + private static class AClass {} + + @ClassLoadingStrategy(ClassLoadingTarget.INSTRUMENTATION_ISOLATED) + private static class BClass {} + + @ClassLoadingStrategy(ClassLoadingTarget.INSTRUMENTATION_SHARED) + private static class CClass {} + + @ClassLoadingStrategy(ClassLoadingTarget.INSTRUMENTATION_TARGET) + private static class DClass {} +} diff --git a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/dummies/targetcl/package-info.java b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/dummies/targetcl/package-info.java new file mode 100644 index 000000000000..502ff1ab6afe --- /dev/null +++ b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/dummies/targetcl/package-info.java @@ -0,0 +1,5 @@ +@ClassLoadingStrategy(ClassLoadingTarget.INSTRUMENTATION_SHARED) +package io.opentelemetry.javaagent.tooling.instrumentation.indy.dummies.targetcl; + +import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingStrategy; +import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingTarget; From 3187a96dc29d5ae5913db190913df9881a54f4b1 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 22 May 2026 13:51:00 +0200 Subject: [PATCH 2/4] add fallback + improve API --- .../indy/ClassLoadingTargetUtil.java | 59 ++++++++++++++++++- .../indy/ClassLoadingTargetUtilTest.java | 57 +++++++++--------- .../indy/dummies/targetcl/DummyInherit.java | 8 +++ .../indy/dummies/targetcl/DummyOverride.java | 12 ++++ 4 files changed, 104 insertions(+), 32 deletions(-) create mode 100644 javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/dummies/targetcl/DummyInherit.java create mode 100644 javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/dummies/targetcl/DummyOverride.java diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java index 6c2e1e8b263e..c883c32f05c5 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java @@ -7,6 +7,10 @@ import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingStrategy; import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingTarget; +import java.io.IOException; +import java.io.InputStream; +import javax.annotation.Nullable; +import net.bytebuddy.utility.StreamDrainer; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AnnotationNode; @@ -28,7 +32,8 @@ private ClassLoadingTargetUtil() {} * if annotation is not present. */ // package-protected for testing - static ClassLoadingTarget getTarget(byte[] bytecode) { + @Nullable + private static ClassLoadingTarget getTarget(byte[] bytecode) { ClassReader cr = new ClassReader(bytecode); ClassNode classNode = new ClassNode(); cr.accept(classNode, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG); @@ -46,10 +51,58 @@ static ClassLoadingTarget getTarget(byte[] bytecode) { } } } + return null; + } + + /** + * Get class target with fallback on package target. + * + * @param className class name + * @param classLoader class loader to load class/package bytecode + * @return class loading target, defaults to {@link ClassLoadingTarget#INSTRUMENTATION_ISOLATED} + * if annotation is not present + */ + public static ClassLoadingTarget getClassTarget(String className, ClassLoader classLoader) { + ClassLoadingTarget classTarget = classTarget(className, classLoader); + if (null != classTarget) { + return classTarget; + } + String packageName = className.substring(0, className.lastIndexOf('.')); + ClassLoadingTarget packageTarget = packageTarget(packageName, classLoader); + if (packageTarget != null) { + return packageTarget; + } return ClassLoadingTarget.INSTRUMENTATION_ISOLATED; } - public static ClassLoadingTarget getTarget(String className, ClassLoader classLoader) { - return null; + // package-private for testing + @Nullable + static ClassLoadingTarget packageTarget(String packageName, ClassLoader classLoader) { + String packagePath = packageName.replace(".", "/") + "/package-info.class"; + byte[] byteCode = getByteCode(packagePath, classLoader); + return byteCode == null ? null : getTarget(byteCode); + } + + // package-private for testing + @Nullable + static ClassLoadingTarget classTarget(String className, ClassLoader classLoader) { + String classPath = className.replace(".", "/") + ".class"; + byte[] byteCode = getByteCode(classPath, classLoader); + if (byteCode == null) { + throw new IllegalArgumentException("missing class: " + classPath); + } + return getTarget(byteCode); + } + + @Nullable + private static byte[] getByteCode(String resourcePath, ClassLoader classLoader) { + try (InputStream input = classLoader.getResourceAsStream(resourcePath)) { + if (input == null) { + return null; + } + return StreamDrainer.DEFAULT.drain(input); + } catch (IOException e) { + throw new IllegalStateException(e); + } } } diff --git a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtilTest.java b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtilTest.java index 81ee354efa4c..4b7f34731dad 100644 --- a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtilTest.java +++ b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtilTest.java @@ -6,12 +6,12 @@ package io.opentelemetry.javaagent.tooling.instrumentation.indy; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingStrategy; import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingTarget; -import java.io.IOException; -import java.io.InputStream; -import net.bytebuddy.utility.StreamDrainer; +import io.opentelemetry.javaagent.tooling.instrumentation.indy.dummies.targetcl.DummyInherit; +import io.opentelemetry.javaagent.tooling.instrumentation.indy.dummies.targetcl.DummyOverride; import org.junit.jupiter.api.Test; public class ClassLoadingTargetUtilTest { @@ -26,17 +26,35 @@ void checkTarget() { testExplicitAnnotation(CClass.class, ClassLoadingTarget.INSTRUMENTATION_SHARED); testExplicitAnnotation(DClass.class, ClassLoadingTarget.INSTRUMENTATION_TARGET); - // explicitly set at package level, we test only once as the implementation is same as class - byte[] packageByteCode = - getPackageByteCode( - "io.opentelemetry.javaagent.tooling.instrumentation.indy.dummies.targetcl"); - assertThat(ClassLoadingTargetUtil.getTarget(packageByteCode)) + // explicitly set at package level + String packageName = "io.opentelemetry.javaagent.tooling.instrumentation.indy.dummies.targetcl"; + assertThat(ClassLoadingTargetUtil.packageTarget(packageName, getClass().getClassLoader())) .isEqualTo(ClassLoadingTarget.INSTRUMENTATION_SHARED); + + // package defines values inherited on class + assertThat(DummyInherit.class.getAnnotation(ClassLoadingStrategy.class)) + .describedAs("no annotation is present on class") + .isNull(); + assertThat( + ClassLoadingTargetUtil.getClassTarget( + DummyInherit.class.getName(), getClass().getClassLoader())) + .describedAs("package annotation is applied for class lookup") + .isEqualTo(ClassLoadingTarget.INSTRUMENTATION_SHARED); + + // class lookup has priority over package when defined on both + testExplicitAnnotation(DummyOverride.class, ClassLoadingTarget.INSTRUMENTATION_ISOLATED); + + // should defend against non-existing class + assertThatThrownBy( + () -> + ClassLoadingTargetUtil.getClassTarget( + "this.class.does.not.Exists", getClass().getClassLoader())) + .isInstanceOf(IllegalArgumentException.class); } private void testStrategy(Class type, ClassLoadingTarget expected) { - byte[] bytecode = getClassByteCode(type); - assertThat(ClassLoadingTargetUtil.getTarget(bytecode)).isEqualTo(expected); + assertThat(ClassLoadingTargetUtil.getClassTarget(type.getName(), getClass().getClassLoader())) + .isEqualTo(expected); } private void testExplicitAnnotation(Class type, ClassLoadingTarget expected) { @@ -46,25 +64,6 @@ private void testExplicitAnnotation(Class type, ClassLoadingTarget expected) testStrategy(type, expected); } - private byte[] getClassByteCode(Class type) { - String classFileName = type.getName().replace('.', '/') + ".class"; - return getByteCode(classFileName); - } - - private byte[] getByteCode(String resourcePath) { - try (InputStream input = getClass().getClassLoader().getResourceAsStream(resourcePath)) { - assertThat(input).isNotNull(); - return StreamDrainer.DEFAULT.drain(input); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - - private byte[] getPackageByteCode(String packageName) { - String packageFileName = packageName.replace('.', '/') + "/package-info.class"; - return getByteCode(packageFileName); - } - private static class AClass {} @ClassLoadingStrategy(ClassLoadingTarget.INSTRUMENTATION_ISOLATED) diff --git a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/dummies/targetcl/DummyInherit.java b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/dummies/targetcl/DummyInherit.java new file mode 100644 index 000000000000..0dcbfa8d6963 --- /dev/null +++ b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/dummies/targetcl/DummyInherit.java @@ -0,0 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling.instrumentation.indy.dummies.targetcl; + +public class DummyInherit {} diff --git a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/dummies/targetcl/DummyOverride.java b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/dummies/targetcl/DummyOverride.java new file mode 100644 index 000000000000..dabf14e99e8c --- /dev/null +++ b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/dummies/targetcl/DummyOverride.java @@ -0,0 +1,12 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling.instrumentation.indy.dummies.targetcl; + +import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingStrategy; +import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingTarget; + +@ClassLoadingStrategy(ClassLoadingTarget.INSTRUMENTATION_ISOLATED) +public class DummyOverride {} From ac797231fa124e8ea4d1225709c04e9fa8f7deda Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 22 May 2026 14:17:20 +0200 Subject: [PATCH 3/4] remove bogus comment --- .../tooling/instrumentation/indy/ClassLoadingTargetUtil.java | 1 - 1 file changed, 1 deletion(-) diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java index c883c32f05c5..09c0a24d95ec 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java @@ -31,7 +31,6 @@ private ClassLoadingTargetUtil() {} * @return class loading strategy, defaults to {@link ClassLoadingTarget#INSTRUMENTATION_ISOLATED} * if annotation is not present. */ - // package-protected for testing @Nullable private static ClassLoadingTarget getTarget(byte[] bytecode) { ClassReader cr = new ClassReader(bytecode); From c9932ccb3f04b349a6e4c6dad9d71f17a69ccef7 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 22 May 2026 15:38:18 +0200 Subject: [PATCH 4/4] better deal with missing classes --- .../indy/ClassLoadingTargetUtil.java | 16 +++++++++++----- .../indy/ClassLoadingTargetUtilTest.java | 16 +++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java index 09c0a24d95ec..6b818aa51f07 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtil.java @@ -58,11 +58,17 @@ private static ClassLoadingTarget getTarget(byte[] bytecode) { * * @param className class name * @param classLoader class loader to load class/package bytecode - * @return class loading target, defaults to {@link ClassLoadingTarget#INSTRUMENTATION_ISOLATED} - * if annotation is not present + * @return class loading target, or {@literal null} of no annotation is present */ + @Nullable public static ClassLoadingTarget getClassTarget(String className, ClassLoader classLoader) { - ClassLoadingTarget classTarget = classTarget(className, classLoader); + String classPath = className.replace(".", "/") + ".class"; + byte[] byteCode = getByteCode(classPath, classLoader); + if (byteCode == null) { + // class is not present in this CL + return null; + } + ClassLoadingTarget classTarget = getTarget(byteCode); if (null != classTarget) { return classTarget; } @@ -71,7 +77,7 @@ public static ClassLoadingTarget getClassTarget(String className, ClassLoader cl if (packageTarget != null) { return packageTarget; } - return ClassLoadingTarget.INSTRUMENTATION_ISOLATED; + return null; } // package-private for testing @@ -88,7 +94,7 @@ static ClassLoadingTarget classTarget(String className, ClassLoader classLoader) String classPath = className.replace(".", "/") + ".class"; byte[] byteCode = getByteCode(classPath, classLoader); if (byteCode == null) { - throw new IllegalArgumentException("missing class: " + classPath); + return null; } return getTarget(byteCode); } diff --git a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtilTest.java b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtilTest.java index 4b7f34731dad..d3c7c7007028 100644 --- a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtilTest.java +++ b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/ClassLoadingTargetUtilTest.java @@ -6,7 +6,6 @@ package io.opentelemetry.javaagent.tooling.instrumentation.indy; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingStrategy; import io.opentelemetry.javaagent.extension.instrumentation.internal.ClassLoadingTarget; @@ -18,8 +17,8 @@ public class ClassLoadingTargetUtilTest { @Test void checkTarget() { - // isolated by default when not explicitly set - testStrategy(AClass.class, ClassLoadingTarget.INSTRUMENTATION_ISOLATED); + // not defined at class nor package level + testStrategy(AClass.class, null); // explicitly set at class level testExplicitAnnotation(BClass.class, ClassLoadingTarget.INSTRUMENTATION_ISOLATED); @@ -31,7 +30,7 @@ void checkTarget() { assertThat(ClassLoadingTargetUtil.packageTarget(packageName, getClass().getClassLoader())) .isEqualTo(ClassLoadingTarget.INSTRUMENTATION_SHARED); - // package defines values inherited on class + // package defined values inherited on class assertThat(DummyInherit.class.getAnnotation(ClassLoadingStrategy.class)) .describedAs("no annotation is present on class") .isNull(); @@ -45,11 +44,10 @@ void checkTarget() { testExplicitAnnotation(DummyOverride.class, ClassLoadingTarget.INSTRUMENTATION_ISOLATED); // should defend against non-existing class - assertThatThrownBy( - () -> - ClassLoadingTargetUtil.getClassTarget( - "this.class.does.not.Exists", getClass().getClassLoader())) - .isInstanceOf(IllegalArgumentException.class); + assertThat( + ClassLoadingTargetUtil.getClassTarget( + "this.class.does.not.Exists", getClass().getClassLoader())) + .isNull(); } private void testStrategy(Class type, ClassLoadingTarget expected) {