Make muzzle generate helperClassNames() method (#1714)

This commit is contained in:
Mateusz Rzeszutek 2020-11-24 19:07:22 +01:00 committed by GitHub
parent 45646ff367
commit 13c405c174
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1114 additions and 736 deletions

View File

@ -12,7 +12,7 @@ Muzzle will prevent loading an instrumentation if it detects any mismatch or con
## How it works ## How it works
Muzzle has two phases: Muzzle has two phases:
* at compile time it collects references to the third-party symbols; * at compile time it collects references to the third-party symbols and used helper classes;
* at runtime it compares those references to the actual API symbols on the classpath. * at runtime it compares those references to the actual API symbols on the classpath.
### Compile-time reference collection ### Compile-time reference collection
@ -24,12 +24,16 @@ For each instrumentation module the ByteBuddy plugin collects symbols referring
third party APIs used by the currently processed module's type instrumentations (`InstrumentationModule#typeInstrumentations()`). third party APIs used by the currently processed module's type instrumentations (`InstrumentationModule#typeInstrumentations()`).
The reference collection process starts from advice classes (values of the map returned by the The reference collection process starts from advice classes (values of the map returned by the
`TypeInstrumentation#transformers()`method) and traverses the class graph until it encounters `TypeInstrumentation#transformers()`method) and traverses the class graph until it encounters
a reference to a non-instrumentation class. a reference to a non-instrumentation class (determined by `InstrumentationClassPredicate`).
Aside from references, the collection process also builds a graph of dependencies between internal
instrumentation helper classes - this dependency graph is later used to construct a list of helper
classes that will be injected to the application classloader (`InstrumentationModule#getMuzzleHelperClassNames()`).
All collected references are then used to create a `ReferenceMatcher` instance. This matcher All collected references are then used to create a `ReferenceMatcher` instance. This matcher
is stored in the instrumentation module class in the method `InstrumentationModule#getMuzzleReferenceMatcher()` is stored in the instrumentation module class in the method `InstrumentationModule#getMuzzleReferenceMatcher()`
and is shared between all type instrumentations. The bytecode of this method (basically an array of and is shared between all type instrumentations. The bytecode of this method (basically an array of
`Reference` builder calls) is generated automatically by the ByteBuddy plugin using an ASM code visitor. `Reference` builder calls) and the `getMuzzleHelperClassNames()` is generated automatically by the
ByteBuddy plugin using an ASM code visitor.
The source code of the compile-time plugin is located in the `javaagent-tooling` module, The source code of the compile-time plugin is located in the `javaagent-tooling` module,
package `io.opentelemetry.javaagent.tooling.muzzle.collector`. package `io.opentelemetry.javaagent.tooling.muzzle.collector`.

View File

@ -23,6 +23,11 @@ public class ClassLoaderInstrumentationModule extends InstrumentationModule {
return new String[] {"io.opentelemetry.javaagent.tooling.Constants"}; return new String[] {"io.opentelemetry.javaagent.tooling.Constants"};
} }
@Override
protected String[] additionalHelperClassNames() {
return new String[] {"io.opentelemetry.javaagent.tooling.Constants"};
}
@Override @Override
public List<TypeInstrumentation> typeInstrumentations() { public List<TypeInstrumentation> typeInstrumentations() {
return asList(new ClassLoaderInstrumentation(), new ResourceInjectionInstrumentation()); return asList(new ClassLoaderInstrumentation(), new ResourceInjectionInstrumentation());

View File

@ -18,6 +18,13 @@ public class CouchbaseInstrumentationModule extends InstrumentationModule {
super("couchbase", "couchbase-2.0"); super("couchbase", "couchbase-2.0");
} }
@Override
protected String[] additionalHelperClassNames() {
return new String[] {
"rx.__OpenTelemetryTracingUtil",
};
}
@Override @Override
public String[] helperClassNames() { public String[] helperClassNames() {
return new String[] { return new String[] {

View File

@ -34,6 +34,18 @@ public class Elasticsearch5TransportClientInstrumentationModule extends Instrume
super("elasticsearch-transport", "elasticsearch-transport-5.0", "elasticsearch"); super("elasticsearch-transport", "elasticsearch-transport-5.0", "elasticsearch");
} }
@Override
protected String[] additionalHelperClassNames() {
return new String[] {
// TODO: use Java 8 Collectors.joining() instead
"com.google.common.base.Preconditions",
"com.google.common.base.Joiner",
"com.google.common.base.Joiner$1",
"com.google.common.base.Joiner$2",
"com.google.common.base.Joiner$MapJoiner",
};
}
@Override @Override
public String[] helperClassNames() { public String[] helperClassNames() {
return new String[] { return new String[] {

View File

@ -35,6 +35,18 @@ public class Elasticsearch53TransportClientInstrumentationModule extends Instrum
super("elasticsearch-transport", "elasticsearch-transport-5.3", "elasticsearch"); super("elasticsearch-transport", "elasticsearch-transport-5.3", "elasticsearch");
} }
@Override
protected String[] additionalHelperClassNames() {
return new String[] {
// TODO: use Java 8 Collectors.joining() instead
"com.google.common.base.Preconditions",
"com.google.common.base.Joiner",
"com.google.common.base.Joiner$1",
"com.google.common.base.Joiner$2",
"com.google.common.base.Joiner$MapJoiner",
};
}
@Override @Override
public String[] helperClassNames() { public String[] helperClassNames() {
return new String[] { return new String[] {

View File

@ -38,6 +38,18 @@ public class Elasticsearch6TransportClientInstrumentationModule extends Instrume
super("elasticsearch-transport", "elasticsearch-transport-6.0", "elasticsearch"); super("elasticsearch-transport", "elasticsearch-transport-6.0", "elasticsearch");
} }
@Override
protected String[] additionalHelperClassNames() {
return new String[] {
// TODO: use Java 8 Collectors.joining() instead
"com.google.common.base.Preconditions",
"com.google.common.base.Joiner",
"com.google.common.base.Joiner$1",
"com.google.common.base.Joiner$2",
"com.google.common.base.Joiner$MapJoiner",
};
}
@Override @Override
public String[] helperClassNames() { public String[] helperClassNames() {
return new String[] { return new String[] {

View File

@ -38,6 +38,13 @@ public class HystrixInstrumentationModule extends InstrumentationModule {
super("hystrix", "hystrix-1.4"); super("hystrix", "hystrix-1.4");
} }
@Override
protected String[] additionalHelperClassNames() {
return new String[] {
"rx.__OpenTelemetryTracingUtil",
};
}
@Override @Override
public String[] helperClassNames() { public String[] helperClassNames() {
return new String[] { return new String[] {

View File

@ -31,6 +31,13 @@ public class KubernetesClientInstrumentationModule extends InstrumentationModule
super("kubernetes-client", "kubernetes-client-3.0"); super("kubernetes-client", "kubernetes-client-3.0");
} }
@Override
protected String[] additionalHelperClassNames() {
return new String[] {
"com.google.common.base.Strings",
};
}
@Override @Override
public String[] helperClassNames() { public String[] helperClassNames() {
return new String[] { return new String[] {

View File

@ -19,6 +19,7 @@ import io.opentelemetry.javaagent.tooling.bytebuddy.ExceptionHandlers;
import io.opentelemetry.javaagent.tooling.context.FieldBackedProvider; import io.opentelemetry.javaagent.tooling.context.FieldBackedProvider;
import io.opentelemetry.javaagent.tooling.context.InstrumentationContextProvider; import io.opentelemetry.javaagent.tooling.context.InstrumentationContextProvider;
import io.opentelemetry.javaagent.tooling.context.NoopContextProvider; import io.opentelemetry.javaagent.tooling.context.NoopContextProvider;
import io.opentelemetry.javaagent.tooling.muzzle.InstrumentationClassPredicate;
import io.opentelemetry.javaagent.tooling.muzzle.matcher.Mismatch; import io.opentelemetry.javaagent.tooling.muzzle.matcher.Mismatch;
import io.opentelemetry.javaagent.tooling.muzzle.matcher.ReferenceMatcher; import io.opentelemetry.javaagent.tooling.muzzle.matcher.ReferenceMatcher;
import java.security.ProtectionDomain; import java.security.ProtectionDomain;
@ -55,6 +56,12 @@ public abstract class InstrumentationModule {
private final Set<String> instrumentationNames; private final Set<String> instrumentationNames;
protected final boolean enabled; protected final boolean enabled;
/**
* Deprecated, will be removed.
*
* @deprecated Will be removed together with {@link #helperClassNames()}
*/
@Deprecated
protected final String packageName = protected final String packageName =
getClass().getPackage() == null ? "" : getClass().getPackage().getName(); getClass().getPackage() == null ? "" : getClass().getPackage().getName();
@ -113,7 +120,7 @@ public abstract class InstrumentationModule {
return parentAgentBuilder; return parentAgentBuilder;
} }
List<String> helperClassNames = asList(helperClassNames()); List<String> helperClassNames = getAllHelperClassNames();
List<String> helperResourceNames = asList(helperResourceNames()); List<String> helperResourceNames = asList(helperResourceNames());
List<TypeInstrumentation> typeInstrumentations = typeInstrumentations(); List<TypeInstrumentation> typeInstrumentations = typeInstrumentations();
if (typeInstrumentations.isEmpty()) { if (typeInstrumentations.isEmpty()) {
@ -160,6 +167,17 @@ public abstract class InstrumentationModule {
return agentBuilder; return agentBuilder;
} }
/**
* Returns all helper classes that will be injected into the application classloader, both ones
* provided by the implementation and ones that were collected by muzzle during compilation.
*/
public final List<String> getAllHelperClassNames() {
List<String> helperClassNames = new ArrayList<>();
helperClassNames.addAll(asList(additionalHelperClassNames()));
helperClassNames.addAll(asList(getMuzzleHelperClassNames()));
return helperClassNames;
}
private AgentBuilder.Identified.Extendable applyInstrumentationTransformers( private AgentBuilder.Identified.Extendable applyInstrumentationTransformers(
Map<? extends ElementMatcher<? super MethodDescription>, String> transformers, Map<? extends ElementMatcher<? super MethodDescription>, String> transformers,
AgentBuilder.Identified.Extendable agentBuilder) { AgentBuilder.Identified.Extendable agentBuilder) {
@ -246,6 +264,30 @@ public abstract class InstrumentationModule {
return null; return null;
} }
/**
* Returns a list of instrumentation helper classes, automatically detected by muzzle during
* compilation. Those helpers will be injected into the application classloader.
*
* <p>The actual implementation of this method is generated automatically during compilation by
* the {@link io.opentelemetry.javaagent.tooling.muzzle.collector.MuzzleCodeGenerationPlugin}
* ByteBuddy plugin.
*
* <p><b>This method is generated automatically, do not override it.</b>
*/
protected String[] getMuzzleHelperClassNames() {
return EMPTY;
}
/**
* Instrumentation modules can override this method to provide additional helper classes that are
* not located in instrumentation packages described in {@link InstrumentationClassPredicate} (and
* not automatically detected by muzzle). These additional classes will be injected into the
* application classloader first.
*/
protected String[] additionalHelperClassNames() {
return EMPTY;
}
/** /**
* Order of adding instrumentation to ByteBuddy. For example instrumentation with order 1 runs * Order of adding instrumentation to ByteBuddy. For example instrumentation with order 1 runs
* after an instrumentation with order 0 (default) matched on the same API. * after an instrumentation with order 0 (default) matched on the same API.
@ -256,7 +298,13 @@ public abstract class InstrumentationModule {
return 0; return 0;
} }
/** Returns class names of helpers to inject into the user's classloader. */ /**
* Deprecated, will be removed.
*
* @deprecated This method is replaced by {@link #getMuzzleHelperClassNames()} and {@link
* #additionalHelperClassNames()}, extending it provides no effect.
*/
@Deprecated
public String[] helperClassNames() { public String[] helperClassNames() {
return EMPTY; return EMPTY;
} }

View File

@ -9,8 +9,7 @@ import io.opentelemetry.javaagent.tooling.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.Utils; import io.opentelemetry.javaagent.tooling.Utils;
import io.opentelemetry.javaagent.tooling.muzzle.Reference; import io.opentelemetry.javaagent.tooling.muzzle.Reference;
import io.opentelemetry.javaagent.tooling.muzzle.matcher.ReferenceMatcher; import io.opentelemetry.javaagent.tooling.muzzle.matcher.ReferenceMatcher;
import java.util.HashMap; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -40,8 +39,9 @@ import net.bytebuddy.pool.TypePool;
* <p>This class is run at compile time by the {@link MuzzleCodeGenerationPlugin} ByteBuddy plugin. * <p>This class is run at compile time by the {@link MuzzleCodeGenerationPlugin} ByteBuddy plugin.
*/ */
class MuzzleCodeGenerator implements AsmVisitorWrapper { class MuzzleCodeGenerator implements AsmVisitorWrapper {
public static final String MUZZLE_FIELD_NAME = "muzzleReferenceMatcher"; public static final String MUZZLE_REF_MATCHER_FIELD_NAME = "muzzleReferenceMatcher";
public static final String MUZZLE_METHOD_NAME = "getMuzzleReferenceMatcher"; public static final String MUZZLE_REF_MATCHER_METHOD_NAME = "getMuzzleReferenceMatcher";
public static final String MUZZLE_HELPER_CLASSES_METHOD_NAME = "getMuzzleHelperClassNames";
@Override @Override
public int mergeWriter(int flags) { public int mergeWriter(int flags) {
@ -106,7 +106,7 @@ class MuzzleCodeGenerator implements AsmVisitorWrapper {
@Override @Override
public FieldVisitor visitField( public FieldVisitor visitField(
int access, String name, String descriptor, String signature, Object value) { int access, String name, String descriptor, String signature, Object value) {
if (MUZZLE_FIELD_NAME.equals(name)) { if (MUZZLE_REF_MATCHER_FIELD_NAME.equals(name)) {
// muzzle field has been generated // muzzle field has been generated
// by previous compilation // by previous compilation
// ignore and recompute in visitEnd // ignore and recompute in visitEnd
@ -118,7 +118,7 @@ class MuzzleCodeGenerator implements AsmVisitorWrapper {
@Override @Override
public MethodVisitor visitMethod( public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) { int access, String name, String descriptor, String signature, String[] exceptions) {
if (MUZZLE_METHOD_NAME.equals(name)) { if (MUZZLE_REF_MATCHER_METHOD_NAME.equals(name)) {
// muzzle getter has been generated // muzzle getter has been generated
// by previous compilation // by previous compilation
// ignore and recompute in visitEnd // ignore and recompute in visitEnd
@ -132,212 +132,183 @@ class MuzzleCodeGenerator implements AsmVisitorWrapper {
return methodVisitor; return methodVisitor;
} }
public Reference[] generateReferences() { @Override
Map<String, Reference> references = new HashMap<>(); public void visitEnd() {
ReferenceCollector collector = collectReferences();
generateMuzzleHelperClassNamesMethod(collector);
generateMuzzleReferenceMatcherMethod(collector);
generateMuzzleReferenceMatcherField();
super.visitEnd();
}
private ReferenceCollector collectReferences() {
Set<String> adviceClassNames = Set<String> adviceClassNames =
instrumenter.typeInstrumentations().stream() instrumenter.typeInstrumentations().stream()
.flatMap(typeInstrumentation -> typeInstrumentation.transformers().values().stream()) .flatMap(typeInstrumentation -> typeInstrumentation.transformers().values().stream())
.collect(Collectors.toSet()); .collect(Collectors.toSet());
ReferenceCollector collector = new ReferenceCollector();
for (String adviceClass : adviceClassNames) { for (String adviceClass : adviceClassNames) {
for (Map.Entry<String, Reference> entry : collector.collectReferencesFrom(adviceClass);
ReferenceCollector.collectReferencesFrom(adviceClass).entrySet()) {
if (references.containsKey(entry.getKey())) {
references.put(entry.getKey(), references.get(entry.getKey()).merge(entry.getValue()));
} else {
references.put(entry.getKey(), entry.getValue());
}
}
} }
return references.values().toArray(new Reference[0]); return collector;
} }
@Override private void generateMuzzleHelperClassNamesMethod(ReferenceCollector collector) {
public void visitEnd() { /*
{ // generate getMuzzleReferenceMatcher method * protected String[] getMuzzleHelperClassNames() {
/* * return new String[] {
* protected synchronized ReferenceMatcher getMuzzleReferenceMatcher() { * // sorted helper class names
* if (null == this.muzzleReferenceMatcher) { * };
* this.muzzleReferenceMatcher = new ReferenceMatcher(this.helperClassNames(), * }
* new Reference[]{ */
* //reference builders MethodVisitor mv =
* }); super.visitMethod(
* } Opcodes.ACC_PROTECTED,
* return this.muzzleReferenceMatcher; MUZZLE_HELPER_CLASSES_METHOD_NAME,
* }
*/
try {
MethodVisitor mv =
super.visitMethod(
Opcodes.ACC_PROTECTED + Opcodes.ACC_SYNCHRONIZED,
MUZZLE_METHOD_NAME,
"()Lio/opentelemetry/javaagent/tooling/muzzle/matcher/ReferenceMatcher;",
null,
null);
mv.visitCode();
Label start = new Label();
Label ret = new Label();
Label finish = new Label();
mv.visitLabel(start);
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(
Opcodes.GETFIELD,
instrumentationClassName,
MUZZLE_FIELD_NAME,
"Lio/opentelemetry/javaagent/tooling/muzzle/matcher/ReferenceMatcher;");
mv.visitJumpInsn(Opcodes.IF_ACMPNE, ret);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitTypeInsn(
Opcodes.NEW, "io/opentelemetry/javaagent/tooling/muzzle/matcher/ReferenceMatcher");
mv.visitInsn(Opcodes.DUP);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
instrumentationClassName,
"helperClassNames",
"()[Ljava/lang/String;", "()[Ljava/lang/String;",
false); null,
null);
mv.visitCode();
Reference[] references = generateReferences(); List<String> helperClassNames = collector.getSortedHelperClasses();
mv.visitLdcInsn(references.length);
mv.visitLdcInsn(helperClassNames.size());
// stack: size
mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
// stack: array
for (int i = 0; i < helperClassNames.size(); ++i) {
String helperClassName = helperClassNames.get(i);
mv.visitInsn(Opcodes.DUP);
// stack: array, array
mv.visitLdcInsn(i);
// stack: array, array, i
mv.visitLdcInsn(helperClassName);
// stack: array, array, i, helperClassName
mv.visitInsn(Opcodes.AASTORE);
// stack: array
}
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void generateMuzzleReferenceMatcherMethod(ReferenceCollector collector) {
/*
* protected synchronized ReferenceMatcher getMuzzleReferenceMatcher() {
* if (null == this.muzzleReferenceMatcher) {
* this.muzzleReferenceMatcher = new ReferenceMatcher(this.helperClassNames(),
* new Reference[]{
* //reference builders
* });
* }
* return this.muzzleReferenceMatcher;
* }
*/
try {
MethodVisitor mv =
super.visitMethod(
Opcodes.ACC_PROTECTED + Opcodes.ACC_SYNCHRONIZED,
MUZZLE_REF_MATCHER_METHOD_NAME,
"()Lio/opentelemetry/javaagent/tooling/muzzle/matcher/ReferenceMatcher;",
null,
null);
mv.visitCode();
Label start = new Label();
Label ret = new Label();
Label finish = new Label();
mv.visitLabel(start);
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(
Opcodes.GETFIELD,
instrumentationClassName,
MUZZLE_REF_MATCHER_FIELD_NAME,
"Lio/opentelemetry/javaagent/tooling/muzzle/matcher/ReferenceMatcher;");
mv.visitJumpInsn(Opcodes.IF_ACMPNE, ret);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitTypeInsn(
Opcodes.NEW, "io/opentelemetry/javaagent/tooling/muzzle/matcher/ReferenceMatcher");
mv.visitInsn(Opcodes.DUP);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
instrumentationClassName,
"getAllHelperClassNames",
"()Ljava/util/List;",
false);
Reference[] references = collector.getReferences().values().toArray(new Reference[0]);
mv.visitLdcInsn(references.length);
mv.visitTypeInsn(Opcodes.ANEWARRAY, "io/opentelemetry/javaagent/tooling/muzzle/Reference");
for (int i = 0; i < references.length; ++i) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(i);
mv.visitTypeInsn( mv.visitTypeInsn(
Opcodes.ANEWARRAY, "io/opentelemetry/javaagent/tooling/muzzle/Reference"); Opcodes.NEW, "io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder");
mv.visitInsn(Opcodes.DUP);
for (int i = 0; i < references.length; ++i) { mv.visitLdcInsn(references[i].getClassName());
mv.visitInsn(Opcodes.DUP); mv.visitMethodInsn(
mv.visitLdcInsn(i); Opcodes.INVOKESPECIAL,
mv.visitTypeInsn( "io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder",
Opcodes.NEW, "io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder"); "<init>",
mv.visitInsn(Opcodes.DUP); "(Ljava/lang/String;)V",
mv.visitLdcInsn(references[i].getClassName()); false);
for (Reference.Source source : references[i].getSources()) {
mv.visitLdcInsn(source.getName());
mv.visitLdcInsn(source.getLine());
mv.visitMethodInsn( mv.visitMethodInsn(
Opcodes.INVOKESPECIAL, Opcodes.INVOKEVIRTUAL,
"io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder", "io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder",
"<init>", "withSource",
"(Ljava/lang/String;)V", "(Ljava/lang/String;I)Lio/opentelemetry/javaagent/tooling/muzzle/Reference$Builder;",
false); false);
for (Reference.Source source : references[i].getSources()) { }
mv.visitLdcInsn(source.getName()); for (Reference.Flag flag : references[i].getFlags()) {
mv.visitLdcInsn(source.getLine()); String enumClassName = getEnumClassInternalName(flag);
mv.visitMethodInsn( mv.visitFieldInsn(
Opcodes.INVOKEVIRTUAL, Opcodes.GETSTATIC, enumClassName, flag.name(), "L" + enumClassName + ";");
"io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder", mv.visitMethodInsn(
"withSource", Opcodes.INVOKEVIRTUAL,
"(Ljava/lang/String;I)Lio/opentelemetry/javaagent/tooling/muzzle/Reference$Builder;", "io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder",
false); "withFlag",
} "(Lio/opentelemetry/javaagent/tooling/muzzle/Reference$Flag;)Lio/opentelemetry/javaagent/tooling/muzzle/Reference$Builder;",
for (Reference.Flag flag : references[i].getFlags()) { false);
String enumClassName = getEnumClassInternalName(flag); }
mv.visitFieldInsn( if (null != references[i].getSuperName()) {
Opcodes.GETSTATIC, enumClassName, flag.name(), "L" + enumClassName + ";"); mv.visitLdcInsn(references[i].getSuperName());
mv.visitMethodInsn( mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, Opcodes.INVOKEVIRTUAL,
"io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder", "io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder",
"withFlag", "withSuperName",
"(Lio/opentelemetry/javaagent/tooling/muzzle/Reference$Flag;)Lio/opentelemetry/javaagent/tooling/muzzle/Reference$Builder;", "(Ljava/lang/String;)Lio/opentelemetry/javaagent/tooling/muzzle/Reference$Builder;",
false); false);
} }
if (null != references[i].getSuperName()) { for (String interfaceName : references[i].getInterfaces()) {
mv.visitLdcInsn(references[i].getSuperName()); mv.visitLdcInsn(interfaceName);
mv.visitMethodInsn( mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, Opcodes.INVOKEVIRTUAL,
"io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder", "io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder",
"withSuperName", "withInterface",
"(Ljava/lang/String;)Lio/opentelemetry/javaagent/tooling/muzzle/Reference$Builder;", "(Ljava/lang/String;)Lio/opentelemetry/javaagent/tooling/muzzle/Reference$Builder;",
false); false);
} }
for (String interfaceName : references[i].getInterfaces()) { for (Reference.Field field : references[i].getFields()) {
mv.visitLdcInsn(interfaceName); { // sources
mv.visitMethodInsn( mv.visitLdcInsn(field.getSources().size());
Opcodes.INVOKEVIRTUAL,
"io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder",
"withInterface",
"(Ljava/lang/String;)Lio/opentelemetry/javaagent/tooling/muzzle/Reference$Builder;",
false);
}
for (Reference.Field field : references[i].getFields()) {
{ // sources
mv.visitLdcInsn(field.getSources().size());
mv.visitTypeInsn(
Opcodes.ANEWARRAY,
"io/opentelemetry/javaagent/tooling/muzzle/Reference$Source");
int j = 0;
for (Reference.Source source : field.getSources()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
mv.visitTypeInsn(
Opcodes.NEW, "io/opentelemetry/javaagent/tooling/muzzle/Reference$Source");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(source.getName());
mv.visitLdcInsn(source.getLine());
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
"io/opentelemetry/javaagent/tooling/muzzle/Reference$Source",
"<init>",
"(Ljava/lang/String;I)V",
false);
mv.visitInsn(Opcodes.AASTORE);
++j;
}
}
{ // flags
mv.visitLdcInsn(field.getFlags().size());
mv.visitTypeInsn(
Opcodes.ANEWARRAY, "io/opentelemetry/javaagent/tooling/muzzle/Reference$Flag");
int j = 0;
for (Reference.Flag flag : field.getFlags()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
String enumClassName = getEnumClassInternalName(flag);
mv.visitFieldInsn(
Opcodes.GETSTATIC, enumClassName, flag.name(), "L" + enumClassName + ";");
mv.visitInsn(Opcodes.AASTORE);
++j;
}
}
mv.visitLdcInsn(field.getName());
{ // field type
mv.visitLdcInsn(field.getType().getDescriptor());
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
Type.getInternalName(Type.class),
"getType",
Type.getMethodDescriptor(Type.class.getMethod("getType", String.class)),
false);
}
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder",
"withField",
Type.getMethodDescriptor(
Reference.Builder.class.getMethod(
"withField",
Reference.Source[].class,
Reference.Flag[].class,
String.class,
Type.class)),
false);
}
for (Reference.Method method : references[i].getMethods()) {
mv.visitLdcInsn(method.getSources().size());
mv.visitTypeInsn( mv.visitTypeInsn(
Opcodes.ANEWARRAY, "io/opentelemetry/javaagent/tooling/muzzle/Reference$Source"); Opcodes.ANEWARRAY, "io/opentelemetry/javaagent/tooling/muzzle/Reference$Source");
int j = 0; int j = 0;
for (Reference.Source source : method.getSources()) { for (Reference.Source source : field.getSources()) {
mv.visitInsn(Opcodes.DUP); mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j); mv.visitLdcInsn(j);
@ -356,12 +327,15 @@ class MuzzleCodeGenerator implements AsmVisitorWrapper {
mv.visitInsn(Opcodes.AASTORE); mv.visitInsn(Opcodes.AASTORE);
++j; ++j;
} }
}
mv.visitLdcInsn(method.getFlags().size()); { // flags
mv.visitLdcInsn(field.getFlags().size());
mv.visitTypeInsn( mv.visitTypeInsn(
Opcodes.ANEWARRAY, "io/opentelemetry/javaagent/tooling/muzzle/Reference$Flag"); Opcodes.ANEWARRAY, "io/opentelemetry/javaagent/tooling/muzzle/Reference$Flag");
j = 0;
for (Reference.Flag flag : method.getFlags()) { int j = 0;
for (Reference.Flag flag : field.getFlags()) {
mv.visitInsn(Opcodes.DUP); mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j); mv.visitLdcInsn(j);
String enumClassName = getEnumClassInternalName(flag); String enumClassName = getEnumClassInternalName(flag);
@ -370,102 +344,166 @@ class MuzzleCodeGenerator implements AsmVisitorWrapper {
mv.visitInsn(Opcodes.AASTORE); mv.visitInsn(Opcodes.AASTORE);
++j; ++j;
} }
}
mv.visitLdcInsn(method.getName()); mv.visitLdcInsn(field.getName());
{ // return type
mv.visitLdcInsn(method.getReturnType().getDescriptor());
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
Type.getInternalName(Type.class),
"getType",
Type.getMethodDescriptor(Type.class.getMethod("getType", String.class)),
false);
}
mv.visitLdcInsn(method.getParameterTypes().size());
mv.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(Type.class));
j = 0;
for (Type parameterType : method.getParameterTypes()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
mv.visitLdcInsn(parameterType.getDescriptor());
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
Type.getInternalName(Type.class),
"getType",
Type.getMethodDescriptor(Type.class.getMethod("getType", String.class)),
false);
mv.visitInsn(Opcodes.AASTORE);
j++;
}
{ // field type
mv.visitLdcInsn(field.getType().getDescriptor());
mv.visitMethodInsn( mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, Opcodes.INVOKESTATIC,
"io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder", Type.getInternalName(Type.class),
"withMethod", "getType",
Type.getMethodDescriptor( Type.getMethodDescriptor(Type.class.getMethod("getType", String.class)),
Reference.Builder.class.getMethod(
"withMethod",
Reference.Source[].class,
Reference.Flag[].class,
String.class,
Type.class,
Type[].class)),
false); false);
} }
mv.visitMethodInsn( mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL, Opcodes.INVOKEVIRTUAL,
"io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder", "io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder",
"build", "withField",
"()Lio/opentelemetry/javaagent/tooling/muzzle/Reference;", Type.getMethodDescriptor(
Reference.Builder.class.getMethod(
"withField",
Reference.Source[].class,
Reference.Flag[].class,
String.class,
Type.class)),
false); false);
mv.visitInsn(Opcodes.AASTORE);
} }
for (Reference.Method method : references[i].getMethods()) {
mv.visitLdcInsn(method.getSources().size());
mv.visitTypeInsn(
Opcodes.ANEWARRAY, "io/opentelemetry/javaagent/tooling/muzzle/Reference$Source");
int j = 0;
for (Reference.Source source : method.getSources()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
mv.visitTypeInsn(
Opcodes.NEW, "io/opentelemetry/javaagent/tooling/muzzle/Reference$Source");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(source.getName());
mv.visitLdcInsn(source.getLine());
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
"io/opentelemetry/javaagent/tooling/muzzle/Reference$Source",
"<init>",
"(Ljava/lang/String;I)V",
false);
mv.visitInsn(Opcodes.AASTORE);
++j;
}
mv.visitLdcInsn(method.getFlags().size());
mv.visitTypeInsn(
Opcodes.ANEWARRAY, "io/opentelemetry/javaagent/tooling/muzzle/Reference$Flag");
j = 0;
for (Reference.Flag flag : method.getFlags()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
String enumClassName = getEnumClassInternalName(flag);
mv.visitFieldInsn(
Opcodes.GETSTATIC, enumClassName, flag.name(), "L" + enumClassName + ";");
mv.visitInsn(Opcodes.AASTORE);
++j;
}
mv.visitLdcInsn(method.getName());
{ // return type
mv.visitLdcInsn(method.getReturnType().getDescriptor());
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
Type.getInternalName(Type.class),
"getType",
Type.getMethodDescriptor(Type.class.getMethod("getType", String.class)),
false);
}
mv.visitLdcInsn(method.getParameterTypes().size());
mv.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(Type.class));
j = 0;
for (Type parameterType : method.getParameterTypes()) {
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(j);
mv.visitLdcInsn(parameterType.getDescriptor());
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
Type.getInternalName(Type.class),
"getType",
Type.getMethodDescriptor(Type.class.getMethod("getType", String.class)),
false);
mv.visitInsn(Opcodes.AASTORE);
j++;
}
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder",
"withMethod",
Type.getMethodDescriptor(
Reference.Builder.class.getMethod(
"withMethod",
Reference.Source[].class,
Reference.Flag[].class,
String.class,
Type.class,
Type[].class)),
false);
}
mv.visitMethodInsn( mv.visitMethodInsn(
Opcodes.INVOKESPECIAL, Opcodes.INVOKEVIRTUAL,
"io/opentelemetry/javaagent/tooling/muzzle/matcher/ReferenceMatcher", "io/opentelemetry/javaagent/tooling/muzzle/Reference$Builder",
"<init>", "build",
"([Ljava/lang/String;[Lio/opentelemetry/javaagent/tooling/muzzle/Reference;)V", "()Lio/opentelemetry/javaagent/tooling/muzzle/Reference;",
false); false);
mv.visitFieldInsn( mv.visitInsn(Opcodes.AASTORE);
Opcodes.PUTFIELD,
instrumentationClassName,
MUZZLE_FIELD_NAME,
"Lio/opentelemetry/javaagent/tooling/muzzle/matcher/ReferenceMatcher;");
mv.visitLabel(ret);
if (frames) {
mv.visitFrame(Opcodes.F_SAME, 1, null, 0, null);
}
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(
Opcodes.GETFIELD,
instrumentationClassName,
MUZZLE_FIELD_NAME,
"Lio/opentelemetry/javaagent/tooling/muzzle/matcher/ReferenceMatcher;");
mv.visitInsn(Opcodes.ARETURN);
mv.visitLabel(finish);
mv.visitLocalVariable(
"this", "L" + instrumentationClassName + ";", null, start, finish, 0);
mv.visitMaxs(0, 0); // recomputed
mv.visitEnd();
} catch (Exception e) {
throw new RuntimeException(e);
} }
}
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
"io/opentelemetry/javaagent/tooling/muzzle/matcher/ReferenceMatcher",
"<init>",
"(Ljava/util/List;[Lio/opentelemetry/javaagent/tooling/muzzle/Reference;)V",
false);
mv.visitFieldInsn(
Opcodes.PUTFIELD,
instrumentationClassName,
MUZZLE_REF_MATCHER_FIELD_NAME,
"Lio/opentelemetry/javaagent/tooling/muzzle/matcher/ReferenceMatcher;");
mv.visitLabel(ret);
if (frames) {
mv.visitFrame(Opcodes.F_SAME, 1, null, 0, null);
}
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(
Opcodes.GETFIELD,
instrumentationClassName,
MUZZLE_REF_MATCHER_FIELD_NAME,
"Lio/opentelemetry/javaagent/tooling/muzzle/matcher/ReferenceMatcher;");
mv.visitInsn(Opcodes.ARETURN);
mv.visitLabel(finish);
mv.visitLocalVariable("this", "L" + instrumentationClassName + ";", null, start, finish, 0);
mv.visitMaxs(0, 0); // recomputed
mv.visitEnd();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void generateMuzzleReferenceMatcherField() {
super.visitField( super.visitField(
Opcodes.ACC_PRIVATE + Opcodes.ACC_VOLATILE, Opcodes.ACC_PRIVATE + Opcodes.ACC_VOLATILE,
MUZZLE_FIELD_NAME, MUZZLE_REF_MATCHER_FIELD_NAME,
Type.getDescriptor(ReferenceMatcher.class), Type.getDescriptor(ReferenceMatcher.class),
null, null,
null); null);
super.visitEnd();
} }
private static final Pattern ANONYMOUS_ENUM_CONSTANT_CLASS = private static final Pattern ANONYMOUS_ENUM_CONSTANT_CLASS =
@ -494,7 +532,7 @@ class MuzzleCodeGenerator implements AsmVisitorWrapper {
super.visitFieldInsn( super.visitFieldInsn(
Opcodes.PUTFIELD, Opcodes.PUTFIELD,
instrumentationClassName, instrumentationClassName,
MUZZLE_FIELD_NAME, MUZZLE_REF_MATCHER_FIELD_NAME,
Type.getDescriptor(ReferenceMatcher.class)); Type.getDescriptor(ReferenceMatcher.class));
} }
super.visitInsn(opcode); super.visitInsn(opcode);

View File

@ -0,0 +1,456 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.muzzle.collector;
import static io.opentelemetry.javaagent.tooling.muzzle.InstrumentationClassPredicate.isInstrumentationClass;
import io.opentelemetry.javaagent.tooling.Utils;
import io.opentelemetry.javaagent.tooling.muzzle.Reference;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag.ManifestationFlag;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag.MinimumVisibilityFlag;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag.OwnershipFlag;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag.VisibilityFlag;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Source;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.FieldVisitor;
import net.bytebuddy.jar.asm.Handle;
import net.bytebuddy.jar.asm.Label;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
/** Visit a class and collect all references made by the visited class. */
// Additional things we could check
// - annotations on class
// - outer class
// - inner class
// - cast opcodes in method bodies
class ReferenceCollectingClassVisitor extends ClassVisitor {
/**
* Get the package of an internal class name.
*
* <p>foo/bar/Baz -> foo/bar/
*/
private static String internalPackageName(String internalName) {
return internalName.replaceAll("/[^/]+$", "");
}
/**
* Compute the minimum required access for FROM class to access the TO class.
*
* @return A reference flag with the required level of access.
*/
private static MinimumVisibilityFlag computeMinimumClassAccess(Type from, Type to) {
if (from.getInternalName().equalsIgnoreCase(to.getInternalName())) {
return MinimumVisibilityFlag.PRIVATE_OR_HIGHER;
} else if (internalPackageName(from.getInternalName())
.equals(internalPackageName(to.getInternalName()))) {
return MinimumVisibilityFlag.PACKAGE_OR_HIGHER;
} else {
return MinimumVisibilityFlag.PUBLIC;
}
}
/**
* Compute the minimum required access for FROM class to access a field on the TO class.
*
* @return A reference flag with the required level of access.
*/
private static MinimumVisibilityFlag computeMinimumFieldAccess(Type from, Type to) {
if (from.getInternalName().equalsIgnoreCase(to.getInternalName())) {
return MinimumVisibilityFlag.PRIVATE_OR_HIGHER;
} else if (internalPackageName(from.getInternalName())
.equals(internalPackageName(to.getInternalName()))) {
return MinimumVisibilityFlag.PACKAGE_OR_HIGHER;
} else {
// Additional references: check the type hierarchy of FROM to distinguish public from
// protected
return MinimumVisibilityFlag.PROTECTED_OR_HIGHER;
}
}
/**
* Compute the minimum required access for FROM class to access METHODTYPE on the TO class.
*
* @return A reference flag with the required level of access.
*/
private static MinimumVisibilityFlag computeMinimumMethodAccess(
Type from, Type to, Type methodType) {
if (from.getInternalName().equalsIgnoreCase(to.getInternalName())) {
return MinimumVisibilityFlag.PRIVATE_OR_HIGHER;
} else {
// Additional references: check the type hierarchy of FROM to distinguish public from
// protected
return MinimumVisibilityFlag.PROTECTED_OR_HIGHER;
}
}
/**
* If TYPE is an array, returns the underlying type. If TYPE is not an array simply return the
* type.
*/
private static Type underlyingType(Type type) {
while (type.getSort() == Type.ARRAY) {
type = type.getElementType();
}
return type;
}
private final boolean isAdviceClass;
private final Map<String, Reference> references = new LinkedHashMap<>();
private final Set<String> helperClasses = new HashSet<>();
// helper super classes which are themselves also helpers
// this is needed for injecting the helper classes into the class loader in the correct order
private final Set<String> helperSuperClasses = new HashSet<>();
private String refSourceClassName;
private Type refSourceType;
ReferenceCollectingClassVisitor(boolean isAdviceClass) {
super(Opcodes.ASM7);
this.isAdviceClass = isAdviceClass;
}
Map<String, Reference> getReferences() {
return references;
}
Set<String> getHelperClasses() {
return helperClasses;
}
Set<String> getHelperSuperClasses() {
return helperSuperClasses;
}
private void addExtendsReference(Reference ref) {
addReference(ref);
if (isInstrumentationClass(ref.getClassName())) {
helperSuperClasses.add(ref.getClassName());
}
}
private void addReference(Reference ref) {
if (!ref.getClassName().startsWith("java.")) {
Reference reference = references.get(ref.getClassName());
if (null == reference) {
references.put(ref.getClassName(), ref);
} else {
references.put(ref.getClassName(), reference.merge(ref));
}
}
if (isInstrumentationClass(ref.getClassName())) {
helperClasses.add(ref.getClassName());
}
}
@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
refSourceClassName = Utils.getClassName(name);
refSourceType = Type.getType("L" + name + ";");
// class references are not generated for advice classes, only for helper classes
if (!isAdviceClass) {
String fixedSuperClassName = Utils.getClassName(superName);
addExtendsReference(
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);
addExtendsReference(
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);
}
@Override
public FieldVisitor visitField(
int access, String name, String descriptor, String signature, Object value) {
// Additional references we could check
// - annotations on field
// intentionally not creating refs to fields here.
// Will create refs in method instructions to include line numbers.
return super.visitField(access, name, descriptor, signature, value);
}
@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 (!isAdviceClass) {
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 == VisibilityFlag.PRIVATE
|| ownershipFlag == OwnershipFlag.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));
}
private static VisibilityFlag computeVisibilityFlag(int access) {
if (VisibilityFlag.PUBLIC.matches(access)) {
return VisibilityFlag.PUBLIC;
} else if (VisibilityFlag.PROTECTED.matches(access)) {
return VisibilityFlag.PROTECTED;
} else if (VisibilityFlag.PACKAGE.matches(access)) {
return VisibilityFlag.PACKAGE;
} else {
return VisibilityFlag.PRIVATE;
}
}
private static OwnershipFlag computeOwnershipFlag(int access) {
if (OwnershipFlag.STATIC.matches(access)) {
return OwnershipFlag.STATIC;
} else {
return OwnershipFlag.NON_STATIC;
}
}
private static ManifestationFlag computeTypeManifestationFlag(int access) {
if (ManifestationFlag.ABSTRACT.matches(access)) {
return ManifestationFlag.ABSTRACT;
} else if (ManifestationFlag.FINAL.matches(access)) {
return ManifestationFlag.FINAL;
} else {
return ManifestationFlag.NON_FINAL;
}
}
private class AdviceReferenceMethodVisitor extends MethodVisitor {
private int currentLineNumber = -1;
public AdviceReferenceMethodVisitor(MethodVisitor methodVisitor) {
super(Opcodes.ASM7, methodVisitor);
}
@Override
public void visitLineNumber(int line, Label start) {
currentLineNumber = line;
super.visitLineNumber(line, start);
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
// Additional references we could check
// * DONE owner class
// * DONE owner class has a field (name)
// * DONE field is static or non-static
// * DONE field's visibility from this point (NON_PRIVATE?)
// * DONE owner class's visibility from this point (NON_PRIVATE?)
//
// * DONE field-source class (descriptor)
// * DONE field-source visibility from this point (PRIVATE?)
Type ownerType =
owner.startsWith("[")
? underlyingType(Type.getType(owner))
: Type.getType("L" + owner + ";");
Type fieldType = Type.getType(descriptor);
List<Flag> fieldFlags = new ArrayList<>();
fieldFlags.add(computeMinimumFieldAccess(refSourceType, ownerType));
fieldFlags.add(
opcode == Opcodes.GETSTATIC || opcode == Opcodes.PUTSTATIC
? OwnershipFlag.STATIC
: OwnershipFlag.NON_STATIC);
addReference(
new Reference.Builder(ownerType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, ownerType))
.withField(
new Reference.Source[] {
new Reference.Source(refSourceClassName, currentLineNumber)
},
fieldFlags.toArray(new Reference.Flag[0]),
name,
fieldType)
.build());
Type underlyingFieldType = underlyingType(fieldType);
if (underlyingFieldType.getSort() == Type.OBJECT) {
addReference(
new Reference.Builder(underlyingFieldType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, underlyingFieldType))
.build());
}
super.visitFieldInsn(opcode, owner, name, descriptor);
}
@Override
public void visitMethodInsn(
int opcode, String owner, String name, String descriptor, boolean isInterface) {
// Additional references we could check
// * DONE name of method owner's class
// * DONE is the owner an interface?
// * DONE owner's access from here (PRIVATE?)
// * DONE method on the owner class
// * DONE is the method static? Is it visible from here?
// * Class names from the method descriptor
// * params classes
// * return type
Type methodType = Type.getMethodType(descriptor);
{ // ref for method return type
Type returnType = underlyingType(methodType.getReturnType());
if (returnType.getSort() == Type.OBJECT) {
addReference(
new Reference.Builder(returnType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, returnType))
.build());
}
}
// refs for method param types
for (Type paramType : methodType.getArgumentTypes()) {
paramType = underlyingType(paramType);
if (paramType.getSort() == Type.OBJECT) {
addReference(
new Reference.Builder(paramType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, paramType))
.build());
}
}
Type ownerType =
owner.startsWith("[")
? underlyingType(Type.getType(owner))
: Type.getType("L" + owner + ";");
List<Reference.Flag> methodFlags = new ArrayList<>();
methodFlags.add(
opcode == Opcodes.INVOKESTATIC ? OwnershipFlag.STATIC : OwnershipFlag.NON_STATIC);
methodFlags.add(computeMinimumMethodAccess(refSourceType, ownerType, methodType));
addReference(
new Reference.Builder(ownerType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(isInterface ? ManifestationFlag.INTERFACE : ManifestationFlag.NON_INTERFACE)
.withFlag(computeMinimumClassAccess(refSourceType, ownerType))
.withMethod(
new Reference.Source[] {
new Reference.Source(refSourceClassName, currentLineNumber)
},
methodFlags.toArray(new Reference.Flag[0]),
name,
methodType.getReturnType(),
methodType.getArgumentTypes())
.build());
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
@Override
public void visitTypeInsn(int opcode, String type) {
Type typeObj = underlyingType(Type.getObjectType(type));
if (typeObj.getSort() == Type.OBJECT) {
addReference(
new Reference.Builder(typeObj.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, typeObj))
.build());
}
super.visitTypeInsn(opcode, type);
}
@Override
public void visitInvokeDynamicInsn(
String name,
String descriptor,
Handle bootstrapMethodHandle,
Object... bootstrapMethodArguments) {
// This part might be unnecessary...
addReference(
new Reference.Builder(bootstrapMethodHandle.getOwner())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(
computeMinimumClassAccess(
refSourceType, Type.getObjectType(bootstrapMethodHandle.getOwner())))
.build());
for (Object arg : bootstrapMethodArguments) {
if (arg instanceof Handle) {
Handle handle = (Handle) arg;
addReference(
new Reference.Builder(handle.getOwner())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(
computeMinimumClassAccess(
refSourceType, Type.getObjectType(handle.getOwner())))
.build());
}
}
super.visitInvokeDynamicInsn(
name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
}
@Override
public void visitLdcInsn(Object value) {
if (value instanceof Type) {
Type type = underlyingType((Type) value);
if (type.getSort() == Type.OBJECT) {
addReference(
new Reference.Builder(type.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, type))
.build());
}
}
super.visitLdcInsn(value);
}
}
}

View File

@ -8,44 +8,33 @@ package io.opentelemetry.javaagent.tooling.muzzle.collector;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static io.opentelemetry.javaagent.tooling.muzzle.InstrumentationClassPredicate.isInstrumentationClass; import static io.opentelemetry.javaagent.tooling.muzzle.InstrumentationClassPredicate.isInstrumentationClass;
import com.google.common.graph.Graph;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.Graphs;
import com.google.common.graph.MutableGraph;
import io.opentelemetry.javaagent.tooling.Utils; import io.opentelemetry.javaagent.tooling.Utils;
import io.opentelemetry.javaagent.tooling.muzzle.Reference; import io.opentelemetry.javaagent.tooling.muzzle.Reference;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag.ManifestationFlag;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag.MinimumVisibilityFlag;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag.OwnershipFlag;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Flag.VisibilityFlag;
import io.opentelemetry.javaagent.tooling.muzzle.Reference.Source;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Queue; import java.util.Queue;
import java.util.Set; import java.util.Set;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.jar.asm.ClassReader; import net.bytebuddy.jar.asm.ClassReader;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.FieldVisitor;
import net.bytebuddy.jar.asm.Handle;
import net.bytebuddy.jar.asm.Label;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
/** Visit a class and collect all references made by the visited class. */ public class ReferenceCollector {
// Additional things we could check private final Map<String, Reference> references = new HashMap<>();
// - annotations on class private final MutableGraph<String> helperSuperClassGraph = GraphBuilder.directed().build();
// - outer class private final Set<String> visitedClasses = new HashSet<>();
// - inner class
// - cast opcodes in method bodies
public class ReferenceCollector extends ClassVisitor {
/** /**
* Traverse a graph of classes starting from {@code entryPointClassName} (usually an advice class) * Traverse a graph of classes starting from {@code adviceClassName} and collect all references to
* and collect all references to both internal (instrumentation) and external classes. * both internal (instrumentation) and external classes.
* *
* <p>The graph of classes is traversed until a non-instrumentation (external) class is * <p>The graph of classes is traversed until a non-instrumentation (external) class is
* encountered. * encountered.
@ -53,452 +42,113 @@ public class ReferenceCollector extends ClassVisitor {
* <p>This class is only called at compile time by the {@link MuzzleCodeGenerationPlugin} * <p>This class is only called at compile time by the {@link MuzzleCodeGenerationPlugin}
* ByteBuddy plugin. * ByteBuddy plugin.
* *
* @param entryPointClassName Starting point for generating references. * @param adviceClassName Starting point for generating references.
* @return Map of [referenceClassName to Reference]
* @see io.opentelemetry.javaagent.tooling.muzzle.InstrumentationClassPredicate * @see io.opentelemetry.javaagent.tooling.muzzle.InstrumentationClassPredicate
*/ */
public static Map<String, Reference> collectReferencesFrom(String entryPointClassName) { public void collectReferencesFrom(String adviceClassName) {
Set<String> visitedSources = new HashSet<>();
Map<String, Reference> references = new HashMap<>();
Queue<String> instrumentationQueue = new ArrayDeque<>(); Queue<String> instrumentationQueue = new ArrayDeque<>();
instrumentationQueue.add(entryPointClassName); instrumentationQueue.add(adviceClassName);
boolean isEntryPoint = true; boolean isAdviceClass = true;
while (!instrumentationQueue.isEmpty()) { while (!instrumentationQueue.isEmpty()) {
String className = instrumentationQueue.remove(); String visitedClassName = instrumentationQueue.remove();
visitedSources.add(className); visitedClasses.add(visitedClassName);
try (InputStream in = try (InputStream in =
checkNotNull( checkNotNull(
ReferenceCollector.class ReferenceCollector.class
.getClassLoader() .getClassLoader()
.getResourceAsStream(Utils.getResourceName(className)), .getResourceAsStream(Utils.getResourceName(visitedClassName)),
"Couldn't find class file %s", "Couldn't find class file %s",
className)) { visitedClassName)) {
// only start from method bodies for entry point class (skips class/method references) // only start from method bodies for the advice class (skips class/method references)
ReferenceCollector cv = new ReferenceCollector(isEntryPoint); ReferenceCollectingClassVisitor cv = new ReferenceCollectingClassVisitor(isAdviceClass);
ClassReader reader = new ClassReader(in); ClassReader reader = new ClassReader(in);
reader.accept(cv, ClassReader.SKIP_FRAMES); reader.accept(cv, ClassReader.SKIP_FRAMES);
Map<String, Reference> instrumentationReferences = cv.getReferences(); for (Map.Entry<String, Reference> entry : cv.getReferences().entrySet()) {
for (Map.Entry<String, Reference> entry : instrumentationReferences.entrySet()) { String refClassName = entry.getKey();
String key = entry.getKey(); Reference reference = entry.getValue();
// Don't generate references created outside of the instrumentation package. // Don't generate references created outside of the instrumentation package.
if (!visitedSources.contains(entry.getKey()) && isInstrumentationClass(key)) { if (!visitedClasses.contains(refClassName) && isInstrumentationClass(refClassName)) {
instrumentationQueue.add(key); instrumentationQueue.add(refClassName);
}
if (references.containsKey(key)) {
references.put(key, references.get(key).merge(entry.getValue()));
} else {
references.put(key, entry.getValue());
} }
addReference(refClassName, reference);
} }
collectHelperClasses(
isAdviceClass, visitedClassName, cv.getHelperClasses(), cv.getHelperSuperClasses());
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Error reading class " + className, e); throw new IllegalStateException("Error reading class " + visitedClassName, e);
} }
if (isEntryPoint) { if (isAdviceClass) {
isEntryPoint = false; isAdviceClass = false;
} }
} }
return references;
} }
/** private void addReference(String refClassName, Reference reference) {
* Get the package of an internal class name. if (references.containsKey(refClassName)) {
* references.put(refClassName, references.get(refClassName).merge(reference));
* <p>foo/bar/Baz -> foo/bar/
*/
private static String internalPackageName(String internalName) {
return internalName.replaceAll("/[^/]+$", "");
}
/**
* Compute the minimum required access for FROM class to access the TO class.
*
* @return A reference flag with the required level of access.
*/
private static MinimumVisibilityFlag computeMinimumClassAccess(Type from, Type to) {
if (from.getInternalName().equalsIgnoreCase(to.getInternalName())) {
return MinimumVisibilityFlag.PRIVATE_OR_HIGHER;
} else if (internalPackageName(from.getInternalName())
.equals(internalPackageName(to.getInternalName()))) {
return MinimumVisibilityFlag.PACKAGE_OR_HIGHER;
} else { } else {
return MinimumVisibilityFlag.PUBLIC; references.put(refClassName, reference);
} }
} }
/** private void collectHelperClasses(
* Compute the minimum required access for FROM class to access a field on the TO class. boolean isAdviceClass,
* String className,
* @return A reference flag with the required level of access. Set<String> helperClasses,
*/ Set<String> helperSuperClasses) {
private static MinimumVisibilityFlag computeMinimumFieldAccess(Type from, Type to) { for (String helperClass : helperClasses) {
if (from.getInternalName().equalsIgnoreCase(to.getInternalName())) { helperSuperClassGraph.addNode(helperClass);
return MinimumVisibilityFlag.PRIVATE_OR_HIGHER;
} else if (internalPackageName(from.getInternalName())
.equals(internalPackageName(to.getInternalName()))) {
return MinimumVisibilityFlag.PACKAGE_OR_HIGHER;
} else {
// Additional references: check the type hierarchy of FROM to distinguish public from
// protected
return MinimumVisibilityFlag.PROTECTED_OR_HIGHER;
} }
} if (!isAdviceClass) {
for (String helperSuperClass : helperSuperClasses) {
/** helperSuperClassGraph.putEdge(className, helperSuperClass);
* Compute the minimum required access for FROM class to access METHODTYPE on the TO class. }
*
* @return A reference flag with the required level of access.
*/
private static MinimumVisibilityFlag computeMinimumMethodAccess(
Type from, Type to, Type methodType) {
if (from.getInternalName().equalsIgnoreCase(to.getInternalName())) {
return MinimumVisibilityFlag.PRIVATE_OR_HIGHER;
} else {
// Additional references: check the type hierarchy of FROM to distinguish public from
// protected
return MinimumVisibilityFlag.PROTECTED_OR_HIGHER;
} }
} }
/**
* If TYPE is an array, returns the underlying type. If TYPE is not an array simply return the
* type.
*/
private static Type underlyingType(Type type) {
while (type.getSort() == Type.ARRAY) {
type = type.getElementType();
}
return type;
}
private final boolean skipClassReferenceGeneration;
private final Map<String, Reference> references = new HashMap<>();
private String refSourceClassName;
private Type refSourceType;
private ReferenceCollector(boolean skipClassReferenceGeneration) {
super(Opcodes.ASM7);
this.skipClassReferenceGeneration = skipClassReferenceGeneration;
}
public Map<String, Reference> getReferences() { public Map<String, Reference> getReferences() {
return references; return references;
} }
private void addReference(Reference ref) { // see https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
if (!ref.getClassName().startsWith("java.")) { public List<String> getSortedHelperClasses() {
Reference reference = references.get(ref.getClassName()); MutableGraph<String> dependencyGraph = Graphs.copyOf(Graphs.transpose(helperSuperClassGraph));
if (null == reference) { List<String> helperClasses = new ArrayList<>(dependencyGraph.nodes().size());
references.put(ref.getClassName(), ref);
} else {
references.put(ref.getClassName(), reference.merge(ref));
}
}
}
@Override Queue<String> helpersWithNoDeps = findAllHelperClassesWithoutDependencies(dependencyGraph);
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
refSourceClassName = Utils.getClassName(name);
refSourceType = Type.getType("L" + name + ";");
// class references are not generated for advice classes, only for helper classes while (!helpersWithNoDeps.isEmpty()) {
if (!skipClassReferenceGeneration) { String helperClass = helpersWithNoDeps.remove();
String fixedSuperClassName = Utils.getClassName(superName); helperClasses.add(helperClass);
addReference( Set<String> dependencies = new HashSet<>(dependencyGraph.successors(helperClass));
new Reference.Builder(fixedSuperClassName).withSource(refSourceClassName).build()); for (String dependency : dependencies) {
dependencyGraph.removeEdge(helperClass, dependency);
List<String> fixedInterfaceNames = new ArrayList<>(interfaces.length); if (dependencyGraph.predecessors(dependency).isEmpty()) {
for (String interfaceName : interfaces) { helpersWithNoDeps.add(dependency);
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);
}
@Override
public FieldVisitor visitField(
int access, String name, String descriptor, String signature, Object value) {
// Additional references we could check
// - annotations on field
// intentionally not creating refs to fields here.
// Will create refs in method instructions to include line numbers.
return super.visitField(access, name, descriptor, signature, value);
}
@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 == VisibilityFlag.PRIVATE
|| ownershipFlag == OwnershipFlag.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));
}
private static VisibilityFlag computeVisibilityFlag(int access) {
if (VisibilityFlag.PUBLIC.matches(access)) {
return VisibilityFlag.PUBLIC;
} else if (VisibilityFlag.PROTECTED.matches(access)) {
return VisibilityFlag.PROTECTED;
} else if (VisibilityFlag.PACKAGE.matches(access)) {
return VisibilityFlag.PACKAGE;
} else {
return VisibilityFlag.PRIVATE;
}
}
private static OwnershipFlag computeOwnershipFlag(int access) {
if (OwnershipFlag.STATIC.matches(access)) {
return OwnershipFlag.STATIC;
} else {
return OwnershipFlag.NON_STATIC;
}
}
private static ManifestationFlag computeTypeManifestationFlag(int access) {
if (ManifestationFlag.ABSTRACT.matches(access)) {
return ManifestationFlag.ABSTRACT;
} else if (ManifestationFlag.FINAL.matches(access)) {
return ManifestationFlag.FINAL;
} else {
return ManifestationFlag.NON_FINAL;
}
}
private class AdviceReferenceMethodVisitor extends MethodVisitor {
private int currentLineNumber = -1;
public AdviceReferenceMethodVisitor(MethodVisitor methodVisitor) {
super(Opcodes.ASM7, methodVisitor);
}
@Override
public void visitLineNumber(int line, Label start) {
currentLineNumber = line;
super.visitLineNumber(line, start);
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
// Additional references we could check
// * DONE owner class
// * DONE owner class has a field (name)
// * DONE field is static or non-static
// * DONE field's visibility from this point (NON_PRIVATE?)
// * DONE owner class's visibility from this point (NON_PRIVATE?)
//
// * DONE field-source class (descriptor)
// * DONE field-source visibility from this point (PRIVATE?)
Type ownerType =
owner.startsWith("[")
? underlyingType(Type.getType(owner))
: Type.getType("L" + owner + ";");
Type fieldType = Type.getType(descriptor);
List<Flag> fieldFlags = new ArrayList<>();
fieldFlags.add(computeMinimumFieldAccess(refSourceType, ownerType));
fieldFlags.add(
opcode == Opcodes.GETSTATIC || opcode == Opcodes.PUTSTATIC
? OwnershipFlag.STATIC
: OwnershipFlag.NON_STATIC);
addReference(
new Reference.Builder(ownerType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, ownerType))
.withField(
new Reference.Source[] {
new Reference.Source(refSourceClassName, currentLineNumber)
},
fieldFlags.toArray(new Reference.Flag[0]),
name,
fieldType)
.build());
Type underlyingFieldType = underlyingType(fieldType);
if (underlyingFieldType.getSort() == Type.OBJECT) {
addReference(
new Reference.Builder(underlyingFieldType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, underlyingFieldType))
.build());
}
super.visitFieldInsn(opcode, owner, name, descriptor);
}
@Override
public void visitMethodInsn(
int opcode, String owner, String name, String descriptor, boolean isInterface) {
// Additional references we could check
// * DONE name of method owner's class
// * DONE is the owner an interface?
// * DONE owner's access from here (PRIVATE?)
// * DONE method on the owner class
// * DONE is the method static? Is it visible from here?
// * Class names from the method descriptor
// * params classes
// * return type
Type methodType = Type.getMethodType(descriptor);
{ // ref for method return type
Type returnType = underlyingType(methodType.getReturnType());
if (returnType.getSort() == Type.OBJECT) {
addReference(
new Reference.Builder(returnType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, returnType))
.build());
} }
} }
// refs for method param types
for (Type paramType : methodType.getArgumentTypes()) {
paramType = underlyingType(paramType);
if (paramType.getSort() == Type.OBJECT) {
addReference(
new Reference.Builder(paramType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, paramType))
.build());
}
}
Type ownerType =
owner.startsWith("[")
? underlyingType(Type.getType(owner))
: Type.getType("L" + owner + ";");
List<Reference.Flag> methodFlags = new ArrayList<>();
methodFlags.add(
opcode == Opcodes.INVOKESTATIC ? OwnershipFlag.STATIC : OwnershipFlag.NON_STATIC);
methodFlags.add(computeMinimumMethodAccess(refSourceType, ownerType, methodType));
addReference(
new Reference.Builder(ownerType.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(isInterface ? ManifestationFlag.INTERFACE : ManifestationFlag.NON_INTERFACE)
.withFlag(computeMinimumClassAccess(refSourceType, ownerType))
.withMethod(
new Reference.Source[] {
new Reference.Source(refSourceClassName, currentLineNumber)
},
methodFlags.toArray(new Reference.Flag[0]),
name,
methodType.getReturnType(),
methodType.getArgumentTypes())
.build());
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
} }
@Override return helperClasses;
public void visitTypeInsn(int opcode, String type) { }
Type typeObj = underlyingType(Type.getObjectType(type));
if (typeObj.getSort() == Type.OBJECT) {
addReference(
new Reference.Builder(typeObj.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, typeObj))
.build());
}
super.visitTypeInsn(opcode, type);
}
@Override private static Queue<String> findAllHelperClassesWithoutDependencies(
public void visitInvokeDynamicInsn( Graph<String> dependencyGraph) {
String name, Queue<String> helpersWithNoDeps = new LinkedList<>();
String descriptor, for (String helperClass : dependencyGraph.nodes()) {
Handle bootstrapMethodHandle, if (dependencyGraph.predecessors(helperClass).isEmpty()) {
Object... bootstrapMethodArguments) { helpersWithNoDeps.add(helperClass);
// This part might be unnecessary...
addReference(
new Reference.Builder(bootstrapMethodHandle.getOwner())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(
computeMinimumClassAccess(
refSourceType, Type.getObjectType(bootstrapMethodHandle.getOwner())))
.build());
for (Object arg : bootstrapMethodArguments) {
if (arg instanceof Handle) {
Handle handle = (Handle) arg;
addReference(
new Reference.Builder(handle.getOwner())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(
computeMinimumClassAccess(
refSourceType, Type.getObjectType(handle.getOwner())))
.build());
}
} }
super.visitInvokeDynamicInsn(
name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
}
@Override
public void visitLdcInsn(Object value) {
if (value instanceof Type) {
Type type = underlyingType((Type) value);
if (type.getSort() == Type.OBJECT) {
addReference(
new Reference.Builder(type.getInternalName())
.withSource(refSourceClassName, currentLineNumber)
.withFlag(computeMinimumClassAccess(refSourceType, type))
.build());
}
}
super.visitLdcInsn(value);
} }
return helpersWithNoDeps;
} }
} }

View File

@ -10,6 +10,7 @@ import io.opentelemetry.javaagent.tooling.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.muzzle.Reference; import io.opentelemetry.javaagent.tooling.muzzle.Reference;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -30,8 +31,9 @@ public final class MuzzleGradlePluginUtil {
* <li>{@code userClassLoader} is not matched by the {@link * <li>{@code userClassLoader} is not matched by the {@link
* InstrumentationModule#classLoaderMatcher()} method * InstrumentationModule#classLoaderMatcher()} method
* <li>{@link ReferenceMatcher} of any instrumentation module finds any mismatch * <li>{@link ReferenceMatcher} of any instrumentation module finds any mismatch
* <li>any helper class defined in {@link InstrumentationModule#helperClassNames()} fails to be * <li>any helper class defined in {@link InstrumentationModule#getMuzzleHelperClassNames()} or
* injected into {@code userClassLoader} * {@link InstrumentationModule#additionalHelperClassNames()} fails to be injected into
* {@code userClassLoader}
* </ol> * </ol>
* *
* <p>When {@code assertPass = false} this method behaves in an opposite way: failure in any of * <p>When {@code assertPass = false} this method behaves in an opposite way: failure in any of
@ -92,11 +94,11 @@ public final class MuzzleGradlePluginUtil {
ServiceLoader.load(InstrumentationModule.class, agentClassLoader)) { ServiceLoader.load(InstrumentationModule.class, agentClassLoader)) {
try { try {
// verify helper injector works // verify helper injector works
String[] helperClassNames = instrumentationModule.helperClassNames(); List<String> allHelperClasses = instrumentationModule.getAllHelperClassNames();
if (helperClassNames.length > 0) { if (!allHelperClasses.isEmpty()) {
new HelperInjector( new HelperInjector(
MuzzleGradlePluginUtil.class.getSimpleName(), MuzzleGradlePluginUtil.class.getSimpleName(),
createHelperMap(helperClassNames, agentClassLoader)) createHelperMap(allHelperClasses, agentClassLoader))
.transform(null, null, userClassLoader, null); .transform(null, null, userClassLoader, null);
} }
} catch (Exception e) { } catch (Exception e) {
@ -109,8 +111,8 @@ public final class MuzzleGradlePluginUtil {
} }
private static Map<String, byte[]> createHelperMap( private static Map<String, byte[]> createHelperMap(
String[] helperClassNames, ClassLoader agentClassLoader) throws IOException { Collection<String> helperClassNames, ClassLoader agentClassLoader) throws IOException {
Map<String, byte[]> helperMap = new LinkedHashMap<>(helperClassNames.length); Map<String, byte[]> helperMap = new LinkedHashMap<>(helperClassNames.size());
for (String helperName : helperClassNames) { for (String helperName : helperClassNames) {
ClassFileLocator locator = ClassFileLocator.ForClassLoader.of(agentClassLoader); ClassFileLocator locator = ClassFileLocator.ForClassLoader.of(agentClassLoader);
byte[] classBytes = locator.locate(helperName).resolve(); byte[] classBytes = locator.locate(helperName).resolve();

View File

@ -6,6 +6,7 @@
package io.opentelemetry.javaagent.tooling.muzzle.matcher; package io.opentelemetry.javaagent.tooling.muzzle.matcher;
import static io.opentelemetry.javaagent.tooling.muzzle.InstrumentationClassPredicate.isInstrumentationClass; import static io.opentelemetry.javaagent.tooling.muzzle.InstrumentationClassPredicate.isInstrumentationClass;
import static java.util.Collections.emptyList;
import static net.bytebuddy.dynamic.loading.ClassLoadingStrategy.BOOTSTRAP_LOADER; import static net.bytebuddy.dynamic.loading.ClassLoadingStrategy.BOOTSTRAP_LOADER;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@ -17,7 +18,6 @@ import io.opentelemetry.javaagent.tooling.muzzle.Reference.Source;
import io.opentelemetry.javaagent.tooling.muzzle.matcher.HelperReferenceWrapper.Factory; import io.opentelemetry.javaagent.tooling.muzzle.matcher.HelperReferenceWrapper.Factory;
import io.opentelemetry.javaagent.tooling.muzzle.matcher.HelperReferenceWrapper.Method; import io.opentelemetry.javaagent.tooling.muzzle.matcher.HelperReferenceWrapper.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -38,15 +38,15 @@ public final class ReferenceMatcher {
private final Set<String> helperClassNames; private final Set<String> helperClassNames;
public ReferenceMatcher(Reference... references) { public ReferenceMatcher(Reference... references) {
this(new String[0], references); this(emptyList(), references);
} }
public ReferenceMatcher(String[] helperClassNames, Reference[] references) { public ReferenceMatcher(List<String> helperClassNames, Reference[] references) {
this.references = new HashMap<>(references.length); this.references = new HashMap<>(references.length);
for (Reference reference : references) { for (Reference reference : references) {
this.references.put(reference.getClassName(), reference); this.references.put(reference.getClassName(), reference);
} }
this.helperClassNames = new HashSet<>(Arrays.asList(helperClassNames)); this.helperClassNames = new HashSet<>(helperClassNames);
} }
Collection<Reference> getReferences() { Collection<Reference> getReferences() {
@ -88,7 +88,7 @@ public final class ReferenceMatcher {
loader = Utils.getBootstrapProxy(); loader = Utils.getBootstrapProxy();
} }
List<Mismatch> mismatches = Collections.emptyList(); List<Mismatch> mismatches = emptyList();
for (Reference reference : references.values()) { for (Reference reference : references.values()) {
mismatches = lazyAddAll(mismatches, checkMatch(reference, loader)); mismatches = lazyAddAll(mismatches, checkMatch(reference, loader));
@ -119,7 +119,7 @@ public final class ReferenceMatcher {
} else if (helperClassNames.contains(reference.getClassName())) { } else if (helperClassNames.contains(reference.getClassName())) {
// skip muzzle check for those helper classes that are not in instrumentation packages; e.g. // skip muzzle check for those helper classes that are not in instrumentation packages; e.g.
// some instrumentations inject guava types as helper classes // some instrumentations inject guava types as helper classes
return Collections.emptyList(); return emptyList();
} else { } else {
TypePool.Resolution resolution = typePool.describe(reference.getClassName()); TypePool.Resolution resolution = typePool.describe(reference.getClassName());
if (!resolution.isResolved()) { if (!resolution.isResolved()) {
@ -146,7 +146,7 @@ public final class ReferenceMatcher {
// for helper classes we make sure that all abstract methods from super classes and interfaces are // for helper classes we make sure that all abstract methods from super classes and interfaces are
// implemented // implemented
private List<Mismatch> checkHelperClassMatch(Reference helperClass, TypePool typePool) { private List<Mismatch> checkHelperClassMatch(Reference helperClass, TypePool typePool) {
List<Mismatch> mismatches = Collections.emptyList(); List<Mismatch> mismatches = emptyList();
HelperReferenceWrapper helperWrapper = new Factory(typePool, references).create(helperClass); HelperReferenceWrapper helperWrapper = new Factory(typePool, references).create(helperClass);

View File

@ -13,6 +13,7 @@ import static muzzle.TestClasses.HelperAdvice
import static muzzle.TestClasses.LdcAdvice import static muzzle.TestClasses.LdcAdvice
import static muzzle.TestClasses.MethodBodyAdvice import static muzzle.TestClasses.MethodBodyAdvice
import io.opentelemetry.instrumentation.OtherTestHelperClasses
import io.opentelemetry.instrumentation.TestHelperClasses import io.opentelemetry.instrumentation.TestHelperClasses
import io.opentelemetry.instrumentation.test.AgentTestRunner import io.opentelemetry.instrumentation.test.AgentTestRunner
import io.opentelemetry.javaagent.tooling.muzzle.Reference import io.opentelemetry.javaagent.tooling.muzzle.Reference
@ -21,7 +22,9 @@ import io.opentelemetry.javaagent.tooling.muzzle.collector.ReferenceCollector
class ReferenceCollectorTest extends AgentTestRunner { class ReferenceCollectorTest extends AgentTestRunner {
def "method body creates references"() { def "method body creates references"() {
setup: setup:
def references = ReferenceCollector.collectReferencesFrom(MethodBodyAdvice.name) def collector = new ReferenceCollector()
collector.collectReferencesFrom(MethodBodyAdvice.name)
def references = collector.getReferences()
expect: expect:
references.keySet() == [ references.keySet() == [
@ -65,7 +68,9 @@ class ReferenceCollectorTest extends AgentTestRunner {
def "protected ref test"() { def "protected ref test"() {
setup: setup:
def references = ReferenceCollector.collectReferencesFrom(MethodBodyAdvice.B2.name) def collector = new ReferenceCollector()
collector.collectReferencesFrom(MethodBodyAdvice.B2.name)
def references = collector.getReferences()
expect: expect:
assertMethod references[MethodBodyAdvice.B.name], 'protectedMethod', '()V', assertMethod references[MethodBodyAdvice.B.name], 'protectedMethod', '()V',
@ -75,7 +80,9 @@ class ReferenceCollectorTest extends AgentTestRunner {
def "ldc creates references"() { def "ldc creates references"() {
setup: setup:
def references = ReferenceCollector.collectReferencesFrom(LdcAdvice.name) def collector = new ReferenceCollector()
collector.collectReferencesFrom(LdcAdvice.name)
def references = collector.getReferences()
expect: expect:
references[MethodBodyAdvice.A.name] != null references[MethodBodyAdvice.A.name] != null
@ -83,7 +90,9 @@ class ReferenceCollectorTest extends AgentTestRunner {
def "instanceof creates references"() { def "instanceof creates references"() {
setup: setup:
def references = ReferenceCollector.collectReferencesFrom(TestClasses.InstanceofAdvice.name) def collector = new ReferenceCollector()
collector.collectReferencesFrom(TestClasses.InstanceofAdvice.name)
def references = collector.getReferences()
expect: expect:
references[MethodBodyAdvice.A.name] != null references[MethodBodyAdvice.A.name] != null
@ -91,7 +100,9 @@ class ReferenceCollectorTest extends AgentTestRunner {
def "invokedynamic creates references"() { def "invokedynamic creates references"() {
setup: setup:
def references = ReferenceCollector.collectReferencesFrom(TestClasses.InvokeDynamicAdvice.name) def collector = new ReferenceCollector()
collector.collectReferencesFrom(TestClasses.InvokeDynamicAdvice.name)
def references = collector.getReferences()
expect: expect:
references['muzzle.TestClasses$MethodBodyAdvice$SomeImplementation'] != null references['muzzle.TestClasses$MethodBodyAdvice$SomeImplementation'] != null
@ -100,7 +111,9 @@ class ReferenceCollectorTest extends AgentTestRunner {
def "should create references for helper classes"() { def "should create references for helper classes"() {
when: when:
def references = ReferenceCollector.collectReferencesFrom(HelperAdvice.name) def collector = new ReferenceCollector()
collector.collectReferencesFrom(HelperAdvice.name)
def references = collector.getReferences()
then: then:
references.keySet() == [ references.keySet() == [
@ -130,6 +143,55 @@ class ReferenceCollectorTest extends AgentTestRunner {
} }
} }
def "should find all helper classes"() {
when:
def collector = new ReferenceCollector()
collector.collectReferencesFrom(HelperAdvice.name)
def helperClasses = collector.getSortedHelperClasses()
then:
assertThatContainsInOrder helperClasses, [
TestHelperClasses.HelperInterface.name,
TestHelperClasses.Helper.name
]
assertThatContainsInOrder helperClasses, [
TestHelperClasses.HelperSuperClass.name,
TestHelperClasses.Helper.name
]
}
def "should correctly find helper classes from multiple advice classes"() {
when:
def collector = new ReferenceCollector()
collector.collectReferencesFrom(TestClasses.HelperAdvice.name)
collector.collectReferencesFrom(TestClasses.HelperOtherAdvice.name)
def helperClasses = collector.getSortedHelperClasses()
then:
assertThatContainsInOrder helperClasses, [
TestHelperClasses.HelperInterface.name,
TestHelperClasses.Helper.name
]
assertThatContainsInOrder helperClasses, [
TestHelperClasses.HelperSuperClass.name,
TestHelperClasses.Helper.name
]
assertThatContainsInOrder helperClasses, [
OtherTestHelperClasses.TestEnum.name,
OtherTestHelperClasses.TestEnum.name + '$1',
]
new HashSet<>(helperClasses) == new HashSet([
TestHelperClasses.HelperSuperClass.name,
TestHelperClasses.HelperInterface.name,
TestHelperClasses.Helper.name,
OtherTestHelperClasses.Bar.name,
OtherTestHelperClasses.Foo.name,
OtherTestHelperClasses.TestEnum.name,
OtherTestHelperClasses.TestEnum.name + '$1',
OtherTestHelperClasses.name + '$1',
])
}
private static assertHelperSuperClassMethod(Reference reference, boolean isAbstract) { private static assertHelperSuperClassMethod(Reference reference, boolean isAbstract) {
assertMethod reference, 'abstractMethod', '()I', assertMethod reference, 'abstractMethod', '()I',
VisibilityFlag.PROTECTED, VisibilityFlag.PROTECTED,
@ -171,4 +233,19 @@ class ReferenceCollectorTest extends AgentTestRunner {
} }
return null return null
} }
private static assertThatContainsInOrder(List<String> list, List<String> sublist) {
def listIt = list.iterator()
def sublistIt = sublist.iterator()
while (listIt.hasNext() && sublistIt.hasNext()) {
def sublistElem = sublistIt.next()
while (listIt.hasNext()) {
def listElem = listIt.next()
if (listElem == sublistElem) {
break
}
}
}
return !sublistIt.hasNext()
}
} }

View File

@ -46,9 +46,9 @@ class ReferenceMatcherTest extends AgentTestRunner {
def "match safe classpaths"() { def "match safe classpaths"() {
setup: setup:
Reference[] refs = ReferenceCollector.collectReferencesFrom(MethodBodyAdvice.name) def collector = new ReferenceCollector()
.values() collector.collectReferencesFrom(MethodBodyAdvice.name)
.toArray(new Reference[0]) Reference[] refs = collector.getReferences().values().toArray(new Reference[0])
def refMatcher = new ReferenceMatcher(refs) def refMatcher = new ReferenceMatcher(refs)
expect: expect:
@ -83,9 +83,11 @@ class ReferenceMatcherTest extends AgentTestRunner {
MethodBodyAdvice.SomeInterface, MethodBodyAdvice.SomeInterface,
MethodBodyAdvice.SomeImplementation)] as URL[], MethodBodyAdvice.SomeImplementation)] as URL[],
(ClassLoader) null) (ClassLoader) null)
Reference[] refs = ReferenceCollector.collectReferencesFrom(MethodBodyAdvice.name)
.values() def collector = new ReferenceCollector()
.toArray(new Reference[0]) collector.collectReferencesFrom(MethodBodyAdvice.name)
Reference[] refs = collector.getReferences().values().toArray(new Reference[0])
def refMatcher1 = new ReferenceMatcher(refs) def refMatcher1 = new ReferenceMatcher(refs)
def refMatcher2 = new ReferenceMatcher(refs) def refMatcher2 = new ReferenceMatcher(refs)
assert getMismatchClassSet(refMatcher1.getMismatchedReferenceSources(cl)).empty assert getMismatchClassSet(refMatcher1.getMismatchedReferenceSources(cl)).empty
@ -170,7 +172,7 @@ class ReferenceMatcherTest extends AgentTestRunner {
.build() .build()
when: when:
def mismatches = new ReferenceMatcher([reference.className] as String[], [reference] as Reference[]) def mismatches = new ReferenceMatcher([reference.className], [reference] as Reference[])
.getMismatchedReferenceSources(emptyClassLoader) .getMismatchedReferenceSources(emptyClassLoader)
then: then:
@ -186,7 +188,7 @@ class ReferenceMatcherTest extends AgentTestRunner {
.build() .build()
when: when:
def mismatches = new ReferenceMatcher([reference.className] as String[], [reference] as Reference[]) def mismatches = new ReferenceMatcher([reference.className], [reference] as Reference[])
.getMismatchedReferenceSources(this.class.classLoader) .getMismatchedReferenceSources(this.class.classLoader)
then: then:
@ -201,7 +203,7 @@ class ReferenceMatcherTest extends AgentTestRunner {
.build() .build()
when: when:
def mismatches = new ReferenceMatcher([reference.className] as String[], [reference] as Reference[]) def mismatches = new ReferenceMatcher([reference.className], [reference] as Reference[])
.getMismatchedReferenceSources(this.class.classLoader) .getMismatchedReferenceSources(this.class.classLoader)
then: then:
@ -216,7 +218,7 @@ class ReferenceMatcherTest extends AgentTestRunner {
.build() .build()
when: when:
def mismatches = new ReferenceMatcher([reference.className] as String[], [reference] as Reference[]) def mismatches = new ReferenceMatcher([reference.className], [reference] as Reference[])
.getMismatchedReferenceSources(this.class.classLoader) .getMismatchedReferenceSources(this.class.classLoader)
then: then:
@ -233,7 +235,7 @@ class ReferenceMatcherTest extends AgentTestRunner {
.build() .build()
when: when:
def mismatches = new ReferenceMatcher([reference.className, emptySuperClassRef.className] as String[], [reference, emptySuperClassRef] as Reference[]) def mismatches = new ReferenceMatcher([reference.className, emptySuperClassRef.className], [reference, emptySuperClassRef] as Reference[])
.getMismatchedReferenceSources(this.class.classLoader) .getMismatchedReferenceSources(this.class.classLoader)
then: then:
@ -255,7 +257,7 @@ class ReferenceMatcherTest extends AgentTestRunner {
.build() .build()
when: when:
def mismatches = new ReferenceMatcher([helper.className, baseHelper.className] as String[], [helper, baseHelper] as Reference[]) def mismatches = new ReferenceMatcher([helper.className, baseHelper.className], [helper, baseHelper] as Reference[])
.getMismatchedReferenceSources(this.class.classLoader) .getMismatchedReferenceSources(this.class.classLoader)
then: then:

View File

@ -0,0 +1,33 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation;
import muzzle.TestClasses;
public class OtherTestHelperClasses {
public static class Foo implements TestClasses.MethodBodyAdvice.SomeInterface {
@Override
public void someMethod() {}
}
public static class Bar {
public void doSomething() {
new Foo().someMethod();
TestEnum.INSTANCE.getAnswer();
}
}
public enum TestEnum {
INSTANCE {
@Override
int getAnswer() {
return 42;
}
};
abstract int getAnswer();
}
}

View File

@ -22,10 +22,9 @@ public class MuzzleWeakReferenceTest {
public static boolean classLoaderRefIsGarbageCollected() throws InterruptedException { public static boolean classLoaderRefIsGarbageCollected() throws InterruptedException {
ClassLoader loader = new URLClassLoader(new URL[0], null); ClassLoader loader = new URLClassLoader(new URL[0], null);
WeakReference<ClassLoader> clRef = new WeakReference<>(loader); WeakReference<ClassLoader> clRef = new WeakReference<>(loader);
Reference[] refs = ReferenceCollector collector = new ReferenceCollector();
ReferenceCollector.collectReferencesFrom(TestClasses.MethodBodyAdvice.class.getName()) collector.collectReferencesFrom(TestClasses.MethodBodyAdvice.class.getName());
.values() Reference[] refs = collector.getReferences().values().toArray(new Reference[0]);
.toArray(new Reference[0]);
ReferenceMatcher refMatcher = new ReferenceMatcher(refs); ReferenceMatcher refMatcher = new ReferenceMatcher(refs);
refMatcher.getMismatchedReferenceSources(loader); refMatcher.getMismatchedReferenceSources(loader);
loader = null; loader = null;

View File

@ -5,6 +5,7 @@
package muzzle; package muzzle;
import io.opentelemetry.instrumentation.OtherTestHelperClasses;
import io.opentelemetry.instrumentation.TestHelperClasses.Helper; import io.opentelemetry.instrumentation.TestHelperClasses.Helper;
import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.Advice;
@ -100,4 +101,10 @@ public class TestClasses {
Helper h = new Helper(); Helper h = new Helper();
} }
} }
public static class HelperOtherAdvice {
public static void adviceMethod() {
new OtherTestHelperClasses.Bar().doSomething();
}
}
} }