From 5dbe077693da8be041911e2e62051453f99868d7 Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Fri, 10 Jul 2026 10:59:33 -0400 Subject: [PATCH] Move test classloaders to Java --- .../agent-ci-visibility/build.gradle | 10 ------ .../source/ByteCodeLinesResolverTest.groovy | 23 ------------ .../source/MisbehavingClassLoader.java | 36 +++++++++++++++++++ dd-java-agent/agent-tooling/build.gradle | 21 +++-------- .../test/BaseExceptionHandlerTest.groovy | 14 +------- .../NonDelegatingExceptionClassLoader.java | 32 +++++++++++++++++ 6 files changed, 74 insertions(+), 62 deletions(-) create mode 100644 dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/source/MisbehavingClassLoader.java create mode 100644 dd-java-agent/agent-tooling/src/test/java/datadog/trace/agent/test/NonDelegatingExceptionClassLoader.java diff --git a/dd-java-agent/agent-ci-visibility/build.gradle b/dd-java-agent/agent-ci-visibility/build.gradle index 7dc7b558df3..ff4e45538f5 100644 --- a/dd-java-agent/agent-ci-visibility/build.gradle +++ b/dd-java-agent/agent-ci-visibility/build.gradle @@ -48,16 +48,6 @@ tasks.named("jar", Jar) { archiveClassifier = 'unbundled' } -tasks.named("compileTestGroovy") { - configureCompiler( - it, - 8, - JavaVersion.VERSION_1_8, - "Groovy generates synthetic accessors methods from superclass, we don't want that for `MisbehavingClassLoader`," + - " otherwise anonymous class has one `loadClass` accessor's signature has `java.lang.Module`" - ) -} - kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_1_8 diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/ByteCodeLinesResolverTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/ByteCodeLinesResolverTest.groovy index 3c7afc5c994..0960a2a40ab 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/ByteCodeLinesResolverTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/ByteCodeLinesResolverTest.groovy @@ -81,28 +81,5 @@ class ByteCodeLinesResolverTest extends Specification { abstract void abstractMethod() } - - private static final class MisbehavingClassLoader extends ClassLoader { - - private final Map classes = new HashMap<>() - - @Override - InputStream getResourceAsStream(String name) { - throw new RuntimeException("Something went wrong") - } - - @Override - Class loadClass(String name) throws ClassNotFoundException { - def bytes = classes.get(name) - if (bytes != null) { - return defineClass(name, bytes, 0, bytes.length) - } - return super.loadClass(name) - } - - void putClass(String name, byte[] bytes) { - classes.put(name, bytes) - } - } } diff --git a/dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/source/MisbehavingClassLoader.java b/dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/source/MisbehavingClassLoader.java new file mode 100644 index 00000000000..ef17f3f41e0 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/source/MisbehavingClassLoader.java @@ -0,0 +1,36 @@ +package datadog.trace.civisibility.source; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * A {@link ClassLoader} that defines classes from an in-memory map and fails resource lookups, used + * to exercise {@code ByteCodeLinesResolver} when a class cannot be loaded. + * + *

Kept in Java rather than the Groovy test on purpose: Groovy synthesizes super-accessors for + * every {@code ClassLoader} overload, including {@code loadClass(java.lang.Module, String)}, which + * references {@code java.lang.Module} and would break this Java 8 test suite. + */ +final class MisbehavingClassLoader extends ClassLoader { + + private final Map classes = new HashMap<>(); + + @Override + public InputStream getResourceAsStream(String name) { + throw new RuntimeException("Something went wrong"); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + byte[] bytes = classes.get(name); + if (bytes != null) { + return defineClass(name, bytes, 0, bytes.length); + } + return super.loadClass(name); + } + + void putClass(String name, byte[] bytes) { + classes.put(name, bytes); + } +} diff --git a/dd-java-agent/agent-tooling/build.gradle b/dd-java-agent/agent-tooling/build.gradle index 3b60e25d281..81d42459afd 100644 --- a/dd-java-agent/agent-tooling/build.gradle +++ b/dd-java-agent/agent-tooling/build.gradle @@ -60,28 +60,17 @@ jmh { } tasks.named("compileJava") { dependsOn 'generateClassNameTries' } -tasks.named("sourcesJar") { dependsOn 'generateClassNameTries' } - tasks.named("compileJmhJava") { dependsOn tasks.named("compileTestJava") } +tasks.named("compileTestJava") { dependsOn('generateTestClassNameTries') } -tasks.named("forbiddenApisJmh") { - ignoreFailures = true -} +tasks.named("sourcesJar") { dependsOn 'generateClassNameTries' } + +tasks.named("forbiddenApisJmh") { ignoreFailures = true } -tasks.named("compileTestJava") { - dependsOn('generateTestClassNameTries') -} -tasks.named("compileTestGroovy") { - configureCompiler(it, - 8, - JavaVersion.VERSION_1_8, - "Groovy generates synthetic accessors methods from superclass, we don't want that for `URLClassLoader`," + - " otherwise anonymous class has one `loadClass` accessor's signature has `java.lang.Module`" - ) -} tasks.named("compileTest_java11Java") { configureCompiler(it, 11, JavaVersion.VERSION_11) } + tasks.named("compileTest_java21Java") { configureCompiler( it, diff --git a/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/test/BaseExceptionHandlerTest.groovy b/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/test/BaseExceptionHandlerTest.groovy index 5f6980e40d1..d8e668ea9cd 100644 --- a/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/test/BaseExceptionHandlerTest.groovy +++ b/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/test/BaseExceptionHandlerTest.groovy @@ -8,7 +8,6 @@ import datadog.environment.JavaVirtualMachine import datadog.trace.agent.tooling.bytebuddy.ExceptionHandlers import datadog.trace.bootstrap.ExceptionLogger import datadog.trace.bootstrap.InstrumentationErrors -import datadog.trace.bootstrap.blocking.BlockingExceptionHandler import datadog.trace.test.util.DDSpecification import net.bytebuddy.agent.ByteBuddyAgent import net.bytebuddy.agent.builder.AgentBuilder @@ -128,18 +127,7 @@ abstract class BaseExceptionHandlerTest extends DDSpecification { SomeClass.getProtectionDomain().getCodeSource().getLocation(), GroovyObject.getProtectionDomain().getCodeSource().getLocation(), ] - URLClassLoader loader = new URLClassLoader(classpath, null, null) { - @Override - Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - if (name == BlockingExceptionHandler.name) { - return BlockingExceptionHandler - } - if (name == BlockingException.name) { - return BlockingException - } - return super.loadClass(name, resolve) - } - } + URLClassLoader loader = new NonDelegatingExceptionClassLoader(classpath) when: loader.loadClass(InstrumentationErrors.getName()) diff --git a/dd-java-agent/agent-tooling/src/test/java/datadog/trace/agent/test/NonDelegatingExceptionClassLoader.java b/dd-java-agent/agent-tooling/src/test/java/datadog/trace/agent/test/NonDelegatingExceptionClassLoader.java new file mode 100644 index 00000000000..401c56b2415 --- /dev/null +++ b/dd-java-agent/agent-tooling/src/test/java/datadog/trace/agent/test/NonDelegatingExceptionClassLoader.java @@ -0,0 +1,32 @@ +package datadog.trace.agent.test; + +import datadog.appsec.api.blocking.BlockingException; +import datadog.trace.bootstrap.blocking.BlockingExceptionHandler; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * A {@link URLClassLoader} that resolves the blocking helper classes directly and delegates + * everything else to the supplied classpath only (no parent). + * + *

Kept in Java rather than the Groovy test on purpose: Groovy synthesizes a {@code + * super$N$loadClass(java.lang.Module, String)} accessor for {@code ClassLoader} subclasses, which + * references {@code java.lang.Module} and would break this Java 8 test suite. + */ +final class NonDelegatingExceptionClassLoader extends URLClassLoader { + + NonDelegatingExceptionClassLoader(URL[] classpath) { + super(classpath, null, null); + } + + @Override + public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.equals(BlockingExceptionHandler.class.getName())) { + return BlockingExceptionHandler.class; + } + if (name.equals(BlockingException.class.getName())) { + return BlockingException.class; + } + return super.loadClass(name, resolve); + } +}