diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java index 7ebb99c36a3..246ebe2b336 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java @@ -18,6 +18,7 @@ import datadog.trace.api.git.GitInfoProvider; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.civisibility.compiler.CompilerModuleExporter; import datadog.trace.civisibility.config.ExecutionSettings; import datadog.trace.civisibility.config.JvmInfo; import datadog.trace.civisibility.coverage.file.instrumentation.CoverageClassTransformer; @@ -75,6 +76,10 @@ public static void start(Instrumentation inst, SharedCommunicationObjects sco) { sco.createRemaining(config); + if (config.isCiVisibilityCompilerPluginAutoConfigurationEnabled()) { + inst.addTransformer(new CompilerModuleExporter(inst)); + } + CiVisibilityMetricCollector metricCollector = config.isCiVisibilityTelemetryEnabled() ? new CiVisibilityMetricCollectorImpl() diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/compiler/CompilerModuleExporter.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/compiler/CompilerModuleExporter.java new file mode 100644 index 00000000000..31344f9b298 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/compiler/CompilerModuleExporter.java @@ -0,0 +1,65 @@ +package datadog.trace.civisibility.compiler; + +import datadog.trace.util.JDK9ModuleAccess; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; +import java.util.concurrent.ConcurrentHashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Exports jdk.compiler internal packages to the classloader that loads dd-javac-plugin. + * + *

On JDK 16+ (strong encapsulation), dd-javac-plugin's CompilerModuleOpener uses burningwave to + * export these packages. On JDK 26+, burningwave fails due to Unsafe restrictions (JEP 471/498). + * This transformer intercepts dd-javac-plugin class loading and does the export using + * Instrumentation.redefineModule() instead. + * + *

Each Maven compilation step (compile, testCompile) may use a different classloader, so we + * track which classloaders have already been exported to and re-export for new ones. + */ +public class CompilerModuleExporter implements ClassFileTransformer { + + private static final Logger LOGGER = LoggerFactory.getLogger(CompilerModuleExporter.class); + + private static final String COMPILER_PLUGIN_CLASS_PREFIX = "datadog/compiler/"; + private static final String[] COMPILER_PACKAGES = { + "com.sun.tools.javac.api", + "com.sun.tools.javac.code", + "com.sun.tools.javac.comp", + "com.sun.tools.javac.tree", + "com.sun.tools.javac.util" + }; + + private final Instrumentation inst; + private final ConcurrentHashMap exportedClassLoaders = + new ConcurrentHashMap<>(); + + public CompilerModuleExporter(Instrumentation inst) { + this.inst = inst; + } + + @Override + public byte[] transform( + ClassLoader loader, + String className, + Class classBeingRedefined, + ProtectionDomain protectionDomain, + byte[] classfileBuffer) { + if (loader != null && className != null && className.startsWith(COMPILER_PLUGIN_CLASS_PREFIX)) { + exportedClassLoaders.computeIfAbsent(loader, this::exportJdkCompilerModule); + } + return null; // no bytecode modification + } + + private Boolean exportJdkCompilerModule(ClassLoader loader) { + try { + JDK9ModuleAccess.exportModuleToUnnamedModule(inst, "jdk.compiler", COMPILER_PACKAGES, loader); + LOGGER.debug("Exported jdk.compiler to classloader {}", loader); + } catch (Throwable e) { + LOGGER.debug("Could not export jdk.compiler packages for compiler plugin", e); + } + return Boolean.TRUE; + } +} diff --git a/dd-smoke-tests/gradle/src/test/groovy/datadog/smoketest/GradleDaemonSmokeTest.groovy b/dd-smoke-tests/gradle/src/test/groovy/datadog/smoketest/GradleDaemonSmokeTest.groovy index a8a65adbfa1..2b240f47ab9 100644 --- a/dd-smoke-tests/gradle/src/test/groovy/datadog/smoketest/GradleDaemonSmokeTest.groovy +++ b/dd-smoke-tests/gradle/src/test/groovy/datadog/smoketest/GradleDaemonSmokeTest.groovy @@ -20,9 +20,6 @@ import spock.lang.IgnoreIf import spock.lang.Shared import spock.lang.TempDir -@IgnoreIf(reason = "TODO: Fix for Java 26. Javac plugin fails to populate source tags correctly.", value = { - JavaVirtualMachine.isJavaVersionAtLeast(26) -}) class GradleDaemonSmokeTest extends AbstractGradleTest { private static final String TEST_SERVICE_NAME = "test-gradle-service" diff --git a/dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy b/dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy index ded0444c940..7bead3003ea 100644 --- a/dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy +++ b/dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy @@ -25,9 +25,6 @@ import java.util.concurrent.TimeoutException import static org.junit.jupiter.api.Assumptions.assumeTrue -@IgnoreIf(reason = "TODO: Fix for Java 26. Maven compiler fails to compile the tests for Java 26-ea.", value = { - JavaVirtualMachine.isJavaVersionAtLeast(26) -}) @IgnoreIf(reason = "IBM8 has flaky AES-GCM TLS failures when downloading Maven artifacts", value = { JavaVirtualMachine.isIbm8() }) diff --git a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_junit_platform_runner/pom.xml b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_junit_platform_runner/pom.xml index 488e3ae676d..58e20e3bfef 100644 --- a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_junit_platform_runner/pom.xml +++ b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_junit_platform_runner/pom.xml @@ -57,7 +57,7 @@ org.apache.groovy groovy-bom - 4.0.28 + 4.0.30 pom import diff --git a/internal-api/internal-api-9/src/main/java/datadog/trace/util/JDK9ModuleAccess.java b/internal-api/internal-api-9/src/main/java/datadog/trace/util/JDK9ModuleAccess.java index bf0ab3c81ab..f292862e029 100644 --- a/internal-api/internal-api-9/src/main/java/datadog/trace/util/JDK9ModuleAccess.java +++ b/internal-api/internal-api-9/src/main/java/datadog/trace/util/JDK9ModuleAccess.java @@ -5,6 +5,9 @@ import java.lang.instrument.Instrumentation; import java.lang.reflect.AnnotatedElement; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Set; /** Use standard API to work with JPMS modules on Java9+. */ @@ -33,4 +36,28 @@ public static void addModuleReads( emptySet(), emptyMap()); } + + /** Exports specific packages of a named module to a classloader's unnamed module. */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public static void exportModuleToUnnamedModule( + Instrumentation inst, + String moduleName, + String[] packageNames, + ClassLoader targetClassLoader) { + java.util.Optional optModule = + java.lang.ModuleLayer.boot().findModule(moduleName); + if (!optModule.isPresent()) { + return; + } + java.lang.Module module = optModule.get(); + java.lang.Module unnamedModule = targetClassLoader.getUnnamedModule(); + + Set target = Collections.singleton(unnamedModule); + Map> extraExports = new HashMap<>(); + for (String packageName : packageNames) { + extraExports.put(packageName, target); + } + + inst.redefineModule(module, emptySet(), extraExports, emptyMap(), emptySet(), emptyMap()); + } }