diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Utils.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Utils.java index 4fc33382a3..127a68ecda 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Utils.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Utils.java @@ -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; diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleVisitor.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleVisitor.java index 9e3bb6f25c..50966e040c 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleVisitor.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleVisitor.java @@ -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", - "", - "(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", "", "(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", - "", - "([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", + "", + "(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", + "", + "(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", + "", + "([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); } } diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/Reference.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/Reference.java index 31f0322d39..83709e8edb 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/Reference.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/Reference.java @@ -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 checkMatch(ClassLoader loader) { - if (loader == BOOTSTRAP_CLASSLOADER) { - throw new IllegalStateException("Cannot directly check against bootstrap classloader"); - } - if (!onClasspath(className, loader)) { - return Collections.singletonList(new Mismatch.MissingClass(sources.toArray(new Source[0]), className)); - } - final List 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 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 getSources() { return sources; } + public Set getSources() { + return sources; + } - public Set getFlags() { return flags; } + public Set 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 getParameterTypes() { return parameterTypes; } + public List 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 mergedSources = new HashSet<>(); @@ -327,7 +437,8 @@ public class Reference { public String toString() { // ()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)); } } } diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/ReferenceCreator.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/ReferenceCreator.java index f40de596af..016d09c0eb 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/ReferenceCreator.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/ReferenceCreator.java @@ -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 createReferencesFrom(String entryPointClassName, ClassLoader loader) { + public static Map 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 createReferencesFrom( String entryPointClassName, ClassLoader loader, boolean startFromMethodBodies) { final Set 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 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 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); } } diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/ReferenceMatcher.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/ReferenceMatcher.java index e5ed74906d..a53193d789 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/ReferenceMatcher.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/ReferenceMatcher.java @@ -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 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.singletonList( + new Mismatch.MissingClass( + reference.getSources().toArray(new Source[0]), reference.getClassName())); + } + final List 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. + * + *

Used to compare an expected Reference with the actual runtime class without causing + * classloads. + */ + public static class UnloadedType extends ClassVisitor { + private static final Map> typeCache = + Collections.synchronizedMap(new WeakHashMap>()); + + private String superName = null; + private String className = null; + private String[] interfaceNames = new String[0]; + private UnloadedType unloadedSuper = null; + private final List unloadedInterfaces = new ArrayList<>(); + private int flags; + private final List methods = new ArrayList<>(); + + public static UnloadedType of(String className, ClassLoader classLoader) throws Exception { + className = Utils.getInternalName(className); + Map 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 checkMatch(Reference reference) { + final List 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 checkMatch(Reference.Method method) { + final List 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(); + } + } + } } diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/UnloadedType.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/UnloadedType.java deleted file mode 100644 index 0b6fe39d2c..0000000000 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/UnloadedType.java +++ /dev/null @@ -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> typeCache = Collections.synchronizedMap(new WeakHashMap>()); - - 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 unloadedInterfaces = new ArrayList<>(); - private final List 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 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 checkMatch(Method method) { - final List 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); - } - -} diff --git a/dd-java-agent/testing/src/test/groovy/muzzle/AdviceReferenceVisitorTest.groovy b/dd-java-agent/testing/src/test/groovy/muzzle/AdviceReferenceVisitorTest.groovy deleted file mode 100644 index 7e6911a810..0000000000 --- a/dd-java-agent/testing/src/test/groovy/muzzle/AdviceReferenceVisitorTest.groovy +++ /dev/null @@ -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 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 - } -} diff --git a/dd-java-agent/testing/src/test/groovy/muzzle/ReferenceCreatorTest.groovy b/dd-java-agent/testing/src/test/groovy/muzzle/ReferenceCreatorTest.groovy index 6f4f640495..d191f311a2 100644 --- a/dd-java-agent/testing/src/test/groovy/muzzle/ReferenceCreatorTest.groovy +++ b/dd-java-agent/testing/src/test/groovy/muzzle/ReferenceCreatorTest.groovy @@ -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 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 references = ReferenceCreator.createReferencesFrom(MethodBodyAdvice.B2.getName(), this.getClass().getClassLoader()) + + expect: + Set 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 methods, String methodName, String methodDesc) { + for (Reference.Method method : methods) { + if (method == new Reference.Method(methodName, methodDesc)) { + return method + } + } + return null } } diff --git a/dd-java-agent/testing/src/test/groovy/muzzle/ReferenceMatcherTest.groovy b/dd-java-agent/testing/src/test/groovy/muzzle/ReferenceMatcherTest.groovy index 0a246dff0f..f89d4f7542 100644 --- a/dd-java-agent/testing/src/test/groovy/muzzle/ReferenceMatcherTest.groovy +++ b/dd-java-agent/testing/src/test/groovy/muzzle/ReferenceMatcherTest.groovy @@ -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]) diff --git a/dd-java-agent/testing/src/test/java/muzzle/MuzzleWeakReferenceTest.java b/dd-java-agent/testing/src/test/java/muzzle/MuzzleWeakReferenceTest.java index cadfc26086..2fe00dfa31 100644 --- a/dd-java-agent/testing/src/test/java/muzzle/MuzzleWeakReferenceTest.java +++ b/dd-java-agent/testing/src/test/java/muzzle/MuzzleWeakReferenceTest.java @@ -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 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; diff --git a/dd-java-agent/testing/src/test/java/muzzle/TestClasses.java b/dd-java-agent/testing/src/test/java/muzzle/TestClasses.java index 4202ce8a97..41b19ba705 100644 --- a/dd-java-agent/testing/src/test/java/muzzle/TestClasses.java +++ b/dd-java-agent/testing/src/test/java/muzzle/TestClasses.java @@ -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 {} } - }