SafeHasSuperTypeMatcher: handle exceptions when getting class erasure
MODIFY
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ByteBuddyElementMatchers.java
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ByteBuddyElementMatchers.java
package datadog.trace.agent.tooling;
-import static net.bytebuddy.matcher.ElementMatchers.erasure;
-
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
@@ -32,7 +30,7 @@ public class ByteBuddyElementMatchers {
*/
public static <T extends TypeDescription> ElementMatcher.Junction<T> safeHasSuperType(
final ElementMatcher<? super TypeDescription> matcher) {
- return safeHasGenericSuperType(erasure(matcher));
+ return safeHasGenericSuperType(new SafeErasureMatcher(matcher));
}
/**
@@ -50,6 +48,29 @@ public class ByteBuddyElementMatchers {
return new SafeHasSuperTypeMatcher<>(matcher);
}
+ /**
+ * Wraps another matcher to assure that an element is not matched in case that the matching causes
+ * an {@link Exception}. Logs exception if it happens.
+ *
+ * @param matcher The element matcher that potentially throws an exception.
+ * @param <T> The type of the matched object.
+ * @return A matcher that returns {@code false} in case that the given matcher throws an
+ * exception.
+ */
+ public static <T> ElementMatcher.Junction<T> failSafe(
+ final ElementMatcher<? super T> matcher, final String description) {
+ return new SafeMatcher<>(matcher, false, description);
+ }
+
+ private static TypeDescription safeAsErasure(final TypeDefinition target) {
+ try {
+ return target.asErasure();
+ } catch (final Exception e) {
+ log.debug("Exception trying to get interfaces:", e);
+ return null;
+ }
+ }
+
/**
* An element matcher that matches a super type. This is different from {@link
* net.bytebuddy.matcher.HasSuperTypeMatcher} in the following way:
@@ -119,12 +140,15 @@ public class ByteBuddyElementMatchers {
private boolean hasInterface(
final TypeDefinition typeDefinition, final Set<TypeDescription> checkedInterfaces) {
for (final TypeDefinition interfaceType : safeGetInterfaces(typeDefinition)) {
+ final TypeDescription erasure = safeAsErasure(interfaceType);
+ if (erasure != null) {
if (checkedInterfaces.add(interfaceType.asErasure())
&& (matcher.matches(interfaceType.asGenericType())
|| hasInterface(interfaceType, checkedInterfaces))) {
return true;
}
}
+ }
return false;
}
@@ -153,4 +177,98 @@ public class ByteBuddyElementMatchers {
return "safeHasSuperType(" + matcher + ")";
}
}
+
+ /**
+ * An element matcher that matches its argument's {@link TypeDescription.Generic} raw type against
+ * the given matcher for a {@link TypeDescription}. As a wildcard does not define an erasure, a
+ * runtime exception is thrown when this matcher is applied to a wildcard.
+ *
+ * <p>Catches and logs exception if it was thrown when getting erasure, returning false.
+ *
+ * @param <T> The type of the matched entity.
+ * @see net.bytebuddy.matcher.ErasureMatcher
+ */
+ @HashCodeAndEqualsPlugin.Enhance
+ public static class SafeErasureMatcher<T extends TypeDefinition>
+ extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /** The matcher to apply to the raw type of the matched element. */
+ private final ElementMatcher<? super TypeDescription> matcher;
+
+ /**
+ * Creates a new erasure matcher.
+ *
+ * @param matcher The matcher to apply to the raw type.
+ */
+ public SafeErasureMatcher(final ElementMatcher<? super TypeDescription> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(final T target) {
+ final TypeDescription erasure = safeAsErasure(target);
+ if (erasure == null) {
+ return false;
+ } else {
+ // We would like matcher exceptions to propagate
+ return matcher.matches(erasure);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "safeErasure(" + matcher + ")";
+ }
+ }
+
+ /**
+ * A fail-safe matcher catches exceptions that are thrown by a delegate matcher and returns an
+ * alternative value.
+ *
+ * <p>Logs exception if it was thrown.
+ *
+ * @param <T> The type of the matched entity.
+ * @see net.bytebuddy.matcher.FailSafeMatcher
+ */
+ @HashCodeAndEqualsPlugin.Enhance
+ public static class SafeMatcher<T> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /** The delegate matcher that might throw an exception. */
+ private final ElementMatcher<? super T> matcher;
+
+ /** The fallback value in case of an exception. */
+ private final boolean fallback;
+
+ /** The text description to log if exception happens. */
+ private final String description;
+
+ /**
+ * Creates a new fail-safe element matcher.
+ *
+ * @param matcher The delegate matcher that might throw an exception.
+ * @param fallback The fallback value in case of an exception.
+ * @param description Descriptive string to log along with exception.
+ */
+ public SafeMatcher(
+ final ElementMatcher<? super T> matcher, final boolean fallback, final String description) {
+ this.matcher = matcher;
+ this.fallback = fallback;
+ this.description = description;
+ }
+
+ @Override
+ public boolean matches(final T target) {
+ try {
+ return matcher.matches(target);
+ } catch (final Exception e) {
+ log.debug(description, e);
+ return fallback;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "safeMatcher(try(" + matcher + ") or " + fallback + ")";
+ }
+ }
}

MODIFY
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Instrumenter.java
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Instrumenter.java
package datadog.trace.agent.tooling;
+import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.failSafe;
import static datadog.trace.agent.tooling.Utils.getConfigEnabled;
import static net.bytebuddy.matcher.ElementMatchers.any;
@@ -80,7 +81,14 @@ public interface Instrumenter {
AgentBuilder.Identified.Extendable agentBuilder =
parentAgentBuilder
- .type(safeTypeMatcher(typeMatcher()), classLoaderMatcher())
+ .type(
+ failSafe(
+ typeMatcher(),
+ "Instrumentation type matcher unexpected exception: " + getClass().getName()),
+ failSafe(
+ classLoaderMatcher(),
+ "Instrumentation class loader matcher unexpected exception: "
+ + getClass().getName()))
.and(new MuzzleMatcher())
.transform(DDTransformers.defaultTransformers());
agentBuilder = injectHelperClasses(agentBuilder);
@@ -88,23 +96,6 @@ public interface Instrumenter {
return agentBuilder.asDecorator();
}
- /** Wrap an ElementMatcher in a try-catch exception and log any exceptions. */
- private ElementMatcher<? super TypeDescription> safeTypeMatcher(
- final ElementMatcher<? super TypeDescription> instrumentationMatcher) {
- return new ElementMatcher<TypeDescription>() {
- @Override
- public boolean matches(TypeDescription target) {
- try {
- return instrumentationMatcher.matches(target);
- } catch (Exception e) {
- log.debug(
- "Instrumentation matcher unexpected exception: " + instrumentationPrimaryName, e);
- return false;
- }
- }
- };
- }
-
private AgentBuilder.Identified.Extendable injectHelperClasses(
AgentBuilder.Identified.Extendable agentBuilder) {
final String[] helperClassNames = helperClassNames();
@@ -151,7 +142,7 @@ public interface Instrumenter {
instrumentationPrimaryName,
getClass().getName(),
classLoader);
- for (Reference.Mismatch mismatch : mismatches) {
+ for (final Reference.Mismatch mismatch : mismatches) {
log.debug("-- {}", mismatch);
}
}
@@ -159,7 +150,7 @@ public interface Instrumenter {
log.debug(
"Applying instrumentation: {} -- {} on {}",
instrumentationPrimaryName,
- this.getClass().getName(),
+ getClass().getName(),
classLoader);
}
return mismatches.size() == 0;

SafeHasSuperTypeMatcher: handle exceptions when getting class erasure