Skip to content

Commit 8fa354e

Browse files
authored
Merge pull request #781 from DataDog/mar-kolya/fix-helper-injection
Fix helper injection in context provider
2 parents bd4e05b + 3b0e769 commit 8fa354e

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)