Muzzle should add SPI classes defined in helperResourceNames as references
MODIFY
buildSrc/src/main/java/io/opentelemetry/instrumentation/gradle/bytebuddy/ByteBuddyPluginConfigurator.java
buildSrc/src/main/java/io/opentelemetry/instrumentation/gradle/bytebuddy/ByteBuddyPluginConfigurator.java
package io.opentelemetry.instrumentation.gradle.bytebuddy;
+import com.google.common.collect.ImmutableList;
import java.io.File;
import java.util.Arrays;
import java.util.List;
@@ -46,7 +47,16 @@ public class ByteBuddyPluginConfigurator {
this.project = project;
this.sourceSet = sourceSet;
this.pluginClassName = pluginClassName;
- this.inputClasspath = inputClasspath;
+
+ // add build resources dir to classpath if it's present
+ File resourcesDir = sourceSet.getOutput().getResourcesDir();
+ this.inputClasspath =
+ resourcesDir == null
+ ? inputClasspath
+ : ImmutableList.<File>builder()
+ .addAll(inputClasspath)
+ .add(sourceSet.getOutput().getResourcesDir())
+ .build();
}
public void configure() {

MODIFY
instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java
instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java
@@ -22,17 +22,7 @@ public class AwsSdkInstrumentationModule extends InstrumentationModule {
@Override
public String[] additionalHelperClassNames() {
- return new String[] {
- "io.opentelemetry.javaagent.instrumentation.awssdk.v2_2.TracingExecutionInterceptor",
- "io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdk",
- "io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkHttpClientTracer",
- "io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkInjectAdapter",
- "io.opentelemetry.instrumentation.awssdk.v2_2.RequestType",
- "io.opentelemetry.instrumentation.awssdk.v2_2.SdkRequestDecorator",
- "io.opentelemetry.instrumentation.awssdk.v2_2.DbRequestDecorator",
- "io.opentelemetry.instrumentation.awssdk.v2_2.TracingExecutionInterceptor",
- "io.opentelemetry.extension.trace.propagation.AwsXRayPropagator"
- };
+ return new String[] {"io.opentelemetry.extension.trace.propagation.AwsXRayPropagator"};
}
/**

MODIFY
instrumentation/log4j/log4j-2.13.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/v2_13_2/Log4j2InstrumentationModule.java
instrumentation/log4j/log4j-2.13.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/v2_13_2/Log4j2InstrumentationModule.java
package io.opentelemetry.javaagent.instrumentation.log4j.v2_13_2;
import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed;
+import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
-import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.named;
-import static net.bytebuddy.matcher.ElementMatchers.none;
import com.google.auto.service.AutoService;
-import io.opentelemetry.instrumentation.log4j.v2_13_2.OpenTelemetryContextDataProvider;
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.List;
import java.util.Map;
-import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@@ -60,17 +57,7 @@ public class Log4j2InstrumentationModule extends InstrumentationModule {
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
// Nothing to instrument, no methods to match
- return singletonMap(none(), getClass().getName() + "$MuzzleCheckAdvice");
- }
-
- // This way muzzle will collect OpenTelemetryContextDataProvider references
- public static class MuzzleCheckAdvice {
- @Advice.OnMethodEnter
- public static void onEnter() {}
-
- public static void muzzleCheck(OpenTelemetryContextDataProvider contextDataProvider) {
- contextDataProvider.supplyContextData();
- }
+ return emptyMap();
}
}
}

MODIFY
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/collector/MuzzleCodeGenerator.java
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/collector/MuzzleCodeGenerator.java
@@ -149,7 +149,10 @@ class MuzzleCodeGenerator implements AsmVisitorWrapper {
ReferenceCollector collector = new ReferenceCollector();
for (String adviceClass : adviceClassNames) {
- collector.collectReferencesFrom(adviceClass);
+ collector.collectReferencesFromAdvice(adviceClass);
+ }
+ for (String resource : instrumenter.helperResourceNames()) {
+ collector.collectReferencesFromResource(resource);
}
return collector;
}

MODIFY
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/collector/ReferenceCollector.java
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/collector/ReferenceCollector.java
@@ -7,18 +7,24 @@ package io.opentelemetry.javaagent.tooling.muzzle.collector;
import static com.google.common.base.Preconditions.checkNotNull;
import static io.opentelemetry.javaagent.tooling.muzzle.InstrumentationClassPredicate.isInstrumentationClass;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Collections.singleton;
+import com.google.common.base.Strings;
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.muzzle.Reference;
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.net.URLConnection;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
@@ -26,17 +32,60 @@ import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
+import java.util.regex.Pattern;
import net.bytebuddy.jar.asm.ClassReader;
/**
* {@link LinkedHashMap} is used for reference map to guarantee a deterministic order of iteration,
* so that bytecode generated based on it would also be deterministic.
+ *
+ * <p>This class is only called at compile time by the {@link MuzzleCodeGenerationPlugin} ByteBuddy
+ * plugin.
*/
public class ReferenceCollector {
private final Map<String, Reference> references = new LinkedHashMap<>();
private final MutableGraph<String> helperSuperClassGraph = GraphBuilder.directed().build();
private final Set<String> visitedClasses = new HashSet<>();
+ /**
+ * If passed {@code resource} path points to an SPI file (either Java {@link
+ * java.util.ServiceLoader} or AWS SDK {@code ExecutionInterceptor}) reads the file and adds every
+ * implementation as a reference, traversing the graph of classes until a non-instrumentation
+ * (external) class is encountered.
+ *
+ * @param resource path to the resource file, same as in {@link ClassLoader#getResource(String)}
+ * @see io.opentelemetry.javaagent.tooling.muzzle.InstrumentationClassPredicate
+ */
+ public void collectReferencesFromResource(String resource) {
+ if (!isSpiFile(resource)) {
+ return;
+ }
+
+ List<String> spiImplementations = new ArrayList<>();
+ try (InputStream stream = getResourceStream(resource)) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(stream, UTF_8));
+ while (reader.ready()) {
+ String line = reader.readLine();
+ if (!Strings.isNullOrEmpty(line)) {
+ spiImplementations.add(line);
+ }
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Error reading resource " + resource, e);
+ }
+
+ visitClassesAndCollectReferences(spiImplementations, false);
+ }
+
+ private static final Pattern AWS_SDK_SERVICE_INTERCEPTOR_SPI =
+ Pattern.compile("software/amazon/awssdk/services/\\w+(/\\w+)?/execution.interceptors");
+
+ private boolean isSpiFile(String resource) {
+ return resource.startsWith("META-INF/services/")
+ || resource.equals("software/amazon/awssdk/global/handlers/execution.interceptors")
+ || AWS_SDK_SERVICE_INTERCEPTOR_SPI.matcher(resource).matches();
+ }
+
/**
* Traverse a graph of classes starting from {@code adviceClassName} and collect all references to
* both internal (instrumentation) and external classes.
@@ -44,17 +93,17 @@ public class ReferenceCollector {
* <p>The graph of classes is traversed until a non-instrumentation (external) class is
* encountered.
*
- * <p>This class is only called at compile time by the {@link MuzzleCodeGenerationPlugin}
- * ByteBuddy plugin.
- *
* @param adviceClassName Starting point for generating references.
* @see io.opentelemetry.javaagent.tooling.muzzle.InstrumentationClassPredicate
*/
- public void collectReferencesFrom(String adviceClassName) {
- Queue<String> instrumentationQueue = new ArrayDeque<>();
- instrumentationQueue.add(adviceClassName);
+ public void collectReferencesFromAdvice(String adviceClassName) {
+ visitClassesAndCollectReferences(singleton(adviceClassName), true);
+ }
- boolean isAdviceClass = true;
+ private void visitClassesAndCollectReferences(
+ Collection<String> startingClasses, boolean startsFromAdviceClass) {
+ Queue<String> instrumentationQueue = new ArrayDeque<>(startingClasses);
+ boolean isAdviceClass = startsFromAdviceClass;
while (!instrumentationQueue.isEmpty()) {
String visitedClassName = instrumentationQueue.remove();
@@ -90,13 +139,15 @@ public class ReferenceCollector {
}
private static InputStream getClassFileStream(String className) throws IOException {
+ return getResourceStream(Utils.getResourceName(className));
+ }
+
+ private static InputStream getResourceStream(String resource) throws IOException {
URLConnection connection =
checkNotNull(
- ReferenceCollector.class
- .getClassLoader()
- .getResource(Utils.getResourceName(className)),
- "Couldn't find class file %s",
- className)
+ ReferenceCollector.class.getClassLoader().getResource(resource),
+ "Couldn't find resource %s",
+ resource)
.openConnection();
// Since the JarFile cache is not per class loader, but global with path as key, using cache may

MODIFY
testing-common/src/test/groovy/muzzle/ReferenceCollectorTest.groovy
testing-common/src/test/groovy/muzzle/ReferenceCollectorTest.groovy
@@ -18,12 +18,13 @@ import io.opentelemetry.instrumentation.TestHelperClasses
import io.opentelemetry.javaagent.tooling.muzzle.Reference
import io.opentelemetry.javaagent.tooling.muzzle.collector.ReferenceCollector
import spock.lang.Specification
+import spock.lang.Unroll
class ReferenceCollectorTest extends Specification {
def "method body creates references"() {
setup:
def collector = new ReferenceCollector()
- collector.collectReferencesFrom(MethodBodyAdvice.name)
+ collector.collectReferencesFromAdvice(MethodBodyAdvice.name)
def references = collector.getReferences()
expect:
@@ -69,7 +70,7 @@ class ReferenceCollectorTest extends Specification {
def "protected ref test"() {
setup:
def collector = new ReferenceCollector()
- collector.collectReferencesFrom(MethodBodyAdvice.B2.name)
+ collector.collectReferencesFromAdvice(MethodBodyAdvice.B2.name)
def references = collector.getReferences()
expect:
@@ -81,7 +82,7 @@ class ReferenceCollectorTest extends Specification {
def "ldc creates references"() {
setup:
def collector = new ReferenceCollector()
- collector.collectReferencesFrom(LdcAdvice.name)
+ collector.collectReferencesFromAdvice(LdcAdvice.name)
def references = collector.getReferences()
expect:
@@ -91,7 +92,7 @@ class ReferenceCollectorTest extends Specification {
def "instanceof creates references"() {
setup:
def collector = new ReferenceCollector()
- collector.collectReferencesFrom(TestClasses.InstanceofAdvice.name)
+ collector.collectReferencesFromAdvice(TestClasses.InstanceofAdvice.name)
def references = collector.getReferences()
expect:
@@ -101,7 +102,7 @@ class ReferenceCollectorTest extends Specification {
def "invokedynamic creates references"() {
setup:
def collector = new ReferenceCollector()
- collector.collectReferencesFrom(TestClasses.InvokeDynamicAdvice.name)
+ collector.collectReferencesFromAdvice(TestClasses.InvokeDynamicAdvice.name)
def references = collector.getReferences()
expect:
@@ -112,7 +113,7 @@ class ReferenceCollectorTest extends Specification {
def "should create references for helper classes"() {
when:
def collector = new ReferenceCollector()
- collector.collectReferencesFrom(HelperAdvice.name)
+ collector.collectReferencesFromAdvice(HelperAdvice.name)
def references = collector.getReferences()
then:
@@ -146,7 +147,7 @@ class ReferenceCollectorTest extends Specification {
def "should find all helper classes"() {
when:
def collector = new ReferenceCollector()
- collector.collectReferencesFrom(HelperAdvice.name)
+ collector.collectReferencesFromAdvice(HelperAdvice.name)
def helperClasses = collector.getSortedHelperClasses()
then:
@@ -163,8 +164,8 @@ class ReferenceCollectorTest extends Specification {
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)
+ collector.collectReferencesFromAdvice(TestClasses.HelperAdvice.name)
+ collector.collectReferencesFromAdvice(TestClasses.HelperOtherAdvice.name)
def helperClasses = collector.getSortedHelperClasses()
then:
@@ -192,6 +193,49 @@ class ReferenceCollectorTest extends Specification {
])
}
+ @Unroll
+ def "should collect helper classes from resource file #desc"() {
+ when:
+ def collector = new ReferenceCollector()
+ collector.collectReferencesFromResource(resource)
+
+ then: "SPI classes are collected as references"
+ def references = collector.references
+ references.keySet() == [
+ TestHelperClasses.Helper.name,
+ TestHelperClasses.HelperSuperClass.name,
+ TestHelperClasses.HelperInterface.name
+ ] as Set
+
+ then: "SPI classes are included in helper classes"
+ def helperClasses = collector.sortedHelperClasses
+ assertThatContainsInOrder helperClasses, [
+ TestHelperClasses.HelperInterface.name,
+ TestHelperClasses.Helper.name
+ ]
+ assertThatContainsInOrder helperClasses, [
+ TestHelperClasses.HelperSuperClass.name,
+ TestHelperClasses.Helper.name
+ ]
+
+ where:
+ desc | resource
+ "Java SPI" | "META-INF/services/test.resource.file"
+ "AWS SDK global interceptors file" | "software/amazon/awssdk/global/handlers/execution.interceptors"
+ "AWS SDK service interceptors file" | "software/amazon/awssdk/services/testservice/execution.interceptors"
+ "AWS SDK service (second level) interceptors file" | "software/amazon/awssdk/services/testservice/testsubservice/execution.interceptors"
+ }
+
+ def "should ignore arbitrary resource file"() {
+ when:
+ def collector = new ReferenceCollector()
+ collector.collectReferencesFromResource("application.properties")
+
+ then:
+ collector.references.isEmpty()
+ collector.sortedHelperClasses.isEmpty()
+ }
+
private static assertHelperSuperClassMethod(Reference reference, boolean isAbstract) {
assertMethod reference, 'abstractMethod', '()I',
VisibilityFlag.PROTECTED,

MODIFY
testing-common/src/test/groovy/muzzle/ReferenceMatcherTest.groovy
testing-common/src/test/groovy/muzzle/ReferenceMatcherTest.groovy
@@ -47,7 +47,7 @@ class ReferenceMatcherTest extends Specification {
def "match safe classpaths"() {
setup:
def collector = new ReferenceCollector()
- collector.collectReferencesFrom(MethodBodyAdvice.name)
+ collector.collectReferencesFromAdvice(MethodBodyAdvice.name)
Reference[] refs = collector.getReferences().values().toArray(new Reference[0])
def refMatcher = new ReferenceMatcher(refs)
@@ -85,7 +85,7 @@ class ReferenceMatcherTest extends Specification {
(ClassLoader) null)
def collector = new ReferenceCollector()
- collector.collectReferencesFrom(MethodBodyAdvice.name)
+ collector.collectReferencesFromAdvice(MethodBodyAdvice.name)
Reference[] refs = collector.getReferences().values().toArray(new Reference[0])
def refMatcher1 = new ReferenceMatcher(refs)

MODIFY
testing-common/src/test/java/muzzle/MuzzleWeakReferenceTest.java
testing-common/src/test/java/muzzle/MuzzleWeakReferenceTest.java
@@ -23,7 +23,7 @@ public class MuzzleWeakReferenceTest {
ClassLoader loader = new URLClassLoader(new URL[0], null);
WeakReference<ClassLoader> clRef = new WeakReference<>(loader);
ReferenceCollector collector = new ReferenceCollector();
- collector.collectReferencesFrom(TestClasses.MethodBodyAdvice.class.getName());
+ collector.collectReferencesFromAdvice(TestClasses.MethodBodyAdvice.class.getName());
Reference[] refs = collector.getReferences().values().toArray(new Reference[0]);
ReferenceMatcher refMatcher = new ReferenceMatcher(refs);
refMatcher.getMismatchedReferenceSources(loader);

ADD
testing-common/src/test/resources/META-INF/services/test.resource.file
+io.opentelemetry.instrumentation.TestHelperClasses$Helper
\ No newline at end of file

ADD
testing-common/src/test/resources/software/amazon/awssdk/global/handlers/execution.interceptors
+io.opentelemetry.instrumentation.TestHelperClasses$Helper
\ No newline at end of file

ADD
testing-common/src/test/resources/software/amazon/awssdk/services/testservice/execution.interceptors
+io.opentelemetry.instrumentation.TestHelperClasses$Helper
\ No newline at end of file

ADD
testing-common/src/test/resources/software/amazon/awssdk/services/testservice/testsubservice/execution.interceptors
+io.opentelemetry.instrumentation.TestHelperClasses$Helper
\ No newline at end of file

Muzzle should add SPI classes defined in helperResourceNames as references (#1918)