From 2d8960be59ca5a5b75f3e1316748071e15647ab4 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 13 Mar 2026 12:19:49 +0100 Subject: [PATCH 1/8] fix: module redefinition to support dd-javac-plugin on jdk26 --- .../civisibility/CiVisibilitySystem.java | 5 ++ .../compiler/CompilerModuleExporter.java | 65 +++++++++++++++++++ .../smoketest/GradleDaemonSmokeTest.groovy | 3 - .../datadog/trace/util/JDK9ModuleAccess.java | 31 +++++++++ 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/compiler/CompilerModuleExporter.java 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..97dabe8c313 --- /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.Collections; +import java.util.Set; +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 Set exportedClassLoaders = + Collections.newSetFromMap(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.add(loader)) { + try { + JDK9ModuleAccess.addModuleExports(inst, "jdk.compiler", COMPILER_PACKAGES, loader); + LOGGER.debug("Exported jdk.compiler packages to classloader {}", loader); + } catch (Throwable e) { + LOGGER.debug("Could not export jdk.compiler packages for compiler plugin", e); + } + } + return null; // no bytecode modification + } +} 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/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..8b2999a1a2f 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,32 @@ public static void addModuleReads( emptySet(), emptyMap()); } + + /** Exports packages from a named module to a classloader's unnamed module. */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public static void addModuleExports( + Instrumentation inst, + String moduleName, + String[] packageNames, + ClassLoader targetClassLoader) { + java.util.Optional optModule = ModuleLayer.boot().findModule(moduleName); + if (!optModule.isPresent()) { + return; + } + Module module = optModule.get(); + Module unnamedModule = targetClassLoader.getUnnamedModule(); + + Map> extraExports = new HashMap<>(); + for (String packageName : packageNames) { + extraExports.put(packageName, Collections.singleton(unnamedModule)); + } + + inst.redefineModule( + module, + (Set) emptySet(), + (Map) extraExports, + (Map) emptyMap(), + emptySet(), + emptyMap()); + } } From fd887e0500776decff6d4761f3de8384df29d421 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 13 Mar 2026 12:22:46 +0100 Subject: [PATCH 2/8] chore: remove ignore to run MavenSmokeTest on JDK26 --- .../src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy | 3 --- 1 file changed, 3 deletions(-) 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 c426478b3ac..093ec05f99f 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 @@ -32,9 +32,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() }) From ce0dcf37af534651e5ffcb413091495c78511658 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 13 Mar 2026 12:23:08 +0100 Subject: [PATCH 3/8] fix: update resource project pom to support JDK26 --- .../test_successful_maven_run_junit_platform_runner/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 34053f5ac3ec517fc8a5ca8de45e2d0517961b47 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 13 Mar 2026 12:47:57 +0100 Subject: [PATCH 4/8] chore: spotless --- .../src/main/java/datadog/trace/util/JDK9ModuleAccess.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) 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 8b2999a1a2f..a77f5cf5e3a 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 @@ -57,11 +57,6 @@ public static void addModuleExports( } inst.redefineModule( - module, - (Set) emptySet(), - (Map) extraExports, - (Map) emptyMap(), - emptySet(), - emptyMap()); + module, (Set) emptySet(), (Map) extraExports, (Map) emptyMap(), emptySet(), emptyMap()); } } From 25dca90ff98e729c3795c88241e16ce65a32138b Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 13 Mar 2026 12:55:24 +0100 Subject: [PATCH 5/8] fix: renaming --- .../civisibility/compiler/CompilerModuleExporter.java | 5 +++-- .../src/main/java/datadog/trace/util/JDK9ModuleAccess.java | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) 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 index 97dabe8c313..d5a040535eb 100644 --- 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 @@ -54,8 +54,9 @@ public byte[] transform( && className.startsWith(COMPILER_PLUGIN_CLASS_PREFIX) && exportedClassLoaders.add(loader)) { try { - JDK9ModuleAccess.addModuleExports(inst, "jdk.compiler", COMPILER_PACKAGES, loader); - LOGGER.debug("Exported jdk.compiler packages to classloader {}", loader); + 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); } 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 a77f5cf5e3a..2f1b7625e01 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 @@ -37,9 +37,9 @@ public static void addModuleReads( emptyMap()); } - /** Exports packages from a named module to a classloader's unnamed module. */ + /** Exports specific packages of a named module to a classloader's unnamed module. */ @SuppressWarnings({"rawtypes", "unchecked"}) - public static void addModuleExports( + public static void exportModuleToUnnamedModule( Instrumentation inst, String moduleName, String[] packageNames, @@ -51,9 +51,10 @@ public static void addModuleExports( Module module = optModule.get(); Module unnamedModule = targetClassLoader.getUnnamedModule(); + Set target = Collections.singleton(unnamedModule); Map> extraExports = new HashMap<>(); for (String packageName : packageNames) { - extraExports.put(packageName, Collections.singleton(unnamedModule)); + extraExports.put(packageName, target); } inst.redefineModule( From 1869284d58226fd5353de4657426082a3e008096 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 13 Mar 2026 16:48:47 +0100 Subject: [PATCH 6/8] fix: fully qualify classes --- .../java/datadog/trace/util/JDK9ModuleAccess.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 2f1b7625e01..eb6ad3de8fd 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 @@ -44,20 +44,20 @@ public static void exportModuleToUnnamedModule( String moduleName, String[] packageNames, ClassLoader targetClassLoader) { - java.util.Optional optModule = ModuleLayer.boot().findModule(moduleName); + java.util.Optional optModule = java.lang.ModuleLayer.boot().findModule(moduleName); if (!optModule.isPresent()) { return; } - Module module = optModule.get(); - Module unnamedModule = targetClassLoader.getUnnamedModule(); + java.lang.Module module = optModule.get(); + java.lang.Module unnamedModule = targetClassLoader.getUnnamedModule(); - Set target = Collections.singleton(unnamedModule); - Map> extraExports = new HashMap<>(); + Set target = Collections.singleton(unnamedModule); + Map> extraExports = new HashMap<>(); for (String packageName : packageNames) { extraExports.put(packageName, target); } inst.redefineModule( - module, (Set) emptySet(), (Map) extraExports, (Map) emptyMap(), emptySet(), emptyMap()); + module, emptySet(), (Map) extraExports, emptyMap(), emptySet(), emptyMap()); } } From c30dfe564d6156a692cce9172baf363f4147f5fa Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 13 Mar 2026 16:51:15 +0100 Subject: [PATCH 7/8] chore: spotless --- .../src/main/java/datadog/trace/util/JDK9ModuleAccess.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 eb6ad3de8fd..0e865fdaacd 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 @@ -44,7 +44,8 @@ public static void exportModuleToUnnamedModule( String moduleName, String[] packageNames, ClassLoader targetClassLoader) { - java.util.Optional optModule = java.lang.ModuleLayer.boot().findModule(moduleName); + java.util.Optional optModule = + java.lang.ModuleLayer.boot().findModule(moduleName); if (!optModule.isPresent()) { return; } @@ -57,7 +58,6 @@ public static void exportModuleToUnnamedModule( extraExports.put(packageName, target); } - inst.redefineModule( - module, emptySet(), (Map) extraExports, emptyMap(), emptySet(), emptyMap()); + inst.redefineModule(module, emptySet(), (Map) extraExports, emptyMap(), emptySet(), emptyMap()); } } From adb3df5bd2c9fcb8426a1fdaddbd62bfa5ced7f8 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 13 Mar 2026 17:42:35 +0100 Subject: [PATCH 8/8] fix: pr suggestions --- .../compiler/CompilerModuleExporter.java | 29 +++++++++---------- .../datadog/trace/util/JDK9ModuleAccess.java | 2 +- 2 files changed, 15 insertions(+), 16 deletions(-) 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 index d5a040535eb..31344f9b298 100644 --- 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 @@ -4,8 +4,6 @@ import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; -import java.util.Collections; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,8 +33,8 @@ public class CompilerModuleExporter implements ClassFileTransformer { }; private final Instrumentation inst; - private final Set exportedClassLoaders = - Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final ConcurrentHashMap exportedClassLoaders = + new ConcurrentHashMap<>(); public CompilerModuleExporter(Instrumentation inst) { this.inst = inst; @@ -49,18 +47,19 @@ public byte[] transform( Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { - if (loader != null - && className != null - && className.startsWith(COMPILER_PLUGIN_CLASS_PREFIX) - && exportedClassLoaders.add(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); - } + 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/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 0e865fdaacd..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 @@ -58,6 +58,6 @@ public static void exportModuleToUnnamedModule( extraExports.put(packageName, target); } - inst.redefineModule(module, emptySet(), (Map) extraExports, emptyMap(), emptySet(), emptyMap()); + inst.redefineModule(module, emptySet(), extraExports, emptyMap(), emptySet(), emptyMap()); } }