Skip to content

Commit 2925df8

Browse files
author
Andrew Kent
committed
Clean up and document
1 parent 8e182ed commit 2925df8

9 files changed

Lines changed: 217 additions & 165 deletions

File tree

buildSrc/src/main/groovy/MuzzlePlugin.groovy

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import org.gradle.api.Project
33

44
import java.lang.reflect.Method
55

6+
/**
7+
* POC muzzle task plugin which runs muzzle validation against an instrumentation's compile-time dependencies.
8+
*
9+
* <p/>TODO: merge this with version scan
10+
*/
611
class MuzzlePlugin implements Plugin<Project> {
712
@Override
813
void apply(Project project) {
@@ -38,8 +43,8 @@ class MuzzlePlugin implements Plugin<Project> {
3843

3944
final ClassLoader agentCL = new URLClassLoader(ddUrls.toArray(new URL[0]), (ClassLoader) null)
4045
// find all instrumenters, get muzzle, and assert
41-
Method assertionMethod = agentCL.loadClass('datadog.trace.agent.tooling.muzzle.MuzzleGradlePlugin')
42-
.getMethod('assertAllInstrumentationIsMuzzled', ClassLoader.class)
46+
Method assertionMethod = agentCL.loadClass('datadog.trace.agent.tooling.muzzle.MuzzleVersionScanPlugin')
47+
.getMethod('assertInstrumentationNotMuzzled', ClassLoader.class)
4348
assertionMethod.invoke(null, userCL)
4449
}
4550
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,8 @@ public boolean matches(
123123
/**
124124
* This method is implemented dynamically by compile-time bytecode transformations.
125125
*
126-
* <p>TODO bytecode magic and documentation
126+
* <p>{@see datadog.trace.agent.tooling.muzzle.MuzzleGradlePlugin}
127127
*/
128-
// TODO: Make final
129128
protected ReferenceMatcher getInstrumentationMuzzle() {
130129
return null;
131130
}

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

Lines changed: 61 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,30 @@
11
package datadog.trace.agent.tooling.muzzle;
22

3-
import datadog.trace.agent.tooling.HelperInjector;
43
import datadog.trace.agent.tooling.Instrumenter;
5-
import java.lang.reflect.Method;
6-
import java.util.List;
7-
import java.util.ServiceLoader;
4+
import net.bytebuddy.asm.AsmVisitorWrapper;
85
import net.bytebuddy.build.Plugin;
6+
import net.bytebuddy.description.field.FieldDescription;
7+
import net.bytebuddy.description.field.FieldList;
8+
import net.bytebuddy.description.method.MethodList;
99
import net.bytebuddy.description.type.TypeDefinition;
1010
import net.bytebuddy.description.type.TypeDescription;
1111
import net.bytebuddy.dynamic.DynamicType.Builder;
12+
import net.bytebuddy.implementation.Implementation;
13+
import net.bytebuddy.jar.asm.ClassVisitor;
14+
import net.bytebuddy.jar.asm.MethodVisitor;
15+
import net.bytebuddy.jar.asm.Opcodes;
16+
import net.bytebuddy.pool.TypePool;
1217

18+
/** Bytebuddy gradle plugin which creates muzzle-references at compile time. */
1319
public class MuzzleGradlePlugin implements Plugin {
14-
// TODO:
15-
// - Additional references to check
16-
// - Fields
17-
// - methods
18-
// - visit annotations
19-
// - visit parameter types
20-
// - visit method instructions
21-
// - method invoke type
22-
// - access flags (including implicit package-private)
23-
// - supertypes
24-
// - Misc
25-
// - Also match interfaces which extend Instrumenter
26-
// - Expose config instead of hardcoding datadog namespace (or reconfigure classpath)
27-
// - Run muzzle in matching phase (may require a rewrite of the instrumentation api)
28-
// - Documentation
29-
// - Fix TraceConfigInstrumentation
30-
// - assert no muzzle field defined in instrumentation
31-
// - make getMuzzle final in default and remove in gradle plugin
32-
// - pull muzzle field + method names into static constants
33-
3420
private static final TypeDescription DefaultInstrumenterTypeDesc =
3521
new TypeDescription.ForLoadedType(Instrumenter.Default.class);
3622

3723
@Override
3824
public boolean matches(final TypeDescription target) {
3925
// AutoService annotation is not retained at runtime. Check for Instrumenter.Default supertype
4026
boolean isInstrumenter = false;
41-
TypeDefinition instrumenter = target;
27+
TypeDefinition instrumenter = null == target ? null : target.getSuperClass();
4228
while (instrumenter != null) {
4329
if (instrumenter.equals(DefaultInstrumenterTypeDesc)) {
4430
isInstrumenter = true;
@@ -51,15 +37,10 @@ public boolean matches(final TypeDescription target) {
5137

5238
@Override
5339
public Builder<?> apply(Builder<?> builder, TypeDescription typeDescription) {
54-
if (typeDescription.equals(DefaultInstrumenterTypeDesc)) {
55-
// FIXME
56-
System.out.println("~~~~FIXME: remove final modifier on Default: " + typeDescription);
57-
return builder;
58-
} else {
59-
return builder.visit(new MuzzleVisitor());
60-
}
40+
return builder.visit(new MuzzleVisitor());
6141
}
6242

43+
/** Compile-time Optimization used by gradle buildscripts. */
6344
public static class NoOp implements Plugin {
6445
@Override
6546
public boolean matches(final TypeDescription target) {
@@ -72,53 +53,56 @@ public Builder<?> apply(Builder<?> builder, TypeDescription typeDescription) {
7253
}
7354
}
7455

75-
public static void assertAllInstrumentationIsMuzzled(ClassLoader cl) throws Exception {
76-
for (Instrumenter instrumenter :
77-
ServiceLoader.load(Instrumenter.class, MuzzleGradlePlugin.class.getClassLoader())) {
78-
if (instrumenter.getClass().getName().endsWith("TraceConfigInstrumentation")) {
79-
// TraceConfigInstrumentation doesn't do muzzle checks
80-
// check on TracerClassInstrumentation instead
81-
instrumenter =
82-
(Instrumenter)
83-
MuzzleGradlePlugin.class
84-
.getClassLoader()
85-
.loadClass(instrumenter.getClass().getName() + "$TracerClassInstrumentation")
86-
.getDeclaredConstructor()
87-
.newInstance();
88-
}
89-
Method m = null;
90-
try {
91-
m = instrumenter.getClass().getDeclaredMethod("getInstrumentationMuzzle");
92-
m.setAccessible(true);
93-
ReferenceMatcher muzzle = (ReferenceMatcher) m.invoke(instrumenter);
94-
List<Reference.Mismatch> mismatches = muzzle.getMismatchedReferenceSources(cl);
95-
if (mismatches.size() > 0) {
96-
System.err.println(
97-
"FAILED MUZZLE VALIDATION: " + instrumenter.getClass().getName() + " mismatches:");
98-
for (Reference.Mismatch mismatch : mismatches) {
99-
System.err.println("-- " + mismatch);
100-
}
101-
throw new RuntimeException("Instrumentation failed Muzzle validation");
102-
}
103-
} finally {
104-
if (null != m) {
105-
m.setAccessible(false);
106-
}
56+
private static class RemoveFinalFlagVisitor implements AsmVisitorWrapper {
57+
final String methodName;
58+
59+
public RemoveFinalFlagVisitor(String methodName) {
60+
this.methodName = methodName;
61+
}
62+
63+
@Override
64+
public int mergeWriter(int flags) {
65+
return flags;
66+
}
67+
68+
@Override
69+
public int mergeReader(int flags) {
70+
return flags;
71+
}
72+
73+
@Override
74+
public ClassVisitor wrap(
75+
TypeDescription instrumentedType,
76+
ClassVisitor classVisitor,
77+
Implementation.Context implementationContext,
78+
TypePool typePool,
79+
FieldList<FieldDescription.InDefinedShape> fields,
80+
MethodList<?> methods,
81+
int writerFlags,
82+
int readerFlags) {
83+
return new Visitor(classVisitor);
84+
}
85+
86+
private class Visitor extends ClassVisitor {
87+
public Visitor(ClassVisitor cv) {
88+
super(Opcodes.ASM6, cv);
10789
}
108-
try {
109-
// Ratpack injects the scope manager as a helper.
110-
// This is likely a bug, but we'll grandfather it out of the helper checks for now.
111-
if (!instrumenter.getClass().getName().contains("Ratpack")) {
112-
// verify helper injector works
113-
final String[] helperClassNames = instrumenter.helperClassNames();
114-
if (helperClassNames.length > 0) {
115-
new HelperInjector(helperClassNames).transform(null, null, cl, null);
116-
}
90+
91+
@Override
92+
public MethodVisitor visitMethod(
93+
final int access,
94+
final String name,
95+
final String descriptor,
96+
final String signature,
97+
final String[] exceptions) {
98+
MethodVisitor methodVisitor =
99+
super.visitMethod(access, name, descriptor, signature, exceptions);
100+
if (name.equals(methodName) && (access & Opcodes.ACC_FINAL) != 0) {
101+
return super.visitMethod(
102+
access ^ Opcodes.ACC_FINAL, name, descriptor, signature, exceptions);
103+
} else {
104+
return super.visitMethod(access, name, descriptor, signature, exceptions);
117105
}
118-
} catch (Exception e) {
119-
System.err.println(
120-
"FAILED HELPER INJECTION. Are Helpers being injected in the correct order?");
121-
throw e;
122106
}
123107
}
124108
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package datadog.trace.agent.tooling.muzzle;
2+
3+
import datadog.trace.agent.tooling.HelperInjector;
4+
import datadog.trace.agent.tooling.Instrumenter;
5+
import java.lang.reflect.Method;
6+
import java.util.List;
7+
import java.util.ServiceLoader;
8+
9+
/**
10+
* Entry point for muzzle version scan gradle plugin.
11+
*
12+
* <p>For each instrumenter on the classpath, run muzzle validation and throw an exception if any
13+
* mismatches are detected.
14+
*
15+
* <p>Additionally, after a successful muzzle validation run each instrumenter's helper injector.
16+
*/
17+
public class MuzzleVersionScanPlugin {
18+
public static void assertInstrumentationNotMuzzled(ClassLoader cl) throws Exception {
19+
// muzzle validate all instrumenters
20+
for (Instrumenter instrumenter :
21+
ServiceLoader.load(Instrumenter.class, MuzzleGradlePlugin.class.getClassLoader())) {
22+
if (instrumenter.getClass().getName().endsWith("TraceConfigInstrumentation")) {
23+
// TraceConfigInstrumentation doesn't do muzzle checks
24+
// check on TracerClassInstrumentation instead
25+
instrumenter =
26+
(Instrumenter)
27+
MuzzleGradlePlugin.class
28+
.getClassLoader()
29+
.loadClass(instrumenter.getClass().getName() + "$TracerClassInstrumentation")
30+
.getDeclaredConstructor()
31+
.newInstance();
32+
}
33+
Method m = null;
34+
try {
35+
m = instrumenter.getClass().getDeclaredMethod("getInstrumentationMuzzle");
36+
m.setAccessible(true);
37+
ReferenceMatcher muzzle = (ReferenceMatcher) m.invoke(instrumenter);
38+
List<Reference.Mismatch> mismatches = muzzle.getMismatchedReferenceSources(cl);
39+
if (mismatches.size() > 0) {
40+
System.err.println(
41+
"FAILED MUZZLE VALIDATION: " + instrumenter.getClass().getName() + " mismatches:");
42+
for (Reference.Mismatch mismatch : mismatches) {
43+
System.err.println("-- " + mismatch);
44+
}
45+
throw new RuntimeException("Instrumentation failed Muzzle validation");
46+
}
47+
} finally {
48+
if (null != m) {
49+
m.setAccessible(false);
50+
}
51+
}
52+
}
53+
// run helper injector on all instrumenters
54+
for (Instrumenter instrumenter :
55+
ServiceLoader.load(Instrumenter.class, MuzzleGradlePlugin.class.getClassLoader())) {
56+
if (instrumenter.getClass().getName().endsWith("TraceConfigInstrumentation")) {
57+
// TraceConfigInstrumentation doesn't do muzzle checks
58+
// check on TracerClassInstrumentation instead
59+
instrumenter =
60+
(Instrumenter)
61+
MuzzleGradlePlugin.class
62+
.getClassLoader()
63+
.loadClass(instrumenter.getClass().getName() + "$TracerClassInstrumentation")
64+
.getDeclaredConstructor()
65+
.newInstance();
66+
}
67+
try {
68+
// Ratpack injects the scope manager as a helper.
69+
// This is likely a bug, but we'll grandfather it out of the helper checks for now.
70+
if (!instrumenter.getClass().getName().contains("Ratpack")) {
71+
// verify helper injector works
72+
final String[] helperClassNames = instrumenter.helperClassNames();
73+
if (helperClassNames.length > 0) {
74+
new HelperInjector(helperClassNames).transform(null, null, cl, null);
75+
}
76+
}
77+
} catch (Exception e) {
78+
System.err.println(
79+
"FAILED HELPER INJECTION. Are Helpers being injected in the correct order?");
80+
throw e;
81+
}
82+
}
83+
}
84+
85+
private MuzzleVersionScanPlugin() {}
86+
}

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,7 @@
1515
import net.bytebuddy.jar.asm.*;
1616
import net.bytebuddy.pool.TypePool;
1717

18-
/**
19-
* TODO: update doc Visit a class and add: 1) a private instrumenationMuzzle field and getter AND 2)
20-
* logic to the end of the instrumentation transformer to assert classpath is safe to apply
21-
* instrumentation to.
22-
*/
18+
/** Visit a class and add: a private instrumenationMuzzle field and getter */
2319
public class MuzzleVisitor implements AsmVisitorWrapper {
2420
@Override
2521
public int mergeWriter(int flags) {
@@ -104,7 +100,7 @@ public Reference[] generateReferences() {
104100
if (!referenceSources.contains(adviceClass)) {
105101
referenceSources.add(adviceClass);
106102
for (Map.Entry<String, Reference> entry :
107-
AdviceReferenceVisitor.createReferencesFrom(
103+
ReferenceCreator.createReferencesFrom(
108104
adviceClass, ReferenceMatcher.class.getClassLoader())
109105
.entrySet()) {
110106
if (references.containsKey(entry.getKey())) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import datadog.trace.agent.tooling.Utils;
66
import java.util.*;
77

8-
/** An immutable reference to a single class file. */
8+
/** An immutable reference to a jvm class. */
99
public class Reference {
1010
private final Set<Source> sources;
1111
private final String className;

0 commit comments

Comments
 (0)