Skip to content

Commit e452ed7

Browse files
Add Java 26 support to CI Visibility (#10839) (#10929)
Add Java 26 support to CI Visibility (#10839) fix: module redefinition to support dd-javac-plugin on jdk26 chore: remove ignore to run MavenSmokeTest on JDK26 fix: update resource project pom to support JDK26 Merge branch 'master' into daniel.mohedano/java26-support-module-redefinition chore: spotless fix: renaming fix: fully qualify classes chore: spotless Merge branch 'master' into daniel.mohedano/java26-support-module-redefinition fix: pr suggestions Co-authored-by: daniel.mohedano <daniel.mohedano@datadoghq.com> (cherry picked from commit 57fc106) Co-authored-by: daniel.mohedano <daniel.mohedano@datadoghq.com>
1 parent 428d682 commit e452ed7

File tree

6 files changed

+98
-7
lines changed

6 files changed

+98
-7
lines changed

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import datadog.trace.api.git.GitInfoProvider;
1919
import datadog.trace.bootstrap.ContextStore;
2020
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
21+
import datadog.trace.civisibility.compiler.CompilerModuleExporter;
2122
import datadog.trace.civisibility.config.ExecutionSettings;
2223
import datadog.trace.civisibility.config.JvmInfo;
2324
import datadog.trace.civisibility.coverage.file.instrumentation.CoverageClassTransformer;
@@ -75,6 +76,10 @@ public static void start(Instrumentation inst, SharedCommunicationObjects sco) {
7576

7677
sco.createRemaining(config);
7778

79+
if (config.isCiVisibilityCompilerPluginAutoConfigurationEnabled()) {
80+
inst.addTransformer(new CompilerModuleExporter(inst));
81+
}
82+
7883
CiVisibilityMetricCollector metricCollector =
7984
config.isCiVisibilityTelemetryEnabled()
8085
? new CiVisibilityMetricCollectorImpl()
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package datadog.trace.civisibility.compiler;
2+
3+
import datadog.trace.util.JDK9ModuleAccess;
4+
import java.lang.instrument.ClassFileTransformer;
5+
import java.lang.instrument.Instrumentation;
6+
import java.security.ProtectionDomain;
7+
import java.util.concurrent.ConcurrentHashMap;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
11+
/**
12+
* Exports jdk.compiler internal packages to the classloader that loads dd-javac-plugin.
13+
*
14+
* <p>On JDK 16+ (strong encapsulation), dd-javac-plugin's CompilerModuleOpener uses burningwave to
15+
* export these packages. On JDK 26+, burningwave fails due to Unsafe restrictions (JEP 471/498).
16+
* This transformer intercepts dd-javac-plugin class loading and does the export using
17+
* Instrumentation.redefineModule() instead.
18+
*
19+
* <p>Each Maven compilation step (compile, testCompile) may use a different classloader, so we
20+
* track which classloaders have already been exported to and re-export for new ones.
21+
*/
22+
public class CompilerModuleExporter implements ClassFileTransformer {
23+
24+
private static final Logger LOGGER = LoggerFactory.getLogger(CompilerModuleExporter.class);
25+
26+
private static final String COMPILER_PLUGIN_CLASS_PREFIX = "datadog/compiler/";
27+
private static final String[] COMPILER_PACKAGES = {
28+
"com.sun.tools.javac.api",
29+
"com.sun.tools.javac.code",
30+
"com.sun.tools.javac.comp",
31+
"com.sun.tools.javac.tree",
32+
"com.sun.tools.javac.util"
33+
};
34+
35+
private final Instrumentation inst;
36+
private final ConcurrentHashMap<ClassLoader, Boolean> exportedClassLoaders =
37+
new ConcurrentHashMap<>();
38+
39+
public CompilerModuleExporter(Instrumentation inst) {
40+
this.inst = inst;
41+
}
42+
43+
@Override
44+
public byte[] transform(
45+
ClassLoader loader,
46+
String className,
47+
Class<?> classBeingRedefined,
48+
ProtectionDomain protectionDomain,
49+
byte[] classfileBuffer) {
50+
if (loader != null && className != null && className.startsWith(COMPILER_PLUGIN_CLASS_PREFIX)) {
51+
exportedClassLoaders.computeIfAbsent(loader, this::exportJdkCompilerModule);
52+
}
53+
return null; // no bytecode modification
54+
}
55+
56+
private Boolean exportJdkCompilerModule(ClassLoader loader) {
57+
try {
58+
JDK9ModuleAccess.exportModuleToUnnamedModule(inst, "jdk.compiler", COMPILER_PACKAGES, loader);
59+
LOGGER.debug("Exported jdk.compiler to classloader {}", loader);
60+
} catch (Throwable e) {
61+
LOGGER.debug("Could not export jdk.compiler packages for compiler plugin", e);
62+
}
63+
return Boolean.TRUE;
64+
}
65+
}

dd-smoke-tests/gradle/src/test/groovy/datadog/smoketest/GradleDaemonSmokeTest.groovy

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ import spock.lang.IgnoreIf
2020
import spock.lang.Shared
2121
import spock.lang.TempDir
2222

23-
@IgnoreIf(reason = "TODO: Fix for Java 26. Javac plugin fails to populate source tags correctly.", value = {
24-
JavaVirtualMachine.isJavaVersionAtLeast(26)
25-
})
2623
class GradleDaemonSmokeTest extends AbstractGradleTest {
2724

2825
private static final String TEST_SERVICE_NAME = "test-gradle-service"

dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ import java.util.concurrent.TimeoutException
3232

3333
import static org.junit.jupiter.api.Assumptions.assumeTrue
3434

35-
@IgnoreIf(reason = "TODO: Fix for Java 26. Maven compiler fails to compile the tests for Java 26-ea.", value = {
36-
JavaVirtualMachine.isJavaVersionAtLeast(26)
37-
})
3835
@IgnoreIf(reason = "IBM8 has flaky AES-GCM TLS failures when downloading Maven artifacts", value = {
3936
JavaVirtualMachine.isIbm8()
4037
})

dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_junit_platform_runner/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
<dependency>
5858
<groupId>org.apache.groovy</groupId>
5959
<artifactId>groovy-bom</artifactId>
60-
<version>4.0.28</version>
60+
<version>4.0.30</version>
6161
<type>pom</type>
6262
<scope>import</scope>
6363
</dependency>

internal-api/internal-api-9/src/main/java/datadog/trace/util/JDK9ModuleAccess.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
import java.lang.instrument.Instrumentation;
77
import java.lang.reflect.AnnotatedElement;
8+
import java.util.Collections;
9+
import java.util.HashMap;
10+
import java.util.Map;
811
import java.util.Set;
912

1013
/** Use standard API to work with JPMS modules on Java9+. */
@@ -33,4 +36,28 @@ public static void addModuleReads(
3336
emptySet(),
3437
emptyMap());
3538
}
39+
40+
/** Exports specific packages of a named module to a classloader's unnamed module. */
41+
@SuppressWarnings({"rawtypes", "unchecked"})
42+
public static void exportModuleToUnnamedModule(
43+
Instrumentation inst,
44+
String moduleName,
45+
String[] packageNames,
46+
ClassLoader targetClassLoader) {
47+
java.util.Optional<java.lang.Module> optModule =
48+
java.lang.ModuleLayer.boot().findModule(moduleName);
49+
if (!optModule.isPresent()) {
50+
return;
51+
}
52+
java.lang.Module module = optModule.get();
53+
java.lang.Module unnamedModule = targetClassLoader.getUnnamedModule();
54+
55+
Set<java.lang.Module> target = Collections.singleton(unnamedModule);
56+
Map<String, Set<java.lang.Module>> extraExports = new HashMap<>();
57+
for (String packageName : packageNames) {
58+
extraExports.put(packageName, target);
59+
}
60+
61+
inst.redefineModule(module, emptySet(), extraExports, emptyMap(), emptySet(), emptyMap());
62+
}
3663
}

0 commit comments

Comments
 (0)