Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitlab/exploration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ exploration-tests-method-jackson-databind:
variables:
PROJECT: jackson-databind
script:
- ./run-exploration-tests.sh "method" "$PROJECT" "./mvnw verify" "include_${PROJECT}.txt" "exclude_$PROJECT.txt"
- ./run-exploration-tests.sh "method" "$PROJECT" "mvn verify" "include_${PROJECT}.txt" "exclude_$PROJECT.txt"

exploration-tests-line-jackson-databind:
needs: [ build ]
Expand All @@ -133,7 +133,7 @@ exploration-tests-line-jackson-databind:
variables:
PROJECT: jackson-databind
script:
- ./run-exploration-tests.sh "line" "$PROJECT" "./mvnw verify" "include_${PROJECT}.txt" "exclude_line_$PROJECT.txt"
- ./run-exploration-tests.sh "line" "$PROJECT" "mvn verify" "include_${PROJECT}.txt" "exclude_line_$PROJECT.txt"

exploration-tests-method-okhttp:
needs: [ build ]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.datadog.debugger.agent;

import static com.datadog.debugger.instrumentation.ASMHelper.getLineNumbers;
import static com.datadog.debugger.util.DebuggerInternalPackages.isDebuggerInternalClass;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;

Expand Down Expand Up @@ -99,18 +100,6 @@ public class DebuggerTransformer implements ClassFileTransformer {
private static final boolean JAVA_AT_LEAST_17_0_20 =
JavaVirtualMachine.isJavaVersionAtLeast(17, 0, 20);
public static Path DUMP_PATH = Paths.get(SystemProperties.get(JAVA_IO_TMPDIR), "debugger");
private static final String[] SKIPPED_PACKAGES =
new String[] {
"com/datadog/debugger/agent/",
"com/datadog/debugger/codeorigin/",
"com/datadog/debugger/exception/",
"com/datadog/debugger/instrumentation/",
"com/datadog/debugger/probe/",
"com/datadog/debugger/sink/",
"com/datadog/debugger/symbol/",
"com/datadog/debugger/uploader/",
"com/datadog/debugger/util/"
};

private final Config config;
private final TransformerDefinitionMatcher definitionMatcher;
Expand Down Expand Up @@ -184,6 +173,11 @@ public DebuggerTransformer(
includeMethods = null;
probeCreator = null;
}
if (isDebuggerInternalClass(null)) {
// Force DebuggerInternalPAckages to be loaded before calling it into the transform method
// avoid LinkageError for duplicated class definition
throw new IllegalArgumentException("DebuggerInternalClass should be loaded");
}
}

// Used only for tests
Expand Down Expand Up @@ -389,17 +383,10 @@ private boolean skipInstrumentation(String classFilePath) {
// in case of anonymous classes
return true;
}
if (classFilePath.startsWith("com/datadog/debugger/")) {
// skip classes/packages that are part of debugger agent to avoid
// LinkageError: attempted duplicate class definition
// while retransforming a class used by instrumentation
for (int i = 0; i < SKIPPED_PACKAGES.length; i++) {
if (classFilePath.startsWith(SKIPPED_PACKAGES[i])) {
return true;
}
}
}
return false;
// skip classes/packages that are part of debugger agent to avoid
// LinkageError: attempted duplicate class definition
// while retransforming a class used by instrumentation
return isDebuggerInternalClass(classFilePath);
}

private byte[] transformTheWorld(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.datadog.debugger.symbol;

import static com.datadog.debugger.util.DebuggerInternalPackages.isDebuggerInternalClass;

import datadog.trace.bootstrap.debugger.DebuggerContext.ClassNameFilter;
import datadog.trace.util.Strings;
import java.lang.instrument.ClassFileTransformer;
Expand All @@ -18,6 +20,11 @@ public SymbolExtractionTransformer(
SymbolAggregator symbolAggregator, ClassNameFilter classNameFiltering) {
this.symbolAggregator = symbolAggregator;
this.classNameFiltering = classNameFiltering;
if (isDebuggerInternalClass(null)) {
// Force DebuggerInternalPAckages to be loaded before calling it into the transform method
// avoid LinkageError for duplicated class definition
throw new IllegalArgumentException("DebuggerInternalClass should be loaded");
}
}

@Override
Expand All @@ -31,8 +38,8 @@ public byte[] transform(
return null;
}
try {
if (className.startsWith("com/datadog/debugger/symbol/")) {
// Don't parse our own classes to avoid duplicate class definition
if (isDebuggerInternalClass(className)) {
Comment thread
jpbempel marked this conversation as resolved.
// Don't parse debugger-internal classes to avoid duplicate class definition
return null;
}
if (classNameFiltering.isExcluded(Strings.getClassName(className))) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.datadog.debugger.util;

/** Identifies class files belonging to the debugger's own internal packages. */
public class DebuggerInternalPackages {
private static final String[] SKIPPED_PACKAGES = {
"com/datadog/debugger/agent/",
"com/datadog/debugger/codeorigin/",
"com/datadog/debugger/exception/",
"com/datadog/debugger/instrumentation/",
"com/datadog/debugger/probe/",
"com/datadog/debugger/sink/",
"com/datadog/debugger/symbol/",
"com/datadog/debugger/uploader/",
"com/datadog/debugger/util/"
};

/**
* @param classFilePath slash-separated class file path (e.g. "com/datadog/debugger/agent/Foo")
* @return true if the class belongs to a debugger-internal package that must never be
* re-transformed/parsed, to avoid a re-entrant LinkageError while it is being loaded.
*/
public static boolean isDebuggerInternalClass(String classFilePath) {
if (classFilePath == null || !classFilePath.startsWith("com/datadog/debugger/")) {
return false;
}
for (String pkg : SKIPPED_PACKAGES) {
if (classFilePath.startsWith(pkg)) {
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import static utils.InstrumentationTestHelper.compileAndLoadClass;

Expand All @@ -19,6 +21,7 @@
import java.io.File;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.net.URISyntaxException;
import java.net.URL;
Expand Down Expand Up @@ -1077,6 +1080,22 @@ public void filterOutClassesFromExcludedPackages() throws IOException, URISyntax
.anyMatch(scope -> scope.getName().equals(CLASS_NAME)));
}

@Test
public void skipDebuggerInternalClasses() throws IllegalClassFormatException {
// Regression test for: DatadogClassLoader attempted duplicate class definition for
// com.datadog.debugger.instrumentation.Types (LinkageError). SymbolExtractionTransformer
// must never parse debugger-internal classes, since doing so while such a class is still
// being loaded/defined can trigger a re-entrant load of the same class on the same thread.
ClassNameFiltering classNameFiltering = new ClassNameFiltering(Collections.emptySet());
SymbolAggregator symbolAggregator = mock(SymbolAggregator.class);
currentTransformer = new SymbolExtractionTransformer(symbolAggregator, classNameFiltering);
byte[] classfileBuffer = new byte[0];
assertNull(
currentTransformer.transform(
null, "com/datadog/debugger/instrumentation/Types", null, null, classfileBuffer));
verifyNoInteractions(symbolAggregator);
}

@Test
@DisabledIf(
value = "datadog.environment.JavaVirtualMachine#isJ9",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.datadog.debugger.util;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

class DebuggerInternalPackagesTest {

@Test
public void nullIsNotDebuggerInternal() {
assertFalse(DebuggerInternalPackages.isDebuggerInternalClass(null));
}

@Test
public void applicationClassIsNotDebuggerInternal() {
assertFalse(DebuggerInternalPackages.isDebuggerInternalClass("com/example/app/MyClass"));
}

@Test
public void debuggerInstrumentationPackageIsInternal() {
assertTrue(
DebuggerInternalPackages.isDebuggerInternalClass(
"com/datadog/debugger/instrumentation/Types"));
}

@Test
public void debuggerSymbolPackageIsInternal() {
assertTrue(
DebuggerInternalPackages.isDebuggerInternalClass(
"com/datadog/debugger/symbol/SymbolAggregator"));
}

@Test
public void debuggerPackageOutsideSkippedListIsNotInternal() {
assertFalse(
DebuggerInternalPackages.isDebuggerInternalClass("com/datadog/debugger/el/Something"));
}
}
Loading