Skip to content

Commit 0317905

Browse files
committed
Prevent instrumenting debugger agent classes
Skip all classes from debugger agent to avoid LinkageError attempted duplicate class definition error
1 parent 5a62ccc commit 0317905

File tree

3 files changed

+66
-5
lines changed

3 files changed

+66
-5
lines changed

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,18 @@ 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 List<String> SKIPPED_PACKAGES =
100+
Arrays.asList(
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/");
100110

101111
private final Config config;
102112
private final TransformerDefinitionMatcher definitionMatcher;
@@ -331,6 +341,16 @@ private boolean skipInstrumentation(ClassLoader loader, String classFilePath) {
331341
// in case of anonymous classes
332342
return true;
333343
}
344+
if (classFilePath.startsWith("com/datadog/debugger/")) {
345+
// skip classes/packages that are part of debugger agent to avoid
346+
// LinkageError: attempted duplicate class definition
347+
// while retransforming a class used by instrumentation
348+
for (int i = 0; i < SKIPPED_PACKAGES.size(); i++) {
349+
if (classFilePath.startsWith(SKIPPED_PACKAGES.get(i))) {
350+
return true;
351+
}
352+
}
353+
}
334354
return false;
335355
}
336356

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;
@@ -3075,6 +3078,44 @@ public void methodParametersAttributeRecord() throws IOException, URISyntaxExcep
30753078
}
30763079
}
30773080

3081+
/*
3082+
* Regression test for: DatadogClassLoader attempted duplicate class definition for
3083+
* com.datadog.debugger.instrumentation.Types (LinkageError).
3084+
*
3085+
* When a CodeOriginProbe matches the agent's Types class (by FQN or simple name), and Types
3086+
* is being loaded for the first time by DatadogClassLoader, DebuggerTransformer.transform() is
3087+
* invoked as a ClassFileTransformer. Inside performInstrumentation(), CodeOriginInstrumenter
3088+
* accesses the static field Types.DEBUGGER_CONTEXT_TYPE, triggering a re-entrant loadClass()
3089+
* call for Types on the same thread. Because Java's synchronized is reentrant and Types is not
3090+
* yet registered in the JVM (defineClass hasn't completed), findLoadedClass() returns null and
3091+
* defineClass is called a second time, producing the LinkageError.
3092+
*/
3093+
@Test
3094+
public void noInstrumentationForAgentClasses() throws Exception {
3095+
// Install a CodeOriginProbe targeting the agent's Types class by FQN.
3096+
// This simulates a probe accidentally matching an agent class (e.g. via simple-name fallback
3097+
// in TransformerDefinitionMatcher when a user class is also named "Types").
3098+
CodeOriginProbe probe =
3099+
new CodeOriginProbe(
3100+
PROBE_ID,
3101+
true,
3102+
Where.of("com.datadog.debugger.instrumentation.Types", "descriptorToSignature", null));
3103+
installProbes(probe);
3104+
byte[] typeBytes = getClassFileBytes(Types.class);
3105+
// transform() proceeds to performInstrumentation(), which calls
3106+
// CodeOriginInstrumenter.codeOriginCall() → accesses Types.DEBUGGER_CONTEXT_TYPE.
3107+
// In production (when Types is not yet loaded), this re-enters DatadogClassLoader.loadClass()
3108+
// and triggers LinkageError: duplicate class definition for Types.
3109+
byte[] result =
3110+
currentTransformer.transform(
3111+
Types.class.getClassLoader(),
3112+
"com/datadog/debugger/instrumentation/Types",
3113+
null,
3114+
null,
3115+
typeBytes);
3116+
assertNull(result);
3117+
}
3118+
30783119
private TestSnapshotListener setupInstrumentTheWorldTransformer(
30793120
String excludeFileName, String includeFileName) {
30803121
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)