Skip to content

Commit c39b811

Browse files
committed
Support instrumentation of repackaged libraries
1 parent 886169b commit c39b811

File tree

8 files changed

+154
-14
lines changed

8 files changed

+154
-14
lines changed

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public final class CombiningTransformerBuilder
7171
private ElementMatcher<ClassLoader> classLoaderMatcher;
7272
private Map<String, String> contextStore;
7373
private AgentBuilder.Transformer contextRequestRewriter;
74+
private AdviceShader adviceShader;
7475
private HelperTransformer helperTransformer;
7576
private Advice.PostProcessor.Factory postProcessor;
7677
private MuzzleCheck muzzle;
@@ -118,14 +119,19 @@ private void prepareInstrumentation(InstrumenterModule module, int instrumentati
118119
new FieldBackedContextRequestRewriter(contextStore, module.name()))
119120
: null;
120121

122+
adviceShader = AdviceShader.with(module.adviceShading());
123+
121124
String[] helperClassNames = module.helperClassNames();
122125
if (module.injectHelperDependencies()) {
123126
helperClassNames = HelperScanner.withClassDependencies(helperClassNames);
124127
}
125128
helperTransformer =
126129
helperClassNames.length > 0
127130
? new HelperTransformer(
128-
module.useAgentCodeSource(), module.getClass().getSimpleName(), helperClassNames)
131+
module.useAgentCodeSource(),
132+
adviceShader,
133+
module.getClass().getSimpleName(),
134+
helperClassNames)
129135
: null;
130136

131137
postProcessor = module.postProcessor();
@@ -238,11 +244,17 @@ public void applyAdvice(ElementMatcher<? super MethodDescription> matcher, Strin
238244
if (postProcessor != null) {
239245
customMapping = customMapping.with(postProcessor);
240246
}
241-
advice.add(
247+
AgentBuilder.Transformer.ForAdvice forAdvice =
242248
new AgentBuilder.Transformer.ForAdvice(customMapping)
243-
.include(Utils.getBootstrapProxy(), Utils.getExtendedClassLoader())
244249
.withExceptionHandler(ExceptionHandlers.defaultExceptionHandler())
245-
.advice(not(ignoredMethods).and(matcher), adviceClass));
250+
.include(Utils.getBootstrapProxy());
251+
ClassLoader adviceLoader = Utils.getExtendedClassLoader();
252+
if (adviceShader != null) {
253+
forAdvice = forAdvice.include(new ShadedAdviceLocator(adviceLoader, adviceShader));
254+
} else {
255+
forAdvice = forAdvice.include(adviceLoader);
256+
}
257+
advice.add(forAdvice.advice(not(ignoredMethods).and(matcher), adviceClass));
246258
}
247259

