Skip to content

Commit 9acdfab

Browse files
committed
Add special case for Record
canonical record constructor has method parameters attribute
1 parent 9c5584f commit 9acdfab

File tree

4 files changed

+64
-2
lines changed

4 files changed

+64
-2
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static java.util.stream.Collectors.toList;
66

77
import com.datadog.debugger.el.ProbeCondition;
8+
import com.datadog.debugger.instrumentation.ASMHelper;
89
import com.datadog.debugger.instrumentation.DiagnosticMessage;
910
import com.datadog.debugger.instrumentation.InstrumentationResult;
1011
import com.datadog.debugger.instrumentation.MethodInfo;
@@ -294,6 +295,7 @@ private void checkMethodParameters(ClassNode classNode) {
294295
// bug is fixed since JDK19, no need to perform check
295296
return;
296297
}
298+
boolean isRecord = ASMHelper.isRecord(classNode);
297299
// capping scanning of methods to 100 to avoid generated class with thousand of methods
298300
// assuming that in those first 100 methods there is at least one with at least one parameter
299301
for (int methodIdx = 0; methodIdx < classNode.methods.size() && methodIdx < 100; methodIdx++) {
@@ -302,6 +304,11 @@ private void checkMethodParameters(ClassNode classNode) {
302304
if (argumentCount == 0) {
303305
continue;
304306
}
307+
if (isRecord && methodNode.name.equals("<init>")) {
308+
// skip record constructors, cannot rely on them because of the canonical one
309+
// use the equals method for this
310+
continue;
311+
}
305312
if (methodNode.parameters != null && !methodNode.parameters.isEmpty()) {
306313
throw new RuntimeException(
307314
"Method Parameters attribute detected, cannot instrument class " + classNode.name);

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/ASMHelper.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ public static boolean isFinalField(Field field) {
138138
return Modifier.isFinal(field.getModifiers());
139139
}
140140

141+
public static boolean isRecord(ClassNode classNode) {
142+
return (classNode.access & Opcodes.ACC_RECORD) > 0;
143+
}
144+
141145
public static void invokeStatic(
142146
InsnList insnList,
143147
org.objectweb.asm.Type owner,

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2832,6 +2832,33 @@ public void methodParametersAttribute() throws IOException, URISyntaxException {
28322832
}
28332833
}
28342834

2835+
@Test
2836+
@EnabledForJreRange(min = JRE.JAVA_17)
2837+
public void methodParametersAttributeRecord() throws IOException, URISyntaxException {
2838+
final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot29";
2839+
final String RECORD_NAME = "com.datadog.debugger.MyRecord1";
2840+
TestSnapshotListener listener = installMethodProbeAtExit(RECORD_NAME, "<init>", null);
2841+
Map<String, byte[]> buffers =
2842+
compile(CLASS_NAME, SourceCompiler.DebugInfo.ALL, "17", Arrays.asList("-parameters"));
2843+
Class<?> testClass = loadClass(CLASS_NAME, buffers);
2844+
int result = Reflect.onClass(testClass).call("main", "1").get();
2845+
assertEquals(42, result);
2846+
if (JavaVirtualMachine.isJavaVersionAtLeast(19)) {
2847+
Snapshot snapshot = assertOneSnapshot(listener);
2848+
assertCaptureArgs(
2849+
snapshot.getCaptures().getReturn(), "firstName", String.class.getTypeName(), "john");
2850+
} else {
2851+
assertEquals(0, listener.snapshots.size());
2852+
ArgumentCaptor<ProbeId> probeIdCaptor = ArgumentCaptor.forClass(ProbeId.class);
2853+
ArgumentCaptor<String> strCaptor = ArgumentCaptor.forClass(String.class);
2854+
verify(probeStatusSink, times(1)).addError(probeIdCaptor.capture(), strCaptor.capture());
2855+
assertEquals(PROBE_ID.getId(), probeIdCaptor.getAllValues().get(0).getId());
2856+
assertEquals(
2857+
"Instrumentation fails for com.datadog.debugger.MyRecord1",
2858+
strCaptor.getAllValues().get(0));
2859+
}
2860+
}
2861+
28352862
private TestSnapshotListener setupInstrumentTheWorldTransformer(
28362863
String excludeFileName, String includeFileName) {
28372864
Config config = mock(Config.class);

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
import org.junit.jupiter.api.Assertions;
4141
import org.junit.jupiter.api.BeforeEach;
4242
import org.junit.jupiter.api.Test;
43+
import org.junit.jupiter.api.condition.EnabledForJreRange;
44+
import org.junit.jupiter.api.condition.JRE;
4345
import org.junit.jupiter.api.extension.ExtendWith;
4446
import org.mockito.ArgumentCaptor;
4547
import org.mockito.Mock;
@@ -640,8 +642,7 @@ public void methodParametersAttribute()
640642
ConfigurationUpdater configurationUpdater = createConfigUpdater(debuggerSinkWithMockStatusSink);
641643
configurationUpdater.accept(
642644
REMOTE_CONFIG,
643-
singletonList(
644-
LogProbe.builder().probeId(PROBE_ID).where("CapturedSnapshot01", "main").build()));
645+
singletonList(LogProbe.builder().probeId(PROBE_ID).where(CLASS_NAME, "main").build()));
645646
if (JavaVirtualMachine.isJavaVersionAtLeast(19)) {
646647
ArgumentCaptor<Class<?>[]> captor = ArgumentCaptor.forClass(Class[].class);
647648
verify(inst, times(1)).retransformClasses(captor.capture());
@@ -653,6 +654,29 @@ public void methodParametersAttribute()
653654
}
654655
}
655656

657+
@Test
658+
@EnabledForJreRange(min = JRE.JAVA_17)
659+
public void methodParametersAttributeRecord()
660+
throws IOException, URISyntaxException, UnmodifiableClassException {
661+
// make sure record method are not detected as having methodParameters attribute.
662+
// /!\ record canonical constructor has the MethodParameters attribute,
663+
// but not returned by Class::getDeclaredMethods()
664+
final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot29";
665+
final String RECORD_NAME = "com.datadog.debugger.MyRecord1";
666+
Map<String, byte[]> buffers = compile(CLASS_NAME, SourceCompiler.DebugInfo.ALL, "17");
667+
Class<?> testClass = loadClass(RECORD_NAME, buffers);
668+
when(inst.getAllLoadedClasses()).thenReturn(new Class[] {testClass});
669+
ConfigurationUpdater configurationUpdater = createConfigUpdater(debuggerSinkWithMockStatusSink);
670+
configurationUpdater.accept(
671+
REMOTE_CONFIG,
672+
singletonList(LogProbe.builder().probeId(PROBE_ID).where(RECORD_NAME, "<init>").build()));
673+
verify(inst).getAllLoadedClasses();
674+
ArgumentCaptor<Class<?>[]> captor = ArgumentCaptor.forClass(Class[].class);
675+
verify(inst, times(1)).retransformClasses(captor.capture());
676+
List<Class<?>[]> allValues = captor.getAllValues();
677+
assertEquals(testClass, allValues.get(0));
678+
}
679+
656680
private DebuggerTransformer createTransformer(
657681
Config tracerConfig,
658682
Configuration configuration,

0 commit comments

Comments
 (0)