Skip to content

Commit 716723b

Browse files
committed
Fix class crawl logic
1 parent 9af5fa3 commit 716723b

2 files changed

Lines changed: 56 additions & 73 deletions

File tree

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleGenerator.java

Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import java.util.List;
1919
import java.util.Map;
2020
import java.util.Set;
21-
import java.util.function.Predicate;
2221
import net.bytebuddy.asm.AsmVisitorWrapper;
2322
import net.bytebuddy.description.field.FieldDescription;
2423
import net.bytebuddy.description.field.FieldList;
@@ -88,8 +87,7 @@ public ClassVisitor wrap(
8887
private static Reference[] generateReferences(
8988
Instrumenter.HasMethodAdvice instrumenter,
9089
AdviceShader adviceShader,
91-
Set<String> allAdviceClasses,
92-
Predicate<String> shouldRecurse) {
90+
Set<String> allAdviceClasses) {
9391
// track sources we've generated references from to avoid recursion
9492
final Set<String> referenceSources = new HashSet<>();
9593
final Map<String, Reference> references = new LinkedHashMap<>();
@@ -107,8 +105,7 @@ private static Reference[] generateReferences(
107105
for (String adviceClass : adviceClasses) {
108106
if (referenceSources.add(adviceClass)) {
109107
for (Map.Entry<String, Reference> entry :
110-
ReferenceCreator.createReferencesFrom(
111-
adviceClass, adviceShader, contextClassLoader, shouldRecurse)
108+
ReferenceCreator.createReferencesFrom(adviceClass, adviceShader, contextClassLoader)
112109
.entrySet()) {
113110
Reference toMerge = references.get(entry.getKey());
114111
if (null == toMerge) {
@@ -128,22 +125,19 @@ private byte[] generateMuzzleClass(InstrumenterModule module) {
128125
AdviceShader adviceShader = AdviceShader.with(module.adviceShading());
129126
HelperClassPredicate helperPredicate = new HelperClassPredicate(this::isOwnOutput);
130127

131-
// Crawl advice, following references into our own helper classes (even in library packages).
128+
// Crawl advice for muzzle references (only recursing into the instrumentation package).
132129
Set<String> adviceClasses = new HashSet<>();
133130
List<Reference> allReferences = new ArrayList<>();
134131
for (Instrumenter instrumenter : module.typeInstrumentations()) {
135132
if (instrumenter instanceof Instrumenter.HasMethodAdvice) {
136133
Collections.addAll(
137134
allReferences,
138135
generateReferences(
139-
(Instrumenter.HasMethodAdvice) instrumenter,
140-
adviceShader,
141-
adviceClasses,
142-
helperPredicate::isHelperClass));
136+
(Instrumenter.HasMethodAdvice) instrumenter, adviceShader, adviceClasses));
143137
}
144138
}
145139

146-
// Advice roots are inlined into the target method, not injected, so they aren't helpers.
140+
// Inferred helpers = our classes referenced from the advice, minus the advice roots.
147141
Set<String> inferredHelpers = new LinkedHashSet<>();
148142
for (Reference reference : allReferences) {
149143
if (!adviceClasses.contains(reference.className)
@@ -153,21 +147,26 @@ private byte[] generateMuzzleClass(InstrumenterModule module) {
153147
}
154148

155149
// Manual additions cover helpers the crawl can't see (reflection, SPI, advice-less injectors).
156-
Set<String> helperClasses = new LinkedHashSet<>(inferredHelpers);
157-
Collections.addAll(helperClasses, module.helperClassNames());
158-
for (String helper : new ArrayList<>(helperClasses)) {
150+
Set<String> manualHelpers = new LinkedHashSet<>(asList(module.helperClassNames()));
151+
Set<String> seedHelpers = new LinkedHashSet<>(inferredHelpers);
152+
seedHelpers.addAll(manualHelpers);
153+
for (String helper : new ArrayList<>(seedHelpers)) {
159154
if (isOwnOutput(helper)) {
160-
addNestedClasses(helper, helperClasses);
155+
addNestedClasses(helper, seedHelpers);
161156
}
162157
}
163158

164159
String[] orderedHelpers =
165-
orderHelpers(helperClasses, Thread.currentThread().getContextClassLoader());
160+
discoverAndOrderHelpers(
161+
seedHelpers,
162+
manualHelpers,
163+
helperPredicate,
164+
Thread.currentThread().getContextClassLoader());
166165

167-
writeInferenceReport(module, adviceClasses.isEmpty(), inferredHelpers, helperClasses);
166+
writeInferenceReport(module, adviceClasses.isEmpty(), inferredHelpers, orderedHelpers);
168167

169168
// Injected helpers are our own classes, so they must not be asserted as library references.
170-
Set<String> ignoredClassNames = new HashSet<>(helperClasses);
169+
Set<String> ignoredClassNames = new HashSet<>(asList(orderedHelpers));
171170
Collections.addAll(ignoredClassNames, module.muzzleIgnoredClassNames());
172171
List<Reference> references = new ArrayList<>();
173172
for (Reference reference : allReferences) {
@@ -225,19 +224,21 @@ private byte[] generateMuzzleClass(InstrumenterModule module) {
225224
mv.visitMaxs(0, 0);
226225
mv.visitEnd();
227226

228-
// Generated: public static String[] helperClassNames()
229-
MethodVisitor hv =
230-
cw.visitMethod(
231-
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
232-
"helperClassNames",
233-
"()[Ljava/lang/String;",
234-
null,
235-
null);
236-
hv.visitCode();
237-
writeStrings(hv, orderedHelpers);
238-
hv.visitInsn(Opcodes.ARETURN);
239-
hv.visitMaxs(0, 0);
240-
hv.visitEnd();
227+
// Generated: public static String[] helperClassNames() — omitted for helper-less modules.
228+
if (orderedHelpers.length > 0) {
229+
MethodVisitor hv =
230+
cw.visitMethod(
231+
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
232+
"helperClassNames",
233+
"()[Ljava/lang/String;",
234+
null,
235+
null);
236+
hv.visitCode();
237+
writeStrings(hv, orderedHelpers);
238+
hv.visitInsn(Opcodes.ARETURN);
239+
hv.visitMaxs(0, 0);
240+
hv.visitEnd();
241+
}
241242

242243
return cw.toByteArray();
243244
}
@@ -270,28 +271,35 @@ private void addNestedClasses(String className, Set<String> helperClasses) {
270271
}
271272

272273
/**
273-
* Load-orders helpers (dependencies first) via {@link HelperScanner}, keeping only our helpers
274-
* (the scanner may pull in library classes) and retaining any helper it could not locate.
274+
* Runs {@link HelperScanner} over the seed helpers to both discover their transitive helper
275+
* dependencies and load-order the result (dependencies first). Keeps only our own helpers (plus
276+
* manual additions), dropping library classes the scanner pulls in, and retains every seed even
277+
* if it could not be located.
275278
*/
276-
private static String[] orderHelpers(Set<String> helpers, ClassLoader loader) {
277-
if (helpers.isEmpty()) {
279+
private static String[] discoverAndOrderHelpers(
280+
Set<String> seedHelpers,
281+
Set<String> manualHelpers,
282+
HelperClassPredicate helperPredicate,
283+
ClassLoader loader) {
284+
if (seedHelpers.isEmpty()) {
278285
return new String[0];
279286
}
280-
List<String> ordered = new ArrayList<>(helpers.size());
287+
List<String> ordered = new ArrayList<>();
281288
try {
282289
for (String name :
283290
HelperScanner.withClassDependencies(
284-
ClassFileLocator.ForClassLoader.of(loader), helpers.toArray(new String[0]))) {
285-
if (helpers.contains(name) && !ordered.contains(name)) {
291+
ClassFileLocator.ForClassLoader.of(loader), seedHelpers.toArray(new String[0]))) {
292+
if ((helperPredicate.isHelperClass(name) || manualHelpers.contains(name))
293+
&& !ordered.contains(name)) {
286294
ordered.add(name);
287295
}
288296
}
289297
} catch (Throwable ignore) {
290-
// best-effort ordering; unordered helpers are appended below
298+
// best-effort ordering; unordered seeds are appended below
291299
}
292-
for (String name : helpers) {
293-
if (!ordered.contains(name)) {
294-
ordered.add(name);
300+
for (String seed : seedHelpers) {
301+
if (!ordered.contains(seed)) {
302+
ordered.add(seed);
295303
}
296304
}
297305
return ordered.toArray(new String[0]);
@@ -305,7 +313,7 @@ private void writeInferenceReport(
305313
InstrumenterModule module,
306314
boolean adviceLess,
307315
Set<String> inferredHelpers,
308-
Set<String> allHelpers) {
316+
String[] injectedHelpers) {
309317
try {
310318
Set<String> declared = new LinkedHashSet<>(asList(module.helperClassNames()));
311319
if (declared.isEmpty() && inferredHelpers.isEmpty()) {
@@ -323,7 +331,7 @@ private void writeInferenceReport(
323331
StringBuilder sb = new StringBuilder();
324332
sb.append("module: ").append(module.getClass().getName()).append('\n');
325333
sb.append("has-advice: ").append(!adviceLess).append('\n');
326-
sb.append("injected-helpers: ").append(allHelpers.size()).append('\n');
334+
sb.append("injected-helpers: ").append(injectedHelpers.length).append('\n');
327335
if (adviceLess && !declared.isEmpty()) {
328336
sb.append("crawl-cannot-cover: advice-less module; declared helpers are all manual-only\n");
329337
}

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/ReferenceCreator.java

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import java.util.Map;
1515
import java.util.Queue;
1616
import java.util.Set;
17-
import java.util.function.Predicate;
1817
import net.bytebuddy.jar.asm.ClassReader;
1918
import net.bytebuddy.jar.asm.ClassVisitor;
2019
import net.bytebuddy.jar.asm.FieldVisitor;
@@ -39,10 +38,6 @@ public class ReferenceCreator extends ClassVisitor {
3938
*/
4039
private static final String REFERENCE_CREATION_PACKAGE = "datadog.trace.instrumentation.";
4140

42-
/** Default recursion rule; callers that identify helpers by other means pass their own. */
43-
private static final Predicate<String> DEFAULT_SHOULD_RECURSE =
44-
name -> name.startsWith(REFERENCE_CREATION_PACKAGE);
45-
4641
private static final int UNDEFINED_LINE = -1;
4742

4843
/** Set containing name+descriptor signatures of Object methods. */
@@ -63,30 +58,9 @@ public class ReferenceCreator extends ClassVisitor {
6358
* @return Map of [referenceClassName -> Reference]
6459
* @throws IllegalStateException if class is not found or unable to be loaded.
6560
*/
66-
public static Map<String, Reference> createReferencesFrom(
67-
final String entryPointClassName, final AdviceShader adviceShader, final ClassLoader loader)
68-
throws IllegalStateException {
69-
return createReferencesFrom(entryPointClassName, adviceShader, loader, DEFAULT_SHOULD_RECURSE);
70-
}
71-
72-
/**
73-
* Generate all references reachable from a given class, following references into classes
74-
* accepted by {@code shouldRecurse}.
75-
*
76-
* @param entryPointClassName Starting point for generating references.
77-
* @param adviceShader Optional shading to apply to the advice.
78-
* @param loader Classloader used to read class bytes.
79-
* @param shouldRecurse Decides whether a referenced class is one of ours to keep crawling into
80-
* (vs. a library/bootstrap leaf that is merely recorded).
81-
* @return Map of [referenceClassName -> Reference]
82-
* @throws IllegalStateException if class is not found or unable to be loaded.
83-
*/
8461
@SuppressForbidden
8562
public static Map<String, Reference> createReferencesFrom(
86-
final String entryPointClassName,
87-
final AdviceShader adviceShader,
88-
final ClassLoader loader,
89-
final Predicate<String> shouldRecurse)
63+
final String entryPointClassName, final AdviceShader adviceShader, final ClassLoader loader)
9064
throws IllegalStateException {
9165
final Set<String> visitedSources = new HashSet<>();
9266
final Map<String, Reference> references = new LinkedHashMap<>();
@@ -113,8 +87,9 @@ public static Map<String, Reference> createReferencesFrom(
11387

11488
final Map<String, Reference> instrumentationReferences = cv.getReferences();
11589
for (final Map.Entry<String, Reference> entry : instrumentationReferences.entrySet()) {
116-
// Only keep crawling into classes that are ours
117-
if (!visitedSources.contains(entry.getKey()) && shouldRecurse.test(entry.getKey())) {
90+
// Don't generate references created outside of the datadog instrumentation package.
91+
if (!visitedSources.contains(entry.getKey())
92+
&& entry.getKey().startsWith(REFERENCE_CREATION_PACKAGE)) {
11893
instrumentationQueue.add(entry.getKey());
11994
}
12095
Reference toMerge = references.get(entry.getKey());

0 commit comments

Comments
 (0)