Replace UnloadedType with ByteBuddy TypeDescription
This commit is contained in:
parent
937e9a6cef
commit
50c5a57c49
|
@ -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."))
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) {
|
||||
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 {
|
||||
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()));
|
||||
}
|
||||
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));
|
||||
}
|
||||
return checkMatch(reference, resolution.resolve());
|
||||
} 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.
|
||||
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());
|
||||
}
|
||||
}
|
||||
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();
|
||||
if (!flag.matches(typeOnClasspath.getModifiers())) {
|
||||
final String desc = reference.getClassName();
|
||||
mismatches.add(
|
||||
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) {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
if (typeOnClasspath.getSuperClass() != null) {
|
||||
FieldDescription.InDefinedShape fieldOnSupertype =
|
||||
findField(fieldRef, typeOnClasspath.getSuperClass().asErasure());
|
||||
if (fieldOnSupertype != null) {
|
||||
return fieldOnSupertype;
|
||||
}
|
||||
}
|
||||
for (UnloadedType unloadedInterface : unloadedInterfaces) {
|
||||
superField = unloadedInterface.findField(fieldRef, false);
|
||||
if (superField != null) {
|
||||
return superField;
|
||||
}
|
||||
for (TypeDescription.Generic interfaceType : typeOnClasspath.getInterfaces()) {
|
||||
FieldDescription.InDefinedShape fieldOnSupertype =
|
||||
findField(fieldRef, interfaceType.asErasure());
|
||||
if (fieldOnSupertype != null) {
|
||||
return fieldOnSupertype;
|
||||
}
|
||||
}
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue