Muzzle method and access matching

This commit is contained in:
Andrew Kent 2018-07-23 17:46:16 -07:00
parent 4887822eba
commit c4daf007e3
11 changed files with 888 additions and 496 deletions

View File

@ -2,6 +2,7 @@ package datadog.trace.agent.tooling;
import datadog.trace.bootstrap.DatadogClassLoader;
import datadog.trace.bootstrap.DatadogClassLoader.BootstrapClassLoaderProxy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;

View File

@ -135,232 +135,248 @@ public class MuzzleVisitor implements AsmVisitorWrapper {
* }
*/
try {
final MethodVisitor mv =
visitMethod(
Opcodes.ACC_PROTECTED + Opcodes.ACC_SYNCHRONIZED,
"getInstrumentationMuzzle",
"()Ldatadog/trace/agent/tooling/muzzle/ReferenceMatcher;",
null,
null);
final MethodVisitor mv =
visitMethod(
Opcodes.ACC_PROTECTED + Opcodes.ACC_SYNCHRONIZED,
"getInstrumentationMuzzle",
"()Ldatadog/trace/agent/tooling/muzzle/ReferenceMatcher;",
null,
null);
mv.visitCode();
final Label start = new Label();
final Label ret = new Label();
final Label finish = new Label();
mv.visitCode();
final Label start = new Label();
final Label ret = new Label();
final Label finish = new Label();
mv.visitLabel(start);
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(
Opcodes.GETFIELD,
instrumentationClassName,
"instrumentationMuzzle",
"Ldatadog/trace/agent/tooling/muzzle/ReferenceMatcher;");
mv.visitJumpInsn(Opcodes.IF_ACMPNE, ret);
mv.visitLabel(start);
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(
Opcodes.GETFIELD,
instrumentationClassName,
"instrumentationMuzzle",
"Ldatadog/trace/agent/tooling/muzzle/ReferenceMatcher;");
mv.visitJumpInsn(Opcodes.IF_ACMPNE, ret);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitTypeInsn(Opcodes.NEW, "datadog/trace/agent/tooling/muzzle/ReferenceMatcher");
mv.visitInsn(Opcodes.DUP);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
instrumentationClassName,
"helperClassNames",
"()[Ljava/lang/String;",
false);
final Reference[] references = generateReferences();
mv.visitLdcInsn(references.length);
mv.visitTypeInsn(Opcodes.ANEWARRAY, "datadog/trace/agent/tooling/muzzle/Reference");
for (int i = 0; i < references.length; ++i) {
mv.visitTypeInsn(Opcodes.NEW, "datadog/trace/agent/tooling/muzzle/ReferenceMatcher");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(i);
mv.visitTypeInsn(Opcodes.NEW, "datadog/trace/agent/tooling/muzzle/Reference$Builder");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(references[i].getClassName());
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"<init>",
"(Ljava/lang/String;)V",
false);
for (Reference.Source source : references[i].getSources()) {
mv.visitLdcInsn(source.getName());
mv.visitLdcInsn(source.getLine());
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"withSource",
"(Ljava/lang/String;I)Ldatadog/trace/agent/tooling/muzzle/Reference$Builder;",
false);
}
for (Reference.Flag flag : references[i].getFlags()) {
mv.visitFieldInsn(
Opcodes.GETSTATIC,
"datadog/trace/agent/tooling/muzzle/Reference$Flag",
flag.name(),
"Ldatadog/trace/agent/tooling/muzzle/Reference$Flag;");
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"withFlag",
"(Ldatadog/trace/agent/tooling/muzzle/Reference$Flag;)Ldatadog/trace/agent/tooling/muzzle/Reference$Builder;",
false);
}
if (null != references[i].getSuperName()) {
mv.visitLdcInsn(references[i].getSuperName());
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"withSuperName",
"(Ljava/lang/String;)Ldatadog/trace/agent/tooling/muzzle/Reference$Builder;",
false);
}
for (String interfaceName : references[i].getInterfaces()) {
mv.visitLdcInsn(interfaceName);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"withInterface",
"(Ljava/lang/String;)Ldatadog/trace/agent/tooling/muzzle/Reference$Builder;",
false);
}
for (Reference.Field field : references[i].getFields()) {
mv.visitLdcInsn(field.getName());
mv.visitLdcInsn(field.getFlags().size());
mv.visitTypeInsn(
Opcodes.ANEWARRAY, "datadog/trace/agent/tooling/muzzle/Reference$Flag");
int j = 0;
for (Reference.Flag flag : field.getFlags()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
mv.visitFieldInsn(
Opcodes.GETSTATIC,
"datadog/trace/agent/tooling/muzzle/Reference$Flag",
flag.name(),
"Ldatadog/trace/agent/tooling/muzzle/Reference$Flag;");
mv.visitInsn(Opcodes.AASTORE);
++j;
}
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"withField",
"(Ljava/lang/String;[Ldatadog/trace/agent/tooling/muzzle/Reference$Flag;)Ldatadog/trace/agent/tooling/muzzle/Reference$Builder;",
false);
}
for (Reference.Method method : references[i].getMethods()) {
mv.visitLdcInsn(method.getSources().size());
mv.visitTypeInsn(
Opcodes.ANEWARRAY, "datadog/trace/agent/tooling/muzzle/Reference$Source");
int j = 0;
for (Reference.Source source : method.getSources()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
mv.visitTypeInsn(Opcodes.NEW, "datadog/trace/agent/tooling/muzzle/Reference$Source");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(source.getName());
mv.visitLdcInsn(source.getLine());mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "datadog/trace/agent/tooling/muzzle/Reference$Source", "<init>", "(Ljava/lang/String;I)V", false);
mv.visitInsn(Opcodes.AASTORE);
++j;
}
mv.visitLdcInsn(method.getFlags().size());
mv.visitTypeInsn(
Opcodes.ANEWARRAY, "datadog/trace/agent/tooling/muzzle/Reference$Flag");
j = 0;
for (Reference.Flag flag : method.getFlags()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
mv.visitFieldInsn(
Opcodes.GETSTATIC,
"datadog/trace/agent/tooling/muzzle/Reference$Flag",
flag.name(),
"Ldatadog/trace/agent/tooling/muzzle/Reference$Flag;");
mv.visitInsn(Opcodes.AASTORE);
++j;
}
mv.visitLdcInsn(method.getName());
{ // return type
mv.visitLdcInsn(method.getReturnType().getDescriptor());
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Type.class),
"getType",
Type.getMethodDescriptor(Type.class.getMethod("getType", String.class)),
false);
}
mv.visitLdcInsn(method.getParameterTypes().size());
mv.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(Type.class));
j = 0;
for (Type parameterType : method.getParameterTypes()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
mv.visitLdcInsn(parameterType.getDescriptor());
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Type.class),
"getType",
Type.getMethodDescriptor(Type.class.getMethod("getType", String.class)),
false);
mv.visitInsn(Opcodes.AASTORE);
j++;
}
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"withMethod",
Type.getMethodDescriptor(Reference.Builder.class.getMethod("withMethod", Reference.Source[].class, Reference.Flag[].class, String.class, Type.class, Type[].class)),
false);
}
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"build",
"()Ldatadog/trace/agent/tooling/muzzle/Reference;",
instrumentationClassName,
"helperClassNames",
"()[Ljava/lang/String;",
false);
mv.visitInsn(Opcodes.AASTORE);
}
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
"datadog/trace/agent/tooling/muzzle/ReferenceMatcher",
"<init>",
"([Ljava/lang/String;[Ldatadog/trace/agent/tooling/muzzle/Reference;)V",
false);
mv.visitFieldInsn(
Opcodes.PUTFIELD,
instrumentationClassName,
"instrumentationMuzzle",
"Ldatadog/trace/agent/tooling/muzzle/ReferenceMatcher;");
final Reference[] references = generateReferences();
mv.visitLdcInsn(references.length);
mv.visitTypeInsn(Opcodes.ANEWARRAY, "datadog/trace/agent/tooling/muzzle/Reference");
mv.visitLabel(ret);
mv.visitFrame(Opcodes.F_SAME, 1, null, 0, null);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(
Opcodes.GETFIELD,
instrumentationClassName,
"instrumentationMuzzle",
"Ldatadog/trace/agent/tooling/muzzle/ReferenceMatcher;");
mv.visitInsn(Opcodes.ARETURN);
mv.visitLabel(finish);
for (int i = 0; i < references.length; ++i) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(i);
mv.visitTypeInsn(Opcodes.NEW, "datadog/trace/agent/tooling/muzzle/Reference$Builder");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(references[i].getClassName());
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"<init>",
"(Ljava/lang/String;)V",
false);
for (Reference.Source source : references[i].getSources()) {
mv.visitLdcInsn(source.getName());
mv.visitLdcInsn(source.getLine());
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"withSource",
"(Ljava/lang/String;I)Ldatadog/trace/agent/tooling/muzzle/Reference$Builder;",
false);
}
for (Reference.Flag flag : references[i].getFlags()) {
mv.visitFieldInsn(
Opcodes.GETSTATIC,
"datadog/trace/agent/tooling/muzzle/Reference$Flag",
flag.name(),
"Ldatadog/trace/agent/tooling/muzzle/Reference$Flag;");
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"withFlag",
"(Ldatadog/trace/agent/tooling/muzzle/Reference$Flag;)Ldatadog/trace/agent/tooling/muzzle/Reference$Builder;",
false);
}
if (null != references[i].getSuperName()) {
mv.visitLdcInsn(references[i].getSuperName());
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"withSuperName",
"(Ljava/lang/String;)Ldatadog/trace/agent/tooling/muzzle/Reference$Builder;",
false);
}
for (String interfaceName : references[i].getInterfaces()) {
mv.visitLdcInsn(interfaceName);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"withInterface",
"(Ljava/lang/String;)Ldatadog/trace/agent/tooling/muzzle/Reference$Builder;",
false);
}
for (Reference.Field field : references[i].getFields()) {
mv.visitLdcInsn(field.getName());
mv.visitLdcInsn(field.getFlags().size());
mv.visitTypeInsn(
Opcodes.ANEWARRAY, "datadog/trace/agent/tooling/muzzle/Reference$Flag");
mv.visitLocalVariable("this", "L" + instrumentationClassName + ";", null, start, finish, 0);
mv.visitMaxs(0, 0); // recomputed
mv.visitEnd();
}
catch(Exception e) {
int j = 0;
for (Reference.Flag flag : field.getFlags()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
mv.visitFieldInsn(
Opcodes.GETSTATIC,
"datadog/trace/agent/tooling/muzzle/Reference$Flag",
flag.name(),
"Ldatadog/trace/agent/tooling/muzzle/Reference$Flag;");
mv.visitInsn(Opcodes.AASTORE);
++j;
}
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"withField",
"(Ljava/lang/String;[Ldatadog/trace/agent/tooling/muzzle/Reference$Flag;)Ldatadog/trace/agent/tooling/muzzle/Reference$Builder;",
false);
}
for (Reference.Method method : references[i].getMethods()) {
mv.visitLdcInsn(method.getSources().size());
mv.visitTypeInsn(
Opcodes.ANEWARRAY, "datadog/trace/agent/tooling/muzzle/Reference$Source");
int j = 0;
for (Reference.Source source : method.getSources()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
mv.visitTypeInsn(
Opcodes.NEW, "datadog/trace/agent/tooling/muzzle/Reference$Source");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(source.getName());
mv.visitLdcInsn(source.getLine());
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
"datadog/trace/agent/tooling/muzzle/Reference$Source",
"<init>",
"(Ljava/lang/String;I)V",
false);
mv.visitInsn(Opcodes.AASTORE);
++j;
}
mv.visitLdcInsn(method.getFlags().size());
mv.visitTypeInsn(
Opcodes.ANEWARRAY, "datadog/trace/agent/tooling/muzzle/Reference$Flag");
j = 0;
for (Reference.Flag flag : method.getFlags()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
mv.visitFieldInsn(
Opcodes.GETSTATIC,
"datadog/trace/agent/tooling/muzzle/Reference$Flag",
flag.name(),
"Ldatadog/trace/agent/tooling/muzzle/Reference$Flag;");
mv.visitInsn(Opcodes.AASTORE);
++j;
}
mv.visitLdcInsn(method.getName());
{ // return type
mv.visitLdcInsn(method.getReturnType().getDescriptor());
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
Type.getInternalName(Type.class),
"getType",
Type.getMethodDescriptor(Type.class.getMethod("getType", String.class)),
false);
}
mv.visitLdcInsn(method.getParameterTypes().size());
mv.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(Type.class));
j = 0;
for (Type parameterType : method.getParameterTypes()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
mv.visitLdcInsn(parameterType.getDescriptor());
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
Type.getInternalName(Type.class),
"getType",
Type.getMethodDescriptor(Type.class.getMethod("getType", String.class)),
false);
mv.visitInsn(Opcodes.AASTORE);
j++;
}
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"withMethod",
Type.getMethodDescriptor(
Reference.Builder.class.getMethod(
"withMethod",
Reference.Source[].class,
Reference.Flag[].class,
String.class,
Type.class,
Type[].class)),
false);
}
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"datadog/trace/agent/tooling/muzzle/Reference$Builder",
"build",
"()Ldatadog/trace/agent/tooling/muzzle/Reference;",
false);
mv.visitInsn(Opcodes.AASTORE);
}
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
"datadog/trace/agent/tooling/muzzle/ReferenceMatcher",
"<init>",
"([Ljava/lang/String;[Ldatadog/trace/agent/tooling/muzzle/Reference;)V",
false);
mv.visitFieldInsn(
Opcodes.PUTFIELD,
instrumentationClassName,
"instrumentationMuzzle",
"Ldatadog/trace/agent/tooling/muzzle/ReferenceMatcher;");
mv.visitLabel(ret);
mv.visitFrame(Opcodes.F_SAME, 1, null, 0, null);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(
Opcodes.GETFIELD,
instrumentationClassName,
"instrumentationMuzzle",
"Ldatadog/trace/agent/tooling/muzzle/ReferenceMatcher;");
mv.visitInsn(Opcodes.ARETURN);
mv.visitLabel(finish);
mv.visitLocalVariable(
"this", "L" + instrumentationClassName + ";", null, start, finish, 0);
mv.visitMaxs(0, 0); // recomputed
mv.visitEnd();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

View File

@ -1,21 +1,15 @@
package datadog.trace.agent.tooling.muzzle;
import static datadog.trace.agent.tooling.ClassLoaderMatcher.BOOTSTRAP_CLASSLOADER;
import static java.util.Collections.singletonList;
import datadog.trace.agent.tooling.Utils;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
import net.bytebuddy.pool.TypePool;
/** An immutable reference to a jvm class. */
public class Reference {
@ -88,7 +82,7 @@ public class Reference {
return new Reference(
merge(sources, anotherReference.sources),
merge(flags, anotherReference.flags),
mergeFlags(flags, anotherReference.flags),
className,
superName,
merge(interfaces, anotherReference.interfaces),
@ -110,39 +104,6 @@ public class Reference {
return merged;
}
/**
* Check this reference against a classloader's classpath.
*
* @param loader
* @return A list of mismatched sources. A list of size 0 means the reference matches the class.
*/
public List<Mismatch> checkMatch(ClassLoader loader) {
if (loader == BOOTSTRAP_CLASSLOADER) {
throw new IllegalStateException("Cannot directly check against bootstrap classloader");
}
if (!onClasspath(className, loader)) {
return Collections.<Mismatch>singletonList(new Mismatch.MissingClass(sources.toArray(new Source[0]), className));
}
final List<Mismatch> mismatches = new ArrayList<>(0);
try {
UnloadedType typeOnClasspath = UnloadedType.of(className, loader);
for (Method requiredMethod : this.getMethods()) {
mismatches.addAll(typeOnClasspath.checkMatch(requiredMethod));
}
} catch (Exception e) {
// Shouldn't happen. Fail the reference check and add a mismatch for debug logging.
mismatches.add(new Mismatch.ReferenceCheckError(e));
}
return mismatches;
}
private boolean onClasspath(final String className, final ClassLoader loader) {
final String resourceName = Utils.getResourceName(className);
return loader.getResource(resourceName) != null
// we can also reach bootstrap classes
|| Utils.getBootstrapProxy().getResource(resourceName) != null;
}
public static class Source {
private final String name;
private final int line;
@ -212,9 +173,26 @@ public class Reference {
}
}
/**
* Fallback mismatch in case an unexpected exception occurs during reference checking.
*/
public static class MissingFlag extends Mismatch {
final Flag expectedFlag;
final String classMethodOrFieldDesc;
final int foundAccess;
public MissingFlag(
Source[] sources, String classMethodOrFieldDesc, Flag expectedFlag, int foundAccess) {
super(sources);
this.classMethodOrFieldDesc = classMethodOrFieldDesc;
this.expectedFlag = expectedFlag;
this.foundAccess = foundAccess;
}
@Override
String getMismatchDetails() {
return classMethodOrFieldDesc + " requires flag " + expectedFlag + " found " + foundAccess;
}
}
/** Fallback mismatch in case an unexpected exception occurs during reference checking. */
public static class ReferenceCheckError extends Mismatch {
private final Exception referenceCheckExcetpion;
@ -252,16 +230,131 @@ public class Reference {
}
/** Expected flag (or lack of flag) on a class, method, or field reference. */
public static enum Flag {
PUBLIC,
PACKAGE_OR_HIGHER,
PROTECTED_OR_HIGHER,
PRIVATE_OR_HIGHER,
NON_FINAL,
STATIC,
NON_STATIC,
INTERFACE,
NON_INTERFACE
public enum Flag {
PUBLIC {
@Override
public boolean supersedes(Flag anotherFlag) {
switch (anotherFlag) {
case PRIVATE_OR_HIGHER:
case PROTECTED_OR_HIGHER:
case PACKAGE_OR_HIGHER:
return true;
default:
return false;
}
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_PUBLIC & asmFlags) != 0;
}
},
PACKAGE_OR_HIGHER {
@Override
public boolean supersedes(Flag anotherFlag) {
return anotherFlag == PRIVATE_OR_HIGHER;
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_PUBLIC & asmFlags) != 0
|| ((Opcodes.ACC_PRIVATE & asmFlags) == 0 && (Opcodes.ACC_PROTECTED & asmFlags) == 0);
}
},
PROTECTED_OR_HIGHER {
@Override
public boolean supersedes(Flag anotherFlag) {
return anotherFlag == PRIVATE_OR_HIGHER;
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_PUBLIC & asmFlags) != 0 || (Opcodes.ACC_PROTECTED & asmFlags) != 0;
}
},
PRIVATE_OR_HIGHER {
@Override
public boolean matches(int asmFlags) {
// you can't out-private a private
return true;
}
},
NON_FINAL {
@Override
public boolean contradicts(Flag anotherFlag) {
return anotherFlag == FINAL;
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_FINAL & asmFlags) == 0;
}
},
FINAL {
@Override
public boolean contradicts(Flag anotherFlag) {
return anotherFlag == NON_FINAL;
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_FINAL & asmFlags) != 0;
}
},
STATIC {
@Override
public boolean contradicts(Flag anotherFlag) {
return anotherFlag == NON_STATIC;
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_STATIC & asmFlags) != 0;
}
},
NON_STATIC {
@Override
public boolean contradicts(Flag anotherFlag) {
return anotherFlag == STATIC;
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_STATIC & asmFlags) == 0;
}
},
INTERFACE {
@Override
public boolean contradicts(Flag anotherFlag) {
return anotherFlag == NON_INTERFACE;
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_INTERFACE & asmFlags) != 0;
}
},
NON_INTERFACE {
@Override
public boolean contradicts(Flag anotherFlag) {
return anotherFlag == INTERFACE;
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_INTERFACE & asmFlags) == 0;
}
};
public boolean contradicts(Flag anotherFlag) {
return false;
}
public boolean supersedes(Flag anotherFlag) {
return false;
}
public abstract boolean matches(int asmFlags);
}
public static class Method {
@ -272,16 +365,22 @@ public class Reference {
private final List<Type> parameterTypes;
public Method(String name, String descriptor) {
this(new Source[0], new Flag[0], name, Type.getMethodType(descriptor).getReturnType(), Type.getMethodType(descriptor).getArgumentTypes());
this(
new Source[0],
new Flag[0],
name,
Type.getMethodType(descriptor).getReturnType(),
Type.getMethodType(descriptor).getArgumentTypes());
}
public Method(
Source[] sources,
Flag[] flags,
String name,
Type returnType,
Type[] parameterTypes) {
this(new HashSet<>(Arrays.asList(sources)), new HashSet<>(Arrays.asList(flags)), name, returnType, Arrays.asList(parameterTypes));
Source[] sources, Flag[] flags, String name, Type returnType, Type[] parameterTypes) {
this(
new HashSet<>(Arrays.asList(sources)),
new HashSet<>(Arrays.asList(flags)),
name,
returnType,
Arrays.asList(parameterTypes));
}
public Method(
@ -297,19 +396,30 @@ public class Reference {
this.parameterTypes = parameterTypes;
}
public Set<Source> getSources() { return sources; }
public Set<Source> getSources() {
return sources;
}
public Set<Flag> getFlags() { return flags; }
public Set<Flag> getFlags() {
return flags;
}
public String getName() { return name; }
public String getName() {
return name;
}
public Type getReturnType() { return returnType; }
public Type getReturnType() {
return returnType;
}
public List<Type> getParameterTypes() { return parameterTypes; }
public List<Type> getParameterTypes() {
return parameterTypes;
}
public Method merge(Method anotherMethod) {
if (!this.equals(anotherMethod)) {
throw new IllegalStateException("Cannot merge incompatible methods " + this + " <> " + anotherMethod);
throw new IllegalStateException(
"Cannot merge incompatible methods " + this + " <> " + anotherMethod);
}
final Set<Source> mergedSources = new HashSet<>();
@ -327,7 +437,8 @@ public class Reference {
public String toString() {
// <init>()V
// toString()Ljava/lang/String;
return name + Type.getMethodType(returnType, parameterTypes.toArray(new Type[0])).getDescriptor();
return name
+ Type.getMethodType(returnType, parameterTypes.toArray(new Type[0])).getDescriptor();
}
@Override
@ -413,7 +524,7 @@ public class Reference {
}
public Builder withFlag(Flag flag) {
// TODO
flags.add(flag);
return this;
}
@ -422,7 +533,12 @@ public class Reference {
return this;
}
public Builder withMethod(Source[] sources, Flag[] methodFlags, String methodName, Type returnType, Type... methodArgs) {
public Builder withMethod(
Source[] sources,
Flag[] methodFlags,
String methodName,
Type returnType,
Type... methodArgs) {
final Method method = new Method(sources, methodFlags, methodName, returnType, methodArgs);
int existingIndex = methods.indexOf(method);
if (existingIndex == -1) {
@ -434,7 +550,14 @@ public class Reference {
}
public Reference build() {
return new Reference(sources, flags, className, superName, interfaces, new HashSet<>(fields), new HashSet<>(methods));
return new Reference(
sources,
flags,
className,
superName,
interfaces,
new HashSet<>(fields),
new HashSet<>(methods));
}
}
}

View File

@ -1,28 +1,33 @@
package datadog.trace.agent.tooling.muzzle;
import datadog.trace.agent.tooling.Utils;
import datadog.trace.agent.tooling.muzzle.Reference.Source;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import net.bytebuddy.jar.asm.ClassReader;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.FieldVisitor;
import net.bytebuddy.jar.asm.Label;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
/** Visit a class and collect all references made by the visited class. */
// additional things we could check
// Additional things we could check
// - annotations on class
// - outer class
// - inner class
// - cast opcodes in method bodies
public class ReferenceCreator extends ClassVisitor {
public static Map<String, Reference> createReferencesFrom(String entryPointClassName, ClassLoader loader) {
public static Map<String, Reference> createReferencesFrom(
String entryPointClassName, ClassLoader loader) {
return ReferenceCreator.createReferencesFrom(entryPointClassName, loader, true);
}
@ -31,9 +36,9 @@ public class ReferenceCreator extends ClassVisitor {
*
* @param entryPointClassName Starting point for generating references.
* @param loader Classloader used to read class bytes.
* @param startFromMethodBodies if true only create refs from method bodies.
* @return Map of [referenceClassName -> Reference]
*/
// TODO: document startFromMethodBodies and explain purpose
private static Map<String, Reference> createReferencesFrom(
String entryPointClassName, ClassLoader loader, boolean startFromMethodBodies) {
final Set<String> visitedSources = new HashSet<>();
@ -81,8 +86,44 @@ public class ReferenceCreator extends ClassVisitor {
return references;
}
private static String internalPackageName(String internalName) {
return internalName.replaceAll("/[^/]+$", "");
}
/**
* Compute the minimum required access for FROM class to access the TO class.
*
* @return A reference flag with the required level of access.
*/
private static Reference.Flag computeMinimumClassAccess(Type from, Type to) {
if (from.getInternalName().equalsIgnoreCase(to.getInternalName())) {
return Reference.Flag.PRIVATE_OR_HIGHER;
} else if (internalPackageName(from.getInternalName())
.equals(internalPackageName(to.getInternalName()))) {
return Reference.Flag.PACKAGE_OR_HIGHER;
} else {
return Reference.Flag.PUBLIC;
}
}
/**
* Compute the minimum required access for FROM class to access METHODTYPE on the TO class.
*
* @return A reference flag with the required level of access.
*/
private static Reference.Flag computeMinimumMethodAccess(Type from, Type to, Type methodType) {
if (from.getInternalName().equalsIgnoreCase(to.getInternalName())) {
return Reference.Flag.PRIVATE_OR_HIGHER;
} else {
// Additional references: check the type hierarchy of FROM to distinguish public from
// protected
return Reference.Flag.PROTECTED_OR_HIGHER;
}
}
private Map<String, Reference> references = new HashMap<>();
private String refSourceClassName;
private Type refSourceType;
private boolean createFromMethodBodiesOnly;
private ReferenceCreator(ClassVisitor classVisitor, boolean createFromMethodBodiesOnly) {
@ -111,6 +152,7 @@ public class ReferenceCreator extends ClassVisitor {
final String superName,
final String[] interfaces) {
refSourceClassName = Utils.getClassName(name);
refSourceType = Type.getType("L" + name + ";");
// Additional references we could check
// - supertype of class and visible from this package
// - interfaces of class and visible from this package
@ -118,7 +160,8 @@ public class ReferenceCreator extends ClassVisitor {
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
public FieldVisitor visitField(
int access, String name, String descriptor, String signature, Object value) {
// Additional references we could check
// - type of field + visible from this package
return super.visitField(access, name, descriptor, signature, value);
@ -163,17 +206,17 @@ public class ReferenceCreator extends ClassVisitor {
// * field-source visibility from this point (PRIVATE?)
// owning class has a field
addReference(new Reference.Builder(owner)
.withSource(refSourceClassName, currentLineNumber)
.build());
addReference(
new Reference.Builder(owner).withSource(refSourceClassName, currentLineNumber).build());
Type fieldType = Type.getType(descriptor);
if (fieldType.getSort() == Type.ARRAY) {
fieldType = fieldType.getElementType();
}
if (fieldType.getSort() == Type.OBJECT) {
addReference(new Reference.Builder(fieldType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.build());
addReference(
new Reference.Builder(fieldType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.build());
}
super.visitFieldInsn(opcode, owner, name, descriptor);
}
@ -188,25 +231,65 @@ public class ReferenceCreator extends ClassVisitor {
// Additional references we could check
// * DONE name of method owner's class
// * DONE is the owner an interface?
// * owner's access from here (PRIVATE?)
// * method on the owner class
// * is the method static? Is it visible from here?
// * DONE owner's access from here (PRIVATE?)
// * DONE method on the owner class
// * DONE is the method static? Is it visible from here?
// * Class names from the method descriptor
// * params classes
// * return type
final Type methodType = Type.getMethodType(descriptor);
Source[] sources = new Reference.Source[]{new Reference.Source(refSourceClassName, currentLineNumber)};
Type ownerType = Type.getType("L"+owner+";");
{ // ref for method return type
Type returnType = methodType.getReturnType();
if (returnType.getSort() == Type.ARRAY) {
returnType = returnType.getElementType();
}
if (returnType.getSort() == Type.OBJECT) {
addReference(
new Reference.Builder(returnType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, returnType))
.build());
}
}
// refs for method param types
for (Type paramType : methodType.getArgumentTypes()) {
if (paramType.getSort() == Type.ARRAY) {
paramType = paramType.getElementType();
}
if (paramType.getSort() == Type.OBJECT) {
addReference(
new Reference.Builder(paramType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, paramType))
.build());
}
}
Type ownerType = Type.getType("L" + owner + ";");
if (ownerType.getSort() == Type.ARRAY) {
ownerType = ownerType.getElementType();
}
addReference(new Reference.Builder(ownerType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(isInterface ? Reference.Flag.INTERFACE : Reference.Flag.NON_INTERFACE)
.withMethod(sources, new Reference.Flag[0], name, methodType.getReturnType(), methodType.getArgumentTypes())
.build());
final List<Reference.Flag> methodFlags = new ArrayList<>();
methodFlags.add(
opcode == Opcodes.INVOKESTATIC ? Reference.Flag.STATIC : Reference.Flag.NON_STATIC);
methodFlags.add(computeMinimumMethodAccess(refSourceType, ownerType, methodType));
addReference(
new Reference.Builder(ownerType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(isInterface ? Reference.Flag.INTERFACE : Reference.Flag.NON_INTERFACE)
.withFlag(computeMinimumClassAccess(refSourceType, ownerType))
.withMethod(
new Reference.Source[] {
new Reference.Source(refSourceClassName, currentLineNumber)
},
methodFlags.toArray(new Reference.Flag[0]),
name,
methodType.getReturnType(),
methodType.getArgumentTypes())
.build());
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}

View File

@ -1,16 +1,27 @@
package datadog.trace.agent.tooling.muzzle;
import static datadog.trace.agent.tooling.ClassLoaderMatcher.BOOTSTRAP_CLASSLOADER;
import static datadog.trace.bootstrap.WeakMap.Provider.newWeakMap;
import static net.bytebuddy.dynamic.loading.ClassLoadingStrategy.BOOTSTRAP_LOADER;
import datadog.trace.agent.tooling.Utils;
import datadog.trace.agent.tooling.muzzle.Reference.Mismatch;
import datadog.trace.agent.tooling.muzzle.Reference.Source;
import datadog.trace.bootstrap.WeakMap;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.ClassReader;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
/** Matches a set of references against a classloader. */
@Slf4j
@ -54,7 +65,7 @@ public class ReferenceMatcher {
// Don't reference-check helper classes.
// They will be injected by the instrumentation's HelperInjector.
if (!helperClassNames.contains(reference.getClassName())) {
mismatches.addAll(reference.checkMatch(loader));
mismatches.addAll(checkMatch(reference, loader));
}
}
mismatchCache.put(loader, mismatches);
@ -63,4 +74,245 @@ public class ReferenceMatcher {
}
return mismatches;
}
/**
* Check a reference against a classloader's classpath.
*
* @param loader
* @return A list of mismatched sources. A list of size 0 means the reference matches the class.
*/
private static List<Reference.Mismatch> checkMatch(Reference reference, ClassLoader loader) {
if (loader == BOOTSTRAP_CLASSLOADER) {
throw new IllegalStateException("Cannot directly check against bootstrap classloader");
}
if (!onClasspath(reference.getClassName(), loader)) {
return Collections.<Mismatch>singletonList(
new Mismatch.MissingClass(
reference.getSources().toArray(new Source[0]), reference.getClassName()));
}
final List<Mismatch> mismatches = new ArrayList<>(0);
try {
ReferenceMatcher.UnloadedType typeOnClasspath =
ReferenceMatcher.UnloadedType.of(reference.getClassName(), loader);
mismatches.addAll(typeOnClasspath.checkMatch(reference));
for (Reference.Method requiredMethod : reference.getMethods()) {
mismatches.addAll(typeOnClasspath.checkMatch(requiredMethod));
}
} catch (Exception e) {
// Shouldn't happen. Fail the reference check and add a mismatch for debug logging.
mismatches.add(new Mismatch.ReferenceCheckError(e));
}
return mismatches;
}
private static boolean onClasspath(final String className, final ClassLoader loader) {
final String resourceName = Utils.getResourceName(className);
return loader.getResource(resourceName) != null
// we can also reach bootstrap classes
|| Utils.getBootstrapProxy().getResource(resourceName) != null;
}
/**
* A representation of a jvm class created from a byte array without loading the class in
* question.
*
* <p>Used to compare an expected Reference with the actual runtime class without causing
* classloads.
*/
public static class UnloadedType extends ClassVisitor {
private static final Map<ClassLoader, Map<String, UnloadedType>> typeCache =
Collections.synchronizedMap(new WeakHashMap<ClassLoader, Map<String, UnloadedType>>());
private String superName = null;
private String className = null;
private String[] interfaceNames = new String[0];
private UnloadedType unloadedSuper = null;
private final List<UnloadedType> unloadedInterfaces = new ArrayList<>();
private int flags;
private final List<Method> methods = new ArrayList<>();
public static UnloadedType of(String className, ClassLoader classLoader) throws Exception {
className = Utils.getInternalName(className);
Map<String, UnloadedType> classLoaderCache = typeCache.get(classLoader);
if (classLoaderCache == null) {
synchronized (classLoader) {
classLoaderCache = typeCache.get(classLoader);
if (classLoaderCache == null) {
classLoaderCache = new ConcurrentHashMap<>();
typeCache.put(classLoader, classLoaderCache);
}
}
}
UnloadedType unloadedType = classLoaderCache.get(className);
if (unloadedType == null) {
final InputStream in = classLoader.getResourceAsStream(Utils.getResourceName(className));
unloadedType = new UnloadedType(null);
final ClassReader reader = new ClassReader(in);
reader.accept(unloadedType, ClassReader.SKIP_CODE);
if (unloadedType.superName != null) {
unloadedType.unloadedSuper = UnloadedType.of(unloadedType.superName, classLoader);
}
for (String interfaceName : unloadedType.interfaceNames) {
unloadedType.unloadedInterfaces.add(UnloadedType.of(interfaceName, classLoader));
}
classLoaderCache.put(className, unloadedType);
}
return unloadedType;
}
private UnloadedType(ClassVisitor cv) {
super(Opcodes.ASM6, cv);
}
public String getClassName() {
return className;
}
public String getSuperName() {
return superName;
}
public int getFlags() {
return flags;
}
public List<Reference.Mismatch> checkMatch(Reference reference) {
final List<Reference.Mismatch> mismatches = new ArrayList<>(0);
for (Reference.Flag flag : reference.getFlags()) {
if (!flag.matches(getFlags())) {
final String desc = this.getClassName();
mismatches.add(
new Mismatch.MissingFlag(
reference.getSources().toArray(new Source[0]), desc, flag, getFlags()));
}
}
return mismatches;
}
public List<Reference.Mismatch> checkMatch(Reference.Method method) {
final List<Reference.Mismatch> mismatches = new ArrayList<>(0);
// does the method exist?
Method unloadedMethod = findMethod(method, true);
if (unloadedMethod == null) {
mismatches.add(
new Reference.Mismatch.MissingMethod(
method.getSources().toArray(new Reference.Source[0]),
className,
method.toString()));
} else {
for (Reference.Flag flag : method.getFlags()) {
if (!flag.matches(unloadedMethod.getFlags())) {
final String desc = this.getClassName() + "#" + unloadedMethod.signature;
mismatches.add(
new Mismatch.MissingFlag(
method.getSources().toArray(new Source[0]),
desc,
flag,
unloadedMethod.getFlags()));
}
}
}
return mismatches;
}
private Method findMethod(Reference.Method method, boolean includePrivateMethods) {
Method unloadedMethod =
new Method(
0,
method.getName(),
Type.getMethodType(
method.getReturnType(), method.getParameterTypes().toArray(new Type[0]))
.getDescriptor());
for (Method meth : methods) {
if (meth.equals(unloadedMethod)) {
if (meth.is(Opcodes.ACC_PRIVATE)) {
return includePrivateMethods ? meth : null;
} else {
return meth;
}
}
}
if (null != unloadedSuper) {
final Method meth = unloadedSuper.findMethod(method, false);
if (null != meth) return meth;
}
for (UnloadedType unloadedInterface : unloadedInterfaces) {
final Method meth = unloadedInterface.findMethod(method, false);
if (null != meth) return meth;
}
return null;
}
public boolean hasField(Reference.Field field) {
// TODO does the field exist?
// TODO are the expected field flags present (static, public, etc)
throw new RuntimeException("TODO");
}
@Override
public void visit(
final int version,
final int access,
final String name,
final String signature,
final String superName,
final String[] interfaces) {
className = Utils.getClassName(name);
if (null != superName) this.superName = Utils.getClassName(superName);
if (null != interfaces) this.interfaceNames = interfaces;
this.flags = access;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(
final int access,
final String name,
final String descriptor,
final String signature,
final String[] exceptions) {
// Additional references we could check
// - Classes in signature (return type, params) and visible from this package
methods.add(new Method(access, name, descriptor));
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
private static class Method {
private final int flags;
// name + descriptor
private final String signature;
public Method(int flags, String name, String desc) {
this.flags = flags;
this.signature = name + desc;
}
public boolean is(int flag) {
boolean result = (flags & flag) != 0;
return result;
}
public int getFlags() {
return flags;
}
@Override
public String toString() {
return new StringBuilder("Unloaded: ").append(signature).toString();
}
@Override
public boolean equals(Object o) {
if (o instanceof Method) {
return signature.toString().equals(((Method) o).signature);
}
return false;
}
@Override
public int hashCode() {
return signature.hashCode();
}
}
}
}

View File

@ -1,132 +0,0 @@
package datadog.trace.agent.tooling.muzzle;
import datadog.trace.agent.tooling.Utils;
import net.bytebuddy.jar.asm.ClassReader;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import datadog.trace.agent.tooling.muzzle.Reference.Method;
import datadog.trace.agent.tooling.muzzle.Reference.Mismatch;
import datadog.trace.agent.tooling.muzzle.Reference.Source;
import datadog.trace.agent.tooling.muzzle.Reference.Field;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
public class UnloadedType extends ClassVisitor {
private static final Map<ClassLoader, Map<String, UnloadedType>> typeCache = Collections.synchronizedMap(new WeakHashMap<ClassLoader, Map<String, UnloadedType>>());
private volatile String superName = null;
private volatile String className = null;
private volatile String[] interfaceNames = new String[0];
private volatile UnloadedType unloadedSuper = null;
private final List<UnloadedType> unloadedInterfaces = new ArrayList<>();
private final List<Method> methods = new ArrayList<>();
public static UnloadedType of(String className, ClassLoader classLoader) throws Exception {
className = Utils.getInternalName(className);
// TODO: triple-check cache logic and weak-refs
Map<String, UnloadedType> classLoaderCache = typeCache.get(classLoader);
if (classLoaderCache == null) {
synchronized (classLoader) {
classLoaderCache = typeCache.get(classLoader);
if (classLoaderCache == null) {
classLoaderCache = new ConcurrentHashMap<>();
typeCache.put(classLoader, classLoaderCache);
}
}
}
UnloadedType unloadedType = classLoaderCache.get(className);
if (unloadedType == null) {
final InputStream in = classLoader.getResourceAsStream(Utils.getResourceName(className));
unloadedType = new UnloadedType(null);
final ClassReader reader = new ClassReader(in);
reader.accept(unloadedType, ClassReader.SKIP_CODE);
if (unloadedType.superName != null) {
unloadedType.unloadedSuper = UnloadedType.of(unloadedType.superName, classLoader);
}
for (String interfaceName : unloadedType.interfaceNames) {
unloadedType.unloadedInterfaces.add(UnloadedType.of(interfaceName, classLoader));
}
classLoaderCache.put(className, unloadedType);
}
return unloadedType;
}
private UnloadedType(ClassVisitor cv) {
super(Opcodes.ASM6, cv);
}
public String getClassName() {
return className;
}
public String getSuperName() {
return superName;
}
public List<Mismatch> checkMatch(Method method) {
final List<Mismatch> mismatches = new ArrayList<>(0);
// does the method exist?
if (!hasMethod(method)) {
mismatches.add(new Mismatch.MissingMethod(method.getSources().toArray(new Source[0]), className, method.toString()));
} else {
// TODO: are the expected method flags present (static, public, etc)
}
return mismatches;
}
private boolean hasMethod(Method method) {
if (methods.contains(method)) {
return true;
}
// FIXME: private methods on the super type are not reachable!
if (null != unloadedSuper && unloadedSuper.hasMethod(method)) {
return true;
}
for (UnloadedType unloadedInterface : unloadedInterfaces) {
if (unloadedInterface.hasMethod(method)) { return true; }
}
return false;
}
public boolean hasField(Field field) {
// TODO does the field exist?
// TODO are the expected field flags present (static, public, etc)
throw new RuntimeException("TODO");
}
@Override
public void visit(
final int version,
final int access,
final String name,
final String signature,
final String superName,
final String[] interfaces) {
className = Utils.getClassName(name);
if (null != superName) this.superName = Utils.getClassName(superName);
if (null != interfaces) this.interfaceNames = interfaces;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(
final int access,
final String name,
final String descriptor,
final String signature,
final String[] exceptions) {
// Additional references we could check
// - Classes in signature (return type, params) and visible from this package
methods.add(new Reference.Method(name, descriptor));
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}

View File

@ -1,39 +0,0 @@
package muzzle
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.TestUtils
import datadog.trace.agent.tooling.muzzle.Reference
import datadog.trace.agent.tooling.muzzle.ReferenceCreator
import datadog.trace.agent.tooling.muzzle.ReferenceMatcher
class AdviceReferenceVisitorTest extends AgentTestRunner {
def "methods body references"() {
setup:
Map<String, Reference> references = ReferenceCreator.createReferencesFrom(AdviceClass.getName(), this.getClass().getClassLoader())
expect:
references.get('java.lang.Object') != null
references.get('muzzle.AdviceClass$A') != null
references.get('muzzle.AdviceClass$SomeInterface') != null
references.get('muzzle.AdviceClass$SomeImplementation') != null
references.keySet().size() == 4
}
def "match safe classpaths"() {
setup:
Reference[] refs = ReferenceCreator.createReferencesFrom(AdviceClass.getName(), this.getClass().getClassLoader()).values().toArray(new Reference[0])
ReferenceMatcher refMatcher = new ReferenceMatcher(refs)
ClassLoader safeClassloader = new URLClassLoader([TestUtils.createJarWithClasses(AdviceClass$A,
AdviceClass$SomeInterface,
AdviceClass$SomeImplementation)] as URL[],
(ClassLoader) null)
ClassLoader unsafeClassloader = new URLClassLoader([TestUtils.createJarWithClasses(AdviceClass$SomeInterface,
AdviceClass$SomeImplementation)] as URL[],
(ClassLoader) null)
expect:
refMatcher.getMismatchedReferenceSources(safeClassloader).size() == 0
refMatcher.getMismatchedReferenceSources(unsafeClassloader).size() == 1
}
}

View File

@ -12,19 +12,47 @@ class ReferenceCreatorTest extends AgentTestRunner {
expect:
references.get('java.lang.Object') != null
references.get('java.lang.String') != null
references.get('muzzle.TestClasses$MethodBodyAdvice$A') != null
references.get('muzzle.TestClasses$MethodBodyAdvice$B') != null
references.get('muzzle.TestClasses$MethodBodyAdvice$SomeInterface') != null
references.get('muzzle.TestClasses$MethodBodyAdvice$SomeImplementation') != null
references.keySet().size() == 6
// interface flags
references.get('muzzle.TestClasses$MethodBodyAdvice$B').getFlags().contains(Reference.Flag.NON_INTERFACE)
references.get('muzzle.TestClasses$MethodBodyAdvice$SomeInterface').getFlags().contains(Reference.Flag.INTERFACE)
// class access flags
references.get('java.lang.Object').getFlags().contains(Reference.Flag.PUBLIC)
references.get('muzzle.TestClasses$MethodBodyAdvice$B').getFlags().contains(Reference.Flag.PACKAGE_OR_HIGHER)
Set<Reference.Method> bMethods = references.get('muzzle.TestClasses$MethodBodyAdvice$B').getMethods()
bMethods.contains(method("aMethod", "(Ljava/lang/String;)Ljava/lang/String;"))
bMethods.contains(method("aMethodWithPrimitives", "(Z)V"))
bMethods.contains(method("aMethodWithArrays", "([Ljava/lang/String;)[Ljava/lang/Object;"))
findMethod(bMethods, "aMethod", "(Ljava/lang/String;)Ljava/lang/String;") != null
findMethod(bMethods, "aMethodWithPrimitives", "(Z)V") != null
findMethod(bMethods, "aStaticMethod", "()V") != null
findMethod(bMethods, "aMethodWithArrays", "([Ljava/lang/String;)[Ljava/lang/Object;") != null
findMethod(bMethods, "aMethod", "(Ljava/lang/String;)Ljava/lang/String;").getFlags().contains(Reference.Flag.NON_STATIC)
findMethod(bMethods, "aStaticMethod", "()V").getFlags().contains(Reference.Flag.STATIC)
}
private def method(String name, String descriptor) {
return new Reference.Method(name, descriptor)
def "protected ref test"() {
setup:
Map<String, Reference> references = ReferenceCreator.createReferencesFrom(MethodBodyAdvice.B2.getName(), this.getClass().getClassLoader())
expect:
Set<Reference.Method> bMethods = references.get('muzzle.TestClasses$MethodBodyAdvice$B').getMethods()
findMethod(bMethods, "protectedMethod", "()V") != null
findMethod(bMethods, "protectedMethod", "()V").getFlags().contains(Reference.Flag.PROTECTED_OR_HIGHER)
}
private static findMethod(Set<Reference.Method> methods, String methodName, String methodDesc) {
for (Reference.Method method : methods) {
if (method == new Reference.Method(methodName, methodDesc)) {
return method
}
}
return null
}
}

View File

@ -2,18 +2,17 @@ package muzzle
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.TestUtils
import datadog.trace.agent.tooling.Utils
import datadog.trace.agent.tooling.muzzle.Reference
import datadog.trace.agent.tooling.muzzle.Reference.Method
import datadog.trace.agent.tooling.muzzle.Reference.Source
import datadog.trace.agent.tooling.muzzle.Reference.Flag
import datadog.trace.agent.tooling.muzzle.ReferenceCreator
import datadog.trace.agent.tooling.muzzle.ReferenceMatcher
import datadog.trace.agent.tooling.muzzle.UnloadedType
import net.bytebuddy.jar.asm.Type
import spock.lang.Shared
import java.lang.ref.WeakReference
import static muzzle.TestClasses.*
class ReferenceMatcherTest extends AgentTestRunner {
@ -46,10 +45,30 @@ class ReferenceMatcherTest extends AgentTestRunner {
MuzzleWeakReferenceTest.classLoaderRefIsGarbageCollected()
}
def "match classes"() {
ReferenceMatcher.UnloadedType unloadedB = ReferenceMatcher.UnloadedType.of(MethodBodyAdvice.B.getName(), MethodBodyAdvice.B.getClassLoader())
Reference ref
when:
ref = new Reference.Builder(Utils.getInternalName(MethodBodyAdvice.B.getName()))
.withFlag(Flag.NON_INTERFACE)
.build()
then:
unloadedB.checkMatch(ref).size() == 0
when:
ref = new Reference.Builder(Utils.getInternalName(MethodBodyAdvice.B.getName()))
.withFlag(Flag.INTERFACE)
.build()
then:
unloadedB.checkMatch(ref).size() == 1
}
def "match methods"() {
setup:
UnloadedType unloadedB = UnloadedType.of(MethodBodyAdvice.B.getName(), MethodBodyAdvice.B.getClassLoader())
UnloadedType unloadedInterface = UnloadedType.of(MethodBodyAdvice.AnotherInterface.getName(), MethodBodyAdvice.AnotherInterface.getClassLoader())
ReferenceMatcher.UnloadedType unloadedB = ReferenceMatcher.UnloadedType.of(MethodBodyAdvice.B.getName(), MethodBodyAdvice.B.getClassLoader())
ReferenceMatcher.UnloadedType unloadedB2 = ReferenceMatcher.UnloadedType.of(MethodBodyAdvice.B2.getName(), MethodBodyAdvice.B2.getClassLoader())
ReferenceMatcher.UnloadedType unloadedInterface = ReferenceMatcher.UnloadedType.of(MethodBodyAdvice.AnotherInterface.getName(), MethodBodyAdvice.AnotherInterface.getClassLoader())
Method methodRef
// match method declared in the class
@ -70,6 +89,24 @@ class ReferenceMatcherTest extends AgentTestRunner {
then:
unloadedInterface.checkMatch(methodRef).size() == 0
// match private method in the class
when:
methodRef = new Method("privateStuff", "()V")
then:
unloadedB.checkMatch(methodRef).size() == 0
// fail to match private method in superclass
when:
methodRef = new Method("privateStuff", "()V")
then:
unloadedB2.checkMatch(methodRef).size() == 1
// static method flag mismatch
when:
methodRef = new Method(new Source[0], [Flag.NON_STATIC] as Flag[], "aStaticMethod", Type.getType("V"))
then:
unloadedB2.checkMatch(methodRef).size() == 1
// missing method mismatch
when:
methodRef = new Method(new Source[0], new Flag[0], "missingTestMethod", Type.VOID_TYPE, new Type[0])

View File

@ -4,7 +4,6 @@ import datadog.trace.agent.test.TestUtils;
import datadog.trace.agent.tooling.muzzle.Reference;
import datadog.trace.agent.tooling.muzzle.ReferenceCreator;
import datadog.trace.agent.tooling.muzzle.ReferenceMatcher;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.net.URLClassLoader;
@ -18,7 +17,12 @@ public class MuzzleWeakReferenceTest {
public static boolean classLoaderRefIsGarbageCollected() {
ClassLoader loader = new URLClassLoader(new URL[0], null);
WeakReference<ClassLoader> clRef = new WeakReference<>(loader);
Reference[] refs = ReferenceCreator.createReferencesFrom(TestClasses.MethodBodyAdvice.class.getName(), MuzzleWeakReferenceTest.class.getClassLoader()).values().toArray(new Reference[0]);
Reference[] refs =
ReferenceCreator.createReferencesFrom(
TestClasses.MethodBodyAdvice.class.getName(),
MuzzleWeakReferenceTest.class.getClassLoader())
.values()
.toArray(new Reference[0]);
ReferenceMatcher refMatcher = new ReferenceMatcher(refs);
refMatcher.getMismatchedReferenceSources(loader);
loader = null;

View File

@ -13,6 +13,7 @@ public class TestClasses {
a.b.aMethod("foo");
a.b.aMethodWithPrimitives(false);
a.b.aMethodWithArrays(new String[0]);
B.aStaticMethod();
}
public static class A {
@ -20,9 +21,28 @@ public class TestClasses {
}
public static class B {
public String aMethod(String s) { return s; }
public String aMethod(String s) {
return s;
}
public void aMethodWithPrimitives(boolean b) {}
public Object[] aMethodWithArrays(String[] s) { return s; }
public Object[] aMethodWithArrays(String[] s) {
return s;
}
private void privateStuff() {}
protected void protectedMethod() {}
public static void aStaticMethod() {}
}
public static class B2 extends B {
public void stuff() {
B b = new B();
b.protectedMethod();
}
}
public interface SomeInterface {
@ -40,7 +60,6 @@ public class TestClasses {
public final int finalField = 0;
}
public interface AnotherInterface extends SomeInterface { }
public interface AnotherInterface extends SomeInterface {}
}
}