Skip to content

Commit 52fa9c9

Browse files
authored
Support instrumentation-based class injection via dd-instrument-java (#9901)
1 parent 364bb8b commit 52fa9c9

File tree

8 files changed

+57
-7
lines changed

8 files changed

+57
-7
lines changed

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import datadog.environment.JavaVirtualMachine;
2222
import datadog.environment.OperatingSystem;
2323
import datadog.environment.SystemProperties;
24+
import datadog.instrument.classinject.ClassInjector;
2425
import datadog.trace.api.Config;
2526
import datadog.trace.api.Platform;
2627
import datadog.trace.api.StatsDClientManager;
@@ -208,6 +209,15 @@ public static void start(
208209
StaticEventLogger.begin("Agent");
209210
StaticEventLogger.begin("Agent.start");
210211

212+
try {
213+
ClassInjector.enableClassInjection(inst);
214+
} catch (Throwable e) {
215+
log.debug("Instrumentation-based class injection is not available", e);
216+
setSystemPropertyDefault(
217+
propertyNameToSystemPropertyName(TraceInstrumentationConfig.UNSAFE_CLASS_INJECTION),
218+
"true");
219+
}
220+
211221
createAgentClassloader(agentJarURL);
212222

213223
if (Platform.isNativeImageBuilder()) {

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

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
import static datadog.trace.bootstrap.AgentClassLoading.INJECTING_HELPERS;
44
import static java.util.Arrays.asList;
55

6+
import datadog.trace.api.InstrumenterConfig;
67
import datadog.trace.bootstrap.instrumentation.api.EagerHelper;
78
import datadog.trace.util.JDK9ModuleAccess;
89
import java.io.IOException;
910
import java.lang.ref.WeakReference;
1011
import java.lang.reflect.AnnotatedElement;
1112
import java.security.CodeSource;
1213
import java.security.ProtectionDomain;
14+
import java.util.Collection;
1315
import java.util.Collections;
1416
import java.util.HashSet;
1517
import java.util.LinkedHashMap;
@@ -22,7 +24,6 @@
2224
import net.bytebuddy.description.type.TypeDescription;
2325
import net.bytebuddy.dynamic.ClassFileLocator;
2426
import net.bytebuddy.dynamic.DynamicType;
25-
import net.bytebuddy.dynamic.loading.ClassInjector;
2627
import net.bytebuddy.utility.JavaModule;
2728
import org.slf4j.Logger;
2829
import org.slf4j.LoggerFactory;
@@ -34,6 +35,9 @@ public class HelperInjector implements Instrumenter.TransformingAdvice {
3435
private static final ClassFileLocator classFileLocator =
3536
ClassFileLocator.ForClassLoader.of(Utils.getExtendedClassLoader());
3637

38+
private static final boolean unsafeClassInjection =
39+
InstrumenterConfig.get().isUnsafeClassInjection();
40+
3741
private final boolean useAgentCodeSource;
3842
private final AdviceShader adviceShader;
3943
private final String requestingName;
@@ -132,7 +136,7 @@ public DynamicType.Builder<?> transform(
132136
}
133137

134138
final Map<String, byte[]> classnameToBytes = getHelperMap();
135-
final Map<String, Class<?>> classes = injectClassLoader(classLoader, classnameToBytes);
139+
final Collection<Class<?>> classes = injectClassLoader(classLoader, classnameToBytes);
136140

137141
// all datadog helper classes are in the unnamed module
138142
// and there's exactly one unnamed module per classloader
@@ -141,7 +145,7 @@ public DynamicType.Builder<?> transform(
141145
}
142146

143147
// forcibly initialize any eager helpers
144-
for (Class<?> clazz : classes.values()) {
148+
for (Class<?> clazz : classes) {
145149
if (EagerHelper.class.isAssignableFrom(clazz)) {
146150
try {
147151
clazz.getMethod("init").invoke(null);
@@ -173,16 +177,30 @@ public DynamicType.Builder<?> transform(
173177
return builder;
174178
}
175179

176-
private Map<String, Class<?>> injectClassLoader(
180+
private Collection<Class<?>> injectClassLoader(
177181
final ClassLoader classLoader, final Map<String, byte[]> classnameToBytes) {
178182
INJECTING_HELPERS.begin();
179183
try {
180184
if (useAgentCodeSource) {
181185
ProtectionDomain protectionDomain = createProtectionDomain(classLoader);
182-
return new ClassInjector.UsingReflection(classLoader, protectionDomain)
183-
.injectRaw(classnameToBytes);
186+
if (unsafeClassInjection) {
187+
return new net.bytebuddy.dynamic.loading.ClassInjector.UsingReflection(
188+
classLoader, protectionDomain)
189+
.injectRaw(classnameToBytes)
190+
.values();
191+
} else {
192+
return datadog.instrument.classinject.ClassInjector.injectClasses(
193+
classnameToBytes, protectionDomain);
194+
}
184195
} else {
185-
return new ClassInjector.UsingReflection(classLoader).injectRaw(classnameToBytes);
196+
if (unsafeClassInjection) {
197+
return new net.bytebuddy.dynamic.loading.ClassInjector.UsingReflection(classLoader)
198+
.injectRaw(classnameToBytes)
199+
.values();
200+
} else {
201+
return datadog.instrument.classinject.ClassInjector.injectClasses(
202+
classnameToBytes, classLoader);
203+
}
186204
}
187205
} finally {
188206
INJECTING_HELPERS.end();

dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/test/HelperInjectionTest.groovy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package datadog.trace.agent.test
22

3+
import datadog.instrument.classinject.ClassInjector
34
import datadog.trace.agent.tooling.HelperInjector
45
import datadog.trace.agent.tooling.Utils
56
import datadog.trace.test.util.DDSpecification
7+
import net.bytebuddy.agent.ByteBuddyAgent
68

79
import java.lang.ref.WeakReference
810
import java.util.concurrent.atomic.AtomicReference
@@ -15,6 +17,7 @@ class HelperInjectionTest extends DDSpecification {
1517

1618
def "helpers injected to non-delegating classloader"() {
1719
setup:
20+
ClassInjector.enableClassInjection(ByteBuddyAgent.getInstrumentation())
1821
HelperInjector injector = new HelperInjector(false, "test", HELPER_CLASS_NAME)
1922
AtomicReference<URLClassLoader> emptyLoader = new AtomicReference<>(new URLClassLoader(new URL[0], (ClassLoader) null))
2023

dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/csi/CallSiteTransformerTest.groovy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package datadog.trace.agent.tooling.csi
22

3+
import datadog.instrument.classinject.ClassInjector
34
import datadog.trace.agent.tooling.bytebuddy.csi.Advices
45
import datadog.trace.agent.tooling.bytebuddy.csi.CallSiteTransformer
56
import datadog.trace.agent.tooling.csi.CallSiteAdvice.MethodHandler
67
import datadog.trace.api.function.TriFunction
78
import groovy.transform.CompileDynamic
9+
import net.bytebuddy.agent.ByteBuddyAgent
810
import net.bytebuddy.description.type.TypeDescription
911
import net.bytebuddy.dynamic.DynamicType
1012
import net.bytebuddy.jar.asm.Opcodes
@@ -206,6 +208,7 @@ class CallSiteTransformerTest extends BaseCallSiteTest {
206208
@SuppressWarnings(['GroovyAccessibility', 'GroovyAssignabilityCheck'])
207209
void 'test call site transformer with helpers'() {
208210
setup:
211+
ClassInjector.enableClassInjection(ByteBuddyAgent.getInstrumentation())
209212
final source = StringConcatExample
210213
final helper = InstrumentationHelper
211214
final customClassLoader = new ClassLoader() { }

dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.datadog.debugger.sink.ProbeStatusSink
1616
import com.google.common.collect.Sets
1717
import datadog.communication.ddagent.DDAgentFeaturesDiscovery
1818
import datadog.communication.monitor.Monitoring
19+
import datadog.instrument.classinject.ClassInjector
1920
import datadog.trace.agent.test.asserts.ListWriterAssert
2021
import datadog.trace.agent.test.datastreams.MockFeaturesDiscovery
2122
import datadog.trace.agent.test.datastreams.RecordingDatastreamsPayloadWriter
@@ -401,6 +402,8 @@ abstract class InstrumentationSpecification extends DDSpecification implements A
401402
return trackingSpan
402403
}
403404

405+
ClassInjector.enableClassInjection(INSTRUMENTATION)
406+
404407
// if a test enables the instrumentation it verifies,
405408
// the cache needs to be recomputed taking into account that instrumentation's matchers
406409
ClassLoaderMatchers.resetState()

dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ public final class TraceInstrumentationConfig {
160160
public static final String RESOLVER_USE_URL_CACHES = "resolver.use.url.caches";
161161
public static final String RESOLVER_RESET_INTERVAL = "resolver.reset.interval";
162162
public static final String RESOLVER_NAMES_ARE_UNIQUE = "resolver.names.are.unique";
163+
164+
public static final String UNSAFE_CLASS_INJECTION = "unsafe.class.injection";
165+
163166
public static final String CASSANDRA_KEYSPACE_STATEMENT_EXTRACTION_ENABLED =
164167
"trace.cassandra.keyspace.statement.extraction.enabled";
165168
public static final String COUCHBASE_INTERNAL_SPANS_ENABLED =

internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_PEKKO_SCHEDULER_ENABLED;
7474
import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_THREAD_POOL_EXECUTORS_EXCLUDE;
7575
import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_WEBSOCKET_MESSAGES_ENABLED;
76+
import static datadog.trace.api.config.TraceInstrumentationConfig.UNSAFE_CLASS_INJECTION;
7677
import static datadog.trace.api.config.UsmConfig.USM_ENABLED;
7778
import static datadog.trace.util.CollectionUtils.tryMakeImmutableList;
7879
import static datadog.trace.util.CollectionUtils.tryMakeImmutableSet;
@@ -162,6 +163,8 @@ public class InstrumenterConfig {
162163
private final Boolean resolverUseUrlCaches;
163164
private final int resolverResetInterval;
164165

166+
private final boolean unsafeClassInjection;
167+
165168
private final boolean runtimeContextFieldInjection;
166169
private final boolean serialVersionUIDFieldInjection;
167170

@@ -280,6 +283,8 @@ private InstrumenterConfig() {
280283
? 0
281284
: configProvider.getInteger(RESOLVER_RESET_INTERVAL, DEFAULT_RESOLVER_RESET_INTERVAL);
282285

286+
unsafeClassInjection = configProvider.getBoolean(UNSAFE_CLASS_INJECTION, false);
287+
283288
runtimeContextFieldInjection =
284289
configProvider.getBoolean(
285290
RUNTIME_CONTEXT_FIELD_INJECTION, DEFAULT_RUNTIME_CONTEXT_FIELD_INJECTION);
@@ -505,6 +510,10 @@ public String getResolverCacheDir() {
505510
return resolverCacheDir;
506511
}
507512

513+
public boolean isUnsafeClassInjection() {
514+
return unsafeClassInjection;
515+
}
516+
508517
public String getInstrumentationConfigId() {
509518
return instrumentationConfigId;
510519
}

metadata/supported-configurations.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,6 +1340,7 @@
13401340
"DD_TRIAGE_REPORT_DIR": ["A"],
13411341
"DD_TRIAGE_REPORT_TRIGGER": ["A"],
13421342
"DD_UNDERTOW_CONTINUATION": ["A"],
1343+
"DD_UNSAFE_CLASS_INJECTION": ["A"],
13431344
"DD_USM_ENABLED": ["A"],
13441345
"DD_VERSION": ["A"],
13451346
"DD_WRITER_BAGGAGE_INJECT": ["A"],

0 commit comments

Comments
 (0)