Iterate type interfaces without materialising a list during super type matching (DataDog/dd-trace-java#1578)
MODIFY
agent-tooling/src/main/java/io/opentelemetry/auto/tooling/bytebuddy/matcher/HasSuperMethodMatcher.java
agent-tooling/src/main/java/io/opentelemetry/auto/tooling/bytebuddy/matcher/HasSuperMethodMatcher.java
@@ -42,7 +42,7 @@ class HasSuperMethodMatcher<T extends MethodDescription>
}
final Junction<MethodDescription> signatureMatcher = hasSignature(target.asSignatureToken());
TypeDefinition declaringType = target.getDeclaringType();
- final Set<TypeDefinition> checkedInterfaces = new HashSet<>();
+ final Set<TypeDefinition> checkedInterfaces = new HashSet<>(8);
while (declaringType != null) {
for (final MethodDescription methodDescription : declaringType.getDeclaredMethods()) {
@@ -63,8 +63,7 @@ class HasSuperMethodMatcher<T extends MethodDescription>
final Junction<MethodDescription> signatureMatcher,
final Set<TypeDefinition> checkedInterfaces) {
for (final TypeDefinition type : interfaces) {
- if (!checkedInterfaces.contains(type)) {
- checkedInterfaces.add(type);
+ if (checkedInterfaces.add(type)) {
for (final MethodDescription methodDescription : type.getDeclaredMethods()) {
if (signatureMatcher.matches(methodDescription) && matcher.matches(methodDescription)) {
return true;

MODIFY
agent-tooling/src/main/java/io/opentelemetry/auto/tooling/bytebuddy/matcher/SafeHasSuperTypeMatcher.java
agent-tooling/src/main/java/io/opentelemetry/auto/tooling/bytebuddy/matcher/SafeHasSuperTypeMatcher.java
@@ -18,10 +18,8 @@ package io.opentelemetry.auto.tooling.bytebuddy.matcher;
import static io.opentelemetry.auto.tooling.bytebuddy.matcher.AgentElementMatchers.safeTypeDefinitionName;
import static io.opentelemetry.auto.tooling.bytebuddy.matcher.SafeErasureMatcher.safeAsErasure;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.description.type.TypeDefinition;
@@ -67,7 +65,7 @@ class SafeHasSuperTypeMatcher<T extends TypeDescription>
@Override
public boolean matches(final T target) {
- final Set<TypeDescription> checkedInterfaces = new HashSet<>();
+ final Set<TypeDescription> checkedInterfaces = new HashSet<>(8);
// We do not use foreach loop and iterator interface here because we need to catch exceptions
// in {@code getSuperClass} calls
TypeDefinition typeDefinition = target;
@@ -104,30 +102,8 @@ class SafeHasSuperTypeMatcher<T extends TypeDescription>
return false;
}
- /**
- * TypeDefinition#getInterfaces() produces an interator which may throw an exception during
- * iteration if an interface is absent from the classpath.
- *
- * <p>This method exists to allow getting interfaces even if the lookup on one fails.
- */
- private List<TypeDefinition> safeGetInterfaces(final TypeDefinition typeDefinition) {
- final List<TypeDefinition> interfaceTypes = new ArrayList<>();
- try {
- final Iterator<TypeDescription.Generic> interfaceIter =
- typeDefinition.getInterfaces().iterator();
- while (interfaceIter.hasNext()) {
- interfaceTypes.add(interfaceIter.next());
- }
- } catch (final Exception e) {
- if (log.isDebugEnabled()) {
- log.debug(
- "{} trying to get interfaces for target {}: {}",
- e.getClass().getSimpleName(),
- safeTypeDefinitionName(typeDefinition),
- e.getMessage());
- }
- }
- return interfaceTypes;
+ private Iterable<TypeDefinition> safeGetInterfaces(final TypeDefinition typeDefinition) {
+ return new SafeInterfaceIterator(typeDefinition);
}
static TypeDefinition safeGetSuperClass(final TypeDefinition typeDefinition) {
@@ -167,4 +143,69 @@ class SafeHasSuperTypeMatcher<T extends TypeDescription>
public int hashCode() {
return 17 * 31 + matcher.hashCode();
}
+
+ /**
+ * TypeDefinition#getInterfaces() produces an iterator which may throw an exception during
+ * iteration if an interface is absent from the classpath.
+ *
+ * <p>The caller MUST call hasNext() before calling next().
+ *
+ * <p>This wrapper exists to allow getting interfaces even if the lookup on one fails.
+ */
+ private static class SafeInterfaceIterator
+ implements Iterator<TypeDefinition>, Iterable<TypeDefinition> {
+ private final TypeDefinition typeDefinition;
+ private final Iterator<TypeDescription.Generic> it;
+ private TypeDefinition next;
+
+ private SafeInterfaceIterator(TypeDefinition typeDefinition) {
+ this.typeDefinition = typeDefinition;
+ Iterator<TypeDescription.Generic> it = null;
+ try {
+ it = typeDefinition.getInterfaces().iterator();
+ } catch (Exception e) {
+ logException(typeDefinition, e);
+ }
+ this.it = it;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (null != it && it.hasNext()) {
+ try {
+ next = it.next();
+ return true;
+ } catch (Exception e) {
+ logException(typeDefinition, e);
+ return false;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public TypeDefinition next() {
+ return next;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Iterator<TypeDefinition> iterator() {
+ return this;
+ }
+
+ private void logException(TypeDefinition typeDefinition, Exception e) {
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "{} trying to get interfaces for target {}: {}",
+ e.getClass().getSimpleName(),
+ safeTypeDefinitionName(typeDefinition),
+ e.getMessage());
+ }
+ }
+ }
}

MODIFY
agent-tooling/src/test/groovy/io/opentelemetry/auto/tooling/bytebuddy/matcher/HasInterfaceMatcherTest.groovy
agent-tooling/src/test/groovy/io/opentelemetry/auto/tooling/bytebuddy/matcher/HasInterfaceMatcherTest.groovy
@@ -23,6 +23,7 @@ import io.opentelemetry.auto.tooling.bytebuddy.matcher.testclasses.F
import io.opentelemetry.auto.tooling.bytebuddy.matcher.testclasses.G
import io.opentelemetry.auto.util.test.AgentSpecification
import net.bytebuddy.description.type.TypeDescription
+import net.bytebuddy.description.type.TypeList
import net.bytebuddy.jar.asm.Opcodes
import spock.lang.Shared
@@ -61,6 +62,8 @@ class HasInterfaceMatcherTest extends AgentSpecification {
def type = Mock(TypeDescription)
def typeGeneric = Mock(TypeDescription.Generic)
def matcher = implementsInterface(named(Object.name))
+ def interfaces = Mock(TypeList.Generic)
+ def it = new ThrowOnFirstElement()
when:
def result = matcher.matches(type)
@@ -73,7 +76,8 @@ class HasInterfaceMatcherTest extends AgentSpecification {
1 * type.asGenericType() >> typeGeneric
1 * typeGeneric.asErasure() >> { throw new Exception("asErasure exception") }
1 * typeGeneric.getTypeName() >> "typeGeneric-name"
- 1 * type.getInterfaces() >> { throw new Exception("getInterfaces exception") }
+ 1 * type.getInterfaces() >> interfaces
+ 1 * interfaces.iterator() >> it
1 * type.getSuperClass() >> { throw new Exception("getSuperClass exception") }
2 * type.getTypeName() >> "type-name"
0 * _

MODIFY
agent-tooling/src/test/groovy/io/opentelemetry/auto/tooling/bytebuddy/matcher/ImplementsInterfaceMatcherTest.groovy
agent-tooling/src/test/groovy/io/opentelemetry/auto/tooling/bytebuddy/matcher/ImplementsInterfaceMatcherTest.groovy
@@ -23,6 +23,7 @@ import io.opentelemetry.auto.tooling.bytebuddy.matcher.testclasses.F
import io.opentelemetry.auto.tooling.bytebuddy.matcher.testclasses.G
import io.opentelemetry.auto.util.test.AgentSpecification
import net.bytebuddy.description.type.TypeDescription
+import net.bytebuddy.description.type.TypeList
import net.bytebuddy.jar.asm.Opcodes
import spock.lang.Shared
@@ -55,7 +56,7 @@ class ImplementsInterfaceMatcherTest extends AgentSpecification {
argument = typePool.describe(type.name).resolve()
}
- def "test traversal exceptions"() {
+ def "test exception getting interfaces"() {
setup:
def type = Mock(TypeDescription)
def typeGeneric = Mock(TypeDescription.Generic)
@@ -77,4 +78,30 @@ class ImplementsInterfaceMatcherTest extends AgentSpecification {
2 * type.getTypeName() >> "type-name"
0 * _
}
+
+ def "test traversal exceptions"() {
+ setup:
+ def type = Mock(TypeDescription)
+ def typeGeneric = Mock(TypeDescription.Generic)
+ def matcher = implementsInterface(named(Object.name))
+ def interfaces = Mock(TypeList.Generic)
+ def it = new ThrowOnFirstElement()
+
+ when:
+ def result = matcher.matches(type)
+
+ then:
+ !result // default to false
+ noExceptionThrown()
+ 1 * type.getModifiers() >> Opcodes.ACC_ABSTRACT
+ 1 * type.isInterface() >> true
+ 1 * type.asGenericType() >> typeGeneric
+ 1 * typeGeneric.asErasure() >> { throw new Exception("asErasure exception") }
+ 1 * typeGeneric.getTypeName() >> "typeGeneric-name"
+ 1 * type.getInterfaces() >> interfaces
+ 1 * interfaces.iterator() >> it
+ 2 * type.getTypeName() >> "type-name"
+ 1 * type.getSuperClass() >> { throw new Exception("getSuperClass exception") }
+ 0 * _
+ }
}

MODIFY
agent-tooling/src/test/groovy/io/opentelemetry/auto/tooling/bytebuddy/matcher/SafeHasSuperTypeMatcherTest.groovy
agent-tooling/src/test/groovy/io/opentelemetry/auto/tooling/bytebuddy/matcher/SafeHasSuperTypeMatcherTest.groovy
@@ -23,6 +23,7 @@ import io.opentelemetry.auto.tooling.bytebuddy.matcher.testclasses.F
import io.opentelemetry.auto.tooling.bytebuddy.matcher.testclasses.G
import io.opentelemetry.auto.util.test.AgentSpecification
import net.bytebuddy.description.type.TypeDescription
+import net.bytebuddy.description.type.TypeList
import net.bytebuddy.jar.asm.Opcodes
import spock.lang.Shared
@@ -55,7 +56,7 @@ class SafeHasSuperTypeMatcherTest extends AgentSpecification {
argument = typePool.describe(type.name).resolve()
}
- def "test traversal exceptions"() {
+ def "test exception getting interfaces"() {
setup:
def type = Mock(TypeDescription)
def typeGeneric = Mock(TypeDescription.Generic)
@@ -76,4 +77,26 @@ class SafeHasSuperTypeMatcherTest extends AgentSpecification {
2 * type.getTypeName() >> "type-name"
0 * _
}
+
+ def "test traversal exceptions"() {
+ setup:
+ def type = Mock(TypeDescription)
+ def typeGeneric = Mock(TypeDescription.Generic)
+ def matcher = safeHasSuperType(named(Object.name))
+ def interfaces = Mock(TypeList.Generic)
+ def it = new ThrowOnFirstElement()
+
+ when:
+ def result = matcher.matches(type)
+
+ then:
+ !result // default to false
+ noExceptionThrown()
+ 1 * type.getModifiers() >> Opcodes.ACC_ABSTRACT
+ 1 * type.getInterfaces() >> interfaces
+ 1 * interfaces.iterator() >> it
+ 1 * type.asGenericType() >> typeGeneric
+ 1 * typeGeneric.asErasure() >> { throw new Exception("asErasure exception") }
+ 1 * typeGeneric.getTypeName() >> "typeGeneric-name"
+ }
}

ADD
agent-tooling/src/test/groovy/io/opentelemetry/auto/tooling/bytebuddy/matcher/ThrowOnFirstElement.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.opentelemetry.auto.tooling.bytebuddy.matcher
+
+class ThrowOnFirstElement implements Iterator<Object> {
+
+ int i = 0
+
+ @Override
+ boolean hasNext() {
+ return i++ < 1
+ }
+
+ @Override
+ Object next() {
+ throw new Exception("iteration exception")
+ }
+}

Iterate type interfaces without materialising a list during super type matching (DataDog/dd-trace-java#1578)