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());
+ }
}