diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/java/module/JpmsHelper.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/java/module/JpmsHelper.java new file mode 100644 index 00000000000..425842c535f --- /dev/null +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/java/module/JpmsHelper.java @@ -0,0 +1,40 @@ +package datadog.trace.bootstrap.instrumentation.java.module; + +import static java.util.Collections.unmodifiableSet; + +import datadog.trace.api.GenericClassValue; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.annotation.concurrent.NotThreadSafe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@NotThreadSafe +public final class JpmsHelper { + private JpmsHelper() {} + + private static final Set TRIGGERS = new HashSet<>(); + + private static final ClassValue CLSVALUE = + GenericClassValue.constructing(AtomicBoolean.class); + + public static final Logger LOGGER = LoggerFactory.getLogger(JpmsHelper.class); + + public static void addAllTriggers(Iterable classes) { + if (classes == null) { + return; + } + for (String cls : classes) { + TRIGGERS.add(cls); + } + } + + public static Set getAllTriggers() { + return unmodifiableSet(TRIGGERS); + } + + public static boolean shouldBeOpened(Class cls) { + return CLSVALUE.get(cls).compareAndSet(false, true); + } +} diff --git a/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java b/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java index b362f480a70..52087a82ce0 100644 --- a/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java +++ b/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java @@ -19,6 +19,7 @@ import datadog.trace.api.telemetry.IntegrationsCollector; import datadog.trace.bootstrap.FieldBackedContextAccessor; import datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter; +import datadog.trace.bootstrap.instrumentation.java.module.JpmsHelper; import datadog.trace.util.AgentTaskScheduler; import de.thetaphi.forbiddenapis.SuppressForbidden; import java.lang.instrument.ClassFileTransformer; @@ -185,18 +186,26 @@ public static ClassFileTransformer installBytebuddyAgent( Iterable instrumenterModules = withExtensions(instrumenterIndex.modules(enabledSystems)); + final boolean javaModuleSupported = JavaModule.isSupported(); + // This needs to be a separate loop through all instrumentations before we start adding // advice so that we can exclude field injection, since that will try to check exclusion // immediately and we don't have the ability to express dependencies between different // instrumentations to control the load order. for (InstrumenterModule module : instrumenterModules) { + boolean added = false; if (module instanceof ExcludeFilterProvider) { ExcludeFilterProvider provider = (ExcludeFilterProvider) module; ExcludeFilter.add(provider.excludedClasses()); - if (DEBUG) { - log.debug( - "Adding filtered classes - instrumentation.class={}", module.getClass().getName()); - } + added = true; + } + if (javaModuleSupported && module instanceof JavaModuleOpenProvider) { + JpmsHelper.addAllTriggers(((JavaModuleOpenProvider) module).triggerClasses()); + added = true; + } + if (DEBUG && added) { + log.debug( + "Adding filtered classes - instrumentation.class={}", module.getClass().getName()); } } diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterIndex.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterIndex.java index 1595ab7ed0d..89bcb97c2a5 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterIndex.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterIndex.java @@ -1,7 +1,7 @@ package datadog.trace.agent.tooling; import static datadog.trace.agent.tooling.InstrumenterModuleFilter.ALL_MODULES; -import static datadog.trace.agent.tooling.InstrumenterModuleFilter.forTargetSystemsOrExcludeProvider; +import static datadog.trace.agent.tooling.InstrumenterModuleFilter.forTargetSystemsOrNeedToEarlyLoad; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.disjoint; @@ -60,6 +60,9 @@ final class InstrumenterIndex { /** Bit to signal that the encoded item is instance of ExcludeFilterProvider */ private static final int IS_EXCLUDE_FILTER_PROVIDER_FLAG = 0x02; + /** Bit to signal that the encoded item is instance of JavaModuleOpenProvider */ + private static final int IS_JAVA_MODULE_OPEN_PROVIDER_FLAG = 0x04; + static final ClassLoader instrumenterClassLoader = Instrumenter.class.getClassLoader(); private final int instrumentationCount; @@ -101,7 +104,7 @@ private InstrumenterIndex(int instrumentationCount, int transformationCount, byt */ public Iterable modules( final Set enabledSystems) { - return modules(forTargetSystemsOrExcludeProvider(enabledSystems)); + return modules(forTargetSystemsOrNeedToEarlyLoad(enabledSystems)); } public Iterable modules() { @@ -217,6 +220,7 @@ InstrumenterModule nextModule(InstrumenterModuleFilter filter) { // flags final byte flags = (byte) readNumber(); final boolean isExcludeProvider = decodeModuleIsExcludeProvider(flags); + final boolean isJavaModuleOpenProvider = decodeModuleJavaModuleOpenProvider(flags); hasTargetSystemOverrides = decodeModuleHasTargetSystemOverrides(flags); memberAdviceTargetSystemOverrides = null; memberCount = readNumber(); @@ -228,7 +232,7 @@ InstrumenterModule nextModule(InstrumenterModuleFilter filter) { } else { memberName = null; } - if (filter.test(moduleName, moduleTargetSystems, isExcludeProvider)) { + if (filter.test(moduleName, moduleTargetSystems, isExcludeProvider, isJavaModuleOpenProvider)) { if (module == null) { module = buildModule(); modules[instrumentationId] = module; @@ -379,6 +383,9 @@ static byte encodeModuleFlags(final InstrumenterModule module, final boolean has if (module instanceof ExcludeFilterProvider) { ret |= (byte) IS_EXCLUDE_FILTER_PROVIDER_FLAG; } + if (module instanceof JavaModuleOpenProvider) { + ret |= (byte) IS_JAVA_MODULE_OPEN_PROVIDER_FLAG; + } return ret; } @@ -386,6 +393,10 @@ static boolean decodeModuleIsExcludeProvider(byte flags) { return (flags & IS_EXCLUDE_FILTER_PROVIDER_FLAG) != 0; } + static boolean decodeModuleJavaModuleOpenProvider(byte flags) { + return (flags & IS_JAVA_MODULE_OPEN_PROVIDER_FLAG) != 0; + } + static boolean decodeModuleHasTargetSystemOverrides(byte flags) { return (flags & HAS_TARGET_SYSTEMS_OVERRIDES_FLAG) != 0; } diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterModuleFilter.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterModuleFilter.java index cd913cad261..1ae5fec4fbe 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterModuleFilter.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterModuleFilter.java @@ -6,16 +6,18 @@ @FunctionalInterface public interface InstrumenterModuleFilter { - InstrumenterModuleFilter ALL_MODULES = (name, systems, excludeProvider) -> true; + InstrumenterModuleFilter ALL_MODULES = + (name, systems, excludeProvider, isJavaModuleOpenProvider) -> true; - static InstrumenterModuleFilter forTargetSystemsOrExcludeProvider( + static InstrumenterModuleFilter forTargetSystemsOrNeedToEarlyLoad( final Set enabledSystems) { - return (instrumenterModuleName, targetSystems, isExcludeProvider) -> - isExcludeProvider || !disjoint(enabledSystems, targetSystems); + return (instrumenterModuleName, targetSystems, isExcludeProvider, isJavaModuleOpenProvider) -> + isExcludeProvider || isJavaModuleOpenProvider || !disjoint(enabledSystems, targetSystems); } boolean test( String instrumenterModuleName, Set targetSystems, - boolean isExcludeProvider); + boolean isExcludeProvider, + boolean isJavaModuleOpenProvider); } diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/JavaModuleOpenProvider.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/JavaModuleOpenProvider.java new file mode 100644 index 00000000000..7a508a4d113 --- /dev/null +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/JavaModuleOpenProvider.java @@ -0,0 +1,12 @@ +package datadog.trace.agent.tooling; + +/** + * Allows an {@link InstrumenterModule} to possibly open a java module to the unnamed module. + * + *

This is typically used when reflective operations need to be done and the agent cannot assume + * that the host application has permitted them. + */ +public interface JavaModuleOpenProvider { + /** Classes whose constructors trigger the one-time module open when first instantiated. */ + Iterable triggerClasses(); +} diff --git a/dd-java-agent/instrumentation/graal/graal-native-image-20.0/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java b/dd-java-agent/instrumentation/graal/graal-native-image-20.0/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java index 9eb726c0180..cf13266a41e 100644 --- a/dd-java-agent/instrumentation/graal/graal-native-image-20.0/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java +++ b/dd-java-agent/instrumentation/graal/graal-native-image-20.0/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java @@ -141,6 +141,7 @@ public static void onEnter(@Advice.Argument(value = 0, readOnly = false) String[ + "datadog.trace.bootstrap.instrumentation.java.concurrent.QueueTimerHelper:build_time," + "datadog.trace.bootstrap.instrumentation.java.concurrent.QueueTimerHelper$RateLimiterHolder:run_time," + "datadog.trace.bootstrap.instrumentation.java.concurrent.TPEHelper:build_time," + + "datadog.trace.bootstrap.instrumentation.java.module.JpmsHelper:build_time," + "datadog.trace.bootstrap.instrumentation.jfr.exceptions.ExceptionCountEvent:build_time," + "datadog.trace.bootstrap.instrumentation.jfr.exceptions.ExceptionSampleEvent:build_time," + "datadog.trace.bootstrap.instrumentation.jfr.backpressure.BackpressureSampleEvent:build_time," diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-9.0/build.gradle b/dd-java-agent/instrumentation/java/java-lang/java-lang-9.0/build.gradle index 23fb07d046e..134382c1f24 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-9.0/build.gradle +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-9.0/build.gradle @@ -34,6 +34,7 @@ dependencies { project.tasks.withType(AbstractCompile).configureEach { switch (it.name) { case 'compileCsiJava': + case 'compileJava': configureCompiler(it, 11, JavaVersion.VERSION_1_8) break default: diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-9.0/src/main/java/datadog/trace/instrumentation/java/lang/module/JpmsClearanceInstrumentation.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-9.0/src/main/java/datadog/trace/instrumentation/java/lang/module/JpmsClearanceInstrumentation.java new file mode 100644 index 00000000000..9801d984566 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-9.0/src/main/java/datadog/trace/instrumentation/java/lang/module/JpmsClearanceInstrumentation.java @@ -0,0 +1,76 @@ +package datadog.trace.instrumentation.java.lang.module; + +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; + +import com.google.auto.service.AutoService; +import datadog.environment.JavaVirtualMachine; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.bootstrap.instrumentation.java.module.JpmsHelper; +import java.util.Collection; +import java.util.Set; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.implementation.bytecode.assign.Assigner; + +/** + * Generic instrumenter module that will advice the constructor of each known classes in order to + * open once their module. This is marked for bootstrap even if it's not for sure, but we cannot + * know in advance (depends to the instrumented types and today we are instrumenting InetAddress + * that's in the bootstrap). + */ +@AutoService(InstrumenterModule.class) +public class JpmsClearanceInstrumentation extends InstrumenterModule + implements Instrumenter.ForConfiguredTypes, + Instrumenter.ForBootstrap, + Instrumenter.HasMethodAdvice { + public JpmsClearanceInstrumentation() { + super("java-module"); + } + + @Override + public boolean isEnabled() { + return super.isEnabled() && JavaVirtualMachine.isJavaVersionAtLeast(9); + } + + @Override + public boolean isApplicable(Set enabledSystems) { + return true; // not directly linked ot a target system + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice(isConstructor(), getClass().getName() + "$OpenModuleAdvice"); + } + + @Override + public Collection configuredMatchingTypes() { + return JpmsHelper.getAllTriggers(); + } + + public static class OpenModuleAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.This(typing = Assigner.Typing.DYNAMIC) Object self) { + final Class cls = self.getClass(); + if (JpmsHelper.shouldBeOpened(cls)) { + final Module module = cls.getModule(); + if (module != null) { + try { + // This call needs imperatively to be done from the same module we're adding exports + // because the jdk is checking that the caller belongs to the same module. + // The code of this advice is getting inlined into the constructor of the class + // belonging + // to that package so it will work. Moving the same to a helper won't. + module.addOpens(cls.getPackageName(), JpmsHelper.class.getModule()); + final ClassLoader loader = cls.getClassLoader(); + if (loader != null) { + module.addOpens(cls.getPackageName(), loader.getUnnamedModule()); + } + } catch (Throwable t) { + JpmsHelper.LOGGER.debug( + "Unable to open package {} to the unnamed module", cls.getPackageName(), t); + } + } + } + } + } +} diff --git a/dd-java-agent/instrumentation/java/java-net/java-net-11.0/build.gradle b/dd-java-agent/instrumentation/java/java-net/java-net-11.0/build.gradle index a1c8f0f9111..200d06ae197 100644 --- a/dd-java-agent/instrumentation/java/java-net/java-net-11.0/build.gradle +++ b/dd-java-agent/instrumentation/java/java-net/java-net-11.0/build.gradle @@ -11,6 +11,10 @@ tracerJava { addSourceSetFor(JavaVersion.VERSION_11) } +dependencies { + testRuntimeOnly(project(":dd-java-agent:instrumentation:java:java-lang:java-lang-9.0")) +} + tasks.named("compileMain_java11Java", JavaCompile) { configureCompiler(it, 11, JavaVersion.VERSION_11) } diff --git a/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java/datadog/trace/instrumentation/httpclient/JpmsInetAddressInstrumentation.java b/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java/datadog/trace/instrumentation/httpclient/JpmsInetAddressInstrumentation.java index 0f971d7a3b0..b7e0f05a9b9 100644 --- a/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java/datadog/trace/instrumentation/httpclient/JpmsInetAddressInstrumentation.java +++ b/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java/datadog/trace/instrumentation/httpclient/JpmsInetAddressInstrumentation.java @@ -1,33 +1,21 @@ package datadog.trace.instrumentation.httpclient; -import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static java.util.Collections.singleton; import com.google.auto.service.AutoService; -import datadog.environment.JavaVirtualMachine; -import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.agent.tooling.JavaModuleOpenProvider; @AutoService(InstrumenterModule.class) -public class JpmsInetAddressInstrumentation extends InstrumenterModule.Tracing - implements Instrumenter.HasMethodAdvice, Instrumenter.ForSingleType { +public class JpmsInetAddressInstrumentation extends InstrumenterModule + implements JavaModuleOpenProvider { public JpmsInetAddressInstrumentation() { super("java-net"); } @Override - public boolean isEnabled() { - return super.isEnabled() && JavaVirtualMachine.isJavaVersionAtLeast(9); - } - - @Override - public String instrumentedType() { - return "java.net.InetAddress"; - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - // it does not work with typeInitializer() - transformer.applyAdvice(isConstructor(), packageName + ".JpmsInetAddressClearanceAdvice"); + public Iterable triggerClasses() { + return singleton("java.net.InetAddress"); } } diff --git a/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java11/datadog/trace/instrumentation/httpclient/JpmsInetAddressClearanceAdvice.java b/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java11/datadog/trace/instrumentation/httpclient/JpmsInetAddressClearanceAdvice.java deleted file mode 100644 index 201b13ba3f5..00000000000 --- a/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java11/datadog/trace/instrumentation/httpclient/JpmsInetAddressClearanceAdvice.java +++ /dev/null @@ -1,19 +0,0 @@ -package datadog.trace.instrumentation.httpclient; - -import datadog.trace.bootstrap.instrumentation.java.net.HostNameResolver; -import datadog.trace.bootstrap.instrumentation.java.net.JpmsInetAddressHelper; -import java.net.InetAddress; -import net.bytebuddy.asm.Advice; - -public class JpmsInetAddressClearanceAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void openOnReturn() { - if (JpmsInetAddressHelper.OPENED.compareAndSet(false, true)) { - // This call needs imperatively to be done from the same module we're adding opens to, - // because the JDK checks that the caller belongs to the same module. - // The code of this advice is inlined into the constructor of InetAddress (java.base), - // so it will work. Moving the same call to a helper class won't. - InetAddress.class.getModule().addOpens("java.net", HostNameResolver.class.getModule()); - } - } -} diff --git a/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/test/groovy/datadog/trace/instrumentation/httpclient/JpmsInetAddressDisabledForkedTest.groovy b/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/test/groovy/datadog/trace/instrumentation/httpclient/JpmsInetAddressDisabledForkedTest.groovy index bcf7fd11042..fc38a05e100 100644 --- a/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/test/groovy/datadog/trace/instrumentation/httpclient/JpmsInetAddressDisabledForkedTest.groovy +++ b/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/test/groovy/datadog/trace/instrumentation/httpclient/JpmsInetAddressDisabledForkedTest.groovy @@ -16,7 +16,7 @@ class JpmsInetAddressDisabledForkedTest extends InstrumentationSpecification { // Disable the JPMS instrumentation so java.net is NOT opened for deep reflection. // HostNameResolver will be unable to bypass the IP→hostname cache and will fall back // to the cache keyed by IP address. - injectSysConfig("dd.trace.java-net.enabled", "false") + injectSysConfig("dd.trace.java-module.enabled", "false") } /** diff --git a/dd-java-agent/instrumentation/mule-4.5/build.gradle b/dd-java-agent/instrumentation/mule-4.5/build.gradle index 518545d6999..a09dc6b0ead 100644 --- a/dd-java-agent/instrumentation/mule-4.5/build.gradle +++ b/dd-java-agent/instrumentation/mule-4.5/build.gradle @@ -78,9 +78,6 @@ configurations.configureEach { } sourceSets { - main_java11 { - java.srcDirs "${project.projectDir}/src/main/java11" - } test { output.dir("$buildDir/generated-resources/test", builtBy: 'generateAppResources') } @@ -92,13 +89,6 @@ sourceSets { } } -tasks.named("compileMain_java11Java", JavaCompile) { - configureCompiler(it, 11, JavaVersion.VERSION_1_8) -} - -tasks.named("jar", Jar) { - from sourceSets.main_java11.output -} tasks.named("forkedTest", Test) { testJvmConstraints { @@ -137,15 +127,12 @@ tasks.named("latestDepForkedTest", Test) { } dependencies { + compileOnly project(':internal-api') + compileOnly project(':dd-java-agent:agent-tooling') + compileOnly project(':dd-java-agent:agent-bootstrap') compileOnly group: 'org.mule.runtime', name: 'mule-core', version: muleVersion compileOnly group: 'org.mule.runtime', name: 'mule-tracer-customization-impl', version: muleVersion - compileOnly sourceSets.main_java11.output - main_java11CompileOnly project(':internal-api') - main_java11CompileOnly project(':dd-java-agent:agent-tooling') - main_java11CompileOnly project(':dd-java-agent:agent-bootstrap') - - testImplementation sourceSets.main_java11.output testImplementation project(':dd-java-agent:instrumentation:aws-java:aws-java-common') testImplementation project(':dd-java-agent:instrumentation:reactor-core-3.1') testImplementation project(':dd-java-agent:instrumentation:reactive-streams-1.0') @@ -154,6 +141,7 @@ dependencies { testImplementation project(':dd-java-agent:instrumentation:grizzly:grizzly-client-1.9') testImplementation project(':dd-java-agent:instrumentation:caffeine-1.0') testImplementation project(':dd-java-agent:instrumentation:quartz-2.0') + testImplementation project(':dd-java-agent:instrumentation:java:java-lang:java-lang-9.0') testImplementation group: 'org.mule.runtime', name: 'mule-module-launcher', version: muleVersion testImplementation group: 'org.mule.runtime', name: 'mule-core', version: muleVersion @@ -282,9 +270,3 @@ spotless { target "**/*.java" } } - -idea { - module { - jdkName = '11' - } -} diff --git a/dd-java-agent/instrumentation/mule-4.5/gradle.lockfile b/dd-java-agent/instrumentation/mule-4.5/gradle.lockfile index f0555e2d1ef..b3910d54328 100644 --- a/dd-java-agent/instrumentation/mule-4.5/gradle.lockfile +++ b/dd-java-agent/instrumentation/mule-4.5/gradle.lockfile @@ -9,15 +9,15 @@ ch.qos.logback:logback-core:1.2.13=latestDepForkedTestCompileClasspath,latestDep ch.randelshofer:fastdoubleparser:0.8.0=mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,mule46Services,muleServices,testCompileClasspath,testRuntimeClasspath com.beust:jcommander:1.78=mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath com.beust:jcommander:1.82=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath -com.blogspot.mydailyjava:weak-lock-free:0.17=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,main_java11CompileClasspath,main_java11RuntimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.blogspot.mydailyjava:weak-lock-free:0.17=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,compileClasspath,runtimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.chuusai:shapeless_2.12:2.3.3=mule46Services,muleServices com.conversantmedia:disruptor:1.2.10=compileClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath com.conversantmedia:disruptor:1.2.21=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath com.damnhandy:handy-uri-templates:2.1.8=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestMuleServices,mule46Services,muleServices com.datadoghq.okhttp3:okhttp:3.12.15=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath com.datadoghq.okio:okio:1.17.6=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:dd-instrument-java:0.0.3=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,main_java11CompileClasspath,main_java11RuntimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:dd-javac-plugin-client:0.2.2=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,main_java11CompileClasspath,main_java11RuntimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-instrument-java:0.0.3=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,compileClasspath,runtimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-javac-plugin-client:0.2.2=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,compileClasspath,runtimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.datadoghq:java-dogstatsd-client:4.4.5=latestDepForkedTestRuntimeClasspath,mule46ForkedTestRuntimeClasspath,testRuntimeClasspath com.datadoghq:sketches-java:0.8.3=latestDepForkedTestRuntimeClasspath,mule46ForkedTestRuntimeClasspath,testRuntimeClasspath com.fasterxml.jackson.core:jackson-annotations:2.10.0=compileClasspath @@ -268,8 +268,8 @@ joda-time:joda-time:2.12.5=mule46ForkedTestCompileClasspath,mule46ForkedTestRunt joda-time:joda-time:2.14.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath joda-time:joda-time:2.9.1=compileClasspath,testCompileClasspath,testRuntimeClasspath junit:junit:4.13.2=latestDepForkedTestRuntimeClasspath,mule46ForkedTestRuntimeClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy-agent:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,main_java11CompileClasspath,main_java11RuntimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.14.18=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,main_java11CompileClasspath,main_java11RuntimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,compileClasspath,runtimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.14.18=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,compileClasspath,runtimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath net.java.dev.jna:jna-platform:5.8.0=latestDepForkedTestRuntimeClasspath,mule46ForkedTestRuntimeClasspath,testRuntimeClasspath net.java.dev.jna:jna:5.8.0=latestDepForkedTestRuntimeClasspath,mule46ForkedTestRuntimeClasspath,testRuntimeClasspath net.java.dev.msv:xsdlib:2013.6.1=mule46ForkedTestRuntimeClasspath,testRuntimeClasspath @@ -789,7 +789,7 @@ org.ow2.asm:asm-tree:9.9.1=latestDepForkedTestRuntimeClasspath,mule46ForkedTestR org.ow2.asm:asm-util:9.7.1=latestDepForkedTestRuntimeClasspath,mule46ForkedTestRuntimeClasspath,testRuntimeClasspath org.ow2.asm:asm-util:9.9=spotbugs org.ow2.asm:asm:9.9=spotbugs -org.ow2.asm:asm:9.9.1=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,main_java11CompileClasspath,main_java11RuntimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.ow2.asm:asm:9.9.1=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,compileClasspath,runtimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.parboiled:parboiled_2.12:2.1.8=mule46Services,muleServices org.parboiled:parboiled_2.12:2.5.1=latestMuleServices org.quartz-scheduler:quartz:2.3.2=mule46Services,muleServices @@ -809,13 +809,13 @@ org.slf4j:jcl-over-slf4j:2.0.7=mule46ForkedTestCompileClasspath,mule46ForkedTest org.slf4j:jul-to-slf4j:2.0.17=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath org.slf4j:jul-to-slf4j:2.0.7=mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.slf4j:log4j-over-slf4j:1.7.30=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:slf4j-api:1.7.30=buildTimeInstrumentationPlugin,main_java11CompileClasspath,main_java11RuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath +org.slf4j:slf4j-api:1.7.30=buildTimeInstrumentationPlugin,compileClasspath,runtimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath org.slf4j:slf4j-api:1.7.36=mule46Services,muleServices org.slf4j:slf4j-api:2.0.16=latestMuleServices org.slf4j:slf4j-api:2.0.17=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,spotbugs,spotbugsSlf4j org.slf4j:slf4j-api:2.0.7=compileClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j -org.snakeyaml:snakeyaml-engine:2.9=buildTimeInstrumentationPlugin,latestDepForkedTestRuntimeClasspath,main_java11RuntimeClasspath,mule46ForkedTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.snakeyaml:snakeyaml-engine:2.9=buildTimeInstrumentationPlugin,latestDepForkedTestRuntimeClasspath,runtimeClasspath,mule46ForkedTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath org.spockframework:spock-bom:2.4-groovy-3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.spockframework:spock-core:2.4-groovy-3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework:spring-aop:5.3.27=mule46ForkedTestCompileClasspath,mule46ForkedTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath @@ -842,4 +842,4 @@ org.yaml:snakeyaml:2.0=compileClasspath,mule46ForkedTestCompileClasspath,mule46F org.yaml:snakeyaml:2.4=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath relaxngDatatype:relaxngDatatype:20020414=mule46ForkedTestRuntimeClasspath,testRuntimeClasspath xpp3:xpp3:1.1.4c=mule46ForkedTestRuntimeClasspath,testRuntimeClasspath -empty=latestDepTestAnnotationProcessor,latestDepTestCompileOnly,latestDepTestImplementation,latestDepTestRuntimeOnly,main_java11AnnotationProcessor,mule46TestAnnotationProcessor,mule46TestCompileOnly,mule46TestImplementation,mule46TestRuntimeOnly,spotbugsPlugins +empty=latestDepTestAnnotationProcessor,latestDepTestCompileOnly,latestDepTestImplementation,latestDepTestRuntimeOnly,annotationProcessor,mule46TestAnnotationProcessor,mule46TestCompileOnly,mule46TestImplementation,mule46TestRuntimeOnly,spotbugsPlugins diff --git a/dd-java-agent/instrumentation/mule-4.5/src/main/java/datadog/trace/instrumentation/mule4/JpmsMuleInstrumentation.java b/dd-java-agent/instrumentation/mule-4.5/src/main/java/datadog/trace/instrumentation/mule4/JpmsMuleInstrumentation.java index f638e91971f..4a7d02510ef 100644 --- a/dd-java-agent/instrumentation/mule-4.5/src/main/java/datadog/trace/instrumentation/mule4/JpmsMuleInstrumentation.java +++ b/dd-java-agent/instrumentation/mule-4.5/src/main/java/datadog/trace/instrumentation/mule4/JpmsMuleInstrumentation.java @@ -1,41 +1,19 @@ package datadog.trace.instrumentation.mule4; -import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static java.util.Arrays.asList; import com.google.auto.service.AutoService; -import datadog.environment.JavaVirtualMachine; -import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.agent.tooling.JavaModuleOpenProvider; import datadog.trace.agent.tooling.muzzle.Reference; @AutoService(InstrumenterModule.class) public class JpmsMuleInstrumentation extends InstrumenterModule.Tracing - implements Instrumenter.HasMethodAdvice, Instrumenter.ForKnownTypes { + implements JavaModuleOpenProvider { public JpmsMuleInstrumentation() { super("mule", "mule-jpms"); } - @Override - public boolean isEnabled() { - return super.isEnabled() && JavaVirtualMachine.isJavaVersionAtLeast(9); - } - - @Override - public String[] knownMatchingTypes() { - return new String[] { - // same module but they can be initialized in any order - "org.mule.runtime.tracer.customization.impl.info.ExecutionInitialSpanInfo", - "org.mule.runtime.tracer.customization.impl.provider.LazyInitialSpanInfo", - }; - } - - @Override - public String[] helperClassNames() { - return new String[] { - packageName + ".JpmsAdvisingHelper", - }; - } - @Override public Reference[] additionalMuzzleReferences() { return new Reference[] { @@ -52,8 +30,9 @@ public Reference[] additionalMuzzleReferences() { } @Override - public void methodAdvice(MethodTransformer transformer) { - // it does not work with typeInitializer() - transformer.applyAdvice(isConstructor(), packageName + ".JpmsClearanceAdvice"); + public Iterable triggerClasses() { + return asList( + "org.mule.runtime.tracer.customization.impl.info.ExecutionInitialSpanInfo", + "org.mule.runtime.tracer.customization.impl.provider.LazyInitialSpanInfo"); } } diff --git a/dd-java-agent/instrumentation/mule-4.5/src/main/java11/datadog/trace/instrumentation/mule4/JpmsAdvisingHelper.java b/dd-java-agent/instrumentation/mule-4.5/src/main/java11/datadog/trace/instrumentation/mule4/JpmsAdvisingHelper.java deleted file mode 100644 index 22313a64889..00000000000 --- a/dd-java-agent/instrumentation/mule-4.5/src/main/java11/datadog/trace/instrumentation/mule4/JpmsAdvisingHelper.java +++ /dev/null @@ -1,11 +0,0 @@ -package datadog.trace.instrumentation.mule4; - -import datadog.trace.api.GenericClassValue; -import java.util.concurrent.atomic.AtomicBoolean; - -public class JpmsAdvisingHelper { - public static final ClassValue ALREADY_PROCESSED_CACHE = - GenericClassValue.constructing(AtomicBoolean.class); - - private JpmsAdvisingHelper() {} -} diff --git a/dd-java-agent/instrumentation/mule-4.5/src/main/java11/datadog/trace/instrumentation/mule4/JpmsClearanceAdvice.java b/dd-java-agent/instrumentation/mule-4.5/src/main/java11/datadog/trace/instrumentation/mule4/JpmsClearanceAdvice.java deleted file mode 100644 index a479ae4ec4c..00000000000 --- a/dd-java-agent/instrumentation/mule-4.5/src/main/java11/datadog/trace/instrumentation/mule4/JpmsClearanceAdvice.java +++ /dev/null @@ -1,26 +0,0 @@ -package datadog.trace.instrumentation.mule4; - -import static datadog.trace.instrumentation.mule4.JpmsAdvisingHelper.ALREADY_PROCESSED_CACHE; - -import net.bytebuddy.asm.Advice; -import net.bytebuddy.implementation.bytecode.assign.Assigner; - -public class JpmsClearanceAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void openOnReturn(@Advice.This(typing = Assigner.Typing.DYNAMIC) Object self) { - final Class cls = self.getClass(); - if (ALREADY_PROCESSED_CACHE.get(cls).compareAndSet(false, true)) { - final Module module = cls.getModule(); - if (module != null) { - try { - // This call needs imperatively to be done from the same module we're adding exports - // because the jdk is checking that the caller belongs to the same module. - // The code of this advice is getting inlined into the constructor of the class belonging - // to that package so it will work. Moving the same to a helper won't. - module.addExports(cls.getPackageName(), module.getClassLoader().getUnnamedModule()); - } catch (Throwable ignored) { - } - } - } - } -} diff --git a/docs/add_new_instrumentation.md b/docs/add_new_instrumentation.md index 2adcdf24842..c5375eb397c 100644 --- a/docs/add_new_instrumentation.md +++ b/docs/add_new_instrumentation.md @@ -50,6 +50,12 @@ include ':dd-java-agent:instrumentation:google-http-client' see [Type Matching](./how_instrumentations_work.md#type-matching)) 7. Pass the instrumentation name to the superclass constructor +> [!NOTE] +> **Need reflective access to a named Java module (Java 9+)?** If your instrumentation performs reflection on types +> inside a module whose packages are not opened by the host application, also implement +> `JavaModuleOpenProvider` on your `InstrumenterModule` and return the trigger classes from `triggerClasses()`. +> See [JPMS Module Opening](./how_instrumentations_work.md#jpms-module-opening) for details. + ```java @AutoService(InstrumenterModule.class) diff --git a/docs/how_instrumentations_work.md b/docs/how_instrumentations_work.md index e4ed01dff8e..0a619b701c7 100644 --- a/docs/how_instrumentations_work.md +++ b/docs/how_instrumentations_work.md @@ -855,6 +855,71 @@ If reflection must be used the reflection usage should be added to See [GraalVM configuration docs](https://www.graalvm.org/jdk17/reference-manual/native-image/dynamic-features/Reflection/#manual-configuration). +## JPMS Module Opening + +Java 9 introduced the Java Platform Module System (JPMS), which restricts reflective access across module boundaries. +When the agent needs to reflect into a named JDK or library module whose packages are not opened (i.e., the host +application has not passed the corresponding `--add-opens` flag), the reflection fails at runtime. + +### The `JavaModuleOpenProvider` interface + +Any `InstrumenterModule` can additionally implement +[`JavaModuleOpenProvider`](../dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/JavaModuleOpenProvider.java) +to declare _trigger classes_. The first time a trigger class is instantiated, the agent opens the class's enclosing +package to its own module so that subsequent reflective operations succeed. + +```java +import static java.util.Collections.singleton; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.agent.tooling.JavaModuleOpenProvider; + +@AutoService(InstrumenterModule.class) +public class JpmsInetAddressInstrumentation extends InstrumenterModule + implements JavaModuleOpenProvider { + + public JpmsInetAddressInstrumentation() { + super("java-net"); + } + + @Override + public Iterable triggerClasses() { + return singleton("java.net.InetAddress"); + } +} +``` + +This module has no `adviceTransformations()` — its only purpose is to register the trigger class. + +### How it works + +1. During startup, `AgentInstaller` collects every `InstrumenterModule` that implements `JavaModuleOpenProvider` + and registers their trigger classes with `JpmsHelper.addAllTriggers()`. +2. `JpmsClearanceInstrumentation` (a built-in module) instruments the **constructors** of all registered trigger + classes. +3. On the first constructor call of a trigger class, its advice (inlined via ByteBuddy) calls + `module.addOpens(packageName, agentModule)`. The call is inlined into the trigger class's constructor, which + satisfies the JDK's requirement that the caller must belong to the module being opened. +4. `JpmsHelper.shouldBeOpened()` ensures the `addOpens` is performed only once per class. + +> [!NOTE] +> The `module.addOpens()` call must be inlined (via ByteBuddy advice) into the trigger class's constructor — it +> cannot be delegated to a helper method, because the JDK verifies that the calling class belongs to the module +> being opened. + +### When to use it + +Implement `JavaModuleOpenProvider` when: +- Your instrumentation uses reflection on types inside a named JDK or library module. +- You cannot rely on the host application having passed `--add-opens` flags. + +**Examples in the codebase:** +- [`JpmsInetAddressInstrumentation`](../dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java/datadog/trace/instrumentation/httpclient/JpmsInetAddressInstrumentation.java) + — opens `java.net` for `InetAddress` reflective operations. +- [`JpmsMuleInstrumentation`](../dd-java-agent/instrumentation/mule-4.5/src/main/java/datadog/trace/instrumentation/mule4/JpmsMuleInstrumentation.java) + — opens Mule's internal tracer packages. + ## Testing ### Instrumentation Tests diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index a1569256c66..2c49084e48b 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -11600,6 +11600,14 @@ "default": "true", "aliases": [] } + ], + "DD_TRACE_JAVA_MODULE_ENABLED": [ + { + "version": "A", + "type": "boolean", + "default": "true", + "aliases": ["DD_TRACE_INTEGRATION_JAVA_MODULE_ENABLED", "DD_INTEGRATION_JAVA_MODULE_ENABLED"] + } ] }, "deprecations": {}