Skip to content

Commit 5ba371e

Browse files
committed
Detect that we are running on Spring6+
1 parent eb8ff73 commit 5ba371e

8 files changed

Lines changed: 103 additions & 16 deletions

File tree

dd-java-agent/agent-debugger/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ dependencies {
6767

6868
testRuntimeOnly group: 'org.scala-lang', name: 'scala-compiler', version: libs.versions.scala213.get()
6969
testRuntimeOnly group: 'antlr', name: 'antlr', version: '2.7.7'
70+
testRuntimeOnly group: 'org.springframework', name: 'spring-web', version: '6.0.0'
7071
}
7172

7273
tasks.named("shadowJar", ShadowJar) {

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ConfigurationUpdater.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.datadog.debugger.probe.Sampled;
1313
import com.datadog.debugger.sink.DebuggerSink;
1414
import com.datadog.debugger.util.ExceptionHelper;
15+
import com.datadog.debugger.util.SpringHelper;
1516
import datadog.environment.JavaVirtualMachine;
1617
import datadog.trace.api.Config;
1718
import datadog.trace.bootstrap.debugger.DebuggerContext;
@@ -216,6 +217,9 @@ private List<Class<?>> detectMethodParameters(
216217
continue;
217218
}
218219
if (parameters[0].isNamePresent()) {
220+
if (!SpringHelper.isSpringUsingOnlyMethodParameters(instrumentation)) {
221+
return changedClasses;
222+
}
219223
LOGGER.debug(
220224
"Detecting method parameter: method={} param={}, Skipping retransforming this class",
221225
method.getName(),

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,10 @@ public static DebuggerSink getSink() {
463463
return sink;
464464
}
465465

466+
static Instrumentation getInstrumentation() {
467+
return instrumentation;
468+
}
469+
466470
private static DebuggerTransformer createTransformer(
467471
Config config,
468472
Configuration configuration,

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerTransformer.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.datadog.debugger.uploader.BatchUploader;
2626
import com.datadog.debugger.util.ClassFileLines;
2727
import com.datadog.debugger.util.DebuggerMetrics;
28+
import com.datadog.debugger.util.SpringHelper;
2829
import datadog.environment.JavaVirtualMachine;
2930
import datadog.environment.SystemProperties;
3031
import datadog.trace.agent.tooling.AgentStrategies;
@@ -309,7 +310,9 @@ private void checkMethodParameters(ClassNode classNode) {
309310
// use the equals method for this
310311
continue;
311312
}
312-
if (methodNode.parameters != null && !methodNode.parameters.isEmpty()) {
313+
if (methodNode.parameters != null
314+
&& !methodNode.parameters.isEmpty()
315+
&& SpringHelper.isSpringUsingOnlyMethodParameters(DebuggerAgent.getInstrumentation())) {
313316
throw new RuntimeException(
314317
"Method Parameters attribute detected, instrumentation not supported");
315318
} else {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.datadog.debugger.util;
2+
3+
import java.lang.instrument.Instrumentation;
4+
5+
public class SpringHelper {
6+
7+
public static boolean isSpringUsingOnlyMethodParameters(Instrumentation inst) {
8+
for (Class<?> clazz : inst.getAllLoadedClasses()) {
9+
if ("org.springframework.web.bind.annotation.ControllerMappingReflectiveProcessor"
10+
.equals(clazz.getName())) {
11+
// If this class (coming from Spring web since version 6) is found loaded it means Spring
12+
// supports only getting parameter names from the MethodParameter attribute
13+
return true;
14+
}
15+
}
16+
// class not found, probably no Spring
17+
return false;
18+
}
19+
}

dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import java.io.File;
6666
import java.io.FileNotFoundException;
6767
import java.io.IOException;
68+
import java.lang.instrument.Instrumentation;
6869
import java.net.URISyntaxException;
6970
import java.net.URL;
7071
import java.util.ArrayList;
@@ -2951,19 +2952,32 @@ public void captureExpressionsWithCaptureLimits() throws IOException, URISyntaxE
29512952
}
29522953

29532954
@Test
2954-
public void methodParametersAttribute() throws IOException, URISyntaxException {
2955+
public void methodParametersAttribute() throws Exception {
29552956
final String CLASS_NAME = "CapturedSnapshot01";
2957+
Config config = mock(Config.class);
2958+
when(config.isDebuggerCodeOriginEnabled()).thenReturn(false);
2959+
when(config.isDebuggerExceptionEnabled()).thenReturn(false);
2960+
when(config.isDynamicInstrumentationEnabled()).thenReturn(false);
2961+
Instrumentation inst = mock(Instrumentation.class);
2962+
if (JavaVirtualMachine.isJavaVersion(17)) {
2963+
// on JDK 17 introduced Spring6 class
2964+
Class<?> springClass =
2965+
Class.forName(
2966+
"org.springframework.web.bind.annotation.ControllerMappingReflectiveProcessor");
2967+
when(inst.getAllLoadedClasses()).thenReturn(new Class[] {springClass});
2968+
} else {
2969+
when(inst.getAllLoadedClasses()).thenReturn(new Class[0]);
2970+
}
2971+
DebuggerAgent.run(config, inst, null);
29562972
TestSnapshotListener listener =
29572973
installMethodProbeAtExit(CLASS_NAME, "main", "int (java.lang.String)");
29582974
Map<String, byte[]> buffers =
29592975
compile(CLASS_NAME, SourceCompiler.DebugInfo.ALL, "8", Arrays.asList("-parameters"));
29602976
Class<?> testClass = loadClass(CLASS_NAME, buffers);
29612977
int result = Reflect.onClass(testClass).call("main", "1").get();
29622978
assertEquals(3, result);
2963-
if (JavaVirtualMachine.isJavaVersionAtLeast(19)) {
2964-
Snapshot snapshot = assertOneSnapshot(listener);
2965-
assertCaptureArgs(snapshot.getCaptures().getReturn(), "arg", String.class.getTypeName(), "1");
2966-
} else {
2979+
if (JavaVirtualMachine.isJavaVersion(17)) {
2980+
// on JDK 17 with Spring6 class, transformation cannot happen
29672981
assertEquals(0, listener.snapshots.size());
29682982
ArgumentCaptor<ProbeId> probeIdCaptor = ArgumentCaptor.forClass(ProbeId.class);
29692983
ArgumentCaptor<String> strCaptor = ArgumentCaptor.forClass(String.class);
@@ -2972,6 +2986,9 @@ public void methodParametersAttribute() throws IOException, URISyntaxException {
29722986
assertEquals(
29732987
"Instrumentation failed for CapturedSnapshot01: java.lang.RuntimeException: Method Parameters attribute detected, instrumentation not supported",
29742988
strCaptor.getAllValues().get(0));
2989+
} else {
2990+
Snapshot snapshot = assertOneSnapshot(listener);
2991+
assertCaptureArgs(snapshot.getCaptures().getReturn(), "arg", String.class.getTypeName(), "1");
29752992
}
29762993
}
29772994

dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ConfigurationUpdaterTest.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -632,24 +632,27 @@ public void handleException() {
632632
}
633633

634634
@Test
635-
public void methodParametersAttribute()
636-
throws IOException, URISyntaxException, UnmodifiableClassException {
635+
public void methodParametersAttribute() throws Exception {
637636
final String CLASS_NAME = "CapturedSnapshot01";
638637
Map<String, byte[]> buffers =
639638
compile(CLASS_NAME, SourceCompiler.DebugInfo.ALL, "8", Arrays.asList("-parameters"));
640639
Class<?> testClass = loadClass(CLASS_NAME, buffers);
641-
when(inst.getAllLoadedClasses()).thenReturn(new Class[] {testClass});
640+
if (JavaVirtualMachine.isJavaVersion(17)) {
641+
// on JDK 17 introduced Spring6 class
642+
Class<?> springClass =
643+
Class.forName(
644+
"org.springframework.web.bind.annotation.ControllerMappingReflectiveProcessor");
645+
when(inst.getAllLoadedClasses()).thenReturn(new Class[] {testClass, springClass});
646+
} else {
647+
when(inst.getAllLoadedClasses()).thenReturn(new Class[] {testClass});
648+
}
642649
ConfigurationUpdater configurationUpdater = createConfigUpdater(debuggerSinkWithMockStatusSink);
643650
configurationUpdater.accept(
644651
REMOTE_CONFIG,
645652
singletonList(LogProbe.builder().probeId(PROBE_ID).where(CLASS_NAME, "main").build()));
646-
if (JavaVirtualMachine.isJavaVersionAtLeast(19)) {
647-
ArgumentCaptor<Class<?>[]> captor = ArgumentCaptor.forClass(Class[].class);
648-
verify(inst, times(1)).retransformClasses(captor.capture());
649-
List<Class<?>[]> allValues = captor.getAllValues();
650-
assertEquals(testClass, allValues.get(0));
651-
} else {
652-
verify(inst).getAllLoadedClasses();
653+
if (JavaVirtualMachine.isJavaVersion(17)) {
654+
// on JDK 17 with Spring6 class, transformation cannot happen
655+
verify(inst, times(2)).getAllLoadedClasses();
653656
verify(inst, times(0)).retransformClasses(any());
654657
ArgumentCaptor<ProbeId> probeIdCaptor = ArgumentCaptor.forClass(ProbeId.class);
655658
ArgumentCaptor<String> strCaptor = ArgumentCaptor.forClass(String.class);
@@ -658,6 +661,11 @@ public void methodParametersAttribute()
658661
assertEquals(
659662
"Method Parameters detected, instrumentation not supported for CapturedSnapshot01",
660663
strCaptor.getAllValues().get(0));
664+
} else {
665+
ArgumentCaptor<Class<?>[]> captor = ArgumentCaptor.forClass(Class[].class);
666+
verify(inst, times(1)).retransformClasses(captor.capture());
667+
List<Class<?>[]> allValues = captor.getAllValues();
668+
assertEquals(testClass, allValues.get(0));
661669
}
662670
}
663671

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.datadog.debugger.util;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
import static org.mockito.Mockito.mock;
5+
import static org.mockito.Mockito.when;
6+
7+
import java.lang.instrument.Instrumentation;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.condition.EnabledForJreRange;
10+
import org.junit.jupiter.api.condition.JRE;
11+
12+
class SpringHelperTest {
13+
14+
@Test
15+
@EnabledForJreRange(min = JRE.JAVA_17)
16+
void isSpringUsingOnlyMethodParametersTrue() throws Exception {
17+
Class<?> clazz =
18+
Class.forName(
19+
"org.springframework.web.bind.annotation.ControllerMappingReflectiveProcessor");
20+
Instrumentation inst = mock(Instrumentation.class);
21+
when(inst.getAllLoadedClasses()).thenReturn(new Class[] {clazz});
22+
assertTrue(SpringHelper.isSpringUsingOnlyMethodParameters(inst));
23+
}
24+
25+
@Test
26+
void isSpringUsingOnlyMethodParametersFalse() throws Exception {
27+
Instrumentation inst = mock(Instrumentation.class);
28+
when(inst.getAllLoadedClasses()).thenReturn(new Class[0]);
29+
assertFalse(SpringHelper.isSpringUsingOnlyMethodParameters(inst));
30+
}
31+
}

0 commit comments

Comments
 (0)