SafeHasSuperTypeMatcher: handle exceptions when getting class erasure

This commit is contained in:
Nikolay Martynov 2018-08-14 17:50:50 -04:00 committed by Andrew Kent
parent a7c63b9261
commit 36ec5d51e5
2 changed files with 136 additions and 27 deletions

View File

@ -1,7 +1,5 @@
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,10 +140,13 @@ public class ByteBuddyElementMatchers {
private boolean hasInterface(
final TypeDefinition typeDefinition, final Set<TypeDescription> checkedInterfaces) {
for (final TypeDefinition interfaceType : safeGetInterfaces(typeDefinition)) {
if (checkedInterfaces.add(interfaceType.asErasure())
&& (matcher.matches(interfaceType.asGenericType())
|| hasInterface(interfaceType, checkedInterfaces))) {
return true;
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 + ")";
}
}
}

View File

@ -1,5 +1,6 @@
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;