Skip to content

Commit 09c024b

Browse files
[mq] [skip ddci] working branch - merge d3aa838 on top of master at 7b903a5
{"baseBranch":"master","baseCommit":"7b903a53644abc39f55b2fb21283546ae9801f35","createdAt":"2026-09-21T07:23:33.429202Z","headSha":"d3aa838407b30d29431bd51066b5927b893c6562","id":"566a9b8c-b170-46b9-81b0-d8f742c50826","priority":"200","pullRequestNumber":"12545","queuedAt":"2026-09-21T07:23:33.427783Z","retryAttempt":"1","status":"STATUS_QUEUED"}
2 parents 8a842d1 + d3aa838 commit 09c024b

5 files changed

Lines changed: 100 additions & 25 deletions

File tree

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

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.datadog.debugger.agent;
22

33
import static com.datadog.debugger.instrumentation.ASMHelper.getLineNumbers;
4+
import static com.datadog.debugger.util.DebuggerInternalPackages.isDebuggerInternalClass;
45
import static java.util.Collections.singletonList;
56
import static java.util.stream.Collectors.toList;
67

@@ -99,18 +100,6 @@ public class DebuggerTransformer implements ClassFileTransformer {
99100
private static final boolean JAVA_AT_LEAST_17_0_20 =
100101
JavaVirtualMachine.isJavaVersionAtLeast(17, 0, 20);
101102
public static Path DUMP_PATH = Paths.get(SystemProperties.get(JAVA_IO_TMPDIR), "debugger");
102-
private static final String[] SKIPPED_PACKAGES =
103-
new String[] {
104-
"com/datadog/debugger/agent/",
105-
"com/datadog/debugger/codeorigin/",
106-
"com/datadog/debugger/exception/",
107-
"com/datadog/debugger/instrumentation/",
108-
"com/datadog/debugger/probe/",
109-
"com/datadog/debugger/sink/",
110-
"com/datadog/debugger/symbol/",
111-
"com/datadog/debugger/uploader/",
112-
"com/datadog/debugger/util/"
113-
};
114103

115104
private final Config config;
116105
private final TransformerDefinitionMatcher definitionMatcher;
@@ -389,17 +378,10 @@ private boolean skipInstrumentation(String classFilePath) {
389378
// in case of anonymous classes
390379
return true;
391380
}
392-
if (classFilePath.startsWith("com/datadog/debugger/")) {
393-
// skip classes/packages that are part of debugger agent to avoid
394-
// LinkageError: attempted duplicate class definition
395-
// while retransforming a class used by instrumentation
396-
for (int i = 0; i < SKIPPED_PACKAGES.length; i++) {
397-
if (classFilePath.startsWith(SKIPPED_PACKAGES[i])) {
398-
return true;
399-
}
400-
}
401-
}
402-
return false;
381+
// skip classes/packages that are part of debugger agent to avoid
382+
// LinkageError: attempted duplicate class definition
383+
// while retransforming a class used by instrumentation
384+
return isDebuggerInternalClass(classFilePath);
403385
}
404386

405387
private byte[] transformTheWorld(

‎dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractionTransformer.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.datadog.debugger.symbol;
22

3+
import static com.datadog.debugger.util.DebuggerInternalPackages.isDebuggerInternalClass;
4+
35
import datadog.trace.bootstrap.debugger.DebuggerContext.ClassNameFilter;
46
import datadog.trace.util.Strings;
57
import java.lang.instrument.ClassFileTransformer;
@@ -31,8 +33,8 @@ public byte[] transform(
3133
return null;
3234
}
3335
try {
34-
if (className.startsWith("com/datadog/debugger/symbol/")) {
35-
// Don't parse our own classes to avoid duplicate class definition
36+
if (isDebuggerInternalClass(className)) {
37+
// Don't parse debugger-internal classes to avoid duplicate class definition
3638
return null;
3739
}
3840
if (classNameFiltering.isExcluded(Strings.getClassName(className))) {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.datadog.debugger.util;
2+
3+
/** Identifies class files belonging to the debugger's own internal packages. */
4+
public class DebuggerInternalPackages {
5+
private static final String[] SKIPPED_PACKAGES = {
6+
"com/datadog/debugger/agent/",
7+
"com/datadog/debugger/codeorigin/",
8+
"com/datadog/debugger/exception/",
9+
"com/datadog/debugger/instrumentation/",
10+
"com/datadog/debugger/probe/",
11+
"com/datadog/debugger/sink/",
12+
"com/datadog/debugger/symbol/",
13+
"com/datadog/debugger/uploader/",
14+
"com/datadog/debugger/util/"
15+
};
16+
17+
/**
18+
* @param classFilePath slash-separated class file path (e.g. "com/datadog/debugger/agent/Foo")
19+
* @return true if the class belongs to a debugger-internal package that must never be
20+
* re-transformed/parsed, to avoid a re-entrant LinkageError while it is being loaded.
21+
*/
22+
public static boolean isDebuggerInternalClass(String classFilePath) {
23+
if (classFilePath == null || !classFilePath.startsWith("com/datadog/debugger/")) {
24+
return false;
25+
}
26+
for (String pkg : SKIPPED_PACKAGES) {
27+
if (classFilePath.startsWith(pkg)) {
28+
return true;
29+
}
30+
}
31+
return false;
32+
}
33+
}

‎dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolExtractionTransformerTest.java‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import static org.junit.jupiter.api.Assertions.assertNotNull;
99
import static org.junit.jupiter.api.Assertions.assertNull;
1010
import static org.junit.jupiter.api.Assertions.assertTrue;
11+
import static org.mockito.Mockito.mock;
12+
import static org.mockito.Mockito.verifyNoInteractions;
1113
import static org.mockito.Mockito.when;
1214
import static utils.InstrumentationTestHelper.compileAndLoadClass;
1315

@@ -19,6 +21,7 @@
1921
import java.io.File;
2022
import java.io.IOException;
2123
import java.lang.instrument.ClassFileTransformer;
24+
import java.lang.instrument.IllegalClassFormatException;
2225
import java.lang.instrument.Instrumentation;
2326
import java.net.URISyntaxException;
2427
import java.net.URL;
@@ -1077,6 +1080,22 @@ public void filterOutClassesFromExcludedPackages() throws IOException, URISyntax
10771080
.anyMatch(scope -> scope.getName().equals(CLASS_NAME)));
10781081
}
10791082

1083+
@Test
1084+
public void skipDebuggerInternalClasses() throws IllegalClassFormatException {
1085+
// Regression test for: DatadogClassLoader attempted duplicate class definition for
1086+
// com.datadog.debugger.instrumentation.Types (LinkageError). SymbolExtractionTransformer
1087+
// must never parse debugger-internal classes, since doing so while such a class is still
1088+
// being loaded/defined can trigger a re-entrant load of the same class on the same thread.
1089+
ClassNameFiltering classNameFiltering = new ClassNameFiltering(Collections.emptySet());
1090+
SymbolAggregator symbolAggregator = mock(SymbolAggregator.class);
1091+
currentTransformer = new SymbolExtractionTransformer(symbolAggregator, classNameFiltering);
1092+
byte[] classfileBuffer = new byte[0];
1093+
assertNull(
1094+
currentTransformer.transform(
1095+
null, "com/datadog/debugger/instrumentation/Types", null, null, classfileBuffer));
1096+
verifyNoInteractions(symbolAggregator);
1097+
}
1098+
10801099
@Test
10811100
@DisabledIf(
10821101
value = "datadog.environment.JavaVirtualMachine#isJ9",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.datadog.debugger.util;
2+
3+
import static org.junit.jupiter.api.Assertions.assertFalse;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
class DebuggerInternalPackagesTest {
9+
10+
@Test
11+
public void nullIsNotDebuggerInternal() {
12+
assertFalse(DebuggerInternalPackages.isDebuggerInternalClass(null));
13+
}
14+
15+
@Test
16+
public void applicationClassIsNotDebuggerInternal() {
17+
assertFalse(DebuggerInternalPackages.isDebuggerInternalClass("com/example/app/MyClass"));
18+
}
19+
20+
@Test
21+
public void debuggerInstrumentationPackageIsInternal() {
22+
assertTrue(
23+
DebuggerInternalPackages.isDebuggerInternalClass(
24+
"com/datadog/debugger/instrumentation/Types"));
25+
}
26+
27+
@Test
28+
public void debuggerSymbolPackageIsInternal() {
29+
assertTrue(
30+
DebuggerInternalPackages.isDebuggerInternalClass(
31+
"com/datadog/debugger/symbol/SymbolAggregator"));
32+
}
33+
34+
@Test
35+
public void debuggerPackageOutsideSkippedListIsNotInternal() {
36+
assertFalse(
37+
DebuggerInternalPackages.isDebuggerInternalClass("com/datadog/debugger/el/Something"));
38+
}
39+
}

0 commit comments

Comments
 (0)