From 50c5a57c49ca815a2b37e466e5defc7a1e357eb0 Mon Sep 17 00:00:00 2001 From: Andrew Kent Date: Thu, 2 Aug 2018 11:10:01 -0700 Subject: [PATCH] Replace UnloadedType with ByteBuddy TypeDescription --- .../trace/agent/tooling/AgentInstaller.java | 3 +- .../agent/tooling/DDLocationStrategy.java | 4 + .../trace/agent/tooling/muzzle/Reference.java | 7 +- .../tooling/muzzle/ReferenceMatcher.java | 397 +++++------------- .../groovy/muzzle/ReferenceMatcherTest.groovy | 156 +++---- 5 files changed, 177 insertions(+), 390 deletions(-) diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java index c8cce78f1e..f94d1bd2cd 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java @@ -20,6 +20,7 @@ import net.bytebuddy.utility.JavaModule; @Slf4j public class AgentInstaller { + public static final DDLocationStrategy LOCATION_STRATEGY = new DDLocationStrategy(); private static volatile Instrumentation INSTRUMENTATION; public static Instrumentation getInstrumentation() { @@ -47,7 +48,7 @@ public class AgentInstaller { .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .with(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY) .with(new LoggingListener()) - .with(new DDLocationStrategy()) + .with(LOCATION_STRATEGY) .ignore(any(), skipClassLoader()) .or(nameStartsWith("datadog.trace.")) .or(nameStartsWith("datadog.opentracing.")) diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/DDLocationStrategy.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/DDLocationStrategy.java index dfa5c07b5f..bca82886fb 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/DDLocationStrategy.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/DDLocationStrategy.java @@ -12,6 +12,10 @@ import net.bytebuddy.utility.JavaModule; * reached. */ public class DDLocationStrategy implements AgentBuilder.LocationStrategy { + public ClassFileLocator classFileLocator(ClassLoader classLoader) { + return classFileLocator(classLoader, null); + } + @Override public ClassFileLocator classFileLocator(ClassLoader classLoader, final JavaModule javaModule) { final List locators = new ArrayList<>(); 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 e288d87fa3..3f253ac6d7 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 @@ -466,8 +466,11 @@ public class Reference { public String toString() { // ()V // toString()Ljava/lang/String; - return name - + Type.getMethodType(returnType, parameterTypes.toArray(new Type[0])).getDescriptor(); + return name + getDescriptor(); + } + + public String getDescriptor() { + return Type.getMethodType(returnType, parameterTypes.toArray(new Type[0])).getDescriptor(); } @Override 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 13b093fd33..72348265da 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 @@ -4,6 +4,7 @@ import static datadog.trace.agent.tooling.ClassLoaderMatcher.BOOTSTRAP_CLASSLOAD import static datadog.trace.bootstrap.WeakMap.Provider.newWeakMap; import static net.bytebuddy.dynamic.loading.ClassLoadingStrategy.BOOTSTRAP_LOADER; +import datadog.trace.agent.tooling.AgentInstaller; import datadog.trace.agent.tooling.Utils; import datadog.trace.agent.tooling.muzzle.Reference.Mismatch; import datadog.trace.agent.tooling.muzzle.Reference.Source; @@ -15,14 +16,11 @@ 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.FieldVisitor; -import net.bytebuddy.jar.asm.MethodVisitor; -import net.bytebuddy.jar.asm.Opcodes; -import net.bytebuddy.jar.asm.Type; +import net.bytebuddy.description.field.FieldDescription; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.pool.TypePool; /** Matches a set of references against a classloader. */ @Slf4j @@ -82,335 +80,158 @@ public class ReferenceMatcher { * @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())); - } + public static List checkMatch(Reference reference, ClassLoader loader) { + final TypePool typePool = + TypePool.Default.of(AgentInstaller.LOCATION_STRATEGY.classFileLocator(loader)); 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)); + final TypePool.Resolution resolution = + typePool.describe(Utils.getClassName(reference.getClassName())); + if (!resolution.isResolved()) { + return Collections.singletonList( + new Mismatch.MissingClass( + reference.getSources().toArray(new Source[0]), reference.getClassName())); } + return checkMatch(reference, resolution.resolve()); } catch (Exception e) { - // Shouldn't happen. Fail the reference check and add a mismatch for debug logging. - mismatches.add(new Mismatch.ReferenceCheckError(e, reference, loader)); + if (e.getMessage().startsWith("Cannot resolve type description for ")) { + // bytebuddy throws an illegal state exception with this message if it cannot resolve types + // TODO: handle missing type resolutions without catching bytebuddy's exceptions + final String className = e.getMessage().replace("Cannot resolve type description for ", ""); + mismatches.add( + new Mismatch.MissingClass(reference.getSources().toArray(new Source[0]), className)); + } else { + // Shouldn't happen. Fail the reference check and add a mismatch for debug logging. + mismatches.add(new Mismatch.ReferenceCheckError(e, reference, loader)); + } } 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; - } + public static List checkMatch( + Reference reference, TypeDescription typeOnClasspath) { + final List mismatches = new ArrayList<>(0); - /** - * 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<>(); - private final List fields = new ArrayList<>(); - - public static UnloadedType of(String className, ClassLoader classLoader) throws Exception { - if (classLoader != Utils.getBootstrapProxy()) { - // getResource delegation won't see our bootstrap classes so do the delegation here. - if (Utils.getBootstrapProxy().getResource(Utils.getResourceName(className)) != null) { - return of(className, Utils.getBootstrapProxy()); - } + for (Reference.Flag flag : reference.getFlags()) { + if (!flag.matches(typeOnClasspath.getModifiers())) { + final String desc = reference.getClassName(); + mismatches.add( + new Mismatch.MissingFlag( + reference.getSources().toArray(new Source[0]), + desc, + flag, + typeOnClasspath.getModifiers())); } - 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.Field fieldRef) { - final List mismatches = new ArrayList<>(0); - final Field unloadedField = findField(fieldRef, true); - if (unloadedField == null) { + for (Reference.Field fieldRef : reference.getFields()) { + FieldDescription.InDefinedShape fieldDescription = findField(fieldRef, typeOnClasspath); + if (fieldDescription == null) { mismatches.add( new Reference.Mismatch.MissingField( fieldRef.getSources().toArray(new Reference.Source[0]), - className, + reference.getClassName(), fieldRef.getName(), fieldRef.getType().getInternalName())); } else { for (Reference.Flag flag : fieldRef.getFlags()) { - if (!flag.matches(unloadedField.getFlags())) { - final String desc = this.getClassName() + "#" + unloadedField.signature; + if (!flag.matches(fieldDescription.getModifiers())) { + final String desc = + reference.getClassName() + + "#" + + fieldRef.getName() + + fieldRef.getType().getInternalName(); mismatches.add( new Mismatch.MissingFlag( fieldRef.getSources().toArray(new Source[0]), desc, flag, - unloadedField.getFlags())); + fieldDescription.getModifiers())); } } } - return mismatches; } - public List checkMatch(Reference.Method methodRef) { - final List mismatches = new ArrayList<>(0); - final Method unloadedMethod = findMethod(methodRef, true); - if (unloadedMethod == null) { + for (Reference.Method methodRef : reference.getMethods()) { + final MethodDescription.InDefinedShape methodDescription = + findMethod(methodRef, typeOnClasspath); + if (methodDescription == null) { mismatches.add( new Reference.Mismatch.MissingMethod( methodRef.getSources().toArray(new Reference.Source[0]), - className, - methodRef.toString())); + methodRef.getName(), + methodRef.getDescriptor())); } else { for (Reference.Flag flag : methodRef.getFlags()) { - if (!flag.matches(unloadedMethod.getFlags())) { - final String desc = this.getClassName() + "#" + unloadedMethod.signature; + if (!flag.matches(methodDescription.getModifiers())) { + final String desc = + reference.getClassName() + "#" + methodRef.getName() + methodRef.getDescriptor(); mismatches.add( new Mismatch.MissingFlag( methodRef.getSources().toArray(new Source[0]), desc, flag, - unloadedMethod.getFlags())); + methodDescription.getModifiers())); } } } - 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; - } + return mismatches; + } - private Field findField(Reference.Field fieldRef, boolean includePrivateFields) { - final Field key = new Field(0, fieldRef.getName(), fieldRef.getType().getDescriptor()); - final int index = fields.indexOf(key); - if (index != -1) { - final Field foundField = fields.get(index); - if (foundField.is(Opcodes.ACC_PRIVATE)) { - return includePrivateFields ? foundField : null; - } else { - return foundField; - } - } else { - Field superField = null; - if (unloadedSuper != null) { - superField = unloadedSuper.findField(fieldRef, false); - if (superField != null) { - return superField; - } - } - for (UnloadedType unloadedInterface : unloadedInterfaces) { - superField = unloadedInterface.findField(fieldRef, false); - if (superField != null) { - return superField; - } - } - } - return null; - } - - @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 FieldVisitor visitField( - int access, String name, String descriptor, String signature, Object value) { - fields.add(new Field(access, name, descriptor)); - return super.visitField(access, name, descriptor, signature, value); - } - - @Override - public MethodVisitor visitMethod( - final int access, - final String name, - final String descriptor, - final String signature, - final String[] exceptions) { - 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.equals(((Method) o).signature); - } - return false; - } - - @Override - public int hashCode() { - return signature.hashCode(); + private static FieldDescription.InDefinedShape findField( + Reference.Field fieldRef, TypeDescription typeOnClasspath) { + for (FieldDescription.InDefinedShape fieldType : typeOnClasspath.getDeclaredFields()) { + if (fieldType.getName().equals(fieldRef.getName()) + && fieldType + .getType() + .asErasure() + .getInternalName() + .equals(fieldRef.getType().getInternalName())) { + return fieldType; } } - - private static class Field { - private final int flags; - // name + typeDesc - private final String signature; - - public Field(int flags, String name, String typeDesc) { - this.flags = flags; - this.signature = name + typeDesc; - } - - private int getFlags() { - return flags; - } - - public boolean is(int flag) { - boolean result = (flags & flag) != 0; - return result; - } - - @Override - public String toString() { - return new StringBuilder("Unloaded: ").append(signature).toString(); - } - - @Override - public boolean equals(Object o) { - if (o instanceof Field) { - return signature.equals(((Field) o).signature); - } - return false; - } - - @Override - public int hashCode() { - return signature.hashCode(); + if (typeOnClasspath.getSuperClass() != null) { + FieldDescription.InDefinedShape fieldOnSupertype = + findField(fieldRef, typeOnClasspath.getSuperClass().asErasure()); + if (fieldOnSupertype != null) { + return fieldOnSupertype; } } + for (TypeDescription.Generic interfaceType : typeOnClasspath.getInterfaces()) { + FieldDescription.InDefinedShape fieldOnSupertype = + findField(fieldRef, interfaceType.asErasure()); + if (fieldOnSupertype != null) { + return fieldOnSupertype; + } + } + return null; + } + + private static MethodDescription.InDefinedShape findMethod( + Reference.Method methodRef, TypeDescription typeOnClasspath) { + for (MethodDescription.InDefinedShape methodDescription : + typeOnClasspath.getDeclaredMethods()) { + if (methodDescription.getInternalName().equals(methodRef.getName()) + && methodDescription.getDescriptor().equals(methodRef.getDescriptor())) { + return methodDescription; + } + } + if (typeOnClasspath.getSuperClass() != null) { + MethodDescription.InDefinedShape methodOnSupertype = + findMethod(methodRef, typeOnClasspath.getSuperClass().asErasure()); + if (methodOnSupertype != null) { + return methodOnSupertype; + } + } + for (TypeDescription.Generic interfaceType : typeOnClasspath.getInterfaces()) { + MethodDescription.InDefinedShape methodOnSupertype = + findMethod(methodRef, interfaceType.asErasure()); + if (methodOnSupertype != null) { + return methodOnSupertype; + } + } + 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 168554b35e..12ac88344b 100644 --- a/dd-java-agent/testing/src/test/groovy/muzzle/ReferenceMatcherTest.groovy +++ b/dd-java-agent/testing/src/test/groovy/muzzle/ReferenceMatcherTest.groovy @@ -1,11 +1,12 @@ package muzzle +import static datadog.trace.agent.tooling.muzzle.Reference.Flag.* +import static datadog.trace.agent.tooling.muzzle.Reference.Mismatch.* + 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.Source -import datadog.trace.agent.tooling.muzzle.Reference.Flag import datadog.trace.agent.tooling.muzzle.ReferenceCreator import datadog.trace.agent.tooling.muzzle.ReferenceMatcher @@ -35,8 +36,8 @@ class ReferenceMatcherTest extends AgentTestRunner { ReferenceMatcher refMatcher = new ReferenceMatcher(refs) expect: - refMatcher.getMismatchedReferenceSources(safeClasspath).size() == 0 - refMatcher.getMismatchedReferenceSources(unsafeClasspath).size() == 1 + getMismatchClassSet(refMatcher.getMismatchedReferenceSources(safeClasspath)) == new HashSet<>() + getMismatchClassSet(refMatcher.getMismatchedReferenceSources(unsafeClasspath)) == new HashSet<>([MissingClass]) } def "matching does not hold a strong reference to classloaders"() { @@ -44,111 +45,68 @@ 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"() { + def "matching ref #referenceName #referenceFlags against #classToCheck produces #expectedMismatches"() { setup: - 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()) - Reference.Method methodRef + Reference.Builder builder = new Reference.Builder(referenceName) + for (Reference.Flag refFlag : referenceFlags) { + builder = builder.withFlag(refFlag) + } + Reference ref = builder.build() - // match method declared in the class - when: - methodRef = new Reference.Method("aMethod", "(Ljava/lang/String;)Ljava/lang/String;") - then: - unloadedB.checkMatch(methodRef).size() == 0 + expect: + getMismatchClassSet(ReferenceMatcher.checkMatch(ref, this.getClass().getClassLoader())) == new HashSet(expectedMismatches) - // match method declared in the supertype - when: - methodRef = new Reference.Method("hashCode", "()I") - then: - unloadedB.checkMatch(methodRef).size() == 0 - - // match method declared in interface - when: - methodRef = new Reference.Method("someMethod", "()V") - then: - unloadedInterface.checkMatch(methodRef).size() == 0 - - // match private method in the class - when: - methodRef = new Reference.Method("privateStuff", "()V") - then: - unloadedB.checkMatch(methodRef).size() == 0 - - // fail to match private method in superclass - when: - methodRef = new Reference.Method("privateStuff", "()V") - then: - unloadedB2.checkMatch(methodRef).size() == 1 - - // static method flag mismatch - when: - methodRef = new Reference.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 Reference.Method(new Source[0], new Flag[0], "missingTestMethod", Type.VOID_TYPE, new Type[0]) - then: - unloadedB.checkMatch(methodRef).size() == 1 + where: + referenceName | referenceFlags | classToCheck | expectedMismatches + MethodBodyAdvice.B.getName() | [NON_INTERFACE] | MethodBodyAdvice.B | [] + MethodBodyAdvice.B.getName() | [INTERFACE] | MethodBodyAdvice.B | [MissingFlag] } - def "match fields" () { - ReferenceMatcher.UnloadedType unloadedA = ReferenceMatcher.UnloadedType.of(MethodBodyAdvice.A.getName(), MethodBodyAdvice.A.getClassLoader()) - ReferenceMatcher.UnloadedType unloadedA2 = ReferenceMatcher.UnloadedType.of(MethodBodyAdvice.A2.getName(), MethodBodyAdvice.A2.getClassLoader()) - Reference.Field fieldRef + def "method match #methodTestDesc"() { + setup: + Type methodType = Type.getMethodType(methodDesc) + Reference reference = new Reference.Builder(classToCheck.getName()) + .withMethod(new Source[0], methodFlags as Reference.Flag[], methodName, methodType.getReturnType(), methodType.getArgumentTypes()) + .build() - when: - fieldRef = new Reference.Field(new Source[0], new Flag[0], "missingField", Type.getType("Ljava/lang/String;")) - then: - unloadedA.checkMatch(fieldRef).size() == 1 + expect: + getMismatchClassSet(ReferenceMatcher.checkMatch(reference, this.getClass().getClassLoader())) == new HashSet(expectedMismatches) - when: - // wrong field type sig should create a mismatch - fieldRef = new Reference.Field(new Source[0], new Flag[0], "privateField", Type.getType("Ljava/lang/String;")) - then: - unloadedA.checkMatch(fieldRef).size() == 1 + where: + methodName | methodDesc | methodFlags | classToCheck | expectedMismatches | methodTestDesc + "aMethod" | "(Ljava/lang/String;)Ljava/lang/String;" | [] | MethodBodyAdvice.B | [] | "match method declared in class" + "hashCode" | "()I" | [] | MethodBodyAdvice.B | [] | "match method declared in superclass" + "someMethod" | "()V" | [] | MethodBodyAdvice.SomeInterface | [] | "match method declared in interface" + "privateStuff" | "()V" | [PRIVATE_OR_HIGHER] | MethodBodyAdvice.B | [] | "match private method" + "privateStuff" | "()V" | [PROTECTED_OR_HIGHER] | MethodBodyAdvice.B2 | [MissingFlag] | "fail match private in supertype" + "aStaticMethod" | "()V" | [NON_STATIC] | MethodBodyAdvice.B | [MissingFlag] | "static method mismatch" + "missingMethod" | "()V" | [] | MethodBodyAdvice.B | [MissingMethod] | "missing method mismatch" + } - when: - fieldRef = new Reference.Field(new Source[0], new Flag[0], "privateField", Type.getType("Ljava/lang/Object;")) - then: - unloadedA.checkMatch(fieldRef).size() == 0 - unloadedA2.checkMatch(fieldRef).size() == 1 + def "field match #fieldTestDesc"() { + setup: + Reference reference = new Reference.Builder(classToCheck.getName()) + .withField(new Source[0], fieldFlags as Reference.Flag[], fieldName, Type.getType(fieldType)) + .build() - when: - fieldRef = new Reference.Field(new Source[0], [Flag.NON_STATIC, Flag.PROTECTED_OR_HIGHER] as Flag[], "protectedField", Type.getType("Ljava/lang/Object;")) - then: - unloadedA.checkMatch(fieldRef).size() == 0 - unloadedA2.checkMatch(fieldRef).size() == 0 + expect: + getMismatchClassSet(ReferenceMatcher.checkMatch(reference, this.getClass().getClassLoader())) == new HashSet(expectedMismatches) - when: - fieldRef = new Reference.Field(new Source[0], [Flag.STATIC] as Flag[], "protectedField", Type.getType("Ljava/lang/Object;")) - then: - unloadedA.checkMatch(fieldRef).size() == 1 + where: + fieldName | fieldType | fieldFlags | classToCheck | expectedMismatches | fieldTestDesc + "missingField" | "Ljava/lang/String;" | [] | MethodBodyAdvice.A | [MissingField] | "mismatch missing field" + "privateField" | "Ljava/lang/String;" | [] | MethodBodyAdvice.A | [MissingField] | "mismatch field type signature" + "privateField" | "Ljava/lang/Object;" | [PRIVATE_OR_HIGHER] | MethodBodyAdvice.A | [] | "match private field" + "privateField" | "Ljava/lang/Object;" | [PROTECTED_OR_HIGHER] | MethodBodyAdvice.A2 | [MissingFlag] | "mismatch private field in supertype" + "protectedField" | "Ljava/lang/Object;" | [STATIC] | MethodBodyAdvice.A | [MissingFlag] | "mismatch static field" + "staticB" | Type.getType(MethodBodyAdvice.B).getDescriptor() | [STATIC, PROTECTED_OR_HIGHER] | MethodBodyAdvice.A | [] | "match static field" + } - when: - fieldRef = new Reference.Field(new Source[0], [Flag.PROTECTED_OR_HIGHER, Flag.STATIC] as Flag[], "staticB", Type.getType(MethodBodyAdvice.B)) - then: - unloadedA.checkMatch(fieldRef).size() == 0 + private static Set getMismatchClassSet(List mismatches) { + final Set mismatchClasses = new HashSet<>(mismatches.size()) + for (Reference.Mismatch mismatch : mismatches) { + mismatchClasses.add(mismatch.getClass()) + } + return mismatchClasses } }