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
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."))

View File

@ -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<ClassFileLocator> locators = new ArrayList<>();

View File

@ -466,8 +466,11 @@ public class Reference {
public String toString() {
// <init>()V
// toString()Ljava/lang/String;
return name
+ Type.getMethodType(returnType, parameterTypes.toArray(new Type[0])).getDescriptor();
return name + getDescriptor();
}
public String getDescriptor() {
return Type.getMethodType(returnType, parameterTypes.toArray(new Type[0])).getDescriptor();
}
@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 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<Reference.Mismatch> checkMatch(Reference reference, ClassLoader loader) {
if (loader == BOOTSTRAP_CLASSLOADER) {
throw new IllegalStateException("Cannot directly check against bootstrap classloader");
}
if (!onClasspath(reference.getClassName(), loader)) {
return Collections.<Mismatch>singletonList(
new Mismatch.MissingClass(
reference.getSources().toArray(new Source[0]), reference.getClassName()));
}
public static List<Reference.Mismatch> checkMatch(Reference reference, ClassLoader loader) {
final TypePool typePool =
TypePool.Default.of(AgentInstaller.LOCATION_STRATEGY.classFileLocator(loader));
final List<Mismatch> mismatches = new ArrayList<>(0);
try {
ReferenceMatcher.UnloadedType typeOnClasspath =
ReferenceMatcher.UnloadedType.of(reference.getClassName(), loader);
mismatches.addAll(typeOnClasspath.checkMatch(reference));
for (Reference.Method requiredMethod : reference.getMethods()) {
mismatches.addAll(typeOnClasspath.checkMatch(requiredMethod));
final TypePool.Resolution resolution =
typePool.describe(Utils.getClassName(reference.getClassName()));
if (!resolution.isResolved()) {
return Collections.<Mismatch>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<Reference.Mismatch> checkMatch(
Reference reference, TypeDescription typeOnClasspath) {
final List<Mismatch> mismatches = new ArrayList<>(0);
/**
* 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());
}
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<String, UnloadedType> classLoaderCache = typeCache.get(classLoader);
if (classLoaderCache == null) {
synchronized (classLoader) {
classLoaderCache = typeCache.get(classLoader);
if (classLoaderCache == null) {
classLoaderCache = new ConcurrentHashMap<>();
typeCache.put(classLoader, classLoaderCache);
}
}
}
UnloadedType unloadedType = classLoaderCache.get(className);
if (unloadedType == null) {
final InputStream in = classLoader.getResourceAsStream(Utils.getResourceName(className));
unloadedType = new UnloadedType(null);
final ClassReader reader = new ClassReader(in);
reader.accept(unloadedType, ClassReader.SKIP_CODE);
if (unloadedType.superName != null) {
unloadedType.unloadedSuper = UnloadedType.of(unloadedType.superName, classLoader);
}
for (String interfaceName : unloadedType.interfaceNames) {
unloadedType.unloadedInterfaces.add(UnloadedType.of(interfaceName, classLoader));
}
classLoaderCache.put(className, unloadedType);
}
return unloadedType;
}
private UnloadedType(ClassVisitor cv) {
super(Opcodes.ASM6, cv);
}
public String getClassName() {
return className;
}
public String getSuperName() {
return superName;
}
public int getFlags() {
return flags;
}
public List<Reference.Mismatch> checkMatch(Reference reference) {
final List<Reference.Mismatch> mismatches = new ArrayList<>(0);
for (Reference.Flag flag : reference.getFlags()) {
if (!flag.matches(getFlags())) {
final String desc = this.getClassName();
mismatches.add(
new Mismatch.MissingFlag(
reference.getSources().toArray(new Source[0]), desc, flag, getFlags()));
}
}
return mismatches;
}
public List<Reference.Mismatch> checkMatch(Reference.Field fieldRef) {
final List<Reference.Mismatch> 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<Reference.Mismatch> checkMatch(Reference.Method methodRef) {
final List<Reference.Mismatch> 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;
}
}

View File

@ -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<Object>(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<Object>(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<Object>(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<Class> getMismatchClassSet(List<Reference.Mismatch> mismatches) {
final Set<Class> mismatchClasses = new HashSet<>(mismatches.size())
for (Reference.Mismatch mismatch : mismatches) {
mismatchClasses.add(mismatch.getClass())
}
return mismatchClasses
}
}