Skip unsafe classloaders.
MODIFY
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java
package datadog.trace.agent.tooling;
-import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderWithName;
-import static datadog.trace.agent.tooling.ClassLoaderMatcher.isReflectionClassLoader;
+import static datadog.trace.agent.tooling.ClassLoaderMatcher.skipClassLoader;
import static net.bytebuddy.matcher.ElementMatchers.any;
-import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.nameMatches;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
@@ -32,12 +30,6 @@ public class AgentInstaller {
*/
public static ResettableClassFileTransformer installBytebuddyAgent(
final Instrumentation inst, final AgentBuilder.Listener... listeners) {
- // Classloader notes:
-
- // 1. Skip classloaders which don't delegate to bootstrap
-
- // 2. Skip incompatible versions of OpenTracing
-
AgentBuilder agentBuilder =
new AgentBuilder.Default()
.disableClassFormatChanges()
@@ -57,13 +49,7 @@ public class AgentInstaller {
.or(nameContains("javassist"))
.or(nameContains(".asm."))
.or(nameMatches("com\\.mchange\\.v2\\.c3p0\\..*Proxy"))
- .ignore(
- any(),
- isBootstrapClassLoader()
- .or(isReflectionClassLoader())
- .or(
- classLoaderWithName(
- "org.codehaus.groovy.runtime.callsite.CallSiteClassLoader")));
+ .ignore(any(), skipClassLoader());
for (final AgentBuilder.Listener listener : listeners) {
agentBuilder = agentBuilder.with(listener);
}

MODIFY
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ClassLoaderMatcher.java
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ClassLoaderMatcher.java
package datadog.trace.agent.tooling;
-import java.util.Collections;
-import java.util.Map;
-import java.util.WeakHashMap;
+import datadog.trace.agent.bootstrap.DatadogClassLoader;
+import datadog.trace.agent.bootstrap.PatchLogger;
+import io.opentracing.util.GlobalTracer;
+import java.util.*;
+import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.matcher.ElementMatcher;
+@Slf4j
public class ClassLoaderMatcher {
/** A private constructor that must not be invoked. */
private ClassLoaderMatcher() {
throw new UnsupportedOperationException();
}
- public static ElementMatcher.Junction.AbstractBase<ClassLoader> classLoaderWithName(
- final String name) {
- return new ClassLoaderNameMatcher(name);
- }
-
- public static ElementMatcher.Junction.AbstractBase<ClassLoader> isReflectionClassLoader() {
- return new ClassLoaderNameMatcher("sun.reflect.DelegatingClassLoader");
+ public static ElementMatcher.Junction.AbstractBase<ClassLoader> skipClassLoader() {
+ return SkipClassLoaderMatcher.INSTANCE;
}
public static ElementMatcher.Junction.AbstractBase<ClassLoader> classLoaderHasClasses(
@@ -30,6 +28,79 @@ public class ClassLoaderMatcher {
return new ClassLoaderHasClassWithFieldMatcher(className, fieldName);
}
+ private static class SkipClassLoaderMatcher
+ extends ElementMatcher.Junction.AbstractBase<ClassLoader> {
+ public static final SkipClassLoaderMatcher INSTANCE = new SkipClassLoaderMatcher();
+ /* Cache of classloader-instance -> (true|false). True = skip instrumentation. False = safe to instrument. */
+ private static final Map<ClassLoader, Boolean> SKIP_CACHE =
+ Collections.synchronizedMap(new WeakHashMap<ClassLoader, Boolean>());
+ private static final Set<String> CLASSLOADER_CLASSES_TO_SKIP;
+
+ static {
+ final Set<String> classesToSkip = new HashSet<String>();
+ classesToSkip.add("org.codehaus.groovy.runtime.callsite.CallSiteClassLoader");
+ classesToSkip.add("sun.reflect.DelegatingClassLoader");
+ classesToSkip.add(DatadogClassLoader.class.getName());
+ CLASSLOADER_CLASSES_TO_SKIP = Collections.unmodifiableSet(classesToSkip);
+ }
+
+ private SkipClassLoaderMatcher() {}
+
+ @Override
+ public boolean matches(ClassLoader target) {
+ if (null == target) {
+ // bootstrap instrumentation not supported yet.
+ return true;
+ }
+ return shouldSkipClass(target) || shouldSkipInstance(target);
+ }
+
+ private boolean shouldSkipClass(ClassLoader loader) {
+ return CLASSLOADER_CLASSES_TO_SKIP.contains(loader.getClass().getName());
+ }
+
+ private boolean shouldSkipInstance(final ClassLoader loader) {
+ Boolean cached = SKIP_CACHE.get(loader);
+ if (null != cached) {
+ return cached.booleanValue();
+ }
+ synchronized (this) {
+ cached = SKIP_CACHE.get(loader);
+ if (null != cached) {
+ return cached.booleanValue();
+ }
+ boolean skip = !delegatesToBootstrap(loader);
+ if (skip) {
+ log.debug(
+ "skipping classloader instance {} of type {}", loader, loader.getClass().getName());
+ }
+ SKIP_CACHE.put(loader, skip);
+ return skip;
+ }
+ }
+
+ private boolean delegatesToBootstrap(ClassLoader loader) {
+ boolean delegates = true;
+ if (!loadsExpectedClass(loader, GlobalTracer.class)) {
+ log.debug("loader {} failed to delegate bootstrap opentracing class", loader);
+ delegates = false;
+ }
+ if (!loadsExpectedClass(loader, PatchLogger.class)) {
+ log.debug("loader {} failed to delegate bootstrap datadog class", loader);
+ delegates = false;
+ }
+ return delegates;
+ }
+
+ private boolean loadsExpectedClass(ClassLoader loader, Class<?> expectedClass) {
+ try {
+ return loader.loadClass(expectedClass.getName()) == expectedClass;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+ }
+
private static class ClassLoaderNameMatcher
extends ElementMatcher.Junction.AbstractBase<ClassLoader> {

ADD
dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/test/ClassLoaderMatcherTest.groovy
+package datadog.trace.agent.test
+
+import datadog.trace.agent.bootstrap.DatadogClassLoader
+import datadog.trace.agent.tooling.ClassLoaderMatcher
+import spock.lang.Specification
+
+
+class ClassLoaderMatcherTest extends Specification {
+
+ def "skip non-delegating classloader"() {
+ setup:
+ final URLClassLoader badLoader = new NonDelegatingClassLoader()
+ expect:
+ ClassLoaderMatcher.skipClassLoader().matches(badLoader)
+ }
+
+ def "skips agent classloader"() {
+ setup:
+ URL root = new URL("file://")
+ final URLClassLoader agentLoader = new DatadogClassLoader(root, root, null)
+ expect:
+ ClassLoaderMatcher.skipClassLoader().matches(agentLoader)
+ }
+
+ def "does not skip empty classloader"() {
+ setup:
+ final ClassLoader emptyLoader = new ClassLoader(){}
+ expect:
+ !ClassLoaderMatcher.skipClassLoader().matches(emptyLoader)
+ }
+
+ /*
+ * A URLClassloader which only delegates java.* classes
+ */
+ private static class NonDelegatingClassLoader extends URLClassLoader {
+ NonDelegatingClassLoader() {
+ super(new URL[0], (ClassLoader)null)
+ }
+
+ @Override
+ Class<?> loadClass(String className) {
+ if (className.startsWith("java.")) {
+ return super.loadClass(className)
+ }
+ throw new ClassNotFoundException(className)
+ }
+ }
+
+}

Skip unsafe classloaders.