Skip to content

Commit 38a78ce

Browse files
committed
Fix exploration tests
update the registry for pushing the image install jdk 21 use the jsoup library and patch it for running test with instrument-the-world mode add include file to specify which packages/classes we want to include into the instrumentation instead of instrumenting everything (like build tools) do not collect static inherited fields to avoid duplicate class definition for enums.
1 parent 5a939c0 commit 38a78ce

File tree

14 files changed

+175
-61
lines changed

14 files changed

+175
-61
lines changed

.gitlab/exploration-tests.yml

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
variables:
2-
EXPLORATION_TESTS_IMAGE: $REGISTRY/ci/dd-trace-java:exploration-tests
2+
EXPLORATION_TESTS_IMAGE: registry.ddbuild.io/ci/dd-trace-java:exploration-tests
33

44
build-exploration-tests-image:
55
stage: exploration-tests
@@ -18,24 +18,32 @@ build-exploration-tests-image:
1818
- ./gradlew :dd-java-agent:shadowJar --no-scan
1919
- cp workspace/dd-java-agent/build/libs/*.jar /exploration-tests/dd-java-agent.jar
2020
- cp dd-java-agent/agent-debugger/exploration-tests/run-exploration-tests.sh /exploration-tests
21-
- cp dd-java-agent/agent-debugger/exploration-tests/exclude*.txt /exploration-tests
21+
- cp dd-java-agent/agent-debugger/exploration-tests/exclude_*.txt /exploration-tests
22+
- cp dd-java-agent/agent-debugger/exploration-tests/include_*.txt /exploration-tests
2223
- cd /exploration-tests
2324
after_script:
2425
- cd $CI_PROJECT_DIR
25-
- cp /exploration-tests/$PROJECT/agent.log agent.log
26-
- gzip agent.log
27-
- tar czf surefire-reports.tar.gz /exploration-tests/$PROJECT/target/surefire-reports
28-
- tar czf debugger-dumps.tar.gz /tmp/debugger
26+
- cp /exploration-tests/$PROJECT/agent.log $PROJECT_agent.log
27+
- gzip $PROJECT_agent.log
28+
- tar czf $PROJECT_surefire-reports.tar.gz /exploration-tests/$PROJECT/target/surefire-reports
29+
- tar czf $PROJECT_debugger-dumps.tar.gz /tmp/debugger
2930
stage: exploration-tests
3031
when: manual
3132
tags: [ "runner:main"]
3233
needs: []
3334
image: $EXPLORATION_TESTS_IMAGE
3435
artifacts:
3536
paths:
36-
- agent.log.gz
37-
- surefire-reports.tar.gz
38-
- debugger-dumps.tar.gz
37+
- $PROJECT_agent.log.gz
38+
- $PROJECT_surefire-reports.tar.gz
39+
- $PROJECT_debugger-dumps.tar.gz
40+
41+
exploration-tests-jsoup:
42+
variables:
43+
PROJECT: jsoup
44+
<<: *common-exploration-tests
45+
script:
46+
- ./run-exploration-tests.sh "$PROJECT" "mvn verify" "include_jsoup.txt" "exclude_jsoup.txt"
3947

4048

4149
exploration-tests-jackson-core:

dd-java-agent/agent-debugger/exploration-tests/Dockerfile.exploration-tests

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ FROM debian:bookworm-slim
33
ARG JAVA_8_VERSION="8.0.372-tem"
44
ARG JAVA_11_VERSION="11.0.19-tem"
55
ARG JAVA_17_VERSION="17.0.7-tem"
6+
ARG JAVA_21_VERSION="21.0.4-tem"
67
ARG MAVEN_VERSION=3.8.4
78

89
RUN apt-get update && \
@@ -16,25 +17,36 @@ RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && \
1617
yes | sdk install java $JAVA_8_VERSION && \
1718
yes | sdk install java $JAVA_11_VERSION && \
1819
yes | sdk install java $JAVA_17_VERSION && \
20+
yes | sdk install java $JAVA_21_VERSION && \
1921
yes | sdk install maven $MAVEN_VERSION && \
2022
rm -rf $HOME/.sdkman/archives/* && \
2123
rm -rf $HOME/.sdkman/tmp/*"
2224
ENV JAVA_8_HOME=/root/.sdkman/candidates/java/$JAVA_8_VERSION
2325
ENV JAVA_11_HOME=/root/.sdkman/candidates/java/$JAVA_11_VERSION
2426
ENV JAVA_17_HOME=/root/.sdkman/candidates/java/$JAVA_17_VERSION
27+
ENV JAVA_21_HOME=/root/.sdkman/candidates/java/$JAVA_21_VERSION
2528

2629
RUN mkdir exploration-tests
2730
WORKDIR /exploration-tests
31+
# jsoup
32+
RUN git clone -b jsoup-1.18.1 https://github.com/jhy/jsoup.git
33+
COPY jsoup_exploration-tests.patch .
34+
# fix tests that are failing because checking time to execute
35+
RUN cd jsoup && git apply /exploration-tests/jsoup_exploration-tests.patch
36+
RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && cd jsoup && mvn verify -DskipTests=true"
37+
38+
2839
# Jackson
29-
RUN git clone https://github.com/FasterXML/jackson-core.git
30-
RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && cd jackson-core && ./mvnw dependency:resolve dependency:resolve-plugins"
31-
RUN git clone https://github.com/FasterXML/jackson-databind.git
32-
RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && cd jackson-databind && ./mvnw dependency:resolve dependency:resolve-plugins"
40+
#RUN git clone https://github.com/FasterXML/jackson-core.git
41+
#RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && cd jackson-core && ./mvnw dependency:resolve dependency:resolve-plugins"
42+
#RUN git clone https://github.com/FasterXML/jackson-databind.git
43+
#RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && cd jackson-databind && ./mvnw dependency:resolve dependency:resolve-plugins"
44+
3345
# Netty
34-
RUN git clone https://github.com/netty/netty.git
35-
RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && cd netty && ./mvnw dependency:resolve dependency:resolve-plugins"
46+
#RUN git clone https://github.com/netty/netty.git
47+
#RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && cd netty && ./mvnw dependency:resolve dependency:resolve-plugins"
3648

3749
# Guava
38-
RUN git clone https://github.com/google/guava.git
39-
RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && cd guava && mvn -B -P!standard-with-extra-repos verify -U -Dmaven.javadoc.skip=true -DskipTests=true"
50+
#RUN git clone https://github.com/google/guava.git
51+
#RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && cd guava && mvn -B -P!standard-with-extra-repos verify -U -Dmaven.javadoc.skip=true -DskipTests=true"
4052

dd-java-agent/agent-debugger/exploration-tests/exclude.txt

Lines changed: 0 additions & 15 deletions
This file was deleted.

dd-java-agent/agent-debugger/exploration-tests/exclude_jackson-databind.txt

Lines changed: 0 additions & 4 deletions
This file was deleted.

dd-java-agent/agent-debugger/exploration-tests/exclude_jsoup.txt

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org/jsoup/*
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java
2+
index a67003a8..1201d1af 100644
3+
--- a/src/test/java/org/jsoup/parser/HtmlParserTest.java
4+
+++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java
5+
@@ -1033,7 +1033,7 @@ public class HtmlParserTest {
6+
7+
// Assert
8+
assertEquals(50000, doc.body().childNodeSize());
9+
- assertTrue(System.currentTimeMillis() - start < 1000);
10+
+ //assertTrue(System.currentTimeMillis() - start < 10000);
11+
}
12+
13+
@Test
14+
diff --git a/src/test/java/org/jsoup/parser/ParserIT.java b/src/test/java/org/jsoup/parser/ParserIT.java
15+
index 54d757e7..467c10bc 100644
16+
--- a/src/test/java/org/jsoup/parser/ParserIT.java
17+
+++ b/src/test/java/org/jsoup/parser/ParserIT.java
18+
@@ -48,7 +48,7 @@ public class ParserIT {
19+
// Assert
20+
assertEquals(2, doc.body().childNodeSize());
21+
assertEquals(25000, doc.select("dd").size());
22+
- assertTrue(System.currentTimeMillis() - start < 20000); // I get ~ 1.5 seconds, but others have reported slower
23+
+ //assertTrue(System.currentTimeMillis() - start < 20000); // I get ~ 1.5 seconds, but others have reported slower
24+
// was originally much longer, or stack overflow.
25+
}
26+
}

dd-java-agent/agent-debugger/exploration-tests/run-exploration-tests.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
set -uo pipefail
33
NAME=$1
44
COMMAND=$2
5-
PROJECT_EXCLUDE_FILE=${3:-}
5+
PROJECT_INCLUDE_FILE=${3:-}
6+
PROJECT_EXCLUDE_FILE=${4:-}
67
echo " === running debugger java exploration tests === "
7-
export JAVA_TOOL_OPTIONS="-javaagent:`pwd`/dd-java-agent.jar -Ddatadog.slf4j.simpleLogger.log.com.datadog.debugger=debug -Ddd.trace.enabled=false -Ddd.dynamic.instrumentation.enabled=true -Ddd.dynamic.instrumentation.instrument.the.world=true -Ddd.dynamic.instrumentation.classfile.dump.enabled=true -Ddd.dynamic.instrumentation.verify.bytecode=true -Ddd.dynamic.instrumentation.exclude.files=/exploration-tests/exclude.txt,/exploration-tests/$PROJECT_EXCLUDE_FILE"
8+
export JAVA_TOOL_OPTIONS="-javaagent:`pwd`/dd-java-agent.jar -Ddatadog.slf4j.simpleLogger.log.com.datadog.debugger=debug -Ddd.trace.enabled=false -Ddd.dynamic.instrumentation.enabled=true -Ddd.dynamic.instrumentation.instrument.the.world=true -Ddd.dynamic.instrumentation.classfile.dump.enabled=true -Ddd.dynamic.instrumentation.verify.bytecode=false -Ddd.dynamic.instrumentation.include.files=/exploration-tests/$PROJECT_INCLUDE_FILE -Ddd.dynamic.instrumentation.exclude.files=/exploration-tests/$PROJECT_EXCLUDE_FILE"
89
echo "$JAVA_TOOL_OPTIONS"
910
cd $NAME
1011
echo "Building repository $NAME..."

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

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,12 @@ public class DebuggerTransformer implements ClassFileTransformer {
9393
private final InstrumentationListener listener;
9494
private final DebuggerSink debuggerSink;
9595
private final boolean instrumentTheWorld;
96-
private final Set<String> excludeClasses = new HashSet<>();
97-
private final Trie excludeTrie = new Trie();
96+
private final Set<String> excludeClasses;
97+
private final Set<String> excludeMethods;
98+
private final Trie excludeTrie;
99+
private final Set<String> includeClasses;
100+
private final Set<String> includeMethods;
101+
private final Trie includeTrie;
98102
private final Map<String, LogProbe> instrumentTheWorldProbes;
99103

100104
public interface InstrumentationListener {
@@ -115,9 +119,24 @@ public DebuggerTransformer(
115119
this.instrumentTheWorld = config.isDebuggerInstrumentTheWorld();
116120
if (this.instrumentTheWorld) {
117121
instrumentTheWorldProbes = new ConcurrentHashMap<>();
118-
readExcludeFiles(config.getDebuggerExcludeFiles());
122+
excludeTrie = new Trie();
123+
excludeClasses = new HashSet<>();
124+
excludeMethods = new HashSet<>();
125+
includeTrie = new Trie();
126+
includeClasses = new HashSet<>();
127+
includeMethods = new HashSet<>();
128+
processITWFiles(
129+
config.getDebuggerExcludeFiles(), excludeTrie, excludeClasses, excludeMethods);
130+
processITWFiles(
131+
config.getDebuggerIncludeFiles(), includeTrie, includeClasses, includeMethods);
119132
} else {
120133
instrumentTheWorldProbes = null;
134+
excludeTrie = null;
135+
excludeClasses = null;
136+
excludeMethods = null;
137+
includeTrie = null;
138+
includeClasses = null;
139+
includeMethods = null;
121140
}
122141
}
123142

@@ -137,7 +156,8 @@ public DebuggerTransformer(Config config, Configuration configuration) {
137156
new SymbolSink(config)));
138157
}
139158

140-
private void readExcludeFiles(String commaSeparatedFileNames) {
159+
private void processITWFiles(
160+
String commaSeparatedFileNames, Trie prefixes, Set<String> classes, Set<String> methods) {
141161
if (commaSeparatedFileNames == null) {
142162
return;
143163
}
@@ -156,10 +176,14 @@ private void readExcludeFiles(String commaSeparatedFileNames) {
156176
return;
157177
}
158178
if (line.endsWith("*")) {
159-
excludeTrie.insert(line.substring(0, line.length() - 1));
160-
} else {
161-
excludeClasses.add(line);
179+
prefixes.insert(line.substring(0, line.length() - 1));
180+
return;
181+
}
182+
if (line.contains("::")) {
183+
methods.add(line);
184+
return;
162185
}
186+
classes.add(line);
163187
});
164188
} catch (IOException ex) {
165189
log.warn("Error reading exclude file '{}' for Instrument-The-World: ", fileName, ex);
@@ -263,6 +287,9 @@ private byte[] transformTheWorld(
263287
if (isExcludedFromTransformation(classFilePath)) {
264288
return null;
265289
}
290+
if (!isIncludedForTransformation(classFilePath)) {
291+
return null;
292+
}
266293
URL location = null;
267294
if (protectionDomain != null) {
268295
CodeSource codeSource = protectionDomain.getCodeSource();
@@ -277,15 +304,32 @@ private byte[] transformTheWorld(
277304
loader,
278305
location);
279306
ClassNode classNode = parseClassFile(classFilePath, classfileBuffer);
307+
if (classNode.superName.equals("java/lang/ClassLoader")
308+
|| classNode.superName.equals("java/net/URLClassLoader")
309+
|| excludeClasses.contains(classNode.superName)) {
310+
// Skip ClassLoader classes
311+
log.debug("Skipping ClassLoader class: {}", classFilePath);
312+
excludeClasses.add(classFilePath);
313+
return null;
314+
}
280315
List<ProbeDefinition> probes = new ArrayList<>();
281316
Set<String> methodNames = new HashSet<>();
282317
for (MethodNode methodNode : classNode.methods) {
318+
if (methodNode.name.equals("<clinit>")) {
319+
// skip static class initializer
320+
continue;
321+
}
322+
String fqnMethod = classNode.name + "::" + methodNode.name;
323+
if (excludeMethods.contains(fqnMethod)) {
324+
log.debug("Skipping method: {}", fqnMethod);
325+
continue;
326+
}
283327
if (methodNames.add(methodNode.name)) {
284328
LogProbe probe =
285329
LogProbe.builder()
286330
.probeId(UUID.randomUUID().toString(), 0)
287331
.where(classNode.name, methodNode.name)
288-
.captureSnapshot(true)
332+
.captureSnapshot(false)
289333
.build();
290334
probes.add(probe);
291335
instrumentTheWorldProbes.put(probe.getProbeId().getEncodedId(), probe);
@@ -294,6 +338,8 @@ private byte[] transformTheWorld(
294338
boolean transformed = performInstrumentation(loader, classFilePath, probes, classNode);
295339
if (transformed) {
296340
return writeClassFile(probes, loader, classFilePath, classNode);
341+
} else {
342+
log.debug("Class not transformed: {}", classFilePath);
297343
}
298344
} catch (Throwable ex) {
299345
log.warn("Cannot transform: ", ex);
@@ -328,6 +374,16 @@ private boolean isExcludedFromTransformation(String classFilePath) {
328374
return false;
329375
}
330376

377+
private boolean isIncludedForTransformation(String classFilePath) {
378+
if (includeClasses.contains(classFilePath)) {
379+
return true;
380+
}
381+
if (includeTrie.hasMatchingPrefix(classFilePath)) {
382+
return true;
383+
}
384+
return false;
385+
}
386+
331387
private boolean instrumentationIsAllowed(
332388
String fullyQualifiedClassName, List<ProbeDefinition> definitions) {
333389
if (denyListHelper.isDenied(fullyQualifiedClassName)) {

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

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,11 @@
6464
import org.objectweb.asm.tree.TryCatchBlockNode;
6565
import org.objectweb.asm.tree.TypeInsnNode;
6666
import org.objectweb.asm.tree.VarInsnNode;
67+
import org.slf4j.Logger;
68+
import org.slf4j.LoggerFactory;
6769

6870
public class CapturedContextInstrumentor extends Instrumentor {
71+
private static final Logger log = LoggerFactory.getLogger(CapturedContextInstrumentor.class);
6972
private final boolean captureSnapshot;
7073
private final Limits limits;
7174
private final LabelNode contextInitLabel = new LabelNode();
@@ -1167,7 +1170,12 @@ private static List<FieldNode> extractStaticFields(
11671170
}
11681171
}
11691172
}
1170-
addInheritedStaticFields(classNode, classLoader, limits, results, fieldCount);
1173+
if (!Config.get().isDebuggerInstrumentTheWorld()) {
1174+
// Collects inherited static fields only if the ITW mode is not enabled
1175+
// because it can lead to LinkageError: attempted duplicate class definition
1176+
// for example, when a probe is located in method overridden in enum element
1177+
addInheritedStaticFields(classNode, classLoader, limits, results, fieldCount);
1178+
}
11711179
return results;
11721180
}
11731181

@@ -1185,17 +1193,22 @@ private static void addInheritedStaticFields(
11851193
} catch (ClassNotFoundException ex) {
11861194
break;
11871195
}
1188-
for (Field field : clazz.getDeclaredFields()) {
1189-
if (isStaticField(field) && !isFinalField(field)) {
1190-
String desc = Type.getDescriptor(field.getType());
1191-
FieldNode fieldNode =
1192-
new FieldNode(field.getModifiers(), field.getName(), desc, null, field);
1193-
results.add(fieldNode);
1194-
fieldCount++;
1195-
if (fieldCount > limits.maxFieldCount) {
1196-
return;
1196+
try {
1197+
for (Field field : clazz.getDeclaredFields()) {
1198+
if (isStaticField(field) && !isFinalField(field)) {
1199+
String desc = Type.getDescriptor(field.getType());
1200+
FieldNode fieldNode =
1201+
new FieldNode(field.getModifiers(), field.getName(), desc, null, field);
1202+
results.add(fieldNode);
1203+
log.debug("Adding static inherited field {}", fieldNode.name);
1204+
fieldCount++;
1205+
if (fieldCount > limits.maxFieldCount) {
1206+
return;
1207+
}
11971208
}
11981209
}
1210+
} catch (ClassCircularityError ex) {
1211+
break;
11991212
}
12001213
clazz = clazz.getSuperclass();
12011214
superClassName = clazz.getTypeName();

0 commit comments

Comments
 (0)