248260
public ClassFileTransformer installOn(Instrumentation instrumentation) {
@@ -342,8 +354,11 @@ public DynamicType.Builder<?> transform(
342354

343355
static final class HelperTransformer extends HelperInjector implements AgentBuilder.Transformer {
344356
HelperTransformer(
345-
boolean useAgentCodeSource, String requestingName, String... helperClassNames) {
346-
super(useAgentCodeSource, requestingName, helperClassNames);
357+
boolean useAgentCodeSource,
358+
AdviceShader adviceShader,
359+
String requestingName,
360+
String... helperClassNames) {
361+
super(useAgentCodeSource, adviceShader, requestingName, helperClassNames);
347362
}
348363
}
349364

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package datadog.trace.agent.tooling;
2+
3+
import java.io.IOException;
4+
import net.bytebuddy.dynamic.ClassFileLocator;
5+
6+
/** Locates and shades class-file resources from the advice class-loader. */
7+
public final class ShadedAdviceLocator implements ClassFileLocator {
8+
private final ClassFileLocator adviceLocator;
9+
private final AdviceShader adviceShader;
10+
11+
public ShadedAdviceLocator(ClassLoader adviceLoader, AdviceShader adviceShader) {
12+
this.adviceLocator = ClassFileLocator.ForClassLoader.of(adviceLoader);
13+
this.adviceShader = adviceShader;
14+
}
15+
16+
@Override
17+
public Resolution locate(String className) throws IOException {
18+
final Resolution resolution = adviceLocator.locate(className);
19+
if (resolution.isResolved()) {
20+
return new Resolution.Explicit(adviceShader.shade(resolution.resolve()));
21+
} else {
22+
return resolution;
23+
}
24+
}
25+
26+
@Override
27+
public void close() throws IOException {
28+
adviceLocator.close();
29+
}
30+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package datadog.trace.agent.tooling;
2+
3+
import datadog.trace.api.cache.DDCache;
4+
import datadog.trace.api.cache.DDCaches;
5+
import java.util.Map;
6+
import net.bytebuddy.jar.asm.ClassReader;
7+
import net.bytebuddy.jar.asm.ClassVisitor;
8+
import net.bytebuddy.jar.asm.ClassWriter;
9+
import net.bytebuddy.jar.asm.commons.ClassRemapper;
10+
import net.bytebuddy.jar.asm.commons.Remapper;
11+
12+
/** Shades advice bytecode by applying a shading function to all references. */
13+
public final class AdviceShader extends Remapper {
14+
private final DDCache<String, String> cache = DDCaches.newFixedSizeCache(64);
15+
private final Map<String, String> shading;
16+
17+
public static AdviceShader with(Map<String, String> shading) {
18+
return shading != null ? new AdviceShader(shading) : null;
19+
}
20+
21+
AdviceShader(Map<String, String> shading) {
22+
this.shading = shading;
23+
}
24+
25+
/** Applies shading before calling the given {@link ClassVisitor}. */
26+
public ClassVisitor shade(ClassVisitor cv) {
27+
return new ClassRemapper(cv, this);
28+
}
29+
30+
/** Returns the result of shading the given bytecode. */
31+
public byte[] shade(byte[] bytecode) {
32+
ClassReader cr = new ClassReader(bytecode);
33+
ClassWriter cw = new ClassWriter(null, 0);
34+
cr.accept(shade(cw), 0);
35+
return cw.toByteArray();
36+
}
37+
38+
@Override
39+
public String map(String internalName) {
40+
if (internalName.startsWith("java/")
41+
|| internalName.startsWith("datadog/")
42+
|| internalName.startsWith("net/bytebuddy/")) {
43+
return internalName; // never shade these references
44+
}
45+
return cache.computeIfAbsent(internalName, this::shade);
46+
}
47+
48+
private String shade(String internalName) {
49+
for (Map.Entry<String, String> e : shading.entrySet()) {
50+
if (internalName.startsWith(e.getKey())) {
51+
return e.getValue() + internalName.substring(e.getKey().length());
52+
}
53+
}
54+
return internalName;
55+
}
56+
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class HelperInjector implements Instrumenter.TransformingAdvice {
3333
ClassFileLocator.ForClassLoader.of(Utils.getExtendedClassLoader());
3434

3535
private final boolean useAgentCodeSource;
36+
private final AdviceShader adviceShader;
3637
private final String requestingName;
3738

3839
private final Set<String> helperClassNames;
@@ -58,8 +59,17 @@ public HelperInjector(
5859
final boolean useAgentCodeSource,
5960
final String requestingName,
6061
final String... helperClassNames) {
62+
this(useAgentCodeSource, null, requestingName, helperClassNames);
63+
}
64+
65+
public HelperInjector(
66+
final boolean useAgentCodeSource,
67+
final AdviceShader adviceShader,
68+
final String requestingName,
69+
final String... helperClassNames) {
6170
this.useAgentCodeSource = useAgentCodeSource;
6271
this.requestingName = requestingName;
72+
this.adviceShader = adviceShader;
6373

6474
this.helperClassNames = new LinkedHashSet<>(Arrays.asList(helperClassNames));
6575
}
@@ -70,6 +80,7 @@ public HelperInjector(
7080
final Map<String, byte[]> helperMap) {
7181
this.useAgentCodeSource = useAgentCodeSource;
7282
this.requestingName = requestingName;
83+
this.adviceShader = null;
7384

7485
helperClassNames = helperMap.keySet();
7586
dynamicTypeMap.putAll(helperMap);
@@ -78,9 +89,11 @@ public HelperInjector(
7889
private Map<String, byte[]> getHelperMap() throws IOException {
7990
if (dynamicTypeMap.isEmpty()) {
8091
final Map<String, byte[]> classnameToBytes = new LinkedHashMap<>();
81-
8292
for (final String helperClassName : helperClassNames) {
83-
final byte[] classBytes = classFileLocator.locate(helperClassName).resolve();
93+
byte[] classBytes = classFileLocator.locate(helperClassName).resolve();
94+
if (adviceShader != null) {
95+
classBytes = adviceShader.shade(classBytes);
96+
}
8497
classnameToBytes.put(helperClassName, classBytes);
8598
}
8699

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ public ElementMatcher<? super MethodDescription> methodIgnoreMatcher() {
156156
return isSynthetic();
157157
}
158158

159+
/** Override this to apply shading to method advice and injected helpers. */
160+
public Map<String, String> adviceShading() {
161+
return null;
162+
}
163+
159164
/** Override this to post-process the operand stack of any transformed methods. */
160165
public Advice.PostProcessor.Factory postProcessor() {
161166
return null;

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package datadog.trace.agent.tooling.muzzle;
22

3+
import datadog.trace.agent.tooling.AdviceShader;
34
import datadog.trace.agent.tooling.Instrumenter;
45
import datadog.trace.agent.tooling.InstrumenterModule;
56
import java.io.File;
@@ -78,7 +79,8 @@ public ClassVisitor wrap(
7879
return classVisitor;
7980
}
8081

81-
private static Reference[] generateReferences(Instrumenter.HasMethodAdvice instrumenter) {
82+
private static Reference[] generateReferences(
83+
Instrumenter.HasMethodAdvice instrumenter, AdviceShader adviceShader) {
8284
// track sources we've generated references from to avoid recursion
8385
final Set<String> referenceSources = new HashSet<>();
8486
final Map<String, Reference> references = new LinkedHashMap<>();
@@ -88,7 +90,8 @@ private static Reference[] generateReferences(Instrumenter.HasMethodAdvice instr
8890
for (String adviceClass : adviceClasses) {
8991
if (referenceSources.add(adviceClass)) {
9092
for (Map.Entry<String, Reference> entry :
91-
ReferenceCreator.createReferencesFrom(adviceClass, contextClassLoader).entrySet()) {
93+
ReferenceCreator.createReferencesFrom(adviceClass, adviceShader, contextClassLoader)
94+
.entrySet()) {
9295
Reference toMerge = references.get(entry.getKey());
9396
if (null == toMerge) {
9497
references.put(entry.getKey(), entry.getValue());
@@ -105,12 +108,13 @@ private static Reference[] generateReferences(Instrumenter.HasMethodAdvice instr
105108
private static byte[] generateMuzzleClass(InstrumenterModule module) {
106109

107110
Set<String> ignoredClassNames = new HashSet<>(Arrays.asList(module.muzzleIgnoredClassNames()));
111+
AdviceShader adviceShader = AdviceShader.with(module.adviceShading());
108112

109113
List<Reference> references = new ArrayList<>();
110114
for (Instrumenter instrumenter : module.typeInstrumentations()) {
111115
if (instrumenter instanceof Instrumenter.HasMethodAdvice) {
112116
for (Reference reference :
113-
generateReferences((Instrumenter.HasMethodAdvice) instrumenter)) {
117+
generateReferences((Instrumenter.HasMethodAdvice) instrumenter, adviceShader)) {
114118
// ignore helper classes, they will be injected by the instrumentation's HelperInjector.
115119
if (!ignoredClassNames.contains(reference.className)) {
116120
references.add(reference);

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package datadog.trace.agent.tooling.muzzle;
22

3+
import datadog.trace.agent.tooling.AdviceShader;
34
import datadog.trace.agent.tooling.HelperInjector;
45
import datadog.trace.agent.tooling.InstrumenterModule;
56
import datadog.trace.agent.tooling.bytebuddy.SharedTypePools;
@@ -109,6 +110,7 @@ private static Map<String, byte[]> createHelperMap(final InstrumenterModule modu
109110
String[] helperClasses = module.helperClassNames();
110111
final Map<String, byte[]> helperMap = new LinkedHashMap<>(helperClasses.length);
111112
Set<String> helperClassNames = new HashSet<>(Arrays.asList(helperClasses));
113+
AdviceShader adviceShader = AdviceShader.with(module.adviceShading());
112114
for (final String helperName : helperClasses) {
113115
int nestedClassIndex = helperName.lastIndexOf('$');
114116
if (nestedClassIndex > 0) {
@@ -128,7 +130,10 @@ private static Map<String, byte[]> createHelperMap(final InstrumenterModule modu
128130
}
129131
final ClassFileLocator locator =
130132
ClassFileLocator.ForClassLoader.of(module.getClass().getClassLoader());
131-
final byte[] classBytes = locator.locate(helperName).resolve();
133+
byte[] classBytes = locator.locate(helperName).resolve();
134+
if (null != adviceShader) {
135+
classBytes = adviceShader.shade(classBytes);
136+
}
132137
helperMap.put(helperName, classBytes);
133138
}
134139
return helperMap;

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static datadog.trace.util.Strings.getClassName;
44
import static datadog.trace.util.Strings.getResourceName;
55

6+
import datadog.trace.agent.tooling.AdviceShader;
67
import datadog.trace.bootstrap.Constants;
78
import java.io.InputStream;
89
import java.util.ArrayDeque;
@@ -41,12 +42,14 @@ public class ReferenceCreator extends ClassVisitor {
4142
* Generate all references reachable from a given class.
4243
*
4344
* @param entryPointClassName Starting point for generating references.
45+
* @param adviceShader Optional shading to apply to the advice.
4446
* @param loader Classloader used to read class bytes.
4547
* @return Map of [referenceClassName -> Reference]
4648
* @throws IllegalStateException if class is not found or unable to be loaded.
4749
*/
4850
public static Map<String, Reference> createReferencesFrom(
49-
final String entryPointClassName, final ClassLoader loader) throws IllegalStateException {
51+
final String entryPointClassName, final AdviceShader adviceShader, final ClassLoader loader)
52+
throws IllegalStateException {
5053
final Set<String> visitedSources = new HashSet<>();
5154
final Map<String, Reference> references = new LinkedHashMap<>();
5255

@@ -64,7 +67,11 @@ public static Map<String, Reference> createReferencesFrom(
6467
}
6568
final ReferenceCreator cv = new ReferenceCreator(null);
6669
final ClassReader reader = new ClassReader(in);
67-
reader.accept(cv, ClassReader.SKIP_FRAMES);
70+
if (null == adviceShader) {
71+
reader.accept(cv, ClassReader.SKIP_FRAMES);
72+
} else {
73+
reader.accept(adviceShader.shade(cv), ClassReader.SKIP_FRAMES);
74+
}
6875

6976
final Map<String, Reference> instrumentationReferences = cv.getReferences();
7077
for (final Map.Entry<String, Reference> entry : instrumentationReferences.entrySet()) {
@@ -88,6 +95,11 @@ public static Map<String, Reference> createReferencesFrom(
8895
return references;
8996
}
9097

98+
public static Map<String, Reference> createReferencesFrom(
99+
final String entryPointClassName, final ClassLoader loader) {
100+
return createReferencesFrom(entryPointClassName, null, loader);
101+
}
102+
91103
private static boolean samePackage(String from, String to) {
92104
int fromLength = from.lastIndexOf('/');
93105
int toLength = to.lastIndexOf('/');

0 commit comments

Comments
 (0)