Skip to content

Commit 8f98a0e

Browse files
committed
Preserve all fields in the preserve mode
1 parent 275b0d9 commit 8f98a0e

File tree

5 files changed

+145
-86
lines changed

5 files changed

+145
-86
lines changed

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java

-53
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,6 @@
4040
*/
4141
package org.graalvm.nativeimage.impl;
4242

43-
import java.lang.reflect.Constructor;
44-
import java.lang.reflect.Method;
45-
import java.lang.reflect.Modifier;
46-
47-
import org.graalvm.nativeimage.hosted.RuntimeJNIAccess;
48-
import org.graalvm.nativeimage.hosted.RuntimeProxyCreation;
49-
5043
public interface RuntimeReflectionSupport extends ReflectionRegistry {
5144
// needed as reflection-specific ImageSingletons key
5245
void registerAllMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class<?> clazz);
@@ -75,50 +68,4 @@ public interface RuntimeReflectionSupport extends ReflectionRegistry {
7568

7669
void registerClassLookupException(ConfigurationCondition condition, String typeName, Throwable t);
7770

78-
default void registerClassFully(ConfigurationCondition condition, Class<?> clazz) {
79-
register(condition, false, clazz);
80-
81-
// GR-62143 Register all fields is very slow.
82-
// registerAllDeclaredFields(condition, clazz);
83-
// registerAllFields(condition, clazz);
84-
registerAllDeclaredMethodsQuery(condition, false, clazz);
85-
registerAllMethodsQuery(condition, false, clazz);
86-
registerAllDeclaredConstructorsQuery(condition, false, clazz);
87-
registerAllConstructorsQuery(condition, false, clazz);
88-
registerAllClassesQuery(condition, clazz);
89-
registerAllDeclaredClassesQuery(condition, clazz);
90-
registerAllNestMembersQuery(condition, clazz);
91-
registerAllPermittedSubclassesQuery(condition, clazz);
92-
registerAllRecordComponentsQuery(condition, clazz);
93-
registerAllSignersQuery(condition, clazz);
94-
95-
/* Register every single-interface proxy */
96-
// GR-62293 can't register proxies from jdk modules.
97-
if (clazz.getModule() == null && clazz.isInterface()) {
98-
RuntimeProxyCreation.register(clazz);
99-
}
100-
101-
RuntimeJNIAccess.register(clazz);
102-
try {
103-
for (Method declaredMethod : clazz.getDeclaredMethods()) {
104-
RuntimeJNIAccess.register(declaredMethod);
105-
}
106-
for (Constructor<?> declaredConstructor : clazz.getDeclaredConstructors()) {
107-
RuntimeJNIAccess.register(declaredConstructor);
108-
}
109-
// GR-62143 Registering all fields is very slow.
110-
// for (Field declaredField : clazz.getDeclaredFields()) {
111-
// RuntimeJNIAccess.register(declaredField);
112-
// RuntimeReflection.register(declaredField);
113-
// }
114-
} catch (LinkageError e) {
115-
/* If we can't link we can not register for JNI */
116-
}
117-
118-
// GR-62143 Registering all fields is very slow.
119-
// RuntimeSerialization.register(clazz);
120-
121-
// if we register unsafe allocated earlier there are build-time initialization errors
122-
register(condition, !(clazz.isArray() || clazz.isInterface() || clazz.isPrimitive() || Modifier.isAbstract(clazz.getModifiers())), clazz);
123-
}
12471
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ public void clearPreserveSelectors() {
143143
preserveAllOrigin = null;
144144
}
145145

146+
public boolean isPreserveMode() {
147+
return !preserveSelectors.classpathEntries.isEmpty() || !preserveSelectors.moduleNames.isEmpty() || !preserveSelectors.packages.isEmpty() || preserveAll;
148+
}
149+
146150
public IncludeSelectors getPreserveSelectors() {
147151
return preserveSelectors;
148152
}

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

+17-32
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import java.util.Collection;
4343
import java.util.Collections;
4444
import java.util.EnumSet;
45-
import java.util.HashSet;
4645
import java.util.List;
4746
import java.util.ListIterator;
4847
import java.util.Locale;
@@ -71,12 +70,9 @@
7170
import org.graalvm.nativeimage.c.struct.RawStructure;
7271
import org.graalvm.nativeimage.hosted.Feature;
7372
import org.graalvm.nativeimage.hosted.Feature.OnAnalysisExitAccess;
74-
import org.graalvm.nativeimage.hosted.RuntimeReflection;
7573
import org.graalvm.nativeimage.impl.AnnotationExtractor;
7674
import org.graalvm.nativeimage.impl.CConstantValueSupport;
77-
import org.graalvm.nativeimage.impl.ConfigurationCondition;
7875
import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport;
79-
import org.graalvm.nativeimage.impl.RuntimeReflectionSupport;
8076
import org.graalvm.nativeimage.impl.SizeOfSupport;
8177
import org.graalvm.word.PointerBase;
8278

@@ -227,6 +223,7 @@
227223
import com.oracle.svm.hosted.image.NativeImageCodeCache;
228224
import com.oracle.svm.hosted.image.NativeImageCodeCacheFactory;
229225
import com.oracle.svm.hosted.image.NativeImageHeap;
226+
import com.oracle.svm.hosted.image.PreserveOptionsSupport;
230227
import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport;
231228
import com.oracle.svm.hosted.imagelayer.LoadImageSingletonFeature;
232229
import com.oracle.svm.hosted.imagelayer.SVMImageLayerLoader;
@@ -1104,17 +1101,7 @@ protected void setupNativeImage(String imageName, OptionValues options, Map<Meth
11041101
new SubstrateClassInitializationPlugin((SVMHost) aUniverse.hostVM()), this.isStubBasedPluginsSupported(), aProviders);
11051102

11061103
loader.classLoaderSupport.getClassesToIncludeUnconditionally().forEach(cls -> bb.registerTypeForBaseImage(cls));
1107-
1108-
var runtimeReflection = ImageSingletons.lookup(RuntimeReflectionSupport.class);
1109-
1110-
Set<String> classesOrPackagesToIgnore = ignoredClassesOrPackagesForPreserve();
1111-
loader.classLoaderSupport.getClassesToPreserve().parallel()
1112-
.filter(ClassInclusionPolicy::isClassIncludedBase)
1113-
.filter(c -> !(classesOrPackagesToIgnore.contains(c.getPackageName()) || classesOrPackagesToIgnore.contains(c.getName())))
1114-
.forEach(c -> runtimeReflection.registerClassFully(ConfigurationCondition.alwaysTrue(), c));
1115-
for (String className : loader.classLoaderSupport.getClassNamesToPreserve()) {
1116-
RuntimeReflection.registerClassLookup(className);
1117-
}
1104+
PreserveOptionsSupport.registerPreservedClasses(loader.classLoaderSupport);
11181105

11191106
registerEntryPointStubs(entryPoints);
11201107
}
@@ -1123,13 +1110,6 @@ protected void setupNativeImage(String imageName, OptionValues options, Map<Meth
11231110
}
11241111
}
11251112

1126-
private static Set<String> ignoredClassesOrPackagesForPreserve() {
1127-
Set<String> ignoredClassesOrPackages = new HashSet<>(SubstrateOptions.IgnorePreserveForClasses.getValue().valuesAsSet());
1128-
// GR-63360: Parsing of constant_ lambda forms fails
1129-
ignoredClassesOrPackages.add("java.lang.invoke.LambdaForm$Holder");
1130-
return Collections.unmodifiableSet(ignoredClassesOrPackages);
1131-
}
1132-
11331113
protected void registerEntryPointStubs(Map<Method, CEntryPointData> entryPoints) {
11341114
entryPoints.forEach((method, entryPointData) -> CEntryPointCallStubSupport.singleton().registerStubForMethod(method, () -> entryPointData));
11351115
}
@@ -1785,10 +1765,11 @@ public static void checkName(BigBang bb, AnalysisType type) {
17851765
/**
17861766
* These are legit elements from the JDK that have hotspot in their name.
17871767
*/
1788-
private static final Set<String> HOTSPOT_IN_NAME_EXCEPTIONS = Set.of(
1768+
private static final Set<String> CHECK_NAMING_EXCEPTIONS = Set.of(
17891769
"java.awt.Cursor.DOT_HOTSPOT_SUFFIX",
17901770
"sun.lwawt.macosx.CCustomCursor.fHotspot",
1791-
"sun.lwawt.macosx.CCustomCursor.getHotSpot()");
1771+
"sun.lwawt.macosx.CCustomCursor.getHotSpot()",
1772+
"sun.awt.shell.Win32ShellFolder2.ATTRIB_GHOSTED");
17921773

17931774
private static void checkName(BigBang bb, AnalysisMethod method, String format) {
17941775
/*
@@ -1798,18 +1779,22 @@ private static void checkName(BigBang bb, AnalysisMethod method, String format)
17981779
* JDK internal types.
17991780
*/
18001781
String lformat = format.toLowerCase(Locale.ROOT);
1801-
if (lformat.contains("hosted")) {
1802-
report(bb, format, method, "Hosted element used at run time: " + format + ".");
1803-
} else if (!lformat.startsWith("jdk.internal") && lformat.contains("hotspot")) {
1804-
if (!HOTSPOT_IN_NAME_EXCEPTIONS.contains(format)) {
1805-
report(bb, format, method, "Element with HotSpot in its name used at run time: " + format + System.lineSeparator() +
1806-
"If this is a regular JDK value, and not a HotSpot element that was accidentally included, you can add it to the NativeImageGenerator.HOTSPOT_IN_NAME_EXCEPTIONS" +
1807-
System.lineSeparator() +
1808-
"If this is HotSpot element that was accidentally included find a way to exclude it from the image.");
1782+
if (!CHECK_NAMING_EXCEPTIONS.contains(format)) {
1783+
if (lformat.contains("hosted")) {
1784+
report(bb, format, method, "Hosted element used at run time: " + format + namingConventionsErrorMessageSuffix("hosted"));
1785+
} else if (!lformat.startsWith("jdk.internal") && lformat.contains("hotspot")) {
1786+
report(bb, format, method, "Element with HotSpot in its name used at run time: " + format + namingConventionsErrorMessageSuffix("HotSpot"));
18091787
}
18101788
}
18111789
}
18121790

1791+
private static String namingConventionsErrorMessageSuffix(String elementType) {
1792+
return """
1793+
1794+
If this is a regular JDK value, and not a %s element that was accidentally included, you can add it to the NativeImageGenerator.CHECK_NAMING_EXCEPTIONS
1795+
If this is a %s element that was accidentally included, find a way to exclude it from the image.""".formatted(elementType, elementType);
1796+
}
1797+
18131798
private static void report(BigBang bb, String key, AnalysisMethod method, String message) {
18141799
if (bb != null) {
18151800
bb.getUnsupportedFeatures().addMessage(key, method, message);

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/PreserveOptionsSupport.java

+109-1
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,36 @@
2424
*/
2525
package com.oracle.svm.hosted.image;
2626

27+
import static com.oracle.graal.pointsto.api.PointstoOptions.UseConservativeUnsafeAccess;
28+
import static com.oracle.svm.core.SubstrateOptions.Preserve;
29+
30+
import java.lang.reflect.Constructor;
31+
import java.lang.reflect.Field;
32+
import java.lang.reflect.Method;
33+
import java.lang.reflect.Modifier;
2734
import java.util.Arrays;
35+
import java.util.Collections;
36+
import java.util.Comparator;
37+
import java.util.HashSet;
2838
import java.util.Set;
2939
import java.util.stream.Stream;
3040

3141
import org.graalvm.collections.EconomicMap;
42+
import org.graalvm.nativeimage.ImageSingletons;
43+
import org.graalvm.nativeimage.impl.ConfigurationCondition;
44+
import org.graalvm.nativeimage.impl.RuntimeJNIAccessSupport;
45+
import org.graalvm.nativeimage.impl.RuntimeProxyCreationSupport;
46+
import org.graalvm.nativeimage.impl.RuntimeReflectionSupport;
3247

48+
import com.oracle.graal.pointsto.ClassInclusionPolicy;
3349
import com.oracle.svm.core.SubstrateOptions;
3450
import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue;
3551
import com.oracle.svm.core.option.LocatableMultiOptionValue;
3652
import com.oracle.svm.core.option.SubstrateOptionsParser;
3753
import com.oracle.svm.core.util.UserError;
3854
import com.oracle.svm.hosted.NativeImageClassLoaderSupport;
3955
import com.oracle.svm.hosted.driver.IncludeOptionsSupport;
56+
import com.oracle.svm.util.ReflectionUtil;
4057

4158
import jdk.graal.compiler.options.OptionKey;
4259
import jdk.graal.compiler.options.OptionValues;
@@ -93,7 +110,8 @@ private static String preservePossibleOptions() {
93110
}
94111

95112
public static void parsePreserveOption(EconomicMap<OptionKey<?>, Object> hostedValues, NativeImageClassLoaderSupport classLoaderSupport) {
96-
AccumulatingLocatableMultiOptionValue.Strings preserve = SubstrateOptions.Preserve.getValue(new OptionValues(hostedValues));
113+
OptionValues optionValues = new OptionValues(hostedValues);
114+
AccumulatingLocatableMultiOptionValue.Strings preserve = SubstrateOptions.Preserve.getValue(optionValues);
97115
Stream<LocatableMultiOptionValue.ValueWithOrigin<String>> valuesWithOrigins = preserve.getValuesWithOrigins();
98116
valuesWithOrigins.forEach(valueWithOrigin -> {
99117
String optionArgument = SubstrateOptionsParser.commandArgument(SubstrateOptions.Preserve, valueWithOrigin.value(), true, false);
@@ -112,5 +130,95 @@ public static void parsePreserveOption(EconomicMap<OptionKey<?>, Object> hostedV
112130
}
113131
}
114132
});
133+
if (classLoaderSupport.isPreserveMode()) {
134+
if (UseConservativeUnsafeAccess.hasBeenSet(optionValues)) {
135+
UserError.guarantee(UseConservativeUnsafeAccess.getValue(optionValues), "%s can not be used together with %s. Please unset %s.",
136+
SubstrateOptionsParser.commandArgument(UseConservativeUnsafeAccess, "-"),
137+
SubstrateOptionsParser.commandArgument(Preserve, "<value>"),
138+
SubstrateOptionsParser.commandArgument(UseConservativeUnsafeAccess, "-"));
139+
}
140+
UseConservativeUnsafeAccess.update(hostedValues, true);
141+
}
142+
}
143+
144+
public static void registerPreservedClasses(NativeImageClassLoaderSupport classLoaderSupport) {
145+
var classesOrPackagesToIgnore = ignoredClassesOrPackagesForPreserve();
146+
var classesToPreserve = classLoaderSupport.getClassesToPreserve()
147+
.filter(ClassInclusionPolicy::isClassIncludedBase)
148+
.filter(c -> !(classesOrPackagesToIgnore.contains(c.getPackageName()) || classesOrPackagesToIgnore.contains(c.getName())))
149+
.sorted(Comparator.comparing(ReflectionUtil::getClassHierarchyDepth).reversed())
150+
.toList();
151+
152+
final RuntimeReflectionSupport reflection = ImageSingletons.lookup(RuntimeReflectionSupport.class);
153+
final RuntimeJNIAccessSupport jni = ImageSingletons.lookup(RuntimeJNIAccessSupport.class);
154+
final RuntimeProxyCreationSupport proxy = ImageSingletons.lookup(RuntimeProxyCreationSupport.class);
155+
final ConfigurationCondition always = ConfigurationCondition.alwaysTrue();
156+
157+
/*
158+
* Sort descending by class hierarchy depth to avoid complexity related to field
159+
* registration.
160+
*/
161+
classesToPreserve.forEach(c -> {
162+
reflection.register(always, false, c);
163+
164+
reflection.registerAllDeclaredFields(always, c);
165+
reflection.registerAllDeclaredMethodsQuery(always, false, c);
166+
reflection.registerAllDeclaredConstructorsQuery(always, false, c);
167+
reflection.registerAllConstructorsQuery(always, false, c);
168+
reflection.registerAllClassesQuery(always, c);
169+
reflection.registerAllDeclaredClassesQuery(always, c);
170+
reflection.registerAllNestMembersQuery(always, c);
171+
reflection.registerAllPermittedSubclassesQuery(always, c);
172+
reflection.registerAllRecordComponentsQuery(always, c);
173+
reflection.registerAllSignersQuery(always, c);
174+
175+
/* Register every single-interface proxy */
176+
// GR-62293 can't register proxies from jdk modules.
177+
if (c.getModule() == null && c.isInterface()) {
178+
proxy.addProxyClass(always, c);
179+
}
180+
181+
jni.register(ConfigurationCondition.alwaysTrue(), c);
182+
try {
183+
for (Method declaredMethod : c.getDeclaredMethods()) {
184+
jni.register(always, false, declaredMethod);
185+
}
186+
for (Constructor<?> declaredConstructor : c.getDeclaredConstructors()) {
187+
jni.register(always, false, declaredConstructor);
188+
}
189+
for (Field declaredField : c.getDeclaredFields()) {
190+
jni.register(always, false, declaredField);
191+
reflection.register(always, false, declaredField);
192+
}
193+
} catch (LinkageError e) {
194+
/* If we can't link we can not register for JNI and reflection */
195+
}
196+
197+
// if we register as unsafe allocated earlier there are build-time
198+
// initialization errors
199+
reflection.register(always, !(c.isArray() || c.isInterface() || c.isPrimitive() || Modifier.isAbstract(c.getModifiers())), c);
200+
});
201+
202+
/*
203+
* We now register super-type methods and fields in a separate pass--when all subtypes have
204+
* been fully registered. We do it the opposite order to avoid crawling the hierarchy
205+
* upwards multiple times when caching is implemented.
206+
*/
207+
classesToPreserve.reversed().forEach(c -> {
208+
reflection.registerAllFields(always, c);
209+
reflection.registerAllMethodsQuery(always, false, c);
210+
// RuntimeSerialization.register(c);
211+
});
212+
213+
for (String className : classLoaderSupport.getClassNamesToPreserve()) {
214+
reflection.registerClassLookup(always, className);
215+
}
216+
}
217+
218+
private static Set<String> ignoredClassesOrPackagesForPreserve() {
219+
Set<String> ignoredClassesOrPackages = new HashSet<>(SubstrateOptions.IgnorePreserveForClasses.getValue().valuesAsSet());
220+
// GR-63360: Parsing of constant_ lambda forms fails
221+
ignoredClassesOrPackages.add("java.lang.invoke.LambdaForm$Holder");
222+
return Collections.unmodifiableSet(ignoredClassesOrPackages);
115223
}
116224
}

substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java

+15
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.lang.reflect.Field;
3131
import java.lang.reflect.InvocationTargetException;
3232
import java.lang.reflect.Method;
33+
import java.util.Objects;
3334

3435
/**
3536
* This class contains utility methods for commonly used reflection functionality. Note that lookups
@@ -238,4 +239,18 @@ public static void writeField(Class<?> declaringClass, String fieldName, Object
238239
public static void writeStaticField(Class<?> declaringClass, String fieldName, Object value) {
239240
writeField(declaringClass, fieldName, null, value);
240241
}
242+
243+
/**
244+
* Counts the number of superclasses as returned by {@link Class#getSuperclass()}.
245+
* {@link java.lang.Object} and all primitive types are at depth 0 and all interfaces are at
246+
* depth 1.
247+
*/
248+
public static int getClassHierarchyDepth(Class<?> clazz) {
249+
Objects.requireNonNull(clazz, "Must accept a non-null class argument");
250+
int depth = 0;
251+
for (var cur = clazz.getSuperclass(); cur != null; cur = cur.getSuperclass()) {
252+
depth += 1;
253+
}
254+
return depth;
255+
}
241256
}

0 commit comments

Comments
 (0)