diff --git a/instrumentation/rmi/javaagent/build.gradle.kts b/instrumentation/rmi/javaagent/build.gradle.kts index 5007588c1273..fbd584f7d293 100644 --- a/instrumentation/rmi/javaagent/build.gradle.kts +++ b/instrumentation/rmi/javaagent/build.gradle.kts @@ -34,14 +34,5 @@ tasks { } withType().configureEach { jvmArgs("-Djava.rmi.server.hostname=127.0.0.1") - - // Can only export on Java 9+ - val testJavaVersion = - gradle.startParameter.projectProperties.get("testJavaVersion")?.let(JavaVersion::toVersion) - ?: JavaVersion.current() - if (testJavaVersion.isJava9Compatible) { - jvmArgs("--add-exports=java.rmi/sun.rmi.server=ALL-UNNAMED") - jvmArgs("--add-exports=java.rmi/sun.rmi.transport=ALL-UNNAMED") - } } } 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 20317582d5c8..03043bb0a426 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,25 +10,31 @@ 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.rmi.Remote; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Map; +import net.bytebuddy.utility.JavaModule; @AutoService(InstrumentationModule.class) -public class RmiContextPropagationInstrumentationModule extends InstrumentationModule { +public class RmiContextPropagationInstrumentationModule extends InstrumentationModule + implements ExperimentalInstrumentationModule { public RmiContextPropagationInstrumentationModule() { super("rmi", "rmi-context-propagation"); } @Override - public boolean isIndyModule() { - // java.lang.IllegalAccessError: class - // io.opentelemetry.javaagent.instrumentation.rmi.context.client.RmiClientContextInstrumentation$StreamRemoteCallConstructorAdvice (in unnamed module @0x740ee00f) cannot access class sun.rmi.transport.Connection (in module java.rmi) because module java.rmi does not export sun.rmi.transport to unnamed module @0x740ee00f - return false; + public List typeInstrumentations() { + return asList(new RmiClientContextInstrumentation(), new RmiServerContextInstrumentation()); } @Override - public List typeInstrumentations() { - return asList(new RmiClientContextInstrumentation(), new RmiServerContextInstrumentation()); + public Map> jpmsModulesToOpen() { + return Collections.singletonMap( + JavaModule.ofType(Remote.class), Arrays.asList("sun.rmi.server", "sun.rmi.transport")); } } diff --git a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/client/RmiClientContextInstrumentation.java b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/client/RmiClientContextInstrumentation.java index bacfd1e70d5a..7436e2afee8b 100644 --- a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/client/RmiClientContextInstrumentation.java +++ b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/client/RmiClientContextInstrumentation.java @@ -13,19 +13,13 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.util.VirtualField; -import io.opentelemetry.javaagent.bootstrap.AgentClassLoader; -import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder; import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.lang.instrument.Instrumentation; import java.rmi.server.ObjID; -import java.util.Collections; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.dynamic.loading.ClassInjector; import net.bytebuddy.matcher.ElementMatcher; -import net.bytebuddy.utility.JavaModule; import sun.rmi.transport.Connection; /** @@ -63,30 +57,6 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(0, named("sun.rmi.transport.Connection"))) .and(takesArgument(1, named("java.rmi.server.ObjID"))), getClass().getName() + "$StreamRemoteCallConstructorAdvice"); - - // expose sun.rmi.transport.StreamRemoteCall to helper classes - transformer.applyTransformer( - (builder, typeDescription, classLoader, javaModule, protectionDomain) -> { - if (JavaModule.isSupported() - && classLoader == null - && "sun.rmi.transport.StreamRemoteCall".equals(typeDescription.getName()) - && javaModule != null) { - Instrumentation instrumentation = InstrumentationHolder.getInstrumentation(); - ClassInjector.UsingInstrumentation.redefineModule( - instrumentation, - javaModule, - Collections.emptySet(), - Collections.emptyMap(), - Collections.singletonMap( - "sun.rmi.transport", - // AgentClassLoader is in unnamed module of the bootstrap class loader which is - // where helper classes are also - Collections.singleton(JavaModule.ofType(AgentClassLoader.class))), - Collections.emptySet(), - Collections.emptyMap()); - } - return builder; - }); } @SuppressWarnings("unused") diff --git a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/jpms/ExposeRmiModuleInstrumentation.java b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/jpms/ExposeRmiModuleInstrumentation.java deleted file mode 100644 index e82c4786f286..000000000000 --- a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/jpms/ExposeRmiModuleInstrumentation.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.rmi.context.jpms; - -import static java.util.logging.Level.FINE; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; - -import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder; -import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.util.Collections; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Logger; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.dynamic.loading.ClassInjector; -import net.bytebuddy.matcher.ElementMatcher; -import net.bytebuddy.utility.JavaModule; - -public class ExposeRmiModuleInstrumentation implements TypeInstrumentation { - private static final Logger logger = - Logger.getLogger(ExposeRmiModuleInstrumentation.class.getName()); - - private final AtomicBoolean instrumented = new AtomicBoolean(); - - @Override - public ElementMatcher typeMatcher() { - ElementMatcher.Junction notInstrumented = - new ElementMatcher.Junction.AbstractBase() { - - @Override - public boolean matches(TypeDescription target) { - return !instrumented.get(); - } - }; - - return notInstrumented.and(nameStartsWith("sun.rmi")); - } - - @Override - public ElementMatcher classLoaderOptimization() { - return new ElementMatcher.Junction.AbstractBase() { - - @Override - public boolean matches(ClassLoader target) { - // runs only in bootstrap class loader - return JavaModule.isSupported() && target == null; - } - }; - } - - @Override - public void transform(TypeTransformer transformer) { - transformer.applyTransformer( - (builder, typeDescription, classLoader, javaModule, protectionDomain) -> { - if (javaModule != null && javaModule.isNamed()) { - // using Java8BytecodeBridge because it's in the unnamed module in the bootstrap - // loader, and that's where the rmi instrumentation helper classes will end up - JavaModule helperModule = JavaModule.ofType(Java8BytecodeBridge.class); - // expose sun.rmi.server package to unnamed module - ClassInjector.UsingInstrumentation.redefineModule( - InstrumentationHolder.getInstrumentation(), - javaModule, - Collections.emptySet(), - Collections.singletonMap("sun.rmi.server", Collections.singleton(helperModule)), - Collections.emptyMap(), - Collections.emptySet(), - Collections.emptyMap()); - - instrumented.set(true); - logger.log( - FINE, - "Exposed package \"sun.rmi.server\" in module {0} to unnamed module", - javaModule); - } - return builder; - }); - } -} diff --git a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/jpms/RmiJpmsInstrumentationModule.java b/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/jpms/RmiJpmsInstrumentationModule.java deleted file mode 100644 index 6baf3baad0e0..000000000000 --- a/instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/jpms/RmiJpmsInstrumentationModule.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.rmi.context.jpms; - -import static java.util.Collections.singletonList; - -import com.google.auto.service.AutoService; -import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -import io.opentelemetry.javaagent.instrumentation.rmi.context.server.ContextDispatcher; -import java.util.List; - -/** - * RMI server instrumentation class {@link ContextDispatcher} implements an internal interface that - * is not exported by java module system. This is not allowed on jdk17. This instrumentation module - * exposes JDK internal classes for RMI server instrumentation. - */ -@AutoService(InstrumentationModule.class) -public class RmiJpmsInstrumentationModule extends InstrumentationModule { - - public RmiJpmsInstrumentationModule() { - super("rmi", "rmi-jpms"); - } - - @Override - public List typeInstrumentations() { - return singletonList(new ExposeRmiModuleInstrumentation()); - } -} 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..ab791a23e1e9 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,8 @@ import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector; import java.util.Collections; import java.util.List; +import java.util.Map; +import net.bytebuddy.utility.JavaModule; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -61,4 +63,16 @@ 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. + * + * @return map of module to open as key, list of packages as value. + */ + // TODO: when moving this method outside of experimental API, we need to decide using JavaModule + // instance or a class FQN in the map entry, as it could lead to some limitations + 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..5f5327f56ba7 --- /dev/null +++ b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/ModuleOpener.java @@ -0,0 +1,84 @@ +/* + * 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 io.opentelemetry.javaagent.bootstrap.AgentClassLoader; +import java.lang.instrument.Instrumentation; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; +import javax.annotation.Nullable; +import net.bytebuddy.description.type.PackageDescription; +import net.bytebuddy.dynamic.loading.ClassInjector; +import net.bytebuddy.utility.JavaModule; + +/** + * Module opener provides ability to open JPMS modules and allows instrumentation classloader to + * access module contents without requiring JVM arguments modification.
+ * Usage of this class must be guarded with an {@code net.bytebuddy.utility.JavaModule#isSupported} + * check as it's compiled for Java 9+, otherwise an {@link UnsupportedClassVersionError} will be + * thrown for java 8. + */ +public class ModuleOpener { + + private static final Logger logger = Logger.getLogger(ModuleOpener.class.getName()); + + // AgentClassLoader is in unnamed module of the bootstrap loader + private static final JavaModule UNNAMED_BOOT_MODULE = JavaModule.ofType(AgentClassLoader.class); + + private ModuleOpener() {} + + /** + * Opens JPMS module to a class loader unnamed module + * + * @param targetModule target module + * @param openTo class loader to open module for, {@literal null} to use the unnamed module of + * bootstrap classloader. + * @param packagesToOpen packages to open + */ + public static void open( + Instrumentation instrumentation, + JavaModule targetModule, + @Nullable ClassLoader openTo, + Collection packagesToOpen) { + + JavaModule openToModule = + openTo != null ? JavaModule.of(openTo.getUnnamedModule()) : UNNAMED_BOOT_MODULE; + Set openToModuleSet = Collections.singleton(openToModule); + Map> missingOpens = new HashMap<>(); + for (String packageName : packagesToOpen) { + if (!targetModule.isOpened(new PackageDescription.Simple(packageName), openToModule)) { + missingOpens.put(packageName, openToModuleSet); + logger.log( + FINE, + "Exposing package '{0}' in module '{1}' to module '{2}'", + new Object[] {packageName, targetModule, openToModule}); + } + } + if (missingOpens.isEmpty()) { + return; + } + + try { + ClassInjector.UsingInstrumentation.redefineModule( + instrumentation, + targetModule, + Collections.emptySet(), + Collections.emptyMap(), + missingOpens, + Collections.emptySet(), + Collections.emptyMap()); + } catch (Exception e) { + logger.log(WARNING, "Failed to redefine module '" + targetModule.getActualName() + "'", e); + } + } +} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java index 09bc519812a9..a0924738e44b 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java @@ -17,6 +17,7 @@ import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode; import io.opentelemetry.javaagent.tooling.HelperClassDefinition; import io.opentelemetry.javaagent.tooling.HelperInjector; +import io.opentelemetry.javaagent.tooling.ModuleOpener; import io.opentelemetry.javaagent.tooling.TransformSafeLogger; import io.opentelemetry.javaagent.tooling.Utils; import io.opentelemetry.javaagent.tooling.bytebuddy.LoggingFailSafeMatcher; @@ -36,11 +37,13 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.description.annotation.AnnotationSource; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.utility.JavaModule; public final class InstrumentationModuleInstaller { @@ -202,13 +205,32 @@ private AgentBuilder installInjectingModule( VirtualFieldImplementationInstaller contextProvider = virtualFieldInstallerFactory.create(instrumentationModule); + AtomicBoolean openerRun = new AtomicBoolean(); AgentBuilder agentBuilder = parentAgentBuilder; for (TypeInstrumentation typeInstrumentation : typeInstrumentations) { - AgentBuilder.Identified.Extendable extendableAgentBuilder = setTypeMatcher(agentBuilder, instrumentationModule, typeInstrumentation) .and(muzzleMatcher) .transform(ConstantAdjuster.instance()) + .transform( + (builder, typeDescription, classLoader, module, protectionDomain) -> { + if (JavaModule.isSupported() + && instrumentationModule instanceof ExperimentalInstrumentationModule + && !openerRun.get()) { + ExperimentalInstrumentationModule experimentalModule = + (ExperimentalInstrumentationModule) instrumentationModule; + experimentalModule + .jpmsModulesToOpen() + .forEach( + (javaModule, packages) -> { + ModuleOpener.open( + instrumentation, javaModule, classLoader, packages); + }); + openerRun.set(true); + } + + return builder; + }) .transform(helperInjector); extendableAgentBuilder = contextProvider.injectHelperClasses(extendableAgentBuilder); extendableAgentBuilder = contextProvider.rewriteVirtualFieldsCalls(extendableAgentBuilder); 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..611bf419326e 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,12 +5,16 @@ 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; +import net.bytebuddy.utility.JavaModule; public class IndyModuleRegistry { @@ -65,6 +69,24 @@ public static InstrumentationModuleClassLoader getInstrumentationClassLoader( + " yet"); } + if (module instanceof ExperimentalInstrumentationModule) { + ExperimentalInstrumentationModule experimentalModule = + (ExperimentalInstrumentationModule) module; + + Instrumentation instrumentation = InstrumentationHolder.getInstrumentation(); + if (instrumentation == null) { + throw new IllegalStateException("global instrumentation not available"); + } + + if (JavaModule.isSupported()) { + // module opener only usable for java 9+ + experimentalModule + .jpmsModulesToOpen() + .forEach( + (javaModule, packages) -> + ModuleOpener.open(instrumentation, javaModule, loader, packages)); + } + } return loader; }