Merge pull request #781 from DataDog/mar-kolya/fix-helper-injection

Fix helper injection in context provider
This commit is contained in:
Nikolay Martynov 2019-03-26 16:16:36 -04:00 committed by GitHub
commit 8fa354e198
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 51 deletions

View File

@ -63,7 +63,6 @@ public interface Instrumenter {
return parentAgentBuilder; return parentAgentBuilder;
} }
final MuzzleMatcher muzzleMatcher = new MuzzleMatcher();
AgentBuilder.Identified.Extendable agentBuilder = AgentBuilder.Identified.Extendable agentBuilder =
parentAgentBuilder parentAgentBuilder
.type( .type(
@ -74,13 +73,13 @@ public interface Instrumenter {
classLoaderMatcher(), classLoaderMatcher(),
"Instrumentation class loader matcher unexpected exception: " "Instrumentation class loader matcher unexpected exception: "
+ getClass().getName())) + getClass().getName()))
.and(muzzleMatcher) .and(new MuzzleMatcher())
.and(new PostMatchHook()) .and(new PostMatchHook())
.transform(DDTransformers.defaultTransformers()); .transform(DDTransformers.defaultTransformers());
agentBuilder = injectHelperClasses(agentBuilder); agentBuilder = injectHelperClasses(agentBuilder);
agentBuilder = contextProvider.instrumentationTransformer(agentBuilder); agentBuilder = contextProvider.instrumentationTransformer(agentBuilder);
agentBuilder = applyInstrumentationTransformers(agentBuilder); agentBuilder = applyInstrumentationTransformers(agentBuilder);
agentBuilder = contextProvider.additionalInstrumentation(agentBuilder, muzzleMatcher); agentBuilder = contextProvider.additionalInstrumentation(agentBuilder);
return agentBuilder; return agentBuilder;
} }

View File

@ -102,16 +102,23 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
/** fields-accessor-interface-name -> fields-accessor-interface-dynamic-type */ /** fields-accessor-interface-name -> fields-accessor-interface-dynamic-type */
private final Map<String, DynamicType.Unloaded<?>> fieldAccessorInterfaces; private final Map<String, DynamicType.Unloaded<?>> fieldAccessorInterfaces;
private final AgentBuilder.Transformer fieldAccessorInterfacesInjector;
/** context-store-type-name -> context-store-type-name-dynamic-type */ /** context-store-type-name -> context-store-type-name-dynamic-type */
private final Map<String, DynamicType.Unloaded<?>> contextStoreImplementations; private final Map<String, DynamicType.Unloaded<?>> contextStoreImplementations;
private final AgentBuilder.Transformer contextStoreImplementationsInjector;
private final boolean fieldInjectionEnabled; private final boolean fieldInjectionEnabled;
public FieldBackedProvider(final Instrumenter.Default instrumenter) { public FieldBackedProvider(final Instrumenter.Default instrumenter) {
this.instrumenter = instrumenter; this.instrumenter = instrumenter;
byteBuddy = new ByteBuddy(); byteBuddy = new ByteBuddy();
fieldAccessorInterfaces = generateFieldAccessorInterfaces(); fieldAccessorInterfaces = generateFieldAccessorInterfaces();
fieldAccessorInterfacesInjector = bootstrapHelperInjector(fieldAccessorInterfaces.values());
contextStoreImplementations = generateContextStoreImplementationClasses(); contextStoreImplementations = generateContextStoreImplementationClasses();
contextStoreImplementationsInjector =
bootstrapHelperInjector(contextStoreImplementations.values());
fieldInjectionEnabled = Config.get().isRuntimeContextFieldInjection(); fieldInjectionEnabled = Config.get().isRuntimeContextFieldInjection();
} }
@ -125,46 +132,11 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
*/ */
builder = builder =
builder.transform(getTransformerForASMVisitor(getContextStoreReadsRewritingVisitor())); builder.transform(getTransformerForASMVisitor(getContextStoreReadsRewritingVisitor()));
builder = injectHelpersIntoBootstrapClassloader(builder);
/**
* We inject into bootstrap classloader because field accessor interfaces are needed by
* context store implementations. Unfortunately this forces us to remove stored type checking
* because actual classes may not be available at this point.
*/
builder = builder.transform(bootstrapHelperInjector(fieldAccessorInterfaces.values()));
/**
* We inject context store implementation into bootstrap classloader because same
* implementation may be used by different instrumentations and it has to use same static map
* in case of fallback to map-backed storage.
*/
builder = builder.transform(bootstrapHelperInjector(contextStoreImplementations.values()));
} }
return builder; return builder;
} }
/** Get transformer that forces helper injection onto bootstrap classloader. */
private AgentBuilder.Transformer bootstrapHelperInjector(
final Collection<DynamicType.Unloaded<?>> helpers) {
return new AgentBuilder.Transformer() {
final HelperInjector injector = HelperInjector.forDynamicTypes(helpers);
@Override
public DynamicType.Builder<?> transform(
final DynamicType.Builder<?> builder,
final TypeDescription typeDescription,
final ClassLoader classLoader,
final JavaModule module) {
return injector.transform(
builder,
typeDescription,
// context store implementation classes will always go to the bootstrap
BOOTSTRAP_CLASSLOADER,
module);
}
};
}
private AsmVisitorWrapper getContextStoreReadsRewritingVisitor() { private AsmVisitorWrapper getContextStoreReadsRewritingVisitor() {
return new AsmVisitorWrapper() { return new AsmVisitorWrapper() {
@Override @Override
@ -328,9 +300,49 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
}; };
} }
private AgentBuilder.Identified.Extendable injectHelpersIntoBootstrapClassloader(
AgentBuilder.Identified.Extendable builder) {
/**
* We inject into bootstrap classloader because field accessor interfaces are needed by context
* store implementations. Unfortunately this forces us to remove stored type checking because
* actual classes may not be available at this point.
*/
builder = builder.transform(fieldAccessorInterfacesInjector);
/**
* We inject context store implementation into bootstrap classloader because same implementation
* may be used by different instrumentations and it has to use same static map in case of
* fallback to map-backed storage.
*/
builder = builder.transform(contextStoreImplementationsInjector);
return builder;
}
/** Get transformer that forces helper injection onto bootstrap classloader. */
private AgentBuilder.Transformer bootstrapHelperInjector(
final Collection<DynamicType.Unloaded<?>> helpers) {
return new AgentBuilder.Transformer() {
final HelperInjector injector = HelperInjector.forDynamicTypes(helpers);
@Override
public DynamicType.Builder<?> transform(
final DynamicType.Builder<?> builder,
final TypeDescription typeDescription,
final ClassLoader classLoader,
final JavaModule module) {
return injector.transform(
builder,
typeDescription,
// context store implementation classes will always go to the bootstrap
BOOTSTRAP_CLASSLOADER,
module);
}
};
}
@Override @Override
public AgentBuilder.Identified.Extendable additionalInstrumentation( public AgentBuilder.Identified.Extendable additionalInstrumentation(
AgentBuilder.Identified.Extendable builder, final AgentBuilder.RawMatcher muzzleMatcher) { AgentBuilder.Identified.Extendable builder) {
if (fieldInjectionEnabled) { if (fieldInjectionEnabled) {
for (final Map.Entry<String, String> entry : instrumenter.contextStore().entrySet()) { for (final Map.Entry<String, String> entry : instrumenter.contextStore().entrySet()) {
@ -344,16 +356,19 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
safeHasSuperType(named(entry.getKey())).and(not(isInterface())), safeHasSuperType(named(entry.getKey())).and(not(isInterface())),
instrumenter.classLoaderMatcher()) instrumenter.classLoaderMatcher())
.and(safeToInjectFieldsMatcher()) .and(safeToInjectFieldsMatcher())
/** .transform(AgentBuilder.Transformer.NoOp.INSTANCE);
* By adding the muzzleMatcher here, we are adding risk that the rules for injecting
* the classes into the classloader and the rules for adding the field to the class /**
* might be different. However the consequences are much greater if the class is not * We inject helpers here as well as when instrumentation is applied to ensure that helpers
* injected but the field is added, since that results in a NoClassDef error. * are present even if instrumented classes are not loaded, but classes with state fields
*/ * added are loaded (e.g. sun.net.www.protocol.https.HttpsURLConnectionImpl).
.and(muzzleMatcher) */
.transform( builder = injectHelpersIntoBootstrapClassloader(builder);
getTransformerForASMVisitor(
getFieldInjectionVisitor(entry.getKey(), entry.getValue()))); builder =
builder.transform(
getTransformerForASMVisitor(
getFieldInjectionVisitor(entry.getKey(), entry.getValue())));
} }
} }
return builder; return builder;

View File

@ -13,5 +13,5 @@ public interface InstrumentationContextProvider {
/** Hook to define additional instrumentation. Run at instrumentation advice is hooked up. */ /** Hook to define additional instrumentation. Run at instrumentation advice is hooked up. */
AgentBuilder.Identified.Extendable additionalInstrumentation( AgentBuilder.Identified.Extendable additionalInstrumentation(
AgentBuilder.Identified.Extendable builder, final AgentBuilder.RawMatcher muzzleMatcher); AgentBuilder.Identified.Extendable builder);
} }

View File

@ -5,7 +5,9 @@ import io.opentracing.tag.Tags
import io.opentracing.util.GlobalTracer import io.opentracing.util.GlobalTracer
import org.springframework.web.client.RestTemplate import org.springframework.web.client.RestTemplate
import spock.lang.AutoCleanup import spock.lang.AutoCleanup
import spock.lang.Requires
import spock.lang.Shared import spock.lang.Shared
import sun.net.www.protocol.https.HttpsURLConnectionImpl
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
@ -472,4 +474,14 @@ class HttpUrlConnectionTest extends AgentTestRunner {
where: where:
renameService << [false, true] renameService << [false, true]
} }
// This test makes no sense on IBM JVM because there is no HttpsURLConnectionImpl class there
@Requires({ !System.getProperty("java.vm.name").contains("IBM J9 VM") })
def "Make sure we can load HttpsURLConnectionImpl"() {
when:
def instance = new HttpsURLConnectionImpl(null, null, null)
then:
instance != null
}
} }