Replace UnloadedType with ByteBuddy TypeDescription

This commit is contained in:
Andrew Kent 2018-08-02 11:10:01 -07:00
parent 937e9a6cef
commit 50c5a57c49
5 changed files with 177 additions and 390 deletions

View File

@ -20,6 +20,7 @@ import net.bytebuddy.utility.JavaModule;
@Slf4j @Slf4j
public class AgentInstaller { public class AgentInstaller {
public static final DDLocationStrategy LOCATION_STRATEGY = new DDLocationStrategy();
private static volatile Instrumentation INSTRUMENTATION; private static volatile Instrumentation INSTRUMENTATION;
public static Instrumentation getInstrumentation() { public static Instrumentation getInstrumentation() {
@ -47,7 +48,7 @@ public class AgentInstaller {
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY) .with(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY)
.with(new LoggingListener()) .with(new LoggingListener())
.with(new DDLocationStrategy()) .with(LOCATION_STRATEGY)
.ignore(any(), skipClassLoader()) .ignore(any(), skipClassLoader())
.or(nameStartsWith("datadog.trace.")) .or(nameStartsWith("datadog.trace."))
.or(nameStartsWith("datadog.opentracing.")) .or(nameStartsWith("datadog.opentracing."))

View File

@ -12,6 +12,10 @@ import net.bytebuddy.utility.JavaModule;
* reached. * reached.
*/ */
public class DDLocationStrategy implements AgentBuilder.LocationStrategy { public class DDLocationStrategy implements AgentBuilder.LocationStrategy {
public ClassFileLocator classFileLocator(ClassLoader classLoader) {
return classFileLocator(classLoader, null);
}
@Override @Override
public ClassFileLocator classFileLocator(ClassLoader classLoader, final JavaModule javaModule) { public ClassFileLocator classFileLocator(ClassLoader classLoader, final JavaModule javaModule) {
final List<ClassFileLocator> locators = new ArrayList<>(); final List<ClassFileLocator> locators = new ArrayList<>();

View File

@ -466,8 +466,11 @@ public class Reference {
public String toString() { public String toString() {
// <init>()V // <init>()V
// toString()Ljava/lang/String; // toString()Ljava/lang/String;
return name return name + getDescriptor();
+ Type.getMethodType(returnType, parameterTypes.toArray(new Type[0])).getDescriptor(); }
public String getDescriptor() {
return Type.getMethodType(returnType, parameterTypes.toArray(new Type[0])).getDescriptor();
} }
@Override @Override

View File

@ -4,6 +4,7 @@ import static datadog.trace.agent.tooling.ClassLoaderMatcher.BOOTSTRAP_CLASSLOAD
import static datadog.trace.bootstrap.WeakMap.Provider.newWeakMap; import static datadog.trace.bootstrap.WeakMap.Provider.newWeakMap;
import static net.bytebuddy.dynamic.loading.ClassLoadingStrategy.BOOTSTRAP_LOADER; 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.Utils;
import datadog.trace.agent.tooling.muzzle.Reference.Mismatch; import datadog.trace.agent.tooling.muzzle.Reference.Mismatch;
import datadog.trace.agent.tooling.muzzle.Reference.Source; import datadog.trace.agent.tooling.muzzle.Reference.Source;
@ -15,14 +16,11 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.ClassReader; import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.jar.asm.ClassVisitor; import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.jar.asm.FieldVisitor; import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.pool.TypePool;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
/** Matches a set of references against a classloader. */ /** Matches a set of references against a classloader. */
@Slf4j @Slf4j
@ -82,335 +80,158 @@ public class ReferenceMatcher {
* @param loader * @param loader
* @return A list of mismatched sources. A list of size 0 means the reference matches the class. * @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) { public static List<Reference.Mismatch> checkMatch(Reference reference, ClassLoader loader) {
if (loader == BOOTSTRAP_CLASSLOADER) { final TypePool typePool =
throw new IllegalStateException("Cannot directly check against bootstrap classloader"); TypePool.Default.of(AgentInstaller.LOCATION_STRATEGY.classFileLocator(loader));
} final List<Mismatch> mismatches = new ArrayList<>(0);
if (!onClasspath(reference.getClassName(), loader)) { try {
final TypePool.Resolution resolution =
typePool.describe(Utils.getClassName(reference.getClassName()));
if (!resolution.isResolved()) {
return Collections.<Mismatch>singletonList( return Collections.<Mismatch>singletonList(
new Mismatch.MissingClass( new Mismatch.MissingClass(
reference.getSources().toArray(new Source[0]), reference.getClassName())); reference.getSources().toArray(new Source[0]), reference.getClassName()));
} }
final List<Mismatch> mismatches = new ArrayList<>(0); return checkMatch(reference, resolution.resolve());
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) { } catch (Exception e) {
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. // Shouldn't happen. Fail the reference check and add a mismatch for debug logging.
mismatches.add(new Mismatch.ReferenceCheckError(e, reference, loader)); mismatches.add(new Mismatch.ReferenceCheckError(e, reference, loader));
} }
}
return mismatches; return mismatches;
} }
private static boolean onClasspath(final String className, final ClassLoader loader) { public static List<Reference.Mismatch> checkMatch(
final String resourceName = Utils.getResourceName(className); Reference reference, TypeDescription typeOnClasspath) {
return loader.getResource(resourceName) != null final List<Mismatch> mismatches = new ArrayList<>(0);
// 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<>();
private final List<Field> 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());
}
}
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()) { for (Reference.Flag flag : reference.getFlags()) {
if (!flag.matches(getFlags())) { if (!flag.matches(typeOnClasspath.getModifiers())) {
final String desc = this.getClassName(); final String desc = reference.getClassName();
mismatches.add( mismatches.add(
new Mismatch.MissingFlag( new Mismatch.MissingFlag(
reference.getSources().toArray(new Source[0]), desc, flag, getFlags())); reference.getSources().toArray(new Source[0]),
desc,
flag,
typeOnClasspath.getModifiers()));
} }
} }
return mismatches;
}
public List<Reference.Mismatch> checkMatch(Reference.Field fieldRef) { for (Reference.Field fieldRef : reference.getFields()) {
final List<Reference.Mismatch> mismatches = new ArrayList<>(0); FieldDescription.InDefinedShape fieldDescription = findField(fieldRef, typeOnClasspath);
final Field unloadedField = findField(fieldRef, true); if (fieldDescription == null) {
if (unloadedField == null) {
mismatches.add( mismatches.add(
new Reference.Mismatch.MissingField( new Reference.Mismatch.MissingField(
fieldRef.getSources().toArray(new Reference.Source[0]), fieldRef.getSources().toArray(new Reference.Source[0]),
className, reference.getClassName(),
fieldRef.getName(), fieldRef.getName(),
fieldRef.getType().getInternalName())); fieldRef.getType().getInternalName()));
} else { } else {
for (Reference.Flag flag : fieldRef.getFlags()) { for (Reference.Flag flag : fieldRef.getFlags()) {
if (!flag.matches(unloadedField.getFlags())) { if (!flag.matches(fieldDescription.getModifiers())) {
final String desc = this.getClassName() + "#" + unloadedField.signature; final String desc =
reference.getClassName()
+ "#"
+ fieldRef.getName()
+ fieldRef.getType().getInternalName();
mismatches.add( mismatches.add(
new Mismatch.MissingFlag( new Mismatch.MissingFlag(
fieldRef.getSources().toArray(new Source[0]), fieldRef.getSources().toArray(new Source[0]),
desc, desc,
flag, flag,
unloadedField.getFlags())); fieldDescription.getModifiers()));
} }
} }
} }
return mismatches;
} }
public List<Reference.Mismatch> checkMatch(Reference.Method methodRef) { for (Reference.Method methodRef : reference.getMethods()) {
final List<Reference.Mismatch> mismatches = new ArrayList<>(0); final MethodDescription.InDefinedShape methodDescription =
final Method unloadedMethod = findMethod(methodRef, true); findMethod(methodRef, typeOnClasspath);
if (unloadedMethod == null) { if (methodDescription == null) {
mismatches.add( mismatches.add(
new Reference.Mismatch.MissingMethod( new Reference.Mismatch.MissingMethod(
methodRef.getSources().toArray(new Reference.Source[0]), methodRef.getSources().toArray(new Reference.Source[0]),
className, methodRef.getName(),
methodRef.toString())); methodRef.getDescriptor()));
} else { } else {
for (Reference.Flag flag : methodRef.getFlags()) { for (Reference.Flag flag : methodRef.getFlags()) {
if (!flag.matches(unloadedMethod.getFlags())) { if (!flag.matches(methodDescription.getModifiers())) {
final String desc = this.getClassName() + "#" + unloadedMethod.signature; final String desc =
reference.getClassName() + "#" + methodRef.getName() + methodRef.getDescriptor();
mismatches.add( mismatches.add(
new Mismatch.MissingFlag( new Mismatch.MissingFlag(
methodRef.getSources().toArray(new Source[0]), methodRef.getSources().toArray(new Source[0]),
desc, desc,
flag, flag,
unloadedMethod.getFlags())); methodDescription.getModifiers()));
} }
} }
} }
}
return mismatches; return mismatches;
} }
private Method findMethod(Reference.Method method, boolean includePrivateMethods) { private static FieldDescription.InDefinedShape findField(
Method unloadedMethod = Reference.Field fieldRef, TypeDescription typeOnClasspath) {
new Method( for (FieldDescription.InDefinedShape fieldType : typeOnClasspath.getDeclaredFields()) {
0, if (fieldType.getName().equals(fieldRef.getName())
method.getName(), && fieldType
Type.getMethodType( .getType()
method.getReturnType(), method.getParameterTypes().toArray(new Type[0])) .asErasure()
.getDescriptor()); .getInternalName()
for (Method meth : methods) { .equals(fieldRef.getType().getInternalName())) {
if (meth.equals(unloadedMethod)) { return fieldType;
if (meth.is(Opcodes.ACC_PRIVATE)) {
return includePrivateMethods ? meth : null;
} else {
return meth;
} }
} }
} if (typeOnClasspath.getSuperClass() != null) {
if (null != unloadedSuper) { FieldDescription.InDefinedShape fieldOnSupertype =
final Method meth = unloadedSuper.findMethod(method, false); findField(fieldRef, typeOnClasspath.getSuperClass().asErasure());
if (null != meth) return meth; if (fieldOnSupertype != null) {
} return fieldOnSupertype;
for (UnloadedType unloadedInterface : unloadedInterfaces) {
final Method meth = unloadedInterface.findMethod(method, false);
if (null != meth) return meth;
}
return null;
}
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) { for (TypeDescription.Generic interfaceType : typeOnClasspath.getInterfaces()) {
superField = unloadedInterface.findField(fieldRef, false); FieldDescription.InDefinedShape fieldOnSupertype =
if (superField != null) { findField(fieldRef, interfaceType.asErasure());
return superField; if (fieldOnSupertype != null) {
} return fieldOnSupertype;
} }
} }
return null; return null;
} }
@Override private static MethodDescription.InDefinedShape findMethod(
public void visit( Reference.Method methodRef, TypeDescription typeOnClasspath) {
final int version, for (MethodDescription.InDefinedShape methodDescription :
final int access, typeOnClasspath.getDeclaredMethods()) {
final String name, if (methodDescription.getInternalName().equals(methodRef.getName())
final String signature, && methodDescription.getDescriptor().equals(methodRef.getDescriptor())) {
final String superName, return methodDescription;
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();
} }
} }
if (typeOnClasspath.getSuperClass() != null) {
private static class Field { MethodDescription.InDefinedShape methodOnSupertype =
private final int flags; findMethod(methodRef, typeOnClasspath.getSuperClass().asErasure());
// name + typeDesc if (methodOnSupertype != null) {
private final String signature; return methodOnSupertype;
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();
} }
} }
for (TypeDescription.Generic interfaceType : typeOnClasspath.getInterfaces()) {
MethodDescription.InDefinedShape methodOnSupertype =
findMethod(methodRef, interfaceType.asErasure());
if (methodOnSupertype != null) {
return methodOnSupertype;
}
}
return null;
} }
} }

View File

@ -1,11 +1,12 @@
package muzzle 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.AgentTestRunner
import datadog.trace.agent.test.TestUtils 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
import datadog.trace.agent.tooling.muzzle.Reference.Source 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.ReferenceCreator
import datadog.trace.agent.tooling.muzzle.ReferenceMatcher import datadog.trace.agent.tooling.muzzle.ReferenceMatcher
@ -35,8 +36,8 @@ class ReferenceMatcherTest extends AgentTestRunner {
ReferenceMatcher refMatcher = new ReferenceMatcher(refs) ReferenceMatcher refMatcher = new ReferenceMatcher(refs)
expect: expect:
refMatcher.getMismatchedReferenceSources(safeClasspath).size() == 0 getMismatchClassSet(refMatcher.getMismatchedReferenceSources(safeClasspath)) == new HashSet<>()
refMatcher.getMismatchedReferenceSources(unsafeClasspath).size() == 1 getMismatchClassSet(refMatcher.getMismatchedReferenceSources(unsafeClasspath)) == new HashSet<>([MissingClass])
} }
def "matching does not hold a strong reference to classloaders"() { def "matching does not hold a strong reference to classloaders"() {
@ -44,111 +45,68 @@ class ReferenceMatcherTest extends AgentTestRunner {
MuzzleWeakReferenceTest.classLoaderRefIsGarbageCollected() MuzzleWeakReferenceTest.classLoaderRefIsGarbageCollected()
} }
def "match classes"() { def "matching ref #referenceName #referenceFlags against #classToCheck produces #expectedMismatches"() {
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: setup:
ReferenceMatcher.UnloadedType unloadedB = ReferenceMatcher.UnloadedType.of(MethodBodyAdvice.B.getName(), MethodBodyAdvice.B.getClassLoader()) Reference.Builder builder = new Reference.Builder(referenceName)
ReferenceMatcher.UnloadedType unloadedB2 = ReferenceMatcher.UnloadedType.of(MethodBodyAdvice.B2.getName(), MethodBodyAdvice.B2.getClassLoader()) for (Reference.Flag refFlag : referenceFlags) {
ReferenceMatcher.UnloadedType unloadedInterface = ReferenceMatcher.UnloadedType.of(MethodBodyAdvice.AnotherInterface.getName(), MethodBodyAdvice.AnotherInterface.getClassLoader()) builder = builder.withFlag(refFlag)
Reference.Method methodRef }
Reference ref = builder.build()
// match method declared in the class expect:
when: getMismatchClassSet(ReferenceMatcher.checkMatch(ref, this.getClass().getClassLoader())) == new HashSet<Object>(expectedMismatches)
methodRef = new Reference.Method("aMethod", "(Ljava/lang/String;)Ljava/lang/String;")
then:
unloadedB.checkMatch(methodRef).size() == 0
// match method declared in the supertype where:
when: referenceName | referenceFlags | classToCheck | expectedMismatches
methodRef = new Reference.Method("hashCode", "()I") MethodBodyAdvice.B.getName() | [NON_INTERFACE] | MethodBodyAdvice.B | []
then: MethodBodyAdvice.B.getName() | [INTERFACE] | MethodBodyAdvice.B | [MissingFlag]
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
} }
def "match fields" () { def "method match #methodTestDesc"() {
ReferenceMatcher.UnloadedType unloadedA = ReferenceMatcher.UnloadedType.of(MethodBodyAdvice.A.getName(), MethodBodyAdvice.A.getClassLoader()) setup:
ReferenceMatcher.UnloadedType unloadedA2 = ReferenceMatcher.UnloadedType.of(MethodBodyAdvice.A2.getName(), MethodBodyAdvice.A2.getClassLoader()) Type methodType = Type.getMethodType(methodDesc)
Reference.Field fieldRef Reference reference = new Reference.Builder(classToCheck.getName())
.withMethod(new Source[0], methodFlags as Reference.Flag[], methodName, methodType.getReturnType(), methodType.getArgumentTypes())
.build()
when: expect:
fieldRef = new Reference.Field(new Source[0], new Flag[0], "missingField", Type.getType("Ljava/lang/String;")) getMismatchClassSet(ReferenceMatcher.checkMatch(reference, this.getClass().getClassLoader())) == new HashSet<Object>(expectedMismatches)
then:
unloadedA.checkMatch(fieldRef).size() == 1
when: where:
// wrong field type sig should create a mismatch methodName | methodDesc | methodFlags | classToCheck | expectedMismatches | methodTestDesc
fieldRef = new Reference.Field(new Source[0], new Flag[0], "privateField", Type.getType("Ljava/lang/String;")) "aMethod" | "(Ljava/lang/String;)Ljava/lang/String;" | [] | MethodBodyAdvice.B | [] | "match method declared in class"
then: "hashCode" | "()I" | [] | MethodBodyAdvice.B | [] | "match method declared in superclass"
unloadedA.checkMatch(fieldRef).size() == 1 "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: def "field match #fieldTestDesc"() {
fieldRef = new Reference.Field(new Source[0], new Flag[0], "privateField", Type.getType("Ljava/lang/Object;")) setup:
then: Reference reference = new Reference.Builder(classToCheck.getName())
unloadedA.checkMatch(fieldRef).size() == 0 .withField(new Source[0], fieldFlags as Reference.Flag[], fieldName, Type.getType(fieldType))
unloadedA2.checkMatch(fieldRef).size() == 1 .build()
when: expect:
fieldRef = new Reference.Field(new Source[0], [Flag.NON_STATIC, Flag.PROTECTED_OR_HIGHER] as Flag[], "protectedField", Type.getType("Ljava/lang/Object;")) getMismatchClassSet(ReferenceMatcher.checkMatch(reference, this.getClass().getClassLoader())) == new HashSet<Object>(expectedMismatches)
then:
unloadedA.checkMatch(fieldRef).size() == 0
unloadedA2.checkMatch(fieldRef).size() == 0
when: where:
fieldRef = new Reference.Field(new Source[0], [Flag.STATIC] as Flag[], "protectedField", Type.getType("Ljava/lang/Object;")) fieldName | fieldType | fieldFlags | classToCheck | expectedMismatches | fieldTestDesc
then: "missingField" | "Ljava/lang/String;" | [] | MethodBodyAdvice.A | [MissingField] | "mismatch missing field"
unloadedA.checkMatch(fieldRef).size() == 1 "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: private static Set<Class> getMismatchClassSet(List<Reference.Mismatch> mismatches) {
fieldRef = new Reference.Field(new Source[0], [Flag.PROTECTED_OR_HIGHER, Flag.STATIC] as Flag[], "staticB", Type.getType(MethodBodyAdvice.B)) final Set<Class> mismatchClasses = new HashSet<>(mismatches.size())
then: for (Reference.Mismatch mismatch : mismatches) {
unloadedA.checkMatch(fieldRef).size() == 0 mismatchClasses.add(mismatch.getClass())
}
return mismatchClasses
} }
} }