Muzzle should fail on unimplemented abstract methods (#1193)

* Muzzle should fail on unimplemented abstract methods

* Muzzle should fail on unimplemented abstract methods

* Muzzle should fail on unimplemented abstract methods

* Muzzle should fail on unimplemented abstract methods

* Update javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/Reference.java

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>

* Muzzle should fail on unimplemented abstract methods

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
This commit is contained in:
Mateusz Rzeszutek 2020-09-23 06:38:58 +02:00 committed by GitHub
parent b9bbcd85a9
commit 3d030288d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1131 additions and 228 deletions

View File

@ -0,0 +1,275 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opentelemetry.javaagent.tooling.muzzle;
import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
import static net.bytebuddy.description.method.MethodDescription.CONSTRUCTOR_INTERNAL_NAME;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag;
import java.util.Map;
import net.bytebuddy.description.method.MethodDescription.InDefinedShape;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.pool.TypePool.Resolution;
/** This class provides a common interface for {@link Reference} and {@link TypeDescription}. */
public interface HelperReferenceWrapper {
boolean isAbstract();
/**
* @return true if the wrapped type extends any class other than {@link Object} or implements any
* interface.
*/
boolean hasSuperTypes();
/**
* @return An iterable containing the wrapped type's super class (if exists) and implemented
* interfaces.
*/
Iterable<HelperReferenceWrapper> getSuperTypes();
/** @return An iterable with all non-private, non-static methods declared in the wrapped type. */
Iterable<Method> getMethods();
final class Method {
private final boolean isAbstract;
private final String declaringClass;
private final String name;
private final String descriptor;
public Method(boolean isAbstract, String declaringClass, String name, String descriptor) {
this.isAbstract = isAbstract;
this.declaringClass = declaringClass;
this.name = name;
this.descriptor = descriptor;
}
public boolean isAbstract() {
return isAbstract;
}
public String getDeclaringClass() {
return declaringClass;
}
public String getName() {
return name;
}
public String getDescriptor() {
return descriptor;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Method method = (Method) o;
return Objects.equal(name, method.name) && Objects.equal(descriptor, method.descriptor);
}
@Override
public int hashCode() {
return Objects.hashCode(name, descriptor);
}
}
class Factory {
private final TypePool classpathPool;
private final Map<String, Reference> helperReferences;
public Factory(TypePool classpathPool, Map<String, Reference> helperReferences) {
this.classpathPool = classpathPool;
this.helperReferences = helperReferences;
}
public HelperReferenceWrapper create(Reference reference) {
return new ReferenceType(reference);
}
private HelperReferenceWrapper create(String className) {
Resolution resolution = classpathPool.describe(className);
if (resolution.isResolved()) {
return new ClasspathType(resolution.resolve());
}
if (helperReferences.containsKey(className)) {
return new ReferenceType(helperReferences.get(className));
}
throw new IllegalStateException("Missing class " + className);
}
private final class ReferenceType implements HelperReferenceWrapper {
private final Reference reference;
private ReferenceType(Reference reference) {
this.reference = reference;
}
@Override
public boolean isAbstract() {
return reference.getFlags().contains(Flag.ABSTRACT);
}
@Override
public boolean hasSuperTypes() {
return hasActualSuperType() || reference.getInterfaces().size() > 0;
}
// Uses guava iterables to avoid unnecessary collection copying
@Override
public Iterable<HelperReferenceWrapper> getSuperTypes() {
Iterable<HelperReferenceWrapper> superClass = emptyList();
if (hasActualSuperType()) {
superClass = singleton(Factory.this.create(reference.getSuperName()));
}
Iterable<HelperReferenceWrapper> interfaces =
FluentIterable.from(reference.getInterfaces()).transform(toWrapper());
return Iterables.concat(superClass, interfaces);
}
private boolean hasActualSuperType() {
return reference.getSuperName() != null;
}
private Function<String, HelperReferenceWrapper> toWrapper() {
return new Function<String, HelperReferenceWrapper>() {
@Override
public HelperReferenceWrapper apply(String interfaceName) {
return Factory.this.create(interfaceName);
}
};
}
// Uses guava iterables to avoid unnecessary collection copying
@Override
public Iterable<Method> getMethods() {
return FluentIterable.from(reference.getMethods())
.filter(isOverrideable())
.transform(toMethod());
}
private Predicate<Reference.Method> isOverrideable() {
return new Predicate<Reference.Method>() {
@Override
public boolean apply(Reference.Method input) {
return !(input.getFlags().contains(Flag.STATIC)
|| input.getFlags().contains(Flag.PRIVATE)
|| CONSTRUCTOR_INTERNAL_NAME.equals(input.getName()));
}
};
}
private Function<Reference.Method, Method> toMethod() {
return new Function<Reference.Method, Method>() {
@Override
public Method apply(Reference.Method method) {
return new Method(
method.getFlags().contains(Flag.ABSTRACT),
reference.getClassName(),
method.getName(),
method.getDescriptor());
}
};
}
}
private static final class ClasspathType implements HelperReferenceWrapper {
private final TypeDescription type;
private ClasspathType(TypeDescription type) {
this.type = type;
}
@Override
public boolean isAbstract() {
return type.isAbstract();
}
@Override
public boolean hasSuperTypes() {
return hasActualSuperType() || type.getInterfaces().size() > 0;
}
private boolean hasActualSuperType() {
return type.getSuperClass() != null;
}
// Uses guava iterables to avoid unnecessary collection copying
@Override
public Iterable<HelperReferenceWrapper> getSuperTypes() {
Iterable<HelperReferenceWrapper> superClass = emptyList();
if (hasActualSuperType()) {
superClass =
singleton(
(HelperReferenceWrapper) new ClasspathType(type.getSuperClass().asErasure()));
}
Iterable<HelperReferenceWrapper> interfaces =
Iterables.transform(type.getInterfaces().asErasures(), toWrapper());
return Iterables.concat(superClass, interfaces);
}
private static Function<TypeDescription, HelperReferenceWrapper> toWrapper() {
return new Function<TypeDescription, HelperReferenceWrapper>() {
@Override
public HelperReferenceWrapper apply(TypeDescription input) {
return new ClasspathType(input);
}
};
}
// Uses guava iterables to avoid unnecessary collection copying
@Override
public Iterable<Method> getMethods() {
return FluentIterable.from(type.getDeclaredMethods())
.filter(isOverrideable())
.transform(toMethod());
}
private static Predicate<InDefinedShape> isOverrideable() {
return new Predicate<InDefinedShape>() {
@Override
public boolean apply(InDefinedShape input) {
return !(input.isStatic() || input.isPrivate() || input.isConstructor());
}
};
}
private Function<InDefinedShape, Method> toMethod() {
return new Function<InDefinedShape, Method>() {
@Override
public Method apply(InDefinedShape method) {
return new Method(
method.isAbstract(),
type.getName(),
method.getInternalName(),
method.getDescriptor());
}
};
}
}
}
}

View File

@ -133,28 +133,18 @@ public class MuzzleVisitor implements AsmVisitorWrapper {
}
public Reference[] generateReferences() {
// track sources we've generated references from to avoid recursion
Set<String> referenceSources = new HashSet<>();
Map<String, Reference> references = new HashMap<>();
Set<String> adviceClassNames = new HashSet<>();
for (String adviceClassName : instrumenter.transformers().values()) {
adviceClassNames.add(adviceClassName);
}
Set<String> adviceClassNames = new HashSet<>(instrumenter.transformers().values());
for (String adviceClass : adviceClassNames) {
if (!referenceSources.contains(adviceClass)) {
referenceSources.add(adviceClass);
for (Map.Entry<String, Reference> entry :
ReferenceCreator.createReferencesFrom(
adviceClass, ReferenceMatcher.class.getClassLoader())
.entrySet()) {
if (references.containsKey(entry.getKey())) {
references.put(
entry.getKey(), references.get(entry.getKey()).merge(entry.getValue()));
} else {
references.put(entry.getKey(), entry.getValue());
}
for (Map.Entry<String, Reference> entry :
ReferenceCreator.createReferencesFrom(
adviceClass, ReferenceMatcher.class.getClassLoader())
.entrySet()) {
if (references.containsKey(entry.getKey())) {
references.put(entry.getKey(), references.get(entry.getKey()).merge(entry.getValue()));
} else {
references.put(entry.getKey(), entry.getValue());
}
}
}

View File

@ -21,6 +21,7 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
@ -295,62 +296,66 @@ public class Reference {
public static class MissingMethod extends Mismatch {
private final String className;
private final String method;
private final String methodName;
private final String methodDescriptor;
public MissingMethod(Source[] sources, String className, String method) {
public MissingMethod(
Source[] sources, String className, String methodName, String methodDescriptor) {
super(sources);
this.className = className;
this.method = method;
this.methodName = methodName;
this.methodDescriptor = methodDescriptor;
}
@Override
String getMismatchDetails() {
return "Missing method " + className + "#" + method;
return "Missing method " + className + "#" + methodName + methodDescriptor;
}
}
}
/** Expected flag (or lack of flag) on a class, method, or field reference. */
public enum Flag {
// The following constants represent the exact visibility of a referenced class/method
PUBLIC {
@Override
public boolean supersedes(Flag anotherFlag) {
switch (anotherFlag) {
case PRIVATE_OR_HIGHER:
case PROTECTED_OR_HIGHER:
case PACKAGE_OR_HIGHER:
return true;
default:
return false;
}
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_PUBLIC & asmFlags) != 0;
}
},
PACKAGE_OR_HIGHER {
@Override
public boolean supersedes(Flag anotherFlag) {
return anotherFlag == PRIVATE_OR_HIGHER;
}
PROTECTED {
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_PUBLIC & asmFlags) != 0
|| ((Opcodes.ACC_PRIVATE & asmFlags) == 0 && (Opcodes.ACC_PROTECTED & asmFlags) == 0);
return (Opcodes.ACC_PROTECTED & asmFlags) != 0;
}
},
PROTECTED_OR_HIGHER {
@Override
public boolean supersedes(Flag anotherFlag) {
return anotherFlag == PRIVATE_OR_HIGHER;
}
PACKAGE {
@Override
public boolean matches(int asmFlags) {
return PUBLIC.matches(asmFlags) || (Opcodes.ACC_PROTECTED & asmFlags) != 0;
return !(PUBLIC.matches(asmFlags)
|| PROTECTED.matches(asmFlags)
|| PRIVATE.matches(asmFlags));
}
},
PRIVATE {
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_PRIVATE & asmFlags) != 0;
}
},
// The following constants represent a minimum access level required by a method call or field
// access
PROTECTED_OR_HIGHER {
@Override
public boolean matches(int asmFlags) {
return PUBLIC.matches(asmFlags) || PROTECTED.matches(asmFlags);
}
},
PACKAGE_OR_HIGHER {
@Override
public boolean matches(int asmFlags) {
return !PRIVATE.matches(asmFlags);
}
},
PRIVATE_OR_HIGHER {
@ -360,81 +365,60 @@ public class Reference {
return true;
}
},
NON_FINAL {
@Override
public boolean contradicts(Flag anotherFlag) {
return anotherFlag == FINAL;
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_FINAL & asmFlags) == 0;
}
},
// The following constants describe whether classes and methods are abstract or final
FINAL {
@Override
public boolean contradicts(Flag anotherFlag) {
return anotherFlag == NON_FINAL;
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_FINAL & asmFlags) != 0;
}
},
STATIC {
NON_FINAL {
@Override
public boolean contradicts(Flag anotherFlag) {
return anotherFlag == NON_STATIC;
public boolean matches(int asmFlags) {
return ((Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL) & asmFlags) == 0;
}
},
ABSTRACT {
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_ABSTRACT & asmFlags) != 0;
}
},
// The following constants describe whether a method/field is static or not
STATIC {
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_STATIC & asmFlags) != 0;
}
},
NON_STATIC {
@Override
public boolean contradicts(Flag anotherFlag) {
return anotherFlag == STATIC;
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_STATIC & asmFlags) == 0;
}
},
INTERFACE {
@Override
public boolean contradicts(Flag anotherFlag) {
return anotherFlag == NON_INTERFACE;
}
// The following constants describe whether a class is an interface
INTERFACE {
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_INTERFACE & asmFlags) != 0;
}
},
NON_INTERFACE {
@Override
public boolean contradicts(Flag anotherFlag) {
return anotherFlag == INTERFACE;
}
@Override
public boolean matches(int asmFlags) {
return (Opcodes.ACC_INTERFACE & asmFlags) == 0;
}
};
public boolean contradicts(Flag anotherFlag) {
return false;
}
public boolean supersedes(Flag anotherFlag) {
return false;
}
/**
* Predicate method that determines whether this flag is present in the passed bitmask.
*
* @see Opcodes
*/
public abstract boolean matches(int asmFlags);
}
@ -615,16 +599,30 @@ public class Reference {
return this;
}
public Builder withInterfaces(Collection<String> interfaceNames) {
interfaces.addAll(interfaceNames);
return this;
}
public Builder withInterface(String interfaceName) {
interfaces.add(interfaceName);
return this;
}
public Builder withSource(String sourceName) {
return withSource(sourceName, 0);
}
public Builder withSource(String sourceName, int line) {
sources.add(new Source(sourceName, line));
return this;
}
public Builder withFlags(Collection<Flag> flags) {
this.flags.addAll(flags);
return this;
}
public Builder withFlag(Flag flag) {
flags.add(flag);
return this;

View File

@ -0,0 +1,47 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opentelemetry.javaagent.tooling.muzzle;
/**
* Defines a set of packages for which we'll create references.
*
* <p>For now we're hardcoding this to the instrumentation and javaagent-tooling packages so we only
* create references from the method advice and helper classes.
*/
final class ReferenceCreationPredicate {
private static final String REFERENCE_CREATION_PACKAGE = "io.opentelemetry.instrumentation.";
private static final String JAVA_AGENT_PACKAGE = "io.opentelemetry.javaagent.tooling.";
private static final String[] REFERENCE_CREATION_PACKAGE_EXCLUDES = {
"io.opentelemetry.instrumentation.api.", "io.opentelemetry.instrumentation.auto.api."
};
static boolean shouldCreateReferenceFor(String className) {
if (!className.startsWith(REFERENCE_CREATION_PACKAGE)) {
return className.startsWith(JAVA_AGENT_PACKAGE);
}
for (String exclude : REFERENCE_CREATION_PACKAGE_EXCLUDES) {
if (className.startsWith(exclude)) {
return false;
}
}
return true;
}
private ReferenceCreationPredicate() {}
}

View File

@ -16,7 +16,12 @@
package io.opentelemetry.javaagent.tooling.muzzle;
import static com.google.common.base.Preconditions.checkNotNull;
import static io.opentelemetry.javaagent.tooling.muzzle.ReferenceCreationPredicate.shouldCreateReferenceFor;
import io.opentelemetry.javaagent.tooling.Utils;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Source;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayDeque;
@ -27,6 +32,7 @@ import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.jar.asm.ClassReader;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.FieldVisitor;
@ -43,18 +49,6 @@ import net.bytebuddy.jar.asm.Type;
// - inner class
// - cast opcodes in method bodies
public class ReferenceCreator extends ClassVisitor {
/**
* Classes in this namespace will be scanned and used to create references.
*
* <p>For now we're hardcoding this to the instrumentation package so we only create references
* from the method advice and helper classes.
*/
private static final String REFERENCE_CREATION_PACKAGE = "io.opentelemetry.instrumentation.";
private static final String[] REFERENCE_CREATION_PACKAGE_EXCLUDES = {
"io.opentelemetry.instrumentation.api.", "io.opentelemetry.instrumentation.auto.api."
};
/**
* Generate all references reachable from a given class.
*
@ -71,13 +65,20 @@ public class ReferenceCreator extends ClassVisitor {
Queue<String> instrumentationQueue = new ArrayDeque<>();
instrumentationQueue.add(entryPointClassName);
boolean isEntryPoint = true;
while (!instrumentationQueue.isEmpty()) {
String className = instrumentationQueue.remove();
visitedSources.add(className);
InputStream in = loader.getResourceAsStream(Utils.getResourceName(className));
try {
ReferenceCreator cv = new ReferenceCreator();
// only start from method bodies on the first pass
try (InputStream in =
checkNotNull(
loader.getResourceAsStream(Utils.getResourceName(className)),
"Couldn't find class file %s",
className)) {
// only start from method bodies for entry point class (skips class/method references)
ReferenceCreator cv = new ReferenceCreator(isEntryPoint);
ClassReader reader = new ClassReader(in);
reader.accept(cv, ClassReader.SKIP_FRAMES);
@ -85,7 +86,7 @@ public class ReferenceCreator extends ClassVisitor {
for (Map.Entry<String, Reference> entry : instrumentationReferences.entrySet()) {
String key = entry.getKey();
// Don't generate references created outside of the instrumentation package.
if (!visitedSources.contains(entry.getKey()) && isInReferenceCreationPackage(key)) {
if (!visitedSources.contains(entry.getKey()) && shouldCreateReferenceFor(key)) {
instrumentationQueue.add(key);
}
if (references.containsKey(key)) {
@ -97,31 +98,15 @@ public class ReferenceCreator extends ClassVisitor {
} catch (IOException e) {
throw new IllegalStateException("Error reading class " + className, e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
throw new IllegalStateException("Error closing class " + className, e);
}
}
}
if (isEntryPoint) {
isEntryPoint = false;
}
}
return references;
}
private static boolean isInReferenceCreationPackage(String className) {
if (!className.startsWith(REFERENCE_CREATION_PACKAGE)) {
return false;
}
for (String exclude : REFERENCE_CREATION_PACKAGE_EXCLUDES) {
if (className.startsWith(exclude)) {
return false;
}
}
return true;
}
/**
* Get the package of an internal class name.
*
@ -191,12 +176,14 @@ public class ReferenceCreator extends ClassVisitor {
return type;
}
private final boolean skipClassReferenceGeneration;
private final Map<String, Reference> references = new HashMap<>();
private String refSourceClassName;
private Type refSourceType;
private ReferenceCreator() {
private ReferenceCreator(boolean skipClassReferenceGeneration) {
super(Opcodes.ASM7);
this.skipClassReferenceGeneration = skipClassReferenceGeneration;
}
public Map<String, Reference> getReferences() {
@ -224,9 +211,32 @@ public class ReferenceCreator extends ClassVisitor {
String[] interfaces) {
refSourceClassName = Utils.getClassName(name);
refSourceType = Type.getType("L" + name + ";");
// Additional references we could check
// - supertype of class and visible from this package
// - interfaces of class and visible from this package
// class references are not generated for advice classes, only for helper classes
if (!skipClassReferenceGeneration) {
String fixedSuperClassName = Utils.getClassName(superName);
addReference(
new Reference.Builder(fixedSuperClassName).withSource(refSourceClassName).build());
List<String> fixedInterfaceNames = new ArrayList<>(interfaces.length);
for (String interfaceName : interfaces) {
String fixedInterfaceName = Utils.getClassName(interfaceName);
fixedInterfaceNames.add(fixedInterfaceName);
addReference(
new Reference.Builder(fixedInterfaceName).withSource(refSourceClassName).build());
}
addReference(
new Reference.Builder(refSourceClassName)
.withSource(refSourceClassName)
.withSuperName(fixedSuperClassName)
.withInterfaces(fixedInterfaceNames)
.withFlag(computeTypeManifestationFlag(access))
.build());
}
super.visit(version, access, name, signature, superName, interfaces);
}
@ -244,12 +254,71 @@ public class ReferenceCreator extends ClassVisitor {
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
// declared method references are not generated for advice classes, only for helper classes
if (!skipClassReferenceGeneration) {
Type methodType = Type.getMethodType(descriptor);
Flag visibilityFlag = computeVisibilityFlag(access);
Flag ownershipFlag = computeOwnershipFlag(access);
Flag manifestationFlag = computeTypeManifestationFlag(access);
// as an optimization skip constructors, private and static methods
if (!(visibilityFlag == Flag.PRIVATE
|| ownershipFlag == Flag.STATIC
|| MethodDescription.CONSTRUCTOR_INTERNAL_NAME.equals(name))) {
addReference(
new Reference.Builder(refSourceClassName)
.withSource(refSourceClassName)
.withMethod(
new Source[0],
new Flag[] {visibilityFlag, ownershipFlag, manifestationFlag},
name,
methodType.getReturnType(),
methodType.getArgumentTypes())
.build());
}
}
// Additional references we could check
// - Classes in signature (return type, params) and visible from this package
return new AdviceReferenceMethodVisitor(
super.visitMethod(access, name, descriptor, signature, exceptions));
}
/** @see net.bytebuddy.description.modifier.Visibility */
private static Flag computeVisibilityFlag(int access) {
if (Flag.PUBLIC.matches(access)) {
return Flag.PUBLIC;
} else if (Flag.PROTECTED.matches(access)) {
return Flag.PROTECTED;
} else if (Flag.PACKAGE.matches(access)) {
return Flag.PACKAGE;
} else {
return Flag.PRIVATE;
}
}
/** @see net.bytebuddy.description.modifier.Ownership */
private static Flag computeOwnershipFlag(int access) {
if (Flag.STATIC.matches(access)) {
return Flag.STATIC;
} else {
return Flag.NON_STATIC;
}
}
/** @see net.bytebuddy.description.modifier.TypeManifestation */
private static Flag computeTypeManifestationFlag(int access) {
if (Flag.ABSTRACT.matches(access)) {
return Flag.ABSTRACT;
} else if (Flag.FINAL.matches(access)) {
return Flag.FINAL;
} else {
return Flag.NON_FINAL;
}
}
private class AdviceReferenceMethodVisitor extends MethodVisitor {
private int currentLineNumber = -1;

View File

@ -16,18 +16,25 @@
package io.opentelemetry.javaagent.tooling.muzzle;
import static io.opentelemetry.javaagent.tooling.muzzle.ReferenceCreationPredicate.shouldCreateReferenceFor;
import static net.bytebuddy.dynamic.loading.ClassLoadingStrategy.BOOTSTRAP_LOADER;
import com.google.common.collect.Sets;
import io.opentelemetry.javaagent.bootstrap.WeakCache;
import io.opentelemetry.javaagent.tooling.AgentTooling;
import io.opentelemetry.javaagent.tooling.Utils;
import io.opentelemetry.javaagent.tooling.muzzle.HelperReferenceWrapper.Factory;
import io.opentelemetry.javaagent.tooling.muzzle.HelperReferenceWrapper.Method;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Mismatch;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Source;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import net.bytebuddy.description.field.FieldDescription;
@ -39,7 +46,7 @@ import net.bytebuddy.pool.TypePool;
public final class ReferenceMatcher {
private final WeakCache<ClassLoader, Boolean> mismatchCache = AgentTooling.newWeakCache();
private final Reference[] references;
private final Map<String, Reference> references;
private final Set<String> helperClassNames;
public ReferenceMatcher(Reference... references) {
@ -47,12 +54,15 @@ public final class ReferenceMatcher {
}
public ReferenceMatcher(String[] helperClassNames, Reference[] references) {
this.references = references;
this.references = new HashMap<>(references.length);
for (Reference reference : references) {
this.references.put(reference.getClassName(), reference);
}
this.helperClassNames = new HashSet<>(Arrays.asList(helperClassNames));
}
public Reference[] getReferences() {
return references;
public Collection<Reference> getReferences() {
return references.values();
}
/**
@ -77,13 +87,9 @@ public final class ReferenceMatcher {
}
private boolean doesMatch(ClassLoader loader) {
for (Reference reference : references) {
// Don't reference-check helper classes.
// They will be injected by the instrumentation's HelperInjector.
if (!helperClassNames.contains(reference.getClassName())) {
if (!checkMatch(reference, loader).isEmpty()) {
return false;
}
for (Reference reference : references.values()) {
if (!checkMatch(reference, loader).isEmpty()) {
return false;
}
}
@ -103,12 +109,8 @@ public final class ReferenceMatcher {
List<Mismatch> mismatches = Collections.emptyList();
for (Reference reference : references) {
// Don't reference-check helper classes.
// They will be injected by the instrumentation's HelperInjector.
if (!helperClassNames.contains(reference.getClassName())) {
mismatches = lazyAddAll(mismatches, checkMatch(reference, loader));
}
for (Reference reference : references.values()) {
mismatches = lazyAddAll(mismatches, checkMatch(reference, loader));
}
return mismatches;
@ -117,21 +119,29 @@ public final class ReferenceMatcher {
/**
* Check a reference against a classloader's classpath.
*
* @param loader
* @return A list of mismatched sources. A list of size 0 means the reference matches the class.
*/
private static List<Reference.Mismatch> checkMatch(Reference reference, ClassLoader loader) {
private List<Reference.Mismatch> checkMatch(Reference reference, ClassLoader loader) {
TypePool typePool =
AgentTooling.poolStrategy()
.typePool(AgentTooling.locationStrategy().classFileLocator(loader), loader);
try {
TypePool.Resolution resolution = typePool.describe(reference.getClassName());
if (!resolution.isResolved()) {
return Collections.<Mismatch>singletonList(
new Mismatch.MissingClass(
reference.getSources().toArray(new Source[0]), reference.getClassName()));
// helper classes get their own check: whether they implement all abstract methods
if (shouldCreateReferenceFor(reference.getClassName())) {
return checkHelperClassMatch(reference, typePool);
} else if (helperClassNames.contains(reference.getClassName())) {
// skip muzzle check for those helper classes that are not in instrumentation packages; e.g.
// some instrumentations inject guava types as helper classes
return Collections.emptyList();
} else {
TypePool.Resolution resolution = typePool.describe(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());
}
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
@ -147,7 +157,52 @@ public final class ReferenceMatcher {
}
}
public static List<Reference.Mismatch> checkMatch(
// for helper classes we make sure that all abstract methods from super classes and interfaces are
// implemented
private List<Reference.Mismatch> checkHelperClassMatch(Reference helperClass, TypePool typePool) {
List<Mismatch> mismatches = Collections.emptyList();
HelperReferenceWrapper helperWrapper = new Factory(typePool, references).create(helperClass);
if (!helperWrapper.hasSuperTypes() || helperWrapper.isAbstract()) {
return mismatches;
}
// treat the helper type as a bag of methods: collect all methods defined in the helper class,
// all superclasses and interfaces and check if all abstract methods are implemented somewhere
Set<HelperReferenceWrapper.Method> abstractMethods = new HashSet<>();
Set<HelperReferenceWrapper.Method> plainMethods = new HashSet<>();
collectMethodsFromTypeHierarchy(helperWrapper, abstractMethods, plainMethods);
Set<HelperReferenceWrapper.Method> unimplementedMethods =
Sets.difference(abstractMethods, plainMethods);
for (HelperReferenceWrapper.Method unimplementedMethod : unimplementedMethods) {
mismatches =
lazyAdd(
mismatches,
new Reference.Mismatch.MissingMethod(
helperClass.getSources().toArray(new Reference.Source[0]),
unimplementedMethod.getDeclaringClass(),
unimplementedMethod.getName(),
unimplementedMethod.getDescriptor()));
}
return mismatches;
}
private static void collectMethodsFromTypeHierarchy(
HelperReferenceWrapper type, Set<Method> abstractMethods, Set<Method> plainMethods) {
for (HelperReferenceWrapper.Method method : type.getMethods()) {
(method.isAbstract() ? abstractMethods : plainMethods).add(method);
}
for (HelperReferenceWrapper superType : type.getSuperTypes()) {
collectMethodsFromTypeHierarchy(superType, abstractMethods, plainMethods);
}
}
private static List<Reference.Mismatch> checkMatch(
Reference reference, TypeDescription typeOnClasspath) {
List<Mismatch> mismatches = Collections.emptyList();
@ -205,6 +260,7 @@ public final class ReferenceMatcher {
mismatches,
new Reference.Mismatch.MissingMethod(
methodRef.getSources().toArray(new Reference.Source[0]),
reference.getClassName(),
methodRef.getName(),
methodRef.getDescriptor()));
} else {

View File

@ -0,0 +1,45 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opentelemetry.javaagent.tooling.muzzle
import spock.lang.Specification
import spock.lang.Unroll
class ReferenceCreationPredicateTest extends Specification {
@Unroll
def "should create reference for #desc"() {
expect:
ReferenceCreationPredicate.shouldCreateReferenceFor(className)
where:
desc | className
"Instrumentation class" | "io.opentelemetry.instrumentation.some_instrumentation.Advice"
"javaagent-tooling class" | "io.opentelemetry.javaagent.tooling.Constants"
}
@Unroll
def "should not create reference for #desc"() {
expect:
!ReferenceCreationPredicate.shouldCreateReferenceFor(className)
where:
desc | className
"Java SDK class" | "java.util.ArrayList"
"instrumentation-api class" | "io.opentelemetry.instrumentation.api.InstrumentationVersion"
"auto-api class" | "io.opentelemetry.instrumentation.auto.api.ContextStore"
}
}

View File

@ -0,0 +1,138 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package muzzle
import io.opentelemetry.javaagent.tooling.muzzle.HelperReferenceWrapper
import io.opentelemetry.javaagent.tooling.muzzle.Reference
import net.bytebuddy.jar.asm.Type
import net.bytebuddy.pool.TypePool
import spock.lang.Shared
import spock.lang.Specification
class HelperReferenceWrapperTest extends Specification {
@Shared
def baseHelperClass = new Reference.Builder(HelperReferenceWrapperTest.name + '$BaseHelper')
.withSuperName(HelperReferenceWrapperTestClasses.AbstractClasspathType.name)
.withFlag(Reference.Flag.ABSTRACT)
.withMethod(new Reference.Source[0], new Reference.Flag[0], "foo", Type.VOID_TYPE)
.withMethod(new Reference.Source[0], [Reference.Flag.ABSTRACT] as Reference.Flag[], "abstract", Type.INT_TYPE)
.build()
@Shared
def helperClass = new Reference.Builder(HelperReferenceWrapperTest.name + '$Helper')
.withSuperName(baseHelperClass.className)
.withInterface(HelperReferenceWrapperTestClasses.Interface2.name)
.withMethod(new Reference.Source[0], new Reference.Flag[0], "bar", Type.VOID_TYPE)
.build()
def "should wrap helper types"() {
given:
def typePool = TypePool.Default.of(HelperReferenceWrapperTest.classLoader)
def references = [
(helperClass.className) : helperClass,
(baseHelperClass.className): baseHelperClass
]
when:
def helperWrapper = new HelperReferenceWrapper.Factory(typePool, references).create(helperClass)
then:
with(helperWrapper) { helper ->
!helper.abstract
with(helper.methods.toList()) {
it.size() == 1
with(it[0]) {
!it.abstract
it.name == "bar"
it.descriptor == "()V"
}
}
helper.hasSuperTypes()
with(helper.superTypes.toList()) {
it.size() == 2
with(it[0]) { baseHelper ->
baseHelper.abstract
with(baseHelper.methods.toList()) {
it.size() == 2
with(it[0]) {
!it.abstract
it.name == 'foo'
it.descriptor == '()V'
}
with(it[1]) {
it.abstract
it.name == 'abstract'
it.descriptor == '()I'
}
}
baseHelper.hasSuperTypes()
with(baseHelper.superTypes.toList()) {
it.size() == 1
with(it[0]) { abstractClasspathType ->
abstractClasspathType.abstract
abstractClasspathType.methods.empty
abstractClasspathType.hasSuperTypes()
with(abstractClasspathType.superTypes.toList()) {
it.size() == 2
with(it[0]) { object ->
!object.hasSuperTypes()
}
with(it[1]) { interface1 ->
interface1.abstract
with(interface1.methods.toList()) {
it.size() == 1
with(it[0]) {
it.abstract
it.name == "foo"
it.descriptor == "()V"
}
}
!interface1.hasSuperTypes()
interface1.superTypes.empty
}
}
}
}
}
with(it[1]) { interface2 ->
interface2.abstract
with(interface2.methods.toList()) {
it.size() == 1
with(it[0]) {
it.abstract
it.name == "bar"
it.descriptor == "()V"
}
}
!interface2.hasSuperTypes()
interface2.superTypes.empty
}
}
}
}
}

View File

@ -16,10 +16,12 @@
package muzzle
import static muzzle.TestClasses.HelperAdvice
import static muzzle.TestClasses.LdcAdvice
import static muzzle.TestClasses.MethodBodyAdvice
import io.opentelemetry.auto.test.AgentTestRunner
import io.opentelemetry.instrumentation.TestHelperClasses
import io.opentelemetry.javaagent.tooling.muzzle.Reference
import io.opentelemetry.javaagent.tooling.muzzle.ReferenceCreator
import spock.lang.Ignore
@ -27,82 +29,138 @@ import spock.lang.Ignore
class ReferenceCreatorTest extends AgentTestRunner {
def "method body creates references"() {
setup:
Map<String, Reference> references = ReferenceCreator.createReferencesFrom(MethodBodyAdvice.getName(), this.getClass().getClassLoader())
def references = ReferenceCreator.createReferencesFrom(MethodBodyAdvice.name, this.class.classLoader)
expect:
references.get('muzzle.TestClasses$MethodBodyAdvice$A') != null
references.get('muzzle.TestClasses$MethodBodyAdvice$B') != null
references.get('muzzle.TestClasses$MethodBodyAdvice$SomeInterface') != null
references.get('muzzle.TestClasses$MethodBodyAdvice$SomeImplementation') != null
references.keySet().size() == 4
references.keySet() == [
MethodBodyAdvice.A.name,
MethodBodyAdvice.B.name,
MethodBodyAdvice.SomeInterface.name,
MethodBodyAdvice.SomeImplementation.name
] as Set
def bRef = references[MethodBodyAdvice.B.name]
def aRef = references[MethodBodyAdvice.A.name]
// interface flags
references.get('muzzle.TestClasses$MethodBodyAdvice$B').getFlags().contains(Reference.Flag.NON_INTERFACE)
references.get('muzzle.TestClasses$MethodBodyAdvice$SomeInterface').getFlags().contains(Reference.Flag.INTERFACE)
bRef.flags.contains(Reference.Flag.NON_INTERFACE)
references[MethodBodyAdvice.SomeInterface.name].flags.contains(Reference.Flag.INTERFACE)
// class access flags
references.get('muzzle.TestClasses$MethodBodyAdvice$A').getFlags().contains(Reference.Flag.PACKAGE_OR_HIGHER)
references.get('muzzle.TestClasses$MethodBodyAdvice$B').getFlags().contains(Reference.Flag.PACKAGE_OR_HIGHER)
aRef.flags.contains(Reference.Flag.PACKAGE_OR_HIGHER)
bRef.flags.contains(Reference.Flag.PACKAGE_OR_HIGHER)
// method refs
Set<Reference.Method> bMethods = references.get('muzzle.TestClasses$MethodBodyAdvice$B').getMethods()
findMethod(bMethods, "aMethod", "(Ljava/lang/String;)Ljava/lang/String;") != null
findMethod(bMethods, "aMethodWithPrimitives", "(Z)V") != null
findMethod(bMethods, "aStaticMethod", "()V") != null
findMethod(bMethods, "aMethodWithArrays", "([Ljava/lang/String;)[Ljava/lang/Object;") != null
findMethod(bMethods, "aMethod", "(Ljava/lang/String;)Ljava/lang/String;").getFlags().contains(Reference.Flag.NON_STATIC)
findMethod(bMethods, "aStaticMethod", "()V").getFlags().contains(Reference.Flag.STATIC)
assertMethod bRef, 'aMethod', '(Ljava/lang/String;)Ljava/lang/String;',
Reference.Flag.PROTECTED_OR_HIGHER,
Reference.Flag.NON_STATIC
assertMethod bRef, 'aMethodWithPrimitives', '(Z)V',
Reference.Flag.PROTECTED_OR_HIGHER,
Reference.Flag.NON_STATIC
assertMethod bRef, 'aStaticMethod', '()V',
Reference.Flag.PROTECTED_OR_HIGHER,
Reference.Flag.STATIC
assertMethod bRef, 'aMethodWithArrays', '([Ljava/lang/String;)[Ljava/lang/Object;',
Reference.Flag.PROTECTED_OR_HIGHER,
Reference.Flag.NON_STATIC
// field refs
references.get('muzzle.TestClasses$MethodBodyAdvice$B').getFields().isEmpty()
Set<Reference.Field> aFieldRefs = references.get('muzzle.TestClasses$MethodBodyAdvice$A').getFields()
findField(aFieldRefs, "b").getFlags().contains(Reference.Flag.PACKAGE_OR_HIGHER)
findField(aFieldRefs, "b").getFlags().contains(Reference.Flag.NON_STATIC)
findField(aFieldRefs, "staticB").getFlags().contains(Reference.Flag.PACKAGE_OR_HIGHER)
findField(aFieldRefs, "staticB").getFlags().contains(Reference.Flag.STATIC)
aFieldRefs.size() == 2
bRef.fields.isEmpty()
aRef.fields.size() == 2
assertField aRef, 'b', Reference.Flag.PACKAGE_OR_HIGHER, Reference.Flag.NON_STATIC
assertField aRef, 'staticB', Reference.Flag.PACKAGE_OR_HIGHER, Reference.Flag.STATIC
}
def "protected ref test"() {
setup:
Map<String, Reference> references = ReferenceCreator.createReferencesFrom(MethodBodyAdvice.B2.getName(), this.getClass().getClassLoader())
def references = ReferenceCreator.createReferencesFrom(MethodBodyAdvice.B2.name, this.class.classLoader)
expect:
Set<Reference.Method> bMethods = references.get('muzzle.TestClasses$MethodBodyAdvice$B').getMethods()
findMethod(bMethods, "protectedMethod", "()V") != null
findMethod(bMethods, "protectedMethod", "()V").getFlags().contains(Reference.Flag.PROTECTED_OR_HIGHER)
assertMethod references[MethodBodyAdvice.B.name], 'protectedMethod', '()V',
Reference.Flag.PROTECTED_OR_HIGHER,
Reference.Flag.NON_STATIC
}
def "ldc creates references"() {
setup:
Map<String, Reference> references = ReferenceCreator.createReferencesFrom(LdcAdvice.getName(), this.getClass().getClassLoader())
def references = ReferenceCreator.createReferencesFrom(LdcAdvice.name, this.class.classLoader)
expect:
references.get('muzzle.TestClasses$MethodBodyAdvice$A') != null
references[MethodBodyAdvice.A.name] != null
}
def "instanceof creates references"() {
setup:
Map<String, Reference> references = ReferenceCreator.createReferencesFrom(TestClasses.InstanceofAdvice.getName(), this.getClass().getClassLoader())
def references = ReferenceCreator.createReferencesFrom(TestClasses.InstanceofAdvice.name, this.class.classLoader)
expect:
references.get('muzzle.TestClasses$MethodBodyAdvice$A') != null
references[MethodBodyAdvice.A.name] != null
}
// TODO: remove ignore when we drop java 7 support.
@Ignore
def "invokedynamic creates references"() {
setup:
Map<String, Reference> references = ReferenceCreator.createReferencesFrom(TestClasses.InDyAdvice.getName(), this.getClass().getClassLoader())
def references = ReferenceCreator.createReferencesFrom(TestClasses.InDyAdvice.name, this.class.classLoader)
expect:
references.get('muzzle.TestClasses$MethodBodyAdvice$SomeImplementation') != null
references.get('muzzle.TestClasses$MethodBodyAdvice$B') != null
references['muzzle.TestClasses$MethodBodyAdvice$SomeImplementation'] != null
references['muzzle.TestClasses$MethodBodyAdvice$B'] != null
}
private static Reference.Method findMethod(Set<Reference.Method> methods, String methodName, String methodDesc) {
for (Reference.Method method : methods) {
def "should create references for helper classes"() {
when:
def references = ReferenceCreator.createReferencesFrom(HelperAdvice.name, this.class.classLoader)
then:
references.keySet() == [
TestHelperClasses.Helper.name,
TestHelperClasses.HelperSuperClass.name,
TestHelperClasses.HelperInterface.name
] as Set
with(references[TestHelperClasses.HelperSuperClass.name]) { helperSuperClass ->
helperSuperClass.flags.contains(Reference.Flag.ABSTRACT)
assertHelperSuperClassMethod(helperSuperClass, true)
assertMethod helperSuperClass, 'finalMethod', '()Ljava/lang/String;',
Reference.Flag.PUBLIC,
Reference.Flag.NON_STATIC,
Reference.Flag.FINAL
}
with(references[TestHelperClasses.HelperInterface.name]) { helperInterface ->
helperInterface.flags.contains(Reference.Flag.ABSTRACT)
assertHelperInterfaceMethod helperInterface, true
}
with(references[TestHelperClasses.Helper.name]) { helperClass ->
helperClass.flags.contains(Reference.Flag.NON_FINAL)
assertHelperSuperClassMethod helperClass, false
assertHelperInterfaceMethod helperClass, false
}
}
private static assertHelperSuperClassMethod(Reference reference, boolean isAbstract) {
assertMethod reference, 'abstractMethod', '()I',
Reference.Flag.PROTECTED,
Reference.Flag.NON_STATIC,
isAbstract ? Reference.Flag.ABSTRACT : Reference.Flag.NON_FINAL
}
private static assertHelperInterfaceMethod(Reference reference, boolean isAbstract) {
assertMethod reference, 'foo', '()V',
Reference.Flag.PUBLIC,
Reference.Flag.NON_STATIC,
isAbstract ? Reference.Flag.ABSTRACT : Reference.Flag.NON_FINAL
}
private static assertMethod(Reference reference, String methodName, String methodDesc, Reference.Flag... flags) {
def method = findMethod reference, methodName, methodDesc
method != null && (method.flags == flags as Set)
}
private static findMethod(Reference reference, String methodName, String methodDesc) {
for (def method : reference.methods) {
if (method == new Reference.Method(methodName, methodDesc)) {
return method
}
@ -110,9 +168,14 @@ class ReferenceCreatorTest extends AgentTestRunner {
return null
}
private static Reference.Field findField(Set<Reference.Field> fields, String fieldName) {
for (Reference.Field field : fields) {
if (field.getName().equals(fieldName)) {
private static assertField(Reference reference, String fieldName, Reference.Flag... flags) {
def field = findField reference, fieldName
field != null && (field.flags == flags as Set)
}
private static Reference.Field findField(Reference reference, String fieldName) {
for (def field : reference.fields) {
if (field.name == fieldName) {
return field
}
}

View File

@ -16,6 +16,7 @@
package muzzle
import static io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag.ABSTRACT
import static io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag.INTERFACE
import static io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag.NON_INTERFACE
import static io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag.NON_STATIC
@ -30,6 +31,7 @@ import static muzzle.TestClasses.MethodBodyAdvice
import io.opentelemetry.auto.test.AgentTestRunner
import io.opentelemetry.auto.test.utils.ClasspathUtils
import io.opentelemetry.instrumentation.TestHelperClasses
import io.opentelemetry.javaagent.tooling.muzzle.Reference
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Source
import io.opentelemetry.javaagent.tooling.muzzle.ReferenceCreator
@ -54,12 +56,14 @@ class ReferenceMatcherTest extends AgentTestRunner {
def "match safe classpaths"() {
setup:
Reference[] refs = ReferenceCreator.createReferencesFrom(MethodBodyAdvice.getName(), this.getClass().getClassLoader()).values().toArray(new Reference[0])
ReferenceMatcher refMatcher = new ReferenceMatcher(refs)
Reference[] refs = ReferenceCreator.createReferencesFrom(MethodBodyAdvice.name, this.class.classLoader)
.values()
.toArray(new Reference[0])
def refMatcher = new ReferenceMatcher(refs)
expect:
getMismatchClassSet(refMatcher.getMismatchedReferenceSources(safeClasspath)) == new HashSet<>()
getMismatchClassSet(refMatcher.getMismatchedReferenceSources(unsafeClasspath)) == new HashSet<>([MissingClass])
getMismatchClassSet(refMatcher.getMismatchedReferenceSources(safeClasspath)).empty
getMismatchClassSet(refMatcher.getMismatchedReferenceSources(unsafeClasspath)) == [MissingClass] as Set
}
def "matching does not hold a strong reference to classloaders"() {
@ -83,19 +87,21 @@ class ReferenceMatcherTest extends AgentTestRunner {
def "muzzle type pool caches"() {
setup:
ClassLoader cl = new CountingClassLoader(
def cl = new CountingClassLoader(
[ClasspathUtils.createJarWithClasses(MethodBodyAdvice.A,
MethodBodyAdvice.B,
MethodBodyAdvice.SomeInterface,
MethodBodyAdvice.SomeImplementation)] as URL[],
(ClassLoader) null)
Reference[] refs = ReferenceCreator.createReferencesFrom(MethodBodyAdvice.getName(), this.getClass().getClassLoader()).values().toArray(new Reference[0])
ReferenceMatcher refMatcher1 = new ReferenceMatcher(refs)
ReferenceMatcher refMatcher2 = new ReferenceMatcher(refs)
assert getMismatchClassSet(refMatcher1.getMismatchedReferenceSources(cl)) == new HashSet<>()
Reference[] refs = ReferenceCreator.createReferencesFrom(MethodBodyAdvice.name, this.class.classLoader)
.values()
.toArray(new Reference[0])
def refMatcher1 = new ReferenceMatcher(refs)
def refMatcher2 = new ReferenceMatcher(refs)
assert getMismatchClassSet(refMatcher1.getMismatchedReferenceSources(cl)).empty
int countAfterFirstMatch = cl.count
// the second matcher should be able to used cached type descriptions from the first
assert getMismatchClassSet(refMatcher2.getMismatchedReferenceSources(cl)) == new HashSet<>()
assert getMismatchClassSet(refMatcher2.getMismatchedReferenceSources(cl)).empty
expect:
cl.count == countAfterFirstMatch
@ -103,30 +109,35 @@ class ReferenceMatcherTest extends AgentTestRunner {
def "matching ref #referenceName #referenceFlags against #classToCheck produces #expectedMismatches"() {
setup:
Reference.Builder builder = new Reference.Builder(referenceName)
for (Reference.Flag refFlag : referenceFlags) {
builder = builder.withFlag(refFlag)
}
Reference ref = builder.build()
def ref = new Reference.Builder(referenceName)
.withFlags(referenceFlags)
.build()
expect:
getMismatchClassSet(ReferenceMatcher.checkMatch(ref, this.getClass().getClassLoader())) == new HashSet<Object>(expectedMismatches)
when:
def mismatches = new ReferenceMatcher(ref).getMismatchedReferenceSources(this.class.classLoader)
then:
getMismatchClassSet(mismatches) == expectedMismatches as Set
where:
referenceName | referenceFlags | classToCheck | expectedMismatches
MethodBodyAdvice.B.getName() | [NON_INTERFACE] | MethodBodyAdvice.B | []
MethodBodyAdvice.B.getName() | [INTERFACE] | MethodBodyAdvice.B | [MissingFlag]
referenceName | referenceFlags | classToCheck | expectedMismatches
MethodBodyAdvice.B.name | [NON_INTERFACE] | MethodBodyAdvice.B | []
MethodBodyAdvice.B.name | [INTERFACE] | MethodBodyAdvice.B | [MissingFlag]
}
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())
def methodType = Type.getMethodType(methodDesc)
def reference = new Reference.Builder(classToCheck.name)
.withMethod(new Source[0], methodFlags as Reference.Flag[], methodName, methodType.returnType, methodType.argumentTypes)
.build()
expect:
getMismatchClassSet(ReferenceMatcher.checkMatch(reference, this.getClass().getClassLoader())) == new HashSet<Object>(expectedMismatches)
when:
def mismatches = new ReferenceMatcher(reference)
.getMismatchedReferenceSources(this.class.classLoader)
then:
getMismatchClassSet(mismatches) == expectedMismatches as Set
where:
methodName | methodDesc | methodFlags | classToCheck | expectedMismatches | methodTestDesc
@ -141,12 +152,16 @@ class ReferenceMatcherTest extends AgentTestRunner {
def "field match #fieldTestDesc"() {
setup:
Reference reference = new Reference.Builder(classToCheck.getName())
def reference = new Reference.Builder(classToCheck.name)
.withField(new Source[0], fieldFlags as Reference.Flag[], fieldName, Type.getType(fieldType))
.build()
expect:
getMismatchClassSet(ReferenceMatcher.checkMatch(reference, this.getClass().getClassLoader())) == new HashSet<Object>(expectedMismatches)
when:
def mismatches = new ReferenceMatcher(reference)
.getMismatchedReferenceSources(this.class.classLoader)
then:
getMismatchClassSet(mismatches) == expectedMismatches as Set
where:
fieldName | fieldType | fieldFlags | classToCheck | expectedMismatches | fieldTestDesc
@ -158,10 +173,109 @@ class ReferenceMatcherTest extends AgentTestRunner {
"staticB" | Type.getType(MethodBodyAdvice.B).getDescriptor() | [STATIC, PROTECTED_OR_HIGHER] | MethodBodyAdvice.A | [] | "match static field"
}
def "should ignore helper classes from third-party packages"() {
given:
def emptyClassLoader = new URLClassLoader(new URL[0], (ClassLoader) null)
def reference = new Reference.Builder("com.google.common.base.Strings")
.build()
when:
def mismatches = new ReferenceMatcher([reference.className] as String[], [reference] as Reference[])
.getMismatchedReferenceSources(emptyClassLoader)
then:
mismatches.empty
}
def "should not check abstract helper classes"() {
given:
def reference = new Reference.Builder("io.opentelemetry.instrumentation.Helper")
.withSuperName(TestHelperClasses.HelperSuperClass.name)
.withFlag(ABSTRACT)
.withMethod(new Source[0], [ABSTRACT] as Reference.Flag[], "unimplemented", Type.VOID_TYPE)
.build()
when:
def mismatches = new ReferenceMatcher([reference.className] as String[], [reference] as Reference[])
.getMismatchedReferenceSources(this.class.classLoader)
then:
mismatches.empty
}
def "should not check helper classes with no supertypes"() {
given:
def reference = new Reference.Builder("io.opentelemetry.instrumentation.Helper")
.withSuperName(Object.name)
.withMethod(new Source[0], [] as Reference.Flag[], "someMethod", Type.VOID_TYPE)
.build()
when:
def mismatches = new ReferenceMatcher([reference.className] as String[], [reference] as Reference[])
.getMismatchedReferenceSources(this.class.classLoader)
then:
mismatches.empty
}
def "should fail helper classes that does not implement all abstract methods"() {
given:
def reference = new Reference.Builder("io.opentelemetry.instrumentation.Helper")
.withSuperName(TestHelperClasses.HelperSuperClass.name)
.withMethod(new Source[0], [] as Reference.Flag[], "someMethod", Type.VOID_TYPE)
.build()
when:
def mismatches = new ReferenceMatcher([reference.className] as String[], [reference] as Reference[])
.getMismatchedReferenceSources(this.class.classLoader)
then:
getMismatchClassSet(mismatches) == [MissingMethod] as Set
}
def "should fail helper classes that does not implement all abstract methods - even if emtpy abstract class reference exists"() {
given:
def emptySuperClassRef = new Reference.Builder(TestHelperClasses.HelperSuperClass.name)
.build()
def reference = new Reference.Builder("io.opentelemetry.instrumentation.Helper")
.withSuperName(TestHelperClasses.HelperSuperClass.name)
.withMethod(new Source[0], [] as Reference.Flag[], "someMethod", Type.VOID_TYPE)
.build()
when:
def mismatches = new ReferenceMatcher([reference.className] as String[], [reference, emptySuperClassRef] as Reference[])
.getMismatchedReferenceSources(this.class.classLoader)
then:
getMismatchClassSet(mismatches) == [MissingMethod] as Set
}
def "should check whether interface methods are implemented in the super class"() {
given:
def baseHelper = new Reference.Builder("io.opentelemetry.instrumentation.BaseHelper")
.withSuperName(Object.name)
.withInterface(TestHelperClasses.HelperInterface.name)
.withMethod(new Source[0], [] as Reference.Flag[], "foo", Type.VOID_TYPE)
.build()
// abstract HelperInterface#foo() is implemented by BaseHelper
def helper = new Reference.Builder("io.opentelemetry.instrumentation.Helper")
.withSuperName(baseHelper.className)
.withInterface(TestHelperClasses.AnotherHelperInterface.name)
.withMethod(new Source[0], [] as Reference.Flag[], "bar", Type.VOID_TYPE)
.build()
when:
def mismatches = new ReferenceMatcher([helper.className, baseHelper] as String[], [helper, baseHelper] as Reference[])
.getMismatchedReferenceSources(this.class.classLoader)
then:
mismatches.empty
}
private static Set<Class> getMismatchClassSet(List<Reference.Mismatch> mismatches) {
Set<Class> mismatchClasses = new HashSet<>(mismatches.size())
for (Reference.Mismatch mismatch : mismatches) {
mismatchClasses.add(mismatch.getClass())
mismatchClasses.add(mismatch.class)
}
return mismatchClasses
}

View File

@ -0,0 +1,68 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opentelemetry.instrumentation;
import java.util.ArrayList;
import java.util.List;
public class TestHelperClasses {
public static class Helper extends HelperSuperClass implements HelperInterface {
@Override
public void foo() {
List<String> list = new ArrayList<>();
list.add(getStr());
}
@Override
protected int abstractMethod() {
return 54321;
}
private String getStr() {
return "abc";
}
}
public interface HelperInterface {
void foo();
}
public interface AnotherHelperInterface extends HelperInterface {
void bar();
int hashCode();
boolean equals(Object other);
Object clone();
void finalize();
}
public abstract static class HelperSuperClass {
protected abstract int abstractMethod();
public final String finalMethod() {
return "42";
}
static int bar() {
return 12345;
}
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package muzzle;
public class HelperReferenceWrapperTestClasses {
interface Interface1 {
void foo();
}
interface Interface2 {
void bar();
}
abstract static class AbstractClasspathType implements Interface1 {
static void staticMethodsAreIgnored() {}
private void privateMethodsToo() {}
}
}

View File

@ -16,6 +16,7 @@
package muzzle;
import io.opentelemetry.instrumentation.TestHelperClasses.Helper;
import net.bytebuddy.asm.Advice;
public class TestClasses {
@ -105,4 +106,10 @@ public class TestClasses {
// return a::someMethod;
// }
}
public static class HelperAdvice {
public static void adviceMethod() {
Helper h = new Helper();
}
}
}