Skip to content

Commit 93bca06

Browse files
SylvainJugelaurit
andauthored
make rmi instrumentation indy-compatible + add module opener (#12585)
Co-authored-by: Lauri Tulmin <[email protected]> Co-authored-by: Lauri Tulmin <[email protected]>
1 parent 2da1d1f commit 93bca06

File tree

9 files changed

+156
-161
lines changed

9 files changed

+156
-161
lines changed

instrumentation/rmi/javaagent/build.gradle.kts

-9
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,5 @@ tasks {
3434
}
3535
withType<Test>().configureEach {
3636
jvmArgs("-Djava.rmi.server.hostname=127.0.0.1")
37-
38-
// Can only export on Java 9+
39-
val testJavaVersion =
40-
gradle.startParameter.projectProperties.get("testJavaVersion")?.let(JavaVersion::toVersion)
41-
?: JavaVersion.current()
42-
if (testJavaVersion.isJava9Compatible) {
43-
jvmArgs("--add-exports=java.rmi/sun.rmi.server=ALL-UNNAMED")
44-
jvmArgs("--add-exports=java.rmi/sun.rmi.transport=ALL-UNNAMED")
45-
}
4637
}
4738
}

instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/RmiContextPropagationInstrumentationModule.java

+13-7
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,31 @@
1010
import com.google.auto.service.AutoService;
1111
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1212
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
13+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
1314
import io.opentelemetry.javaagent.instrumentation.rmi.context.client.RmiClientContextInstrumentation;
1415
import io.opentelemetry.javaagent.instrumentation.rmi.context.server.RmiServerContextInstrumentation;
16+
import java.rmi.Remote;
17+
import java.util.Arrays;
18+
import java.util.Collections;
1519
import java.util.List;
20+
import java.util.Map;
21+
import net.bytebuddy.utility.JavaModule;
1622

1723
@AutoService(InstrumentationModule.class)
18-
public class RmiContextPropagationInstrumentationModule extends InstrumentationModule {
24+
public class RmiContextPropagationInstrumentationModule extends InstrumentationModule
25+
implements ExperimentalInstrumentationModule {
1926
public RmiContextPropagationInstrumentationModule() {
2027
super("rmi", "rmi-context-propagation");
2128
}
2229

2330
@Override
24-
public boolean isIndyModule() {
25-
// java.lang.IllegalAccessError: class
26-
// 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
27-
return false;
31+
public List<TypeInstrumentation> typeInstrumentations() {
32+
return asList(new RmiClientContextInstrumentation(), new RmiServerContextInstrumentation());
2833
}
2934

3035
@Override
31-
public List<TypeInstrumentation> typeInstrumentations() {
32-
return asList(new RmiClientContextInstrumentation(), new RmiServerContextInstrumentation());
36+
public Map<JavaModule, List<String>> jpmsModulesToOpen() {
37+
return Collections.singletonMap(
38+
JavaModule.ofType(Remote.class), Arrays.asList("sun.rmi.server", "sun.rmi.transport"));
3339
}
3440
}

instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/client/RmiClientContextInstrumentation.java

-30
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,13 @@
1313
import io.opentelemetry.api.trace.Span;
1414
import io.opentelemetry.context.Context;
1515
import io.opentelemetry.instrumentation.api.util.VirtualField;
16-
import io.opentelemetry.javaagent.bootstrap.AgentClassLoader;
17-
import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder;
1816
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
1917
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
2018
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
21-
import java.lang.instrument.Instrumentation;
2219
import java.rmi.server.ObjID;
23-
import java.util.Collections;
2420
import net.bytebuddy.asm.Advice;
2521
import net.bytebuddy.description.type.TypeDescription;
26-
import net.bytebuddy.dynamic.loading.ClassInjector;
2722
import net.bytebuddy.matcher.ElementMatcher;
28-
import net.bytebuddy.utility.JavaModule;
2923
import sun.rmi.transport.Connection;
3024

3125
/**
@@ -63,30 +57,6 @@ public void transform(TypeTransformer transformer) {
6357
.and(takesArgument(0, named("sun.rmi.transport.Connection")))
6458
.and(takesArgument(1, named("java.rmi.server.ObjID"))),
6559
getClass().getName() + "$StreamRemoteCallConstructorAdvice");
66-
67-
// expose sun.rmi.transport.StreamRemoteCall to helper classes
68-
transformer.applyTransformer(
69-
(builder, typeDescription, classLoader, javaModule, protectionDomain) -> {
70-
if (JavaModule.isSupported()
71-
&& classLoader == null
72-
&& "sun.rmi.transport.StreamRemoteCall".equals(typeDescription.getName())
73-
&& javaModule != null) {
74-
Instrumentation instrumentation = InstrumentationHolder.getInstrumentation();
75-
ClassInjector.UsingInstrumentation.redefineModule(
76-
instrumentation,
77-
javaModule,
78-
Collections.emptySet(),
79-
Collections.emptyMap(),
80-
Collections.singletonMap(
81-
"sun.rmi.transport",
82-
// AgentClassLoader is in unnamed module of the bootstrap class loader which is
83-
// where helper classes are also
84-
Collections.singleton(JavaModule.ofType(AgentClassLoader.class))),
85-
Collections.emptySet(),
86-
Collections.emptyMap());
87-
}
88-
return builder;
89-
});
9060
}
9161

9262
@SuppressWarnings("unused")

instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/jpms/ExposeRmiModuleInstrumentation.java

-82
This file was deleted.

instrumentation/rmi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rmi/context/jpms/RmiJpmsInstrumentationModule.java

-32
This file was deleted.

javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/internal/ExperimentalInstrumentationModule.java

+14
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;
1212
import java.util.Collections;
1313
import java.util.List;
14+
import java.util.Map;
15+
import net.bytebuddy.utility.JavaModule;
1416

1517
/**
1618
* 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() {
6163
default List<String> agentPackagesToHide() {
6264
return Collections.emptyList();
6365
}
66+
67+
/**
68+
* Some instrumentation need to access JPMS modules that are not accessible by default, this
69+
* method provides a way to access those classes like the "--add-opens" JVM command.
70+
*
71+
* @return map of module to open as key, list of packages as value.
72+
*/
73+
// TODO: when moving this method outside of experimental API, we need to decide using JavaModule
74+
// instance or a class FQN in the map entry, as it could lead to some limitations
75+
default Map<JavaModule, List<String>> jpmsModulesToOpen() {
76+
return Collections.emptyMap();
77+
}
6478
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.tooling;
7+
8+
import static java.util.logging.Level.FINE;
9+
import static java.util.logging.Level.WARNING;
10+
11+
import io.opentelemetry.javaagent.bootstrap.AgentClassLoader;
12+
import java.lang.instrument.Instrumentation;
13+
import java.util.Collection;
14+
import java.util.Collections;
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
import java.util.Set;
18+
import java.util.logging.Logger;
19+
import javax.annotation.Nullable;
20+
import net.bytebuddy.description.type.PackageDescription;
21+
import net.bytebuddy.dynamic.loading.ClassInjector;
22+
import net.bytebuddy.utility.JavaModule;
23+
24+
/**
25+
* Module opener provides ability to open JPMS modules and allows instrumentation classloader to
26+
* access module contents without requiring JVM arguments modification. <br>
27+
* Usage of this class must be guarded with an {@code net.bytebuddy.utility.JavaModule#isSupported}
28+
* check as it's compiled for Java 9+, otherwise an {@link UnsupportedClassVersionError} will be
29+
* thrown for java 8.
30+
*/
31+
public class ModuleOpener {
32+
33+
private static final Logger logger = Logger.getLogger(ModuleOpener.class.getName());
34+
35+
// AgentClassLoader is in unnamed module of the bootstrap loader
36+
private static final JavaModule UNNAMED_BOOT_MODULE = JavaModule.ofType(AgentClassLoader.class);
37+
38+
private ModuleOpener() {}
39+
40+
/**
41+
* Opens JPMS module to a class loader unnamed module
42+
*
43+
* @param targetModule target module
44+
* @param openTo class loader to open module for, {@literal null} to use the unnamed module of
45+
* bootstrap classloader.
46+
* @param packagesToOpen packages to open
47+
*/
48+
public static void open(
49+
Instrumentation instrumentation,
50+
JavaModule targetModule,
51+
@Nullable ClassLoader openTo,
52+
Collection<String> packagesToOpen) {
53+
54+
JavaModule openToModule =
55+
openTo != null ? JavaModule.of(openTo.getUnnamedModule()) : UNNAMED_BOOT_MODULE;
56+
Set<JavaModule> openToModuleSet = Collections.singleton(openToModule);
57+
Map<String, Set<JavaModule>> missingOpens = new HashMap<>();
58+
for (String packageName : packagesToOpen) {
59+
if (!targetModule.isOpened(new PackageDescription.Simple(packageName), openToModule)) {
60+
missingOpens.put(packageName, openToModuleSet);
61+
logger.log(
62+
FINE,
63+
"Exposing package '{0}' in module '{1}' to module '{2}'",
64+
new Object[] {packageName, targetModule, openToModule});
65+
}
66+
}
67+
if (missingOpens.isEmpty()) {
68+
return;
69+
}
70+
71+
try {
72+
ClassInjector.UsingInstrumentation.redefineModule(
73+
instrumentation,
74+
targetModule,
75+
Collections.emptySet(),
76+
Collections.emptyMap(),
77+
missingOpens,
78+
Collections.emptySet(),
79+
Collections.emptyMap());
80+
} catch (Exception e) {
81+
logger.log(WARNING, "Failed to redefine module '" + targetModule.getActualName() + "'", e);
82+
}
83+
}
84+
}

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java

+23-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode;
1818
import io.opentelemetry.javaagent.tooling.HelperClassDefinition;
1919
import io.opentelemetry.javaagent.tooling.HelperInjector;
20+
import io.opentelemetry.javaagent.tooling.ModuleOpener;
2021
import io.opentelemetry.javaagent.tooling.TransformSafeLogger;
2122
import io.opentelemetry.javaagent.tooling.Utils;
2223
import io.opentelemetry.javaagent.tooling.bytebuddy.LoggingFailSafeMatcher;
@@ -36,11 +37,13 @@
3637
import java.util.ArrayList;
3738
import java.util.Collections;
3839
import java.util.List;
40+
import java.util.concurrent.atomic.AtomicBoolean;
3941
import java.util.function.Function;
4042
import net.bytebuddy.agent.builder.AgentBuilder;
4143
import net.bytebuddy.description.annotation.AnnotationSource;
4244
import net.bytebuddy.description.type.TypeDescription;
4345
import net.bytebuddy.matcher.ElementMatcher;
46+
import net.bytebuddy.utility.JavaModule;
4447

4548
public final class InstrumentationModuleInstaller {
4649

@@ -202,13 +205,32 @@ private AgentBuilder installInjectingModule(
202205
VirtualFieldImplementationInstaller contextProvider =
203206
virtualFieldInstallerFactory.create(instrumentationModule);
204207

208+
AtomicBoolean openerRun = new AtomicBoolean();
205209
AgentBuilder agentBuilder = parentAgentBuilder;
206210
for (TypeInstrumentation typeInstrumentation : typeInstrumentations) {
207-
208211
AgentBuilder.Identified.Extendable extendableAgentBuilder =
209212
setTypeMatcher(agentBuilder, instrumentationModule, typeInstrumentation)
210213
.and(muzzleMatcher)
211214
.transform(ConstantAdjuster.instance())
215+
.transform(
216+
(builder, typeDescription, classLoader, module, protectionDomain) -> {
217+
if (JavaModule.isSupported()
218+
&& instrumentationModule instanceof ExperimentalInstrumentationModule
219+
&& !openerRun.get()) {
220+
ExperimentalInstrumentationModule experimentalModule =
221+
(ExperimentalInstrumentationModule) instrumentationModule;
222+
experimentalModule
223+
.jpmsModulesToOpen()
224+
.forEach(
225+
(javaModule, packages) -> {
226+
ModuleOpener.open(
227+
instrumentation, javaModule, classLoader, packages);
228+
});
229+
openerRun.set(true);
230+
}
231+
232+
return builder;
233+
})
212234
.transform(helperInjector);
213235
extendableAgentBuilder = contextProvider.injectHelperClasses(extendableAgentBuilder);
214236
extendableAgentBuilder = contextProvider.rewriteVirtualFieldsCalls(extendableAgentBuilder);

0 commit comments

Comments
 (0)