|
26 | 26 |
|
27 | 27 | package com.oracle.svm.hosted; |
28 | 28 |
|
| 29 | +import java.io.BufferedReader; |
| 30 | +import java.io.FileReader; |
29 | 31 | import java.lang.instrument.Instrumentation; |
30 | 32 | import java.lang.reflect.Method; |
31 | 33 | import java.util.ArrayList; |
|
50 | 52 |
|
51 | 53 | import java.io.IOException; |
52 | 54 | import java.util.jar.JarFile; |
| 55 | +import java.util.regex.Matcher; |
| 56 | +import java.util.regex.Pattern; |
53 | 57 |
|
54 | 58 | /** |
55 | 59 | * This feature supports instrumentation in native image. |
56 | 60 | */ |
57 | 61 | @AutomaticallyRegisteredFeature |
58 | 62 | public class InstrumentFeature implements InternalFeature { |
59 | 63 | 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<>( |
62 | 68 | AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); |
63 | 69 |
|
64 | 70 | } |
65 | 71 |
|
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(); |
70 | 75 |
|
71 | 76 | @Override |
72 | 77 | public List<Class<? extends Feature>> getRequiredFeatures() { |
@@ -143,4 +148,57 @@ private static Method findPremainMethod(String premainClass, Class<?> javaAgentC |
143 | 148 |
|
144 | 149 | throw UserError.abort("Could not register agent premain method: class %s neither declares 'premain(String, Instrumentation)' nor 'premain(String)'.", premainClass); |
145 | 150 | } |
| 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 | + } |
146 | 204 | } |
0 commit comments