Skip to content

Commit b3d16ee

Browse files
committed
Implement auto-inferring helper methods
1 parent ca4153b commit b3d16ee

8 files changed

Lines changed: 391 additions & 26 deletions

File tree

dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/CombiningTransformerBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ private void prepareInstrumentation(InstrumenterModule module, int instrumentati
130130

131131
adviceShader = AdviceShader.with(module);
132132

133-
String[] helperClassNames = module.helperClassNames();
133+
String[] helperClassNames = module.getAllHelperClassNames();
134134
if (module.injectHelperDependencies()) {
135135
helperClassNames = HelperScanner.withClassDependencies(helperClassNames);
136136
}

dd-java-agent/agent-tooling/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ dependencies {
4949

5050
testImplementation project(':dd-java-agent:testing')
5151
testImplementation libs.bytebuddy
52+
testImplementation libs.bundles.junit5
5253
testImplementation group: 'com.google.guava', name: 'guava-testlib', version: '20.0'
5354

5455
jmhImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.3.5.RELEASE'

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/HelperScanner.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@
2424
public final class HelperScanner extends ClassVisitor {
2525
static final int READER_OPTIONS = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
2626

27-
static final ClassFileLocator locator =
28-
ClassFileLocator.ForClassLoader.of(Utils.getAgentClassLoader());
27+
final ClassFileLocator locator;
2928

3029
final MethodScanner methodScanner = new MethodScanner();
3130

@@ -41,14 +40,30 @@ public final class HelperScanner extends ClassVisitor {
4140
Set<String> uses;
4241

4342
HelperScanner() {
43+
this(ClassFileLocator.ForClassLoader.of(Utils.getAgentClassLoader()));
44+
}
45+
46+
HelperScanner(ClassFileLocator locator) {
4447
super(Opcodes.ASM7, null);
48+
this.locator = locator;
4549
}
4650

47-
/** Expands helper class names to include any non-bootstrap classes they depend on. */
51+
/**
52+
* Expands helper class names with their non-bootstrap dependencies, via the agent class loader.
53+
*/
4854
public static String[] withClassDependencies(String... helperClassNames) {
4955
return new HelperScanner().simulateClassLoading(helperClassNames);
5056
}
5157

58+
/**
59+
* Same as above, but reads bytecode via the given locator (e.g. during build time where the agent
60+
* loader is absent).
61+
*/
62+
public static String[] withClassDependencies(
63+
ClassFileLocator locator, String... helperClassNames) {
64+
return new HelperScanner(locator).simulateClassLoading(helperClassNames);
65+
}
66+
5267
/**
5368
* Simulates class-loading by finding all classes required to load the helper classes as well as
5469
* optional classes used in method instructions that may be needed later when invoking the method.

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/InstrumenterModule.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,31 @@ public static ReferenceMatcher loadStaticMuzzleReferences(
113113
}
114114

115115
/**
116-
* @return Class names of helpers to inject into the user's classloader.
117-
* <p><b>NOTE:</b> The order of the returned helper classes matters. If a muzzle check fails
118-
* with a NoClassDefFoundError, as logged in build/reports/muzzle-*.txt, it is likely that one
119-
* helper class depends on another that appears later in the list. In this case, the returned
120-
* list must be reordered so that the referred helper class appears before the one that refers
121-
* to it.
116+
* @return the full, load-ordered set of helper classes to inject into the user's classloader: the
117+
* helpers inferred at build time from the advice/helper byte-code, unioned with any {@link
118+
* #helperClassNames() manually-declared} additions. Falls back to {@link #helperClassNames()}
119+
* when no generated {@code $Muzzle} is available.
122120
*/
121+
public final String[] getAllHelperClassNames() {
122+
String[] generated =
123+
loadStaticMuzzleHelperClassNames(getClass().getClassLoader(), getClass().getName());
124+
return null != generated ? generated : helperClassNames();
125+
}
126+
127+
static String[] loadStaticMuzzleHelperClassNames(
128+
ClassLoader classLoader, String instrumentationClass) {
129+
String muzzleClass = instrumentationClass + "$Muzzle";
130+
try {
131+
// helper class names captured at build-time; see MuzzleGenerator
132+
return (String[])
133+
classLoader.loadClass(muzzleClass).getMethod("helperClassNames").invoke(null);
134+
} catch (Throwable e) {
135+
// no generated helper list: caller falls back to helperClassNames()
136+
return null;
137+
}
138+
}
139+
140+
/** Optional manual additions to the injected helper set. */
123141
public String[] helperClassNames() {
124142
return NO_HELPERS;
125143
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package datadog.trace.agent.tooling.muzzle;
2+
3+
import datadog.trace.bootstrap.Constants;
4+
import java.util.function.Predicate;
5+
6+
/**
7+
* Classifies a referenced class as an injectable tracer helper, a bootstrap class, or a library
8+
* class — similar to OpenTelemetry's {@code HelperClassPredicate#isHelperClass}; however, the main
9+
* signal here is whether the class was compiled from the instrumentation subproject's own output
10+
* ({@code ownOutput}). {@link #HELPER_PREFIXES} additionally covers helpers that live in other
11+
* tracer subprojects.
12+
*/
13+
public final class HelperClassPredicate {
14+
15+
static final String[] HELPER_PREFIXES = {
16+
"datadog.trace.instrumentation.",
17+
"datadog.opentelemetry.shim.",
18+
"datadog.trace.agent.tooling.iast.",
19+
"datadog.trace.agent.tooling.nativeimage.",
20+
};
21+
22+
private final Predicate<String> ownOutput;
23+
24+
/**
25+
* @param ownOutput whether a dotted class name was compiled from the instrumentation subproject's
26+
* own output; injected because agent-tooling cannot resolve build directories itself.
27+
*/
28+
public HelperClassPredicate(final Predicate<String> ownOutput) {
29+
this.ownOutput = ownOutput;
30+
}
31+
32+
public boolean isHelperClass(final String className) {
33+
return !isBootstrap(className) && (ownOutput.test(className) || matchesHelperPrefix(className));
34+
}
35+
36+
private static boolean matchesHelperPrefix(final String className) {
37+
for (final String prefix : HELPER_PREFIXES) {
38+
if (className.startsWith(prefix)) {
39+
return true;
40+
}
41+
}
42+
return false;
43+
}
44+
45+
/**
46+
* Whether the class is on the bootstrap class-path (or a JDK/SLF4J type) and so never injected.
47+
*/
48+
public static boolean isBootstrap(final String className) {
49+
if (className.startsWith("java.")
50+
|| className.startsWith("javax.")
51+
|| className.startsWith("jdk.")
52+
|| className.startsWith("com.sun.")
53+
|| className.startsWith("sun.")
54+
|| className.startsWith("org.slf4j.")
55+
|| className.startsWith("datadog.slf4j.")) {
56+
return true;
57+
}
58+
for (final String prefix : Constants.BOOTSTRAP_PACKAGE_PREFIXES) {
59+
if (className.startsWith(prefix)) {
60+
return true;
61+
}
62+
}
63+
return false;
64+
}
65+
}

0 commit comments

Comments
 (0)