Hide our generated fields and methods from reflection
ADD
instrumentation/internal/internal-reflection/javaagent-integration-tests/build.gradle.kts
+plugins {
+ id("otel.javaagent-testing")
+}
+
+dependencies {
+ testInstrumentation(project(":instrumentation:internal:internal-reflection:javaagent"))
+}

ADD
instrumentation/internal/internal-reflection/javaagent-integration-tests/src/main/java/instrumentation/TestInstrumentationModule.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package instrumentation;
+
+import static java.util.Collections.singletonList;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import java.util.List;
+
+@AutoService(InstrumentationModule.class)
+public class TestInstrumentationModule extends InstrumentationModule {
+ public TestInstrumentationModule() {
+ super("test-instrumentation");
+ }
+
+ @Override
+ public List<TypeInstrumentation> typeInstrumentations() {
+ return singletonList(new TestTypeInstrumentation());
+ }
+}

ADD
instrumentation/internal/internal-reflection/javaagent-integration-tests/src/main/java/instrumentation/TestTypeInstrumentation.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package instrumentation;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class TestTypeInstrumentation implements TypeInstrumentation {
+
+ @Override
+ public ElementMatcher<TypeDescription> typeMatcher() {
+ return named("TestClass");
+ }
+
+ @Override
+ public void transform(TypeTransformer transformer) {
+ transformer.applyAdviceToMethod(
+ named("testMethod"), TestTypeInstrumentation.class.getName() + "$TestAdvice");
+ transformer.applyAdviceToMethod(
+ named("testMethod2"), TestTypeInstrumentation.class.getName() + "$TestAdvice2");
+ }
+
+ @SuppressWarnings("unused")
+ public static class TestAdvice {
+
+ @Advice.OnMethodExit
+ public static void methodExit(
+ @Advice.This Runnable test, @Advice.Return(readOnly = false) String result) {
+ InstrumentationContext.get(Runnable.class, String.class).put(test, "instrumented");
+ result = "instrumented";
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class TestAdvice2 {
+
+ @Advice.OnMethodExit
+ public static void methodExit(
+ @Advice.This Runnable test, @Advice.Return(readOnly = false) String result) {
+ result = InstrumentationContext.get(Runnable.class, String.class).get(test);
+ }
+ }
+}

ADD
instrumentation/internal/internal-reflection/javaagent-integration-tests/src/test/groovy/ReflectionTest.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
+import java.lang.reflect.Field
+import java.lang.reflect.Method
+
+class ReflectionTest extends AgentInstrumentationSpecification {
+
+ def "test our fields and methods are not visible with reflection"() {
+ when:
+ TestClass test = new TestClass()
+
+ then:
+ test.testMethod() == "instrumented"
+ test.testMethod2() == "instrumented"
+
+ and:
+ def fieldFound = false
+ for (Field field : TestClass.getDeclaredFields()) {
+ if (field.getName().startsWith("__opentelemetry")) {
+ fieldFound = true
+ }
+ }
+ fieldFound == false
+
+ and:
+ def methodFound = false
+ for (Method method : TestClass.getDeclaredMethods()) {
+ if (method.getName().contains("__opentelemetry")) {
+ methodFound = true
+ }
+ }
+ methodFound == false
+ }
+}

ADD
instrumentation/internal/internal-reflection/javaagent-integration-tests/src/test/java/TestClass.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+public class TestClass implements Runnable {
+
+ @Override
+ public void run() {}
+
+ public String testMethod() {
+ return "not instrumented";
+ }
+
+ public String testMethod2() {
+ return "not instrumented";
+ }
+}

ADD
instrumentation/internal/internal-reflection/javaagent/build.gradle.kts
+plugins {
+ id("otel.javaagent-instrumentation")
+}
+
+dependencies {
+ compileOnly(project(":javaagent-bootstrap"))
+
+ testImplementation(project(":javaagent-bootstrap"))
+}

ADD
instrumentation/internal/internal-reflection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/reflection/ReflectionHelper.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.internal.reflection;
+
+import io.opentelemetry.javaagent.bootstrap.FieldBackedContextStoreAppliedMarker;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class ReflectionHelper {
+
+ private ReflectionHelper() {}
+
+ public static Field[] filterFields(Class<?> containingClass, Field[] fields) {
+ if (fields.length == 0
+ || !FieldBackedContextStoreAppliedMarker.class.isAssignableFrom(containingClass)) {
+ return fields;
+ }
+ List<Field> result = new ArrayList<>(fields.length);
+ for (Field field : fields) {
+ // FieldBackedProvider marks added fields as synthetic
+ if (field.isSynthetic() && field.getName().startsWith("__opentelemetryContext$")) {
+ continue;
+ }
+ result.add(field);
+ }
+ return result.toArray(new Field[0]);
+ }
+
+ public static Method[] filterMethods(Class<?> containingClass, Method[] methods) {
+ if (methods.length == 0
+ || !FieldBackedContextStoreAppliedMarker.class.isAssignableFrom(containingClass)) {
+ return methods;
+ }
+ List<Method> result = new ArrayList<>(methods.length);
+ for (Method method : methods) {
+ // FieldBackedProvider marks added method as synthetic
+ if (method.isSynthetic()
+ && (method.getName().startsWith("get__opentelemetryContext$")
+ || method.getName().startsWith("set__opentelemetryContext$"))) {
+ continue;
+ }
+ result.add(method);
+ }
+ return result.toArray(new Method[0]);
+ }
+}

ADD
instrumentation/internal/internal-reflection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/reflection/ReflectionIgnoredTypesConfigurer.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.internal.reflection;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.instrumentation.api.config.Config;
+import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesBuilder;
+import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesConfigurer;
+
+@AutoService(IgnoredTypesConfigurer.class)
+public class ReflectionIgnoredTypesConfigurer implements IgnoredTypesConfigurer {
+
+ @Override
+ public void configure(Config config, IgnoredTypesBuilder builder) {
+ builder.allowClass("jdk.internal.reflect.Reflection");
+ builder.allowClass("sun.reflect.Reflection");
+ }
+}

ADD
instrumentation/internal/internal-reflection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/reflection/ReflectionInstrumentation.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.internal.reflection;
+
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.isPublic;
+import static net.bytebuddy.matcher.ElementMatchers.isStatic;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class ReflectionInstrumentation implements TypeInstrumentation {
+
+ @Override
+ public ElementMatcher<TypeDescription> typeMatcher() {
+ return named("jdk.internal.reflect.Reflection").or(named("sun.reflect.Reflection"));
+ }
+
+ @Override
+ public void transform(TypeTransformer transformer) {
+ transformer.applyAdviceToMethod(
+ isMethod()
+ .and(named("filterFields"))
+ .and(takesArguments(2))
+ .and(takesArgument(0, Class.class))
+ .and(takesArgument(1, Field[].class))
+ .and(isPublic())
+ .and(isStatic()),
+ ReflectionInstrumentation.class.getName() + "$FilterFieldsAdvice");
+
+ transformer.applyAdviceToMethod(
+ isMethod()
+ .and(named("filterMethods"))
+ .and(takesArguments(2))
+ .and(takesArgument(0, Class.class))
+ .and(takesArgument(1, Method[].class))
+ .and(isPublic())
+ .and(isStatic()),
+ ReflectionInstrumentation.class.getName() + "$FilterMethodsAdvice");
+ }
+
+ @SuppressWarnings("unused")
+ public static class FilterFieldsAdvice {
+ @Advice.OnMethodExit(suppress = Throwable.class)
+ public static void filter(
+ @Advice.Argument(0) Class<?> containingClass,
+ @Advice.Return(readOnly = false) Field[] fields) {
+ fields = ReflectionHelper.filterFields(containingClass, fields);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FilterMethodsAdvice {
+ @Advice.OnMethodExit(suppress = Throwable.class)
+ public static void filter(
+ @Advice.Argument(0) Class<?> containingClass,
+ @Advice.Return(readOnly = false) Method[] methods) {
+ methods = ReflectionHelper.filterMethods(containingClass, methods);
+ }
+ }
+}

ADD
instrumentation/internal/internal-reflection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/reflection/ReflectionInstrumentationModule.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.internal.reflection;
+
+import static java.util.Collections.singletonList;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import java.util.List;
+
+@AutoService(InstrumentationModule.class)
+public class ReflectionInstrumentationModule extends InstrumentationModule {
+ public ReflectionInstrumentationModule() {
+ super("internal-reflection");
+ }
+
+ @Override
+ public boolean defaultEnabled() {
+ // internal instrumentations are always enabled by default
+ return true;
+ }
+
+ @Override
+ public List<TypeInstrumentation> typeInstrumentations() {
+ return singletonList(new ReflectionInstrumentation());
+ }
+}

MODIFY
settings.gradle.kts
settings.gradle.kts
@@ -116,6 +116,8 @@ include(":instrumentation:internal:internal-class-loader:javaagent-integration-t
include(":instrumentation:internal:internal-eclipse-osgi-3.6:javaagent")
include(":instrumentation:internal:internal-proxy:javaagent")
include(":instrumentation:internal:internal-proxy:javaagent-unit-tests")
+include(":instrumentation:internal:internal-reflection:javaagent")
+include(":instrumentation:internal:internal-reflection:javaagent-integration-tests")
include(":instrumentation:internal:internal-url-class-loader:javaagent")
include(":instrumentation:internal:internal-url-class-loader:javaagent-integration-tests")
include(":instrumentation:couchbase:couchbase-2.0:javaagent")

MODIFY
testing-common/integration-tests/build.gradle.kts
testing-common/integration-tests/build.gradle.kts
@@ -31,6 +31,9 @@ tasks {
}
include("**/FieldInjectionDisabledTest.*")
jvmArgs("-Dotel.javaagent.experimental.field-injection.enabled=false")
+ // 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")
}
test {
@@ -41,5 +44,8 @@ tasks {
}
// 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")
}
}

MODIFY
testing/agent-for-testing/build.gradle.kts
testing/agent-for-testing/build.gradle.kts
@@ -46,6 +46,7 @@ dependencies {
javaagentLibs(project(":instrumentation:internal:internal-class-loader:javaagent"))
javaagentLibs(project(":instrumentation:internal:internal-eclipse-osgi-3.6:javaagent"))
javaagentLibs(project(":instrumentation:internal:internal-proxy:javaagent"))
+ javaagentLibs(project(":instrumentation:internal:internal-reflection:javaagent"))
javaagentLibs(project(":instrumentation:internal:internal-url-class-loader:javaagent"))
// Many tests use OpenTelemetry API calls, e.g. via InstrumentationTestRunner.runWithSpan

Hide our generated fields and methods from reflection (#3948)