Skip to content

Commit 3b0e769

Browse files
committed
Fix helper injection in context provider
Currently helpers will not be injected if instrumentation doesn't apply. Unfortunately it is possible for some classes to have context fields added even if instrumentation is not allied (i.g. instrumented classes are not used). Fix this by always injecting all helpers if we inject context fields.
1 parent bd4e05b commit 3b0e769

4 files changed

Lines changed: 77 additions & 51 deletions

File tree

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Instrumenter.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ public final AgentBuilder instrument(final AgentBuilder parentAgentBuilder) {
6363
return parentAgentBuilder;
6464
}
6565

66-
final MuzzleMatcher muzzleMatcher = new MuzzleMatcher();
6766
AgentBuilder.Identified.Extendable agentBuilder =
6867
parentAgentBuilder
6968
.type(
@@ -74,13 +73,13 @@ public final AgentBuilder instrument(final AgentBuilder parentAgentBuilder) {
7473
classLoaderMatcher(),
7574
"Instrumentation class loader matcher unexpected exception: "
7675
+ getClass().getName()))
77-
.and(muzzleMatcher)
76+
.and(new MuzzleMatcher())
7877
.and(new PostMatchHook())
7978
.transform(DDTransformers.defaultTransformers());
8079
agentBuilder = injectHelperClasses(agentBuilder);
8180
agentBuilder = contextProvider.instrumentationTransformer(agentBuilder);
8281
agentBuilder = applyInstrumentationTransformers(agentBuilder);
83-
agentBuilder = contextProvider.additionalInstrumentation(agentBuilder, muzzleMatcher);
82+
agentBuilder = contextProvider.additionalInstrumentation(agentBuilder);
8483
return agentBuilder;
8584
}
8685

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedProvider.java

Lines changed: 62 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,23 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
102102
/** fields-accessor-interface-name -> fields-accessor-interface-dynamic-type */
103103
private final Map<String, DynamicType.Unloaded<?>> fieldAccessorInterfaces;
104104

105+
private final AgentBuilder.Transformer fieldAccessorInterfacesInjector;
106+
105107
/** context-store-type-name -> context-store-type-name-dynamic-type */
106108
private final Map<String, DynamicType.Unloaded<?>> contextStoreImplementations;
107109

110+
private final AgentBuilder.Transformer contextStoreImplementationsInjector;
111+
108112
private final boolean fieldInjectionEnabled;
109113

110114
public FieldBackedProvider(final Instrumenter.Default instrumenter) {
111115
this.instrumenter = instrumenter;
112116
byteBuddy = new ByteBuddy();
113117
fieldAccessorInterfaces = generateFieldAccessorInterfaces();
118+
fieldAccessorInterfacesInjector = bootstrapHelperInjector(fieldAccessorInterfaces.values());
114119
contextStoreImplementations = generateContextStoreImplementationClasses();
120+
contextStoreImplementationsInjector =
121+
bootstrapHelperInjector(contextStoreImplementations.values());
115122
fieldInjectionEnabled = Config.get().isRuntimeContextFieldInjection();
116123
}
117124

@@ -125,46 +132,11 @@ public AgentBuilder.Identified.Extendable instrumentationTransformer(
125132
*/
126133
builder =
127134
builder.transform(getTransformerForASMVisitor(getContextStoreReadsRewritingVisitor()));
128-
129-
/**
130-
* We inject into bootstrap classloader because field accessor interfaces are needed by
131-
* context store implementations. Unfortunately this forces us to remove stored type checking
132-
* because actual classes may not be available at this point.
133-
*/
134-
builder = builder.transform(bootstrapHelperInjector(fieldAccessorInterfaces.values()));
135-
136-
/**
137-
* We inject context store implementation into bootstrap classloader because same
138-
* implementation may be used by different instrumentations and it has to use same static map
139-
* in case of fallback to map-backed storage.
140-
*/
141-
builder = builder.transform(bootstrapHelperInjector(contextStoreImplementations.values()));
135+
builder = injectHelpersIntoBootstrapClassloader(builder);
142136
}
143137
return builder;
144138
}
145139

146-
/** Get transformer that forces helper injection onto bootstrap classloader. */
147-
private AgentBuilder.Transformer bootstrapHelperInjector(
148-
final Collection<DynamicType.Unloaded<?>> helpers) {
149-
return new AgentBuilder.Transformer() {
150-
final HelperInjector injector = HelperInjector.forDynamicTypes(helpers);
151-
152-
@Override
153-
public DynamicType.Builder<?> transform(
154-
final DynamicType.Builder<?> builder,
155-
final TypeDescription typeDescription,
156-
final ClassLoader classLoader,
157-
final JavaModule module) {
158-
return injector.transform(
159-
builder,
160-
typeDescription,
161-
// context store implementation classes will always go to the bootstrap
162-
BOOTSTRAP_CLASSLOADER,
163-
module);
164-
}
165-
};
166-
}
167-
168140
private AsmVisitorWrapper getContextStoreReadsRewritingVisitor() {
169141
return new AsmVisitorWrapper() {
170142
@Override
@@ -328,9 +300,49 @@ public void visitLdcInsn(final Object value) {
328300
};
329301
}
330302

303+
private AgentBuilder.Identified.Extendable injectHelpersIntoBootstrapClassloader(
304+
AgentBuilder.Identified.Extendable builder) {
305+
/**
306+
* We inject into bootstrap classloader because field accessor interfaces are needed by context
307+
* store implementations. Unfortunately this forces us to remove stored type checking because
308+
* actual classes may not be available at this point.
309+
*/
310+
builder = builder.transform(fieldAccessorInterfacesInjector);
311+
312+
/**
313+
* We inject context store implementation into bootstrap classloader because same implementation
314+
* may be used by different instrumentations and it has to use same static map in case of
315+
* fallback to map-backed storage.
316+
*/
317+
builder = builder.transform(contextStoreImplementationsInjector);
318+
return builder;
319+
}
320+
321+
/** Get transformer that forces helper injection onto bootstrap classloader. */
322+
private AgentBuilder.Transformer bootstrapHelperInjector(
323+
final Collection<DynamicType.Unloaded<?>> helpers) {
324+
return new AgentBuilder.Transformer() {
325+
final HelperInjector injector = HelperInjector.forDynamicTypes(helpers);
326+
327+
@Override
328+
public DynamicType.Builder<?> transform(
329+
final DynamicType.Builder<?> builder,
330+
final TypeDescription typeDescription,
331+
final ClassLoader classLoader,
332+
final JavaModule module) {
333+
return injector.transform(
334+
builder,
335+
typeDescription,
336+
// context store implementation classes will always go to the bootstrap
337+
BOOTSTRAP_CLASSLOADER,
338+
module);
339+
}
340+
};
341+
}
342+
331343
@Override
332344
public AgentBuilder.Identified.Extendable additionalInstrumentation(
333-
AgentBuilder.Identified.Extendable builder, final AgentBuilder.RawMatcher muzzleMatcher) {
345+
AgentBuilder.Identified.Extendable builder) {
334346

335347
if (fieldInjectionEnabled) {
336348
for (final Map.Entry<String, String> entry : instrumenter.contextStore().entrySet()) {
@@ -344,16 +356,19 @@ public AgentBuilder.Identified.Extendable additionalInstrumentation(
344356
safeHasSuperType(named(entry.getKey())).and(not(isInterface())),
345357
instrumenter.classLoaderMatcher())
346358
.and(safeToInjectFieldsMatcher())
347-
/**
348-
* By adding the muzzleMatcher here, we are adding risk that the rules for injecting
349-
* the classes into the classloader and the rules for adding the field to the class
350-
* might be different. However the consequences are much greater if the class is not
351-
* injected but the field is added, since that results in a NoClassDef error.
352-
*/
353-
.and(muzzleMatcher)
354-
.transform(
355-
getTransformerForASMVisitor(
356-
getFieldInjectionVisitor(entry.getKey(), entry.getValue())));
359+
.transform(AgentBuilder.Transformer.NoOp.INSTANCE);
360+
361+
/**
362+
* We inject helpers here as well as when instrumentation is applied to ensure that helpers
363+
* are present even if instrumented classes are not loaded, but classes with state fields
364+
* added are loaded (e.g. sun.net.www.protocol.https.HttpsURLConnectionImpl).
365+
*/
366+
builder = injectHelpersIntoBootstrapClassloader(builder);
367+
368+
builder =
369+
builder.transform(
370+
getTransformerForASMVisitor(
371+
getFieldInjectionVisitor(entry.getKey(), entry.getValue())));
357372
}
358373
}
359374
return builder;

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/InstrumentationContextProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ AgentBuilder.Identified.Extendable instrumentationTransformer(
1313

1414
/** Hook to define additional instrumentation. Run at instrumentation advice is hooked up. */
1515
AgentBuilder.Identified.Extendable additionalInstrumentation(
16-
AgentBuilder.Identified.Extendable builder, final AgentBuilder.RawMatcher muzzleMatcher);
16+
AgentBuilder.Identified.Extendable builder);
1717
}

dd-java-agent/instrumentation/http-url-connection/src/test/groovy/HttpUrlConnectionTest.groovy

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import io.opentracing.tag.Tags
55
import io.opentracing.util.GlobalTracer
66
import org.springframework.web.client.RestTemplate
77
import spock.lang.AutoCleanup
8+
import spock.lang.Requires
89
import spock.lang.Shared
10+
import sun.net.www.protocol.https.HttpsURLConnectionImpl
911

1012
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
1113
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
@@ -472,4 +474,14 @@ class HttpUrlConnectionTest extends AgentTestRunner {
472474
where:
473475
renameService << [false, true]
474476
}
477+
478+
// This test makes no sense on IBM JVM because there is no HttpsURLConnectionImpl class there
479+
@Requires({ !System.getProperty("java.vm.name").contains("IBM J9 VM") })
480+
def "Make sure we can load HttpsURLConnectionImpl"() {
481+
when:
482+
def instance = new HttpsURLConnectionImpl(null, null, null)
483+
484+
then:
485+
instance != null
486+
}
475487
}

0 commit comments

Comments
 (0)