diff --git a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/RmiContextPropagationInstrumentationModule.java b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/RmiContextPropagationInstrumentationModule.java index bd09178336e8..deadedc02875 100644 --- a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/RmiContextPropagationInstrumentationModule.java +++ b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/RmiContextPropagationInstrumentationModule.java @@ -10,12 +10,17 @@ import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule; import io.opentelemetry.javaagent.instrumentation.rmi.context.client.RmiClientContextInstrumentation; import io.opentelemetry.javaagent.instrumentation.rmi.context.server.RmiServerContextInstrumentation; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Map; @AutoService(InstrumentationModule.class) -public class RmiContextPropagationInstrumentationModule extends InstrumentationModule { +public class RmiContextPropagationInstrumentationModule extends InstrumentationModule + implements ExperimentalInstrumentationModule { public RmiContextPropagationInstrumentationModule() { super("rmi", "rmi-context-propagation"); } @@ -24,4 +29,11 @@ public RmiContextPropagationInstrumentationModule() { public List typeInstrumentations() { return asList(new RmiClientContextInstrumentation(), new RmiServerContextInstrumentation()); } + + @Override + public Map> jpmsModulesToOpen() { + String witnessClass = "sun.rmi.transport.StreamRemoteCall"; + return Collections.singletonMap( + witnessClass, Arrays.asList("sun.rmi.server", "sun.rmi.transport")); + } } diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/ExperimentalInstrumentationModule.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/ExperimentalInstrumentationModule.java index af8d33d78659..3c404aa5e2c7 100644 --- a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/ExperimentalInstrumentationModule.java +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/ExperimentalInstrumentationModule.java @@ -11,6 +11,7 @@ import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector; import java.util.Collections; import java.util.List; +import java.util.Map; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -61,4 +62,18 @@ default String getModuleGroup() { default List agentPackagesToHide() { return Collections.emptyList(); } + + /** + * Some instrumentation need to access JPMS modules that are not accessible by default, this + * method provides a way to access those classes like the "--add-opens" JVM command.
+ * Map key is the name of a "witness class" belonging to the module that will be loaded and used + * to get a reference to the module.
+ * Map value is a list of packages to open in the module + * + * @return map of "witness class" name as key, list of packages as value. + */ + // TODO: copy the javadoc of the original implementation + default Map> jpmsModulesToOpen() { + return Collections.emptyMap(); + } } diff --git a/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/ModuleOpener.java b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/ModuleOpener.java new file mode 100644 index 000000000000..81ce0e0a1398 --- /dev/null +++ b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/ModuleOpener.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling; + +import static java.util.logging.Level.FINE; +import static java.util.logging.Level.WARNING; + +import java.lang.instrument.Instrumentation; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +public class ModuleOpener { + + private static final Logger logger = Logger.getLogger(ModuleOpener.class.getName()); + + private ModuleOpener() {} + + /** + * Opens JPMS module to a class loader unnamed module + * + * @param classFromTargetModule class from target module + * @param openTo class loader to open module for + * @param packagesToOpen packages to open + */ + public static void open( + Instrumentation instrumentation, + Class classFromTargetModule, + ClassLoader openTo, + Collection packagesToOpen) { + + Module targetModule = classFromTargetModule.getModule(); + Module openToModule = openTo.getUnnamedModule(); + Set openToModuleSet = Collections.singleton(openToModule); + Map> missingOpens = new HashMap<>(); + for (String packageName : packagesToOpen) { + if (!targetModule.isOpen(packageName, openToModule)) { + missingOpens.put(packageName, openToModuleSet); + logger.log( + FINE, + () -> + String.format( + "Exposing package '%s' in module '%s' to module '%s'", + packageName, targetModule, openToModule)); + } + } + if (missingOpens.isEmpty()) { + return; + } + + if (!instrumentation.isModifiableModule(targetModule)) { + logger.log(WARNING, "Module '{}' can't be modified", targetModule); + return; + } + + try { + instrumentation.redefineModule( + targetModule, + Collections.emptySet(), // reads + Collections.>emptyMap(), // exports + missingOpens, // opens + Collections.>emptySet(), // uses + Collections., List>>emptyMap() // provides + ); + } catch (Exception e) { + logger.log(WARNING, "unable to redefine module", e); + } + } +} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/IndyModuleRegistry.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/IndyModuleRegistry.java index 7eaf7f3511b6..94129855cea4 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/IndyModuleRegistry.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/IndyModuleRegistry.java @@ -5,9 +5,12 @@ package io.opentelemetry.javaagent.tooling.instrumentation.indy; +import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule; +import io.opentelemetry.javaagent.tooling.ModuleOpener; import io.opentelemetry.javaagent.tooling.util.ClassLoaderValue; +import java.lang.instrument.Instrumentation; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import net.bytebuddy.agent.builder.AgentBuilder; @@ -65,6 +68,40 @@ public static InstrumentationModuleClassLoader getInstrumentationClassLoader( + " yet"); } + if (module instanceof ExperimentalInstrumentationModule) { + ExperimentalInstrumentationModule experimentalModule = + (ExperimentalInstrumentationModule) module; + + // Opening JPMS modules requires to use a 'witness class' in the target module to get a + // reference to the module, which means we have to eagerly load the class. + // + // However, this code here triggered when the advice is being executed for the first time so + // this only creates a very small eager loading that is unlikely to have impact on the + // application. + // + // Also, using a class that is already loaded like the one that is being instrumented or a + // related one would increase the likeliness of not having an effect on application class + // loading. + + Instrumentation instrumentation = InstrumentationHolder.getInstrumentation(); + if (instrumentation == null) { + throw new IllegalStateException("global instrumentation not available"); + } + + experimentalModule + .jpmsModulesToOpen() + .forEach( + (className, packages) -> { + Class type; + try { + type = Class.forName(className, false, instrumentedClassLoader); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("missing witness class " + className, e); + } + + ModuleOpener.open(instrumentation, type, loader, packages); + }); + } return loader; }