Make muzzle generate helperClassNames() method (#1714)
This commit is contained in:
parent
45646ff367
commit
13c405c174
|
@ -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`.
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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[] {
|
||||||
|
|
|
@ -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[] {
|
||||||
|
|
|
@ -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[] {
|
||||||
|
|
|
@ -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[] {
|
||||||
|
|
|
@ -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[] {
|
||||||
|
|
|
@ -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[] {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue