Skip to content

Commit c1e9ac6

Browse files
authored
Prevent instrumenting debugger agent classes (#10803)
Prevent instrumenting debugger agent classes Skip all classes from debugger agent to avoid LinkageError attempted duplicate class definition error use array instead of list Co-authored-by: jean-philippe.bempel <jean-philippe.bempel@datadoghq.com>
1 parent 4368dcf commit c1e9ac6

File tree

3 files changed

+67
-5
lines changed

3 files changed

+67
-5
lines changed

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,19 @@ public class DebuggerTransformer implements ClassFileTransformer {
9595
SpanProbe.class);
9696
private static final String JAVA_IO_TMPDIR = "java.io.tmpdir";
9797
private static final boolean JAVA_AT_LEAST_19 = JavaVirtualMachine.isJavaVersionAtLeast(19);
98-
9998
public static Path DUMP_PATH = Paths.get(SystemProperties.get(JAVA_IO_TMPDIR), "debugger");
99+
private static final String[] SKIPPED_PACKAGES =
100+
new String[] {
101+
"com/datadog/debugger/agent/",
102+
"com/datadog/debugger/codeorigin/",
103+
"com/datadog/debugger/exception/",
104+
"com/datadog/debugger/instrumentation/",
105+
"com/datadog/debugger/probe/",
106+
"com/datadog/debugger/sink/",
107+
"com/datadog/debugger/symbol/",
108+
"com/datadog/debugger/uploader/",
109+
"com/datadog/debugger/util/"
110+
};
100111

101112
private final Config config;
102113
private final TransformerDefinitionMatcher definitionMatcher;
@@ -331,6 +342,16 @@ private boolean skipInstrumentation(ClassLoader loader, String classFilePath) {
331342
// in case of anonymous classes
332343
return true;
333344
}
345+
if (classFilePath.startsWith("com/datadog/debugger/")) {
346+
// skip classes/packages that are part of debugger agent to avoid
347+
// LinkageError: attempted duplicate class definition
348+
// while retransforming a class used by instrumentation
349+
for (int i = 0; i < SKIPPED_PACKAGES.length; i++) {
350+
if (classFilePath.startsWith(SKIPPED_PACKAGES[i])) {
351+
return true;
352+
}
353+
}
354+
}
334355
return false;
335356
}
336357

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import static utils.InstrumentationTestHelper.compileAndLoadClass;
2525
import static utils.InstrumentationTestHelper.getLineForLineProbe;
2626
import static utils.InstrumentationTestHelper.loadClass;
27+
import static utils.TestClassFileHelper.getClassFileBytes;
2728
import static utils.TestHelper.getFixtureContent;
2829
import static utils.TestHelper.setFieldInConfig;
2930

@@ -32,6 +33,8 @@
3233
import com.datadog.debugger.el.ValueScript;
3334
import com.datadog.debugger.el.values.StringValue;
3435
import com.datadog.debugger.instrumentation.InstrumentationResult;
36+
import com.datadog.debugger.instrumentation.Types;
37+
import com.datadog.debugger.probe.CodeOriginProbe;
3538
import com.datadog.debugger.probe.LogProbe;
3639
import com.datadog.debugger.probe.MetricProbe;
3740
import com.datadog.debugger.probe.SpanDecorationProbe;
@@ -3091,6 +3094,44 @@ public void methodParametersAttributeRecord() throws IOException, URISyntaxExcep
30913094
}
30923095
}
30933096

3097+
/*
3098+
* Regression test for: DatadogClassLoader attempted duplicate class definition for
3099+
* com.datadog.debugger.instrumentation.Types (LinkageError).
3100+
*
3101+
* When a CodeOriginProbe matches the agent's Types class (by FQN or simple name), and Types
3102+
* is being loaded for the first time by DatadogClassLoader, DebuggerTransformer.transform() is
3103+
* invoked as a ClassFileTransformer. Inside performInstrumentation(), CodeOriginInstrumenter
3104+
* accesses the static field Types.DEBUGGER_CONTEXT_TYPE, triggering a re-entrant loadClass()
3105+
* call for Types on the same thread. Because Java's synchronized is reentrant and Types is not
3106+
* yet registered in the JVM (defineClass hasn't completed), findLoadedClass() returns null and
3107+
* defineClass is called a second time, producing the LinkageError.
3108+
*/
3109+
@Test
3110+
public void noInstrumentationForAgentClasses() throws Exception {
3111+
// Install a CodeOriginProbe targeting the agent's Types class by FQN.
3112+
// This simulates a probe accidentally matching an agent class (e.g. via simple-name fallback
3113+
// in TransformerDefinitionMatcher when a user class is also named "Types").
3114+
CodeOriginProbe probe =
3115+
new CodeOriginProbe(
3116+
PROBE_ID,
3117+
true,
3118+
Where.of("com.datadog.debugger.instrumentation.Types", "descriptorToSignature", null));
3119+
installProbes(probe);
3120+
byte[] typeBytes = getClassFileBytes(Types.class);
3121+
// transform() proceeds to performInstrumentation(), which calls
3122+
// CodeOriginInstrumenter.codeOriginCall() → accesses Types.DEBUGGER_CONTEXT_TYPE.
3123+
// In production (when Types is not yet loaded), this re-enters DatadogClassLoader.loadClass()
3124+
// and triggers LinkageError: duplicate class definition for Types.
3125+
byte[] result =
3126+
currentTransformer.transform(
3127+
Types.class.getClassLoader(),
3128+
"com/datadog/debugger/instrumentation/Types",
3129+
null,
3130+
null,
3131+
typeBytes);
3132+
assertNull(result);
3133+
}
3134+
30943135
private TestSnapshotListener setupInstrumentTheWorldTransformer(
30953136
String excludeFileName, String includeFileName) {
30963137
Config config = mock(Config.class);

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,8 @@ public void classBeingRedefinedNull() {
306306
disabledReason = "Issue with J9: Flaky")
307307
public void classGenerationFailed() {
308308
Config config = createConfig();
309-
final String CLASS_NAME = DebuggerAgent.class.getTypeName();
310-
final String METHOD_NAME = "run";
309+
final String CLASS_NAME = ArrayList.class.getTypeName();
310+
final String METHOD_NAME = "add";
311311
MockProbe mockProbe = MockProbe.builder(PROBE_ID).where(CLASS_NAME, METHOD_NAME).build();
312312
LogProbe logProbe1 =
313313
LogProbe.builder().probeId("logprobe1", 0).where(CLASS_NAME, METHOD_NAME).build();
@@ -332,10 +332,10 @@ public void classGenerationFailed() {
332332
byte[] newClassBuffer =
333333
debuggerTransformer.transform(
334334
ClassLoader.getSystemClassLoader(),
335-
"com/datadog/debugger/agent/DebuggerAgent",
335+
"java/util/ArrayList",
336336
null,
337337
null,
338-
getClassFileBytes(DebuggerAgent.class));
338+
getClassFileBytes(ArrayList.class));
339339
assertNull(newClassBuffer);
340340
ArgumentCaptor<String> strCaptor = ArgumentCaptor.forClass(String.class);
341341
ArgumentCaptor<ProbeId> probeIdCaptor = ArgumentCaptor.forClass(ProbeId.class);

0 commit comments

Comments
 (0)