Exclude JDK class references at build time, reduce allocation in ReferenceMatcher (DataDog/dd-trace-java#1613)
MODIFY
agent-tooling/src/main/java/io/opentelemetry/auto/tooling/muzzle/Reference.java
agent-tooling/src/main/java/io/opentelemetry/auto/tooling/muzzle/Reference.java
@@ -21,6 +21,7 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -612,7 +613,7 @@ public class Reference {
public static class Builder {
private final Set<Source> sources = new HashSet<>();
- private final Set<Flag> flags = new HashSet<>();
+ private final Set<Flag> flags = EnumSet.noneOf(Flag.class);
private final String className;
private String superName = null;
private final Set<String> interfaces = new HashSet<>();

MODIFY
agent-tooling/src/main/java/io/opentelemetry/auto/tooling/muzzle/ReferenceCreator.java
agent-tooling/src/main/java/io/opentelemetry/auto/tooling/muzzle/ReferenceCreator.java
@@ -188,10 +188,13 @@ public class ReferenceCreator extends ClassVisitor {
}
private void addReference(final Reference ref) {
- if (references.containsKey(ref.getClassName())) {
- references.put(ref.getClassName(), references.get(ref.getClassName()).merge(ref));
- } else {
+ 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));
+ }
}
}

MODIFY
agent-tooling/src/main/java/io/opentelemetry/auto/tooling/muzzle/ReferenceMatcher.java
agent-tooling/src/main/java/io/opentelemetry/auto/tooling/muzzle/ReferenceMatcher.java
@@ -102,13 +102,13 @@ public final class ReferenceMatcher {
loader = Utils.getBootstrapProxy();
}
- final List<Mismatch> mismatches = new ArrayList<>();
+ List<Mismatch> mismatches = Collections.emptyList();
for (final Reference reference : references) {
// Don't reference-check helper classes.
// They will be injected by the instrumentation's HelperInjector.
if (!helperClassNames.contains(reference.getClassName())) {
- mismatches.addAll(checkMatch(reference, loader));
+ mismatches = lazyAddAll(mismatches, checkMatch(reference, loader));
}
}
@@ -126,10 +126,8 @@ public final class ReferenceMatcher {
final TypePool typePool =
AgentTooling.poolStrategy()
.typePool(AgentTooling.locationStrategy().classFileLocator(loader), loader);
- final List<Mismatch> mismatches = new ArrayList<>();
try {
- final TypePool.Resolution resolution =
- typePool.describe(Utils.getClassName(reference.getClassName()));
+ final TypePool.Resolution resolution = typePool.describe(reference.getClassName());
if (!resolution.isResolved()) {
return Collections.<Mismatch>singletonList(
new Mismatch.MissingClass(
@@ -141,24 +139,26 @@ public final class ReferenceMatcher {
// bytebuddy throws an illegal state exception with this message if it cannot resolve types
// TODO: handle missing type resolutions without catching bytebuddy's exceptions
final String className = e.getMessage().replace("Cannot resolve type description for ", "");
- mismatches.add(
+ return Collections.<Mismatch>singletonList(
new Mismatch.MissingClass(reference.getSources().toArray(new Source[0]), className));
} else {
// Shouldn't happen. Fail the reference check and add a mismatch for debug logging.
- mismatches.add(new Mismatch.ReferenceCheckError(e, reference, loader));
+ return Collections.<Mismatch>singletonList(
+ new Mismatch.ReferenceCheckError(e, reference, loader));
}
}
- return mismatches;
}
public static List<Reference.Mismatch> checkMatch(
final Reference reference, final TypeDescription typeOnClasspath) {
- final List<Mismatch> mismatches = new ArrayList<>();
+ List<Mismatch> mismatches = Collections.emptyList();
for (final Reference.Flag flag : reference.getFlags()) {
if (!flag.matches(typeOnClasspath.getModifiers())) {
final String desc = reference.getClassName();
- mismatches.add(
+ mismatches =
+ lazyAdd(
+ mismatches,
new Mismatch.MissingFlag(
reference.getSources().toArray(new Source[0]),
desc,
@@ -170,7 +170,9 @@ public final class ReferenceMatcher {
for (final Reference.Field fieldRef : reference.getFields()) {
final FieldDescription.InDefinedShape fieldDescription = findField(fieldRef, typeOnClasspath);
if (fieldDescription == null) {
- mismatches.add(
+ mismatches =
+ lazyAdd(
+ mismatches,
new Reference.Mismatch.MissingField(
fieldRef.getSources().toArray(new Reference.Source[0]),
reference.getClassName(),
@@ -184,7 +186,9 @@ public final class ReferenceMatcher {
+ "#"
+ fieldRef.getName()
+ fieldRef.getType().getInternalName();
- mismatches.add(
+ mismatches =
+ lazyAdd(
+ mismatches,
new Mismatch.MissingFlag(
fieldRef.getSources().toArray(new Source[0]),
desc,
@@ -199,7 +203,9 @@ public final class ReferenceMatcher {
final MethodDescription.InDefinedShape methodDescription =
findMethod(methodRef, typeOnClasspath);
if (methodDescription == null) {
- mismatches.add(
+ mismatches =
+ lazyAdd(
+ mismatches,
new Reference.Mismatch.MissingMethod(
methodRef.getSources().toArray(new Reference.Source[0]),
methodRef.getName(),
@@ -209,7 +215,9 @@ public final class ReferenceMatcher {
if (!flag.matches(methodDescription.getModifiers())) {
final String desc =
reference.getClassName() + "#" + methodRef.getName() + methodRef.getDescriptor();
- mismatches.add(
+ mismatches =
+ lazyAdd(
+ mismatches,
new Mismatch.MissingFlag(
methodRef.getSources().toArray(new Source[0]),
desc,
@@ -219,7 +227,6 @@ public final class ReferenceMatcher {
}
}
}
-
return mismatches;
}
@@ -293,4 +300,21 @@ public final class ReferenceMatcher {
}
return null;
}
+
+ // optimization to avoid ArrayList allocation in the common case when there are no mismatches
+ private static List<Mismatch> lazyAdd(List<Mismatch> mismatches, Mismatch mismatch) {
+ List<Mismatch> result = mismatches.isEmpty() ? new ArrayList<Mismatch>() : mismatches;
+ result.add(mismatch);
+ return result;
+ }
+
+ // optimization to avoid ArrayList allocation in the common case when there are no mismatches
+ private static List<Mismatch> lazyAddAll(List<Mismatch> mismatches, List<Mismatch> toAdd) {
+ if (!toAdd.isEmpty()) {
+ List<Mismatch> result = mismatches.isEmpty() ? new ArrayList<Mismatch>() : mismatches;
+ result.addAll(toAdd);
+ return result;
+ }
+ return mismatches;
+ }
}

MODIFY
testing/src/test/groovy/muzzle/ReferenceCreatorTest.groovy
testing/src/test/groovy/muzzle/ReferenceCreatorTest.groovy
@@ -29,20 +29,18 @@ class ReferenceCreatorTest extends AgentTestRunner {
Map<String, Reference> references = ReferenceCreator.createReferencesFrom(MethodBodyAdvice.getName(), this.getClass().getClassLoader())
expect:
- references.get('java.lang.Object') != null
- references.get('java.lang.String') != null
references.get('muzzle.TestClasses$MethodBodyAdvice$A') != null
references.get('muzzle.TestClasses$MethodBodyAdvice$B') != null
references.get('muzzle.TestClasses$MethodBodyAdvice$SomeInterface') != null
references.get('muzzle.TestClasses$MethodBodyAdvice$SomeImplementation') != null
- references.keySet().size() == 6
+ references.keySet().size() == 4
// interface flags
references.get('muzzle.TestClasses$MethodBodyAdvice$B').getFlags().contains(Reference.Flag.NON_INTERFACE)
references.get('muzzle.TestClasses$MethodBodyAdvice$SomeInterface').getFlags().contains(Reference.Flag.INTERFACE)
// class access flags
- references.get('java.lang.Object').getFlags().contains(Reference.Flag.PUBLIC)
+ references.get('muzzle.TestClasses$MethodBodyAdvice$A').getFlags().contains(Reference.Flag.PACKAGE_OR_HIGHER)
references.get('muzzle.TestClasses$MethodBodyAdvice$B').getFlags().contains(Reference.Flag.PACKAGE_OR_HIGHER)
// method refs

Exclude JDK class references at build time, reduce allocation in ReferenceMatcher (DataDog/dd-trace-java#1613)