Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package datadog.trace.bootstrap.instrumentation.java.module;

import static java.util.Collections.unmodifiableSet;

import datadog.trace.api.GenericClassValue;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.concurrent.NotThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@NotThreadSafe
public final class JpmsHelper {
private JpmsHelper() {}

private static final Set<String> TRIGGERS = new HashSet<>();

private static final ClassValue<AtomicBoolean> CLSVALUE =
GenericClassValue.constructing(AtomicBoolean.class);

public static final Logger LOGGER = LoggerFactory.getLogger(JpmsHelper.class);

public static void addAllTriggers(Iterable<String> classes) {
if (classes == null) {
return;
}
for (String cls : classes) {
TRIGGERS.add(cls);
}
}

public static Set<String> getAllTriggers() {
return unmodifiableSet(TRIGGERS);
}

public static boolean shouldBeOpened(Class<?> cls) {
return CLSVALUE.get(cls).compareAndSet(false, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import datadog.trace.api.telemetry.IntegrationsCollector;
import datadog.trace.bootstrap.FieldBackedContextAccessor;
import datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter;
import datadog.trace.bootstrap.instrumentation.java.module.JpmsHelper;
import datadog.trace.util.AgentTaskScheduler;
import de.thetaphi.forbiddenapis.SuppressForbidden;
import java.lang.instrument.ClassFileTransformer;
Expand Down Expand Up @@ -185,18 +186,26 @@ public static ClassFileTransformer installBytebuddyAgent(
Iterable<InstrumenterModule> instrumenterModules =
withExtensions(instrumenterIndex.modules(enabledSystems));

final boolean javaModuleSupported = JavaModule.isSupported();

// This needs to be a separate loop through all instrumentations before we start adding
// advice so that we can exclude field injection, since that will try to check exclusion
// immediately and we don't have the ability to express dependencies between different
// instrumentations to control the load order.
for (InstrumenterModule module : instrumenterModules) {
boolean added = false;
if (module instanceof ExcludeFilterProvider) {
ExcludeFilterProvider provider = (ExcludeFilterProvider) module;
ExcludeFilter.add(provider.excludedClasses());
if (DEBUG) {
log.debug(
"Adding filtered classes - instrumentation.class={}", module.getClass().getName());
}
added = true;
}
if (javaModuleSupported && module instanceof JavaModuleOpenProvider) {
JpmsHelper.addAllTriggers(((JavaModuleOpenProvider) module).triggerClasses());
Comment thread
amarziali marked this conversation as resolved.
added = true;
}
if (DEBUG && added) {
log.debug(
"Adding filtered classes - instrumentation.class={}", module.getClass().getName());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package datadog.trace.agent.tooling;

import static datadog.trace.agent.tooling.InstrumenterModuleFilter.ALL_MODULES;
import static datadog.trace.agent.tooling.InstrumenterModuleFilter.forTargetSystemsOrExcludeProvider;
import static datadog.trace.agent.tooling.InstrumenterModuleFilter.forTargetSystemsOrNeedToEarlyLoad;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.disjoint;
Expand Down Expand Up @@ -60,6 +60,9 @@ final class InstrumenterIndex {
/** Bit to signal that the encoded item is instance of <code>ExcludeFilterProvider</code> */
private static final int IS_EXCLUDE_FILTER_PROVIDER_FLAG = 0x02;

/** Bit to signal that the encoded item is instance of <code>JavaModuleOpenProvider</code> */
private static final int IS_JAVA_MODULE_OPEN_PROVIDER_FLAG = 0x04;

static final ClassLoader instrumenterClassLoader = Instrumenter.class.getClassLoader();

private final int instrumentationCount;
Expand Down Expand Up @@ -101,7 +104,7 @@ private InstrumenterIndex(int instrumentationCount, int transformationCount, byt
*/
public Iterable<InstrumenterModule> modules(
final Set<InstrumenterModule.TargetSystem> enabledSystems) {
return modules(forTargetSystemsOrExcludeProvider(enabledSystems));
return modules(forTargetSystemsOrNeedToEarlyLoad(enabledSystems));
}

public Iterable<InstrumenterModule> modules() {
Expand Down Expand Up @@ -217,6 +220,7 @@ InstrumenterModule nextModule(InstrumenterModuleFilter filter) {
// flags
final byte flags = (byte) readNumber();
final boolean isExcludeProvider = decodeModuleIsExcludeProvider(flags);
final boolean isJavaModuleOpenProvider = decodeModuleJavaModuleOpenProvider(flags);
hasTargetSystemOverrides = decodeModuleHasTargetSystemOverrides(flags);
memberAdviceTargetSystemOverrides = null;
memberCount = readNumber();
Expand All @@ -228,7 +232,7 @@ InstrumenterModule nextModule(InstrumenterModuleFilter filter) {
} else {
memberName = null;
}
if (filter.test(moduleName, moduleTargetSystems, isExcludeProvider)) {
if (filter.test(moduleName, moduleTargetSystems, isExcludeProvider, isJavaModuleOpenProvider)) {
if (module == null) {
module = buildModule();
modules[instrumentationId] = module;
Expand Down Expand Up @@ -379,13 +383,20 @@ static byte encodeModuleFlags(final InstrumenterModule module, final boolean has
if (module instanceof ExcludeFilterProvider) {
ret |= (byte) IS_EXCLUDE_FILTER_PROVIDER_FLAG;
}
if (module instanceof JavaModuleOpenProvider) {
ret |= (byte) IS_JAVA_MODULE_OPEN_PROVIDER_FLAG;
}
return ret;
}

static boolean decodeModuleIsExcludeProvider(byte flags) {
return (flags & IS_EXCLUDE_FILTER_PROVIDER_FLAG) != 0;
}

static boolean decodeModuleJavaModuleOpenProvider(byte flags) {
return (flags & IS_JAVA_MODULE_OPEN_PROVIDER_FLAG) != 0;
}

static boolean decodeModuleHasTargetSystemOverrides(byte flags) {
return (flags & HAS_TARGET_SYSTEMS_OVERRIDES_FLAG) != 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@

@FunctionalInterface
public interface InstrumenterModuleFilter {
InstrumenterModuleFilter ALL_MODULES = (name, systems, excludeProvider) -> true;
InstrumenterModuleFilter ALL_MODULES =
(name, systems, excludeProvider, isJavaModuleOpenProvider) -> true;

static InstrumenterModuleFilter forTargetSystemsOrExcludeProvider(
static InstrumenterModuleFilter forTargetSystemsOrNeedToEarlyLoad(
final Set<InstrumenterModule.TargetSystem> enabledSystems) {
return (instrumenterModuleName, targetSystems, isExcludeProvider) ->
isExcludeProvider || !disjoint(enabledSystems, targetSystems);
return (instrumenterModuleName, targetSystems, isExcludeProvider, isJavaModuleOpenProvider) ->
isExcludeProvider || isJavaModuleOpenProvider || !disjoint(enabledSystems, targetSystems);
}

boolean test(
String instrumenterModuleName,
Set<InstrumenterModule.TargetSystem> targetSystems,
boolean isExcludeProvider);
boolean isExcludeProvider,
boolean isJavaModuleOpenProvider);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package datadog.trace.agent.tooling;

/**
* Allows an {@link InstrumenterModule} to possibly open a java module to the unnamed module.
*
* <p>This is typically used when reflective operations need to be done and the agent cannot assume
* that the host application has permitted them.
*/
public interface JavaModuleOpenProvider {
/** Classes whose constructors trigger the one-time module open when first instantiated. */
Iterable<String> triggerClasses();
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ public static void onEnter(@Advice.Argument(value = 0, readOnly = false) String[
+ "datadog.trace.bootstrap.instrumentation.java.concurrent.QueueTimerHelper:build_time,"
+ "datadog.trace.bootstrap.instrumentation.java.concurrent.QueueTimerHelper$RateLimiterHolder:run_time,"
+ "datadog.trace.bootstrap.instrumentation.java.concurrent.TPEHelper:build_time,"
+ "datadog.trace.bootstrap.instrumentation.java.module.JpmsHelper:build_time,"
+ "datadog.trace.bootstrap.instrumentation.jfr.exceptions.ExceptionCountEvent:build_time,"
+ "datadog.trace.bootstrap.instrumentation.jfr.exceptions.ExceptionSampleEvent:build_time,"
+ "datadog.trace.bootstrap.instrumentation.jfr.backpressure.BackpressureSampleEvent:build_time,"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies {
project.tasks.withType(AbstractCompile).configureEach {
switch (it.name) {
case 'compileCsiJava':
case 'compileJava':
configureCompiler(it, 11, JavaVersion.VERSION_1_8)
break
default:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package datadog.trace.instrumentation.java.lang.module;

import static net.bytebuddy.matcher.ElementMatchers.isConstructor;

import com.google.auto.service.AutoService;
import datadog.environment.JavaVirtualMachine;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.bootstrap.instrumentation.java.module.JpmsHelper;
import java.util.Collection;
import java.util.Set;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.implementation.bytecode.assign.Assigner;

/**
* Generic instrumenter module that will advice the constructor of each known classes in order to
* open once their module. This is marked for bootstrap even if it's not for sure, but we cannot
* know in advance (depends to the instrumented types and today we are instrumenting InetAddress
* that's in the bootstrap).
*/
@AutoService(InstrumenterModule.class)
public class JpmsClearanceInstrumentation extends InstrumenterModule
implements Instrumenter.ForConfiguredTypes,
Instrumenter.ForBootstrap,
Instrumenter.HasMethodAdvice {
public JpmsClearanceInstrumentation() {
super("java-module");
}

@Override
public boolean isEnabled() {
return super.isEnabled() && JavaVirtualMachine.isJavaVersionAtLeast(9);
}

@Override
public boolean isApplicable(Set<TargetSystem> enabledSystems) {
return true; // not directly linked ot a target system
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(isConstructor(), getClass().getName() + "$OpenModuleAdvice");
}

@Override
public Collection<String> configuredMatchingTypes() {
return JpmsHelper.getAllTriggers();
}

public static class OpenModuleAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(@Advice.This(typing = Assigner.Typing.DYNAMIC) Object self) {
final Class<?> cls = self.getClass();
if (JpmsHelper.shouldBeOpened(cls)) {
final Module module = cls.getModule();
if (module != null) {
try {
// This call needs imperatively to be done from the same module we're adding exports
// because the jdk is checking that the caller belongs to the same module.
// The code of this advice is getting inlined into the constructor of the class
// belonging
// to that package so it will work. Moving the same to a helper won't.
module.addOpens(cls.getPackageName(), JpmsHelper.class.getModule());
Comment thread
amarziali marked this conversation as resolved.
final ClassLoader loader = cls.getClassLoader();
if (loader != null) {
module.addOpens(cls.getPackageName(), loader.getUnnamedModule());
}
} catch (Throwable t) {
JpmsHelper.LOGGER.debug(
"Unable to open package {} to the unnamed module", cls.getPackageName(), t);
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ tracerJava {
addSourceSetFor(JavaVersion.VERSION_11)
}

dependencies {
testRuntimeOnly(project(":dd-java-agent:instrumentation:java:java-lang:java-lang-9.0"))
}

tasks.named("compileMain_java11Java", JavaCompile) {
configureCompiler(it, 11, JavaVersion.VERSION_11)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,21 @@
package datadog.trace.instrumentation.httpclient;

import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static java.util.Collections.singleton;

import com.google.auto.service.AutoService;
import datadog.environment.JavaVirtualMachine;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.agent.tooling.JavaModuleOpenProvider;

@AutoService(InstrumenterModule.class)
public class JpmsInetAddressInstrumentation extends InstrumenterModule.Tracing
implements Instrumenter.HasMethodAdvice, Instrumenter.ForSingleType {
public class JpmsInetAddressInstrumentation extends InstrumenterModule
implements JavaModuleOpenProvider {

public JpmsInetAddressInstrumentation() {
super("java-net");
}

@Override
public boolean isEnabled() {
return super.isEnabled() && JavaVirtualMachine.isJavaVersionAtLeast(9);
}

@Override
public String instrumentedType() {
return "java.net.InetAddress";
}

@Override
public void methodAdvice(MethodTransformer transformer) {
// it does not work with typeInitializer()
transformer.applyAdvice(isConstructor(), packageName + ".JpmsInetAddressClearanceAdvice");
public Iterable<String> triggerClasses() {
return singleton("java.net.InetAddress");
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class JpmsInetAddressDisabledForkedTest extends InstrumentationSpecification {
// Disable the JPMS instrumentation so java.net is NOT opened for deep reflection.
// HostNameResolver will be unable to bypass the IP→hostname cache and will fall back
// to the cache keyed by IP address.
injectSysConfig("dd.trace.java-net.enabled", "false")
injectSysConfig("dd.trace.java-module.enabled", "false")
}

/**
Expand Down
Loading
Loading