Skip to content

Commit 37b797d

Browse files
committed
Replace unwanted class instance from agent
As the java agent is running at build time, some classes that should never run in native runtime could get instantiated and referred by classes about to build in native image. Agent developer can provide a black list by setting -H:ExcludeAgentClasses, so that each one of them will be replaced with a safe object at build time.
1 parent 86b7f3d commit 37b797d

File tree

3 files changed

+80
-9
lines changed

3 files changed

+80
-9
lines changed

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/LambdaUtils.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,10 @@ private LambdaUtils() {
8181
public static String findStableLambdaName(ResolvedJavaType lambdaType) {
8282
ResolvedJavaMethod[] lambdaProxyMethods = Arrays.stream(lambdaType.getDeclaredMethods(false)).filter(m -> !m.isBridge() && m.isPublic()).toArray(ResolvedJavaMethod[]::new);
8383
/*
84-
* Traverse all methods in lambda class for invokes because it is possible a javaagent may inject new methods into
85-
* the lambda class. For example, Byte-buddy used by OTele can transform all classes that implements
86-
* {@link java.util.concurrent.Callable} by injecting new methods that may not have any invokes.
84+
* Traverse all methods in lambda class for invokes because it is possible a javaagent may
85+
* inject new methods into the lambda class. For example, Byte-buddy used by OTele can
86+
* transform all classes that implements {@link java.util.concurrent.Callable} by injecting
87+
* new methods that may not have any invokes.
8788
*/
8889
Set<JavaMethod> invokedMethods = new HashSet<>();
8990
for (int i = 0; i < lambdaProxyMethods.length; i++) {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/InstrumentFeature.java

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
package com.oracle.svm.hosted;
2828

29+
import java.io.BufferedReader;
30+
import java.io.FileReader;
2931
import java.lang.instrument.Instrumentation;
3032
import java.lang.reflect.Method;
3133
import java.util.ArrayList;
@@ -50,23 +52,26 @@
5052

5153
import java.io.IOException;
5254
import java.util.jar.JarFile;
55+
import java.util.regex.Matcher;
56+
import java.util.regex.Pattern;
5357

5458
/**
5559
* This feature supports instrumentation in native image.
5660
*/
5761
@AutomaticallyRegisteredFeature
5862
public class InstrumentFeature implements InternalFeature {
5963
public static class Options {
60-
@Option(help = "Specify premain-class list. Multiple classes are separated by comma, and order matters. This is an experimental option.", stability = OptionStability.EXPERIMENTAL)//
61-
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> PremainClasses = new HostedOptionKey<>(
64+
@Option(help = "The classes should be excluded from native image but actually get instantiated at build time." +
65+
" Multiple values are separated by comma. It accepts Java regular expression." +
66+
" Using @ to specify a file in which each line is an item to take.", stability = OptionStability.EXPERIMENTAL)//
67+
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> ExcludeAgentClasses = new HostedOptionKey<>(
6268
AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter());
6369

6470
}
6571

66-
@Override
67-
public boolean isInConfiguration(IsInConfigurationAccess access) {
68-
return !Options.PremainClasses.getValue().values().isEmpty();
69-
}
72+
private static final NativeImageSystemClassLoader nativeImageSystemClassLoader = NativeImageSystemClassLoader.singleton();
73+
private static List<Pattern> excludeClasses;
74+
private static final Object DUMMY_OBJECT = new Object();
7075

7176
@Override
7277
public List<Class<? extends Feature>> getRequiredFeatures() {
@@ -143,4 +148,57 @@ private static Method findPremainMethod(String premainClass, Class<?> javaAgentC
143148

144149
throw UserError.abort("Could not register agent premain method: class %s neither declares 'premain(String, Instrumentation)' nor 'premain(String)'.", premainClass);
145150
}
151+
152+
@Override
153+
public void duringSetup(DuringSetupAccess a) {
154+
FeatureImpl.DuringSetupAccessImpl access = (FeatureImpl.DuringSetupAccessImpl) a;
155+
/**
156+
* As the java agent is running at build time, some classes that should never run in native
157+
* runtime could get instantiated and referred by classes in native image. Agent developer
158+
* can provide a black list by setting {@link Options.ExcludeAgentClasses}, so that each one
159+
* of them will be replaced with a safe object at build time.
160+
*/
161+
getExcludes();
162+
access.registerObjectReplacer(original -> {
163+
String className = original.getClass().getName();
164+
// Replace the classloader with nativeImageSystemClassLoader.defaultSystemClassLoader
165+
if (original instanceof ClassLoader && matchExclude(className)) {
166+
return nativeImageSystemClassLoader.defaultSystemClassLoader;
167+
}
168+
// Replace other unwanted objects with an object
169+
if (matchExclude(className)) {
170+
return DUMMY_OBJECT;
171+
}
172+
return original;
173+
});
174+
}
175+
176+
private static void getExcludes() {
177+
excludeClasses = new ArrayList<>();
178+
for (String excludePattern : Options.ExcludeAgentClasses.getValue().values()) {
179+
if (excludePattern.startsWith("@")) {
180+
String optionFile = excludePattern.substring(1);
181+
try (BufferedReader br = new BufferedReader(new FileReader(optionFile))) {
182+
String line;
183+
while ((line = br.readLine()) != null) {
184+
excludeClasses.add(Pattern.compile(line));
185+
}
186+
} catch (IOException e) {
187+
throw new RuntimeException("Failed to read file " + optionFile + "specified in -H:" + Options.ExcludeAgentClasses.getName(), e);
188+
}
189+
} else {
190+
excludeClasses.add(Pattern.compile(excludePattern));
191+
}
192+
}
193+
}
194+
195+
private boolean matchExclude(String input) {
196+
for (Pattern excludeClass : excludeClasses) {
197+
Matcher matcher = excludeClass.matcher(input);
198+
if (matcher.matches()) {
199+
return true;
200+
}
201+
}
202+
return false;
203+
}
146204
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import com.oracle.svm.core.option.SubstrateOptionsParser;
5757
import com.oracle.svm.core.snippets.SnippetRuntime;
5858
import com.oracle.svm.core.util.UserError;
59+
import com.oracle.svm.hosted.InstrumentFeature;
5960
import com.oracle.svm.hosted.FeatureImpl;
6061
import com.oracle.svm.hosted.FeatureImpl.AfterAnalysisAccessImpl;
6162
import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl;
@@ -66,6 +67,8 @@
6667
import jdk.graal.compiler.options.OptionValues;
6768
import jdk.graal.compiler.phases.util.Providers;
6869

70+
import org.graalvm.nativeimage.hosted.Feature;
71+
6972
@AutomaticallyRegisteredFeature
7073
public class ClassInitializationFeature implements InternalFeature {
7174
private static final String NATIVE_IMAGE_CLASS_REASON = "Native Image classes are always initialized at build time";
@@ -137,6 +140,15 @@ private static void initializeNativeImagePackagesAtBuildTime(ClassInitialization
137140
initializationSupport.initializeAtBuildTime("org.graalvm.jniutils", NATIVE_IMAGE_CLASS_REASON);
138141
}
139142

143+
@Override
144+
public List<Class<? extends Feature>> getRequiredFeatures() {
145+
/**
146+
* {@link InstrumentFeature} replace the objects that should not appear in runtime. It must
147+
* come before this feature, otherwise shouldInitializeAtRuntime will be triggered first.
148+
*/
149+
return List.of(InstrumentFeature.class);
150+
}
151+
140152
@Override
141153
public void duringSetup(DuringSetupAccess a) {
142154
FeatureImpl.DuringSetupAccessImpl access = (FeatureImpl.DuringSetupAccessImpl) a;

0 commit comments

Comments
 (0)