Skip to content

[GR-62143] Preserve all fields in the preserve mode. #11183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,6 @@
*/
package org.graalvm.nativeimage.impl;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.graalvm.nativeimage.hosted.RuntimeJNIAccess;
import org.graalvm.nativeimage.hosted.RuntimeProxyCreation;

public interface RuntimeReflectionSupport extends ReflectionRegistry {
// needed as reflection-specific ImageSingletons key
void registerAllMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class<?> clazz);
Expand Down Expand Up @@ -75,50 +68,4 @@ public interface RuntimeReflectionSupport extends ReflectionRegistry {

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

default void registerClassFully(ConfigurationCondition condition, Class<?> clazz) {
register(condition, false, clazz);

// GR-62143 Register all fields is very slow.
// registerAllDeclaredFields(condition, clazz);
// registerAllFields(condition, clazz);
registerAllDeclaredMethodsQuery(condition, false, clazz);
registerAllMethodsQuery(condition, false, clazz);
registerAllDeclaredConstructorsQuery(condition, false, clazz);
registerAllConstructorsQuery(condition, false, clazz);
registerAllClassesQuery(condition, clazz);
registerAllDeclaredClassesQuery(condition, clazz);
registerAllNestMembersQuery(condition, clazz);
registerAllPermittedSubclassesQuery(condition, clazz);
registerAllRecordComponentsQuery(condition, clazz);
registerAllSignersQuery(condition, clazz);

/* Register every single-interface proxy */
// GR-62293 can't register proxies from jdk modules.
if (clazz.getModule() == null && clazz.isInterface()) {
RuntimeProxyCreation.register(clazz);
}

RuntimeJNIAccess.register(clazz);
try {
for (Method declaredMethod : clazz.getDeclaredMethods()) {
RuntimeJNIAccess.register(declaredMethod);
}
for (Constructor<?> declaredConstructor : clazz.getDeclaredConstructors()) {
RuntimeJNIAccess.register(declaredConstructor);
}
// GR-62143 Registering all fields is very slow.
// for (Field declaredField : clazz.getDeclaredFields()) {
// RuntimeJNIAccess.register(declaredField);
// RuntimeReflection.register(declaredField);
// }
} catch (LinkageError e) {
/* If we can't link we can not register for JNI */
}

// GR-62143 Registering all fields is very slow.
// RuntimeSerialization.register(clazz);

// if we register unsafe allocated earlier there are build-time initialization errors
register(condition, !(clazz.isArray() || clazz.isInterface() || clazz.isPrimitive() || Modifier.isAbstract(clazz.getModifiers())), clazz);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ public void clearPreserveSelectors() {
preserveAllOrigin = null;
}

public boolean isPreserveMode() {
return !preserveSelectors.classpathEntries.isEmpty() || !preserveSelectors.moduleNames.isEmpty() || !preserveSelectors.packages.isEmpty() || preserveAll;
}

public IncludeSelectors getPreserveSelectors() {
return preserveSelectors;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
Expand Down Expand Up @@ -71,12 +70,9 @@
import org.graalvm.nativeimage.c.struct.RawStructure;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.Feature.OnAnalysisExitAccess;
import org.graalvm.nativeimage.hosted.RuntimeReflection;
import org.graalvm.nativeimage.impl.AnnotationExtractor;
import org.graalvm.nativeimage.impl.CConstantValueSupport;
import org.graalvm.nativeimage.impl.ConfigurationCondition;
import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport;
import org.graalvm.nativeimage.impl.RuntimeReflectionSupport;
import org.graalvm.nativeimage.impl.SizeOfSupport;
import org.graalvm.word.PointerBase;

Expand Down Expand Up @@ -227,6 +223,7 @@
import com.oracle.svm.hosted.image.NativeImageCodeCache;
import com.oracle.svm.hosted.image.NativeImageCodeCacheFactory;
import com.oracle.svm.hosted.image.NativeImageHeap;
import com.oracle.svm.hosted.image.PreserveOptionsSupport;
import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport;
import com.oracle.svm.hosted.imagelayer.LoadImageSingletonFeature;
import com.oracle.svm.hosted.imagelayer.SVMImageLayerLoader;
Expand Down Expand Up @@ -1104,17 +1101,7 @@ protected void setupNativeImage(String imageName, OptionValues options, Map<Meth
new SubstrateClassInitializationPlugin((SVMHost) aUniverse.hostVM()), this.isStubBasedPluginsSupported(), aProviders);

loader.classLoaderSupport.getClassesToIncludeUnconditionally().forEach(cls -> bb.registerTypeForBaseImage(cls));

var runtimeReflection = ImageSingletons.lookup(RuntimeReflectionSupport.class);

Set<String> classesOrPackagesToIgnore = ignoredClassesOrPackagesForPreserve();
loader.classLoaderSupport.getClassesToPreserve().parallel()
.filter(ClassInclusionPolicy::isClassIncludedBase)
.filter(c -> !(classesOrPackagesToIgnore.contains(c.getPackageName()) || classesOrPackagesToIgnore.contains(c.getName())))
.forEach(c -> runtimeReflection.registerClassFully(ConfigurationCondition.alwaysTrue(), c));
for (String className : loader.classLoaderSupport.getClassNamesToPreserve()) {
RuntimeReflection.registerClassLookup(className);
}
PreserveOptionsSupport.registerPreservedClasses(loader.classLoaderSupport);

registerEntryPointStubs(entryPoints);
}
Expand All @@ -1123,13 +1110,6 @@ protected void setupNativeImage(String imageName, OptionValues options, Map<Meth
}
}

private static Set<String> ignoredClassesOrPackagesForPreserve() {
Set<String> ignoredClassesOrPackages = new HashSet<>(SubstrateOptions.IgnorePreserveForClasses.getValue().valuesAsSet());
// GR-63360: Parsing of constant_ lambda forms fails
ignoredClassesOrPackages.add("java.lang.invoke.LambdaForm$Holder");
return Collections.unmodifiableSet(ignoredClassesOrPackages);
}

protected void registerEntryPointStubs(Map<Method, CEntryPointData> entryPoints) {
entryPoints.forEach((method, entryPointData) -> CEntryPointCallStubSupport.singleton().registerStubForMethod(method, () -> entryPointData));
}
Expand Down Expand Up @@ -1785,10 +1765,11 @@ public static void checkName(BigBang bb, AnalysisType type) {
/**
* These are legit elements from the JDK that have hotspot in their name.
*/
private static final Set<String> HOTSPOT_IN_NAME_EXCEPTIONS = Set.of(
private static final Set<String> CHECK_NAMING_EXCEPTIONS = Set.of(
"java.awt.Cursor.DOT_HOTSPOT_SUFFIX",
"sun.lwawt.macosx.CCustomCursor.fHotspot",
"sun.lwawt.macosx.CCustomCursor.getHotSpot()");
"sun.lwawt.macosx.CCustomCursor.getHotSpot()",
"sun.awt.shell.Win32ShellFolder2.ATTRIB_GHOSTED");

private static void checkName(BigBang bb, AnalysisMethod method, String format) {
/*
Expand All @@ -1798,18 +1779,22 @@ private static void checkName(BigBang bb, AnalysisMethod method, String format)
* JDK internal types.
*/
String lformat = format.toLowerCase(Locale.ROOT);
if (lformat.contains("hosted")) {
report(bb, format, method, "Hosted element used at run time: " + format + ".");
} else if (!lformat.startsWith("jdk.internal") && lformat.contains("hotspot")) {
if (!HOTSPOT_IN_NAME_EXCEPTIONS.contains(format)) {
report(bb, format, method, "Element with HotSpot in its name used at run time: " + format + System.lineSeparator() +
"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" +
System.lineSeparator() +
"If this is HotSpot element that was accidentally included find a way to exclude it from the image.");
if (!CHECK_NAMING_EXCEPTIONS.contains(format)) {
if (lformat.contains("hosted")) {
report(bb, format, method, "Hosted element used at run time: " + format + namingConventionsErrorMessageSuffix("hosted"));
} else if (!lformat.startsWith("jdk.internal") && lformat.contains("hotspot")) {
report(bb, format, method, "Element with HotSpot in its name used at run time: " + format + namingConventionsErrorMessageSuffix("HotSpot"));
}
}
}

private static String namingConventionsErrorMessageSuffix(String elementType) {
return """

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
If this is a %s element that was accidentally included, find a way to exclude it from the image.""".formatted(elementType, elementType);
}

private static void report(BigBang bb, String key, AnalysisMethod method, String message) {
if (bb != null) {
bb.getUnsupportedFeatures().addMessage(key, method, message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,36 @@
*/
package com.oracle.svm.hosted.image;

import static com.oracle.graal.pointsto.api.PointstoOptions.UseConservativeUnsafeAccess;
import static com.oracle.svm.core.SubstrateOptions.Preserve;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;

import org.graalvm.collections.EconomicMap;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.impl.ConfigurationCondition;
import org.graalvm.nativeimage.impl.RuntimeJNIAccessSupport;
import org.graalvm.nativeimage.impl.RuntimeProxyCreationSupport;
import org.graalvm.nativeimage.impl.RuntimeReflectionSupport;

import com.oracle.graal.pointsto.ClassInclusionPolicy;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue;
import com.oracle.svm.core.option.LocatableMultiOptionValue;
import com.oracle.svm.core.option.SubstrateOptionsParser;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.hosted.NativeImageClassLoaderSupport;
import com.oracle.svm.hosted.driver.IncludeOptionsSupport;
import com.oracle.svm.util.ReflectionUtil;

import jdk.graal.compiler.options.OptionKey;
import jdk.graal.compiler.options.OptionValues;
Expand Down Expand Up @@ -93,7 +110,8 @@ private static String preservePossibleOptions() {
}

public static void parsePreserveOption(EconomicMap<OptionKey<?>, Object> hostedValues, NativeImageClassLoaderSupport classLoaderSupport) {
AccumulatingLocatableMultiOptionValue.Strings preserve = SubstrateOptions.Preserve.getValue(new OptionValues(hostedValues));
OptionValues optionValues = new OptionValues(hostedValues);
AccumulatingLocatableMultiOptionValue.Strings preserve = SubstrateOptions.Preserve.getValue(optionValues);
Stream<LocatableMultiOptionValue.ValueWithOrigin<String>> valuesWithOrigins = preserve.getValuesWithOrigins();
valuesWithOrigins.forEach(valueWithOrigin -> {
String optionArgument = SubstrateOptionsParser.commandArgument(SubstrateOptions.Preserve, valueWithOrigin.value(), true, false);
Expand All @@ -112,5 +130,95 @@ public static void parsePreserveOption(EconomicMap<OptionKey<?>, Object> hostedV
}
}
});
if (classLoaderSupport.isPreserveMode()) {
if (UseConservativeUnsafeAccess.hasBeenSet(optionValues)) {
UserError.guarantee(UseConservativeUnsafeAccess.getValue(optionValues), "%s can not be used together with %s. Please unset %s.",
SubstrateOptionsParser.commandArgument(UseConservativeUnsafeAccess, "-"),
SubstrateOptionsParser.commandArgument(Preserve, "<value>"),
SubstrateOptionsParser.commandArgument(UseConservativeUnsafeAccess, "-"));
}
UseConservativeUnsafeAccess.update(hostedValues, true);
}
}

public static void registerPreservedClasses(NativeImageClassLoaderSupport classLoaderSupport) {
var classesOrPackagesToIgnore = ignoredClassesOrPackagesForPreserve();
var classesToPreserve = classLoaderSupport.getClassesToPreserve()
.filter(ClassInclusionPolicy::isClassIncludedBase)
.filter(c -> !(classesOrPackagesToIgnore.contains(c.getPackageName()) || classesOrPackagesToIgnore.contains(c.getName())))
.sorted(Comparator.comparing(ReflectionUtil::getClassHierarchyDepth).reversed())
.toList();

final RuntimeReflectionSupport reflection = ImageSingletons.lookup(RuntimeReflectionSupport.class);
final RuntimeJNIAccessSupport jni = ImageSingletons.lookup(RuntimeJNIAccessSupport.class);
final RuntimeProxyCreationSupport proxy = ImageSingletons.lookup(RuntimeProxyCreationSupport.class);
final ConfigurationCondition always = ConfigurationCondition.alwaysTrue();

/*
* Sort descending by class hierarchy depth to avoid complexity related to field
* registration.
*/
classesToPreserve.forEach(c -> {
reflection.register(always, false, c);

reflection.registerAllDeclaredFields(always, c);
reflection.registerAllDeclaredMethodsQuery(always, false, c);
reflection.registerAllDeclaredConstructorsQuery(always, false, c);
reflection.registerAllConstructorsQuery(always, false, c);
reflection.registerAllClassesQuery(always, c);
reflection.registerAllDeclaredClassesQuery(always, c);
reflection.registerAllNestMembersQuery(always, c);
reflection.registerAllPermittedSubclassesQuery(always, c);
reflection.registerAllRecordComponentsQuery(always, c);
reflection.registerAllSignersQuery(always, c);

/* Register every single-interface proxy */
// GR-62293 can't register proxies from jdk modules.
if (c.getModule() == null && c.isInterface()) {
proxy.addProxyClass(always, c);
}

jni.register(ConfigurationCondition.alwaysTrue(), c);
try {
for (Method declaredMethod : c.getDeclaredMethods()) {
jni.register(always, false, declaredMethod);
}
for (Constructor<?> declaredConstructor : c.getDeclaredConstructors()) {
jni.register(always, false, declaredConstructor);
}
for (Field declaredField : c.getDeclaredFields()) {
jni.register(always, false, declaredField);
reflection.register(always, false, declaredField);
}
} catch (LinkageError e) {
/* If we can't link we can not register for JNI and reflection */
}

// if we register as unsafe allocated earlier there are build-time
// initialization errors
reflection.register(always, !(c.isArray() || c.isInterface() || c.isPrimitive() || Modifier.isAbstract(c.getModifiers())), c);
});

/*
* We now register super-type methods and fields in a separate pass--when all subtypes have
* been fully registered. We do it the opposite order to avoid crawling the hierarchy
* upwards multiple times when caching is implemented.
*/
classesToPreserve.reversed().forEach(c -> {
reflection.registerAllFields(always, c);
reflection.registerAllMethodsQuery(always, false, c);
// RuntimeSerialization.register(c);
});

for (String className : classLoaderSupport.getClassNamesToPreserve()) {
reflection.registerClassLookup(always, className);
}
}

private static Set<String> ignoredClassesOrPackagesForPreserve() {
Set<String> ignoredClassesOrPackages = new HashSet<>(SubstrateOptions.IgnorePreserveForClasses.getValue().valuesAsSet());
// GR-63360: Parsing of constant_ lambda forms fails
ignoredClassesOrPackages.add("java.lang.invoke.LambdaForm$Holder");
return Collections.unmodifiableSet(ignoredClassesOrPackages);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;

/**
* This class contains utility methods for commonly used reflection functionality. Note that lookups
Expand Down Expand Up @@ -238,4 +239,18 @@ public static void writeField(Class<?> declaringClass, String fieldName, Object
public static void writeStaticField(Class<?> declaringClass, String fieldName, Object value) {
writeField(declaringClass, fieldName, null, value);
}

/**
* Counts the number of superclasses as returned by {@link Class#getSuperclass()}.
* {@link java.lang.Object} and all primitive types are at depth 0 and all interfaces are at
* depth 1.
*/
public static int getClassHierarchyDepth(Class<?> clazz) {
Objects.requireNonNull(clazz, "Must accept a non-null class argument");
int depth = 0;
for (var cur = clazz.getSuperclass(); cur != null; cur = cur.getSuperclass()) {
depth += 1;
}
return depth;
}
}
Loading