From 56ea7651eb546d47d9a72c31da8b88a918e93c76 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 6 May 2024 18:37:46 +0200 Subject: [PATCH 1/3] Adding typeReached conditional for reflection --- .../core/code/RuntimeMetadataDecoderImpl.java | 36 +++- .../configure/ConditionalRuntimeValue.java | 45 ++--- .../core/configure/ConfigurationFiles.java | 9 +- .../core/configure/ConfigurationParser.java | 16 +- .../ConfigurationTypeDescriptor.java | 2 + .../NamedConfigurationTypeDescriptor.java | 13 +- .../configure/ProxyConfigurationParser.java | 2 +- .../ProxyConfigurationTypeDescriptor.java | 5 + .../ReflectionConfigurationParser.java | 40 ++-- .../ResourceConfigurationParser.java | 4 +- .../svm/core/configure/RuntimeCondition.java | 76 ++++++++ .../core/configure/RuntimeConditionSet.java | 173 ++++++++++++++++++ .../SerializationConfigurationParser.java | 11 +- .../core/configure/TypeReachedCondition.java | 75 ++++++++ .../svm/core/hub/ClassForNameSupport.java | 102 ++++++----- .../com/oracle/svm/core/hub/DynamicHub.java | 13 +- .../Target_java_lang_invoke_MethodHandle.java | 7 +- .../MissingReflectionRegistrationUtils.java | 2 +- .../target/ReflectionObjectFactory.java | 21 ++- ...et_java_lang_reflect_AccessibleObject.java | 14 ++ .../Target_java_lang_reflect_Method.java | 22 ++- ...arget_java_lang_reflect_ReflectAccess.java | 2 + ...t_jdk_internal_misc_Unsafe_Reflection.java | 3 +- .../ConditionalConfigurationRegistry.java | 5 +- .../ClassInitializationSupport.java | 6 + .../code/ReflectionRuntimeMetadata.java | 87 +++++---- .../code/RuntimeMetadataEncoderImpl.java | 62 +++++-- .../hosted/image/NativeImageCodeCache.java | 17 +- .../hosted/reflect/ReflectionDataBuilder.java | 110 +++++++---- .../svm/hosted/reflect/ReflectionFeature.java | 7 +- .../reflect/ReflectionHostedSupport.java | 5 +- 31 files changed, 751 insertions(+), 241 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeCondition.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/TypeReachedCondition.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeMetadataDecoderImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeMetadataDecoderImpl.java index 3b149c027137..9b264865ddbf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeMetadataDecoderImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeMetadataDecoderImpl.java @@ -33,6 +33,8 @@ import java.lang.reflect.Parameter; import java.lang.reflect.RecordComponent; import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import java.util.function.Function; import org.graalvm.nativeimage.ImageSingletons; @@ -41,6 +43,8 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.c.NonmovableArrays; +import com.oracle.svm.core.configure.RuntimeCondition; +import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.reflect.RuntimeMetadataDecoder; @@ -320,6 +324,8 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class declaringC int modifiers = buf.getUVInt(); boolean inHeap = (modifiers & IN_HEAP_FLAG_MASK) != 0; boolean complete = (modifiers & COMPLETE_FLAG_MASK) != 0; + + RuntimeConditionSet conditions = decodeConditions(buf); if (inHeap) { Field field = (Field) decodeObject(buf); if (publicOnly && !Modifier.isPublic(field.getModifiers())) { @@ -327,7 +333,7 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class declaringC * Generate negative copy of the field. Finding a non-public field when looking for * a public one should not result in a missing registration exception. */ - return ReflectionObjectFactory.newField(declaringClass, field.getName(), Object.class, field.getModifiers() | NEGATIVE_FLAG_MASK, false, + return ReflectionObjectFactory.newField(conditions, declaringClass, field.getName(), Object.class, field.getModifiers() | NEGATIVE_FLAG_MASK, false, null, null, ReflectionObjectFactory.FIELD_OFFSET_NONE, null, null); } if (reflectOnly) { @@ -356,7 +362,8 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class declaringC if (!reflectOnly) { return new FieldDescriptor(declaringClass, name); } - return ReflectionObjectFactory.newField(declaringClass, name, negative ? Object.class : type, modifiers, false, null, null, ReflectionObjectFactory.FIELD_OFFSET_NONE, null, null); + return ReflectionObjectFactory.newField(conditions, declaringClass, name, negative ? Object.class : type, modifiers, false, null, null, ReflectionObjectFactory.FIELD_OFFSET_NONE, null, + null); } boolean trustedFinal = buf.getU1() == 1; String signature = decodeOtherString(buf); @@ -368,10 +375,19 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class declaringC modifiers |= NEGATIVE_FLAG_MASK; } - Field reflectField = ReflectionObjectFactory.newField(declaringClass, name, type, modifiers, trustedFinal, signature, annotations, offset, deletedReason, typeAnnotations); + Field reflectField = ReflectionObjectFactory.newField(conditions, declaringClass, name, type, modifiers, trustedFinal, signature, annotations, offset, deletedReason, typeAnnotations); return reflectOnly ? reflectField : new FieldDescriptor(reflectField); } + private static RuntimeConditionSet decodeConditions(UnsafeArrayTypeReader buf) { + var conditionTypes = decodeArray(buf, Class.class, i -> decodeType(buf)); + Set runtimeConditions = new HashSet<>(conditionTypes.length); + for (Class conditionType : conditionTypes) { + runtimeConditions.add(RuntimeCondition.createTypeReachedCondition(conditionType)); + } + return RuntimeConditionSet.createRuntime(runtimeConditions); + } + /** * Complete method encoding. * @@ -479,6 +495,7 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class decla int modifiers = buf.getUVInt(); boolean inHeap = (modifiers & IN_HEAP_FLAG_MASK) != 0; boolean complete = (modifiers & COMPLETE_FLAG_MASK) != 0; + RuntimeConditionSet conditions = decodeConditions(buf); if (inHeap) { Executable executable = (Executable) decodeObject(buf); if (publicOnly && !Modifier.isPublic(executable.getModifiers())) { @@ -487,10 +504,11 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class decla * looking for a public one should not result in a missing registration exception. */ if (isMethod) { - executable = ReflectionObjectFactory.newMethod(declaringClass, executable.getName(), executable.getParameterTypes(), Object.class, null, modifiers | NEGATIVE_FLAG_MASK, + executable = ReflectionObjectFactory.newMethod(conditions, declaringClass, executable.getName(), executable.getParameterTypes(), Object.class, null, modifiers | NEGATIVE_FLAG_MASK, null, null, null, null, null, null, null); } else { - executable = ReflectionObjectFactory.newConstructor(declaringClass, executable.getParameterTypes(), null, modifiers | NEGATIVE_FLAG_MASK, null, null, null, null, null, null); + executable = ReflectionObjectFactory.newConstructor(conditions, declaringClass, executable.getParameterTypes(), null, modifiers | NEGATIVE_FLAG_MASK, null, null, null, null, null, + null); } } if (reflectOnly) { @@ -532,13 +550,13 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class decla if (!reflectOnly) { return new MethodDescriptor(declaringClass, name, (String[]) parameterTypes); } - return ReflectionObjectFactory.newMethod(declaringClass, name, (Class[]) parameterTypes, negative ? Object.class : returnType, null, modifiers, + return ReflectionObjectFactory.newMethod(conditions, declaringClass, name, (Class[]) parameterTypes, negative ? Object.class : returnType, null, modifiers, null, null, null, null, null, null, null); } else { if (!reflectOnly) { return new ConstructorDescriptor(declaringClass, (String[]) parameterTypes); } - return ReflectionObjectFactory.newConstructor(declaringClass, (Class[]) parameterTypes, null, modifiers, null, null, null, null, null, null); + return ReflectionObjectFactory.newConstructor(conditions, declaringClass, (Class[]) parameterTypes, null, modifiers, null, null, null, null, null, null); } } Class[] exceptionTypes = decodeArray(buf, Class.class, (i) -> decodeType(buf)); @@ -555,14 +573,14 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class decla Target_java_lang_reflect_Executable executable; if (isMethod) { - Method method = ReflectionObjectFactory.newMethod(declaringClass, name, (Class[]) parameterTypes, returnType, exceptionTypes, modifiers, + Method method = ReflectionObjectFactory.newMethod(conditions, declaringClass, name, (Class[]) parameterTypes, returnType, exceptionTypes, modifiers, signature, annotations, parameterAnnotations, annotationDefault, accessor, reflectParameters, typeAnnotations); if (!reflectOnly) { return new MethodDescriptor(method); } executable = SubstrateUtil.cast(method, Target_java_lang_reflect_Executable.class); } else { - Constructor constructor = ReflectionObjectFactory.newConstructor(declaringClass, (Class[]) parameterTypes, exceptionTypes, + Constructor constructor = ReflectionObjectFactory.newConstructor(conditions, declaringClass, (Class[]) parameterTypes, exceptionTypes, modifiers, signature, annotations, parameterAnnotations, accessor, reflectParameters, typeAnnotations); if (!reflectOnly) { return new ConstructorDescriptor(constructor); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java index b9f080d84140..d78533653924 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java @@ -24,14 +24,9 @@ */ package com.oracle.svm.core.configure; -import java.util.Set; -import java.util.function.Predicate; - import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import com.oracle.svm.core.util.VMError; - /** * A image-heap stored {@link ConditionalRuntimeValue#value} that is guarded by run-time computed * {@link ConditionalRuntimeValue#conditions}. @@ -42,20 +37,11 @@ * @param type of the stored value. */ public final class ConditionalRuntimeValue { - private final Class[] conditions; - private boolean satisfied; + RuntimeConditionSet conditions; volatile T value; - @Platforms(Platform.HOSTED_ONLY.class) - public ConditionalRuntimeValue(Set> conditions, T value) { - if (!conditions.isEmpty()) { - this.conditions = conditions.toArray(Class[]::new); - } else { - this.conditions = null; - satisfied = true; - } - - VMError.guarantee(conditions.stream().noneMatch(c -> c.equals(Object.class)), "java.lang.Object must not be in conditions as it is always true."); + public ConditionalRuntimeValue(RuntimeConditionSet conditions, T value) { + this.conditions = conditions; this.value = value; } @@ -64,25 +50,20 @@ public T getValueUnconditionally() { return value; } - @Platforms(Platform.HOSTED_ONLY.class) - public Set> getConditions() { - return conditions == null ? Set.of() : Set.of(conditions); + public RuntimeConditionSet getConditions() { + return conditions; } - public T getValue(Predicate> conditionSatisfied) { - if (satisfied) { + public T getValue() { + if (conditions.satisfied()) { return value; } else { - for (Class element : conditions) { - if (conditionSatisfied.test(element)) { - satisfied = true; - break; - } - } - if (satisfied) { - return value; - } + return null; } - return null; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void updateValue(T newValue) { + this.value = newValue; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java index c6b7d5aeedc7..9e6d1cd313ad 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java @@ -126,10 +126,17 @@ public static final class Options { public static final HostedOptionKey StrictConfiguration = new HostedOptionKey<>(false); @Option(help = "Testing flag: the typeReachable condition is treated as typeReached so the semantics of programs can change.")// - public static final HostedOptionKey TreatAllReachableConditionsAsReached = new HostedOptionKey<>(false); + public static final HostedOptionKey TreatAllTypeReachableConditionsAsTypeReached = new HostedOptionKey<>(false); + + @Option(help = "Testing flag: the typeReached condition is always satisfied however it prints the stack traces where it would not be satisfied.")// + public static final HostedOptionKey TrackUnsatisfiedTypeReachedConditions = new HostedOptionKey<>(false); + + @Option(help = "Testing flag: print typeReached conditions that are used on interfaces.")// + public static final HostedOptionKey TrackTypeReachedOnInterfaces = new HostedOptionKey<>(false); @Option(help = "Testing flag: every type is considered as it participates in a typeReachable condition.")// public static final HostedOptionKey TreatAllUserSpaceTypesAsTrackedForTypeReached = new HostedOptionKey<>(false); + @Option(help = "Warn when reflection and JNI configuration files have elements that could not be found on the classpath or modulepath.", type = OptionType.Expert)// public static final HostedOptionKey WarnAboutMissingReflectionOrJNIMetadataElements = new HostedOptionKey<>(false); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java index 3f92724cb8ec..e62e0a263913 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java @@ -24,7 +24,7 @@ */ package com.oracle.svm.core.configure; -import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllReachableConditionsAsReached; +import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllTypeReachableConditionsAsTypeReached; import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHABLE_KEY; import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHED_KEY; @@ -205,7 +205,7 @@ protected static long asLong(Object value, String propertyName) { throw new JSONParserException("Invalid long value '" + value + "' for element '" + propertyName + "'"); } - protected UnresolvedConfigurationCondition parseCondition(EconomicMap data) { + protected UnresolvedConfigurationCondition parseCondition(EconomicMap data, boolean runtimeCondition) { Object conditionData = data.get(CONDITIONAL_KEY); if (conditionData != null) { EconomicMap conditionObject = asMap(conditionData, "Attribute 'condition' must be an object"); @@ -214,6 +214,9 @@ protected UnresolvedConfigurationCondition parseCondition(EconomicMap parseType(EconomicMap data) { + protected static Optional parseTypeOrName(EconomicMap data) { Object typeObject = data.get(TYPE_KEY); Object name = data.get(NAME_KEY); if (typeObject != null) { @@ -250,7 +256,7 @@ protected static Optional parseType(EconomicMap parseTypeContents(Object typeObject) { if (typeObject instanceof String stringValue) { - return Optional.of(new NamedConfigurationTypeDescriptor(stringValue)); + return Optional.of(new NamedConfigurationTypeDescriptor(stringValue, true)); } else { EconomicMap type = asMap(typeObject, "type descriptor should be a string or object"); if (type.containsKey(PROXY_KEY)) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java index 2709a86d81bf..1793ba296201 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java @@ -72,4 +72,6 @@ static String checkQualifiedJavaName(String javaName) { assert javaName.indexOf('/') == -1 || javaName.indexOf('/') > javaName.lastIndexOf('.') : "Requires qualified Java name, not internal representation: %s".formatted(javaName); return canonicalizeTypeName(javaName); } + + boolean isType(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java index 86b33152bf47..31137cae8876 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java @@ -30,10 +30,20 @@ import com.oracle.svm.core.util.json.JsonWriter; -public record NamedConfigurationTypeDescriptor(String name) implements ConfigurationTypeDescriptor { +public record NamedConfigurationTypeDescriptor(String name, boolean type) implements ConfigurationTypeDescriptor { public NamedConfigurationTypeDescriptor(String name) { + this(name, false); + } + + public NamedConfigurationTypeDescriptor(String name, boolean type) { this.name = ConfigurationTypeDescriptor.checkQualifiedJavaName(name); + this.type = type; + } + + @Override + public boolean isType() { + return type; } @Override @@ -64,4 +74,5 @@ public int compareTo(ConfigurationTypeDescriptor other) { public void printJson(JsonWriter writer) throws IOException { writer.quote(name); } + } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationParser.java index 48a268311fac..f1b3f50d4594 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationParser.java @@ -90,7 +90,7 @@ private void parseInterfaceList(C condition, List data) { private void parseWithConditionalConfig(EconomicMap proxyConfigObject) { checkAttributes(proxyConfigObject, "proxy descriptor object", Collections.singleton("interfaces"), Collections.singletonList(CONDITIONAL_KEY)); - UnresolvedConfigurationCondition condition = parseCondition(proxyConfigObject); + UnresolvedConfigurationCondition condition = parseCondition(proxyConfigObject, false); TypeResult resolvedCondition = conditionResolver.resolveCondition(condition); if (resolvedCondition.isPresent()) { parseInterfaceList(resolvedCondition.get(), asList(proxyConfigObject.get("interfaces"), "The interfaces property must be an array of fully qualified interface names")); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java index 0d56fa6cff63..72da3452124c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java @@ -64,6 +64,11 @@ public int compareTo(ConfigurationTypeDescriptor other) { } } + @Override + public boolean isType() { + return true; + } + @Override public void printJson(JsonWriter writer) throws IOException { writer.append("{").indent().newline(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java index 1cc31be00d3e..8dcaec3870d4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java @@ -34,6 +34,7 @@ import org.graalvm.collections.EconomicMap; import org.graalvm.collections.MapCursor; +import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; import com.oracle.svm.core.TypeResult; import com.oracle.svm.util.LogUtils; @@ -79,19 +80,15 @@ private void parseClass(EconomicMap data) { checkAttributes(data, "reflection class descriptor object", Collections.emptyList(), OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS); checkHasExactlyOneAttribute(data, "reflection class descriptor object", List.of("name", "type")); - TypeResult conditionResult = conditionResolver.resolveCondition(parseCondition(data)); - if (!conditionResult.isPresent()) { + Optional type = parseTypeOrName(data); + if (type.isEmpty()) { return; } - C condition = conditionResult.get(); + boolean isType = type.get().isType(); - /* - * Classes registered using the old ("class") syntax will require elements (fields, methods, - * constructors, ...) to be registered for runtime queries, whereas the new ("type") syntax - * will automatically register all elements as queried. - */ - Optional type = parseType(data); - if (type.isEmpty()) { + UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, isType); + TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); + if (!conditionResult.isPresent()) { return; } @@ -99,11 +96,14 @@ private void parseClass(EconomicMap data) { * Even if primitives cannot be queried through Class.forName, they can be registered to * allow getDeclaredMethods() and similar bulk queries at run time. */ + C condition = conditionResult.get(); TypeResult result = delegate.resolveType(condition, type.get(), true, false); if (!result.isPresent()) { handleMissingElement("Could not resolve " + type.get() + " for reflection configuration.", result.getException()); return; } + + C queryCondition = isType ? conditionResolver.alwaysTrue() : condition; T clazz = result.get(); delegate.registerType(conditionResult.get(), clazz); @@ -145,52 +145,52 @@ private void parseClass(EconomicMap data) { break; case "allDeclaredClasses": if (asBoolean(value, "allDeclaredClasses")) { - delegate.registerDeclaredClasses(condition, clazz); + delegate.registerDeclaredClasses(queryCondition, clazz); } break; case "allRecordComponents": if (asBoolean(value, "allRecordComponents")) { - delegate.registerRecordComponents(condition, clazz); + delegate.registerRecordComponents(queryCondition, clazz); } break; case "allPermittedSubclasses": if (asBoolean(value, "allPermittedSubclasses")) { - delegate.registerPermittedSubclasses(condition, clazz); + delegate.registerPermittedSubclasses(queryCondition, clazz); } break; case "allNestMembers": if (asBoolean(value, "allNestMembers")) { - delegate.registerNestMembers(condition, clazz); + delegate.registerNestMembers(queryCondition, clazz); } break; case "allSigners": if (asBoolean(value, "allSigners")) { - delegate.registerSigners(condition, clazz); + delegate.registerSigners(queryCondition, clazz); } break; case "allPublicClasses": if (asBoolean(value, "allPublicClasses")) { - delegate.registerPublicClasses(condition, clazz); + delegate.registerPublicClasses(queryCondition, clazz); } break; case "queryAllDeclaredConstructors": if (asBoolean(value, "queryAllDeclaredConstructors")) { - delegate.registerDeclaredConstructors(condition, true, clazz); + delegate.registerDeclaredConstructors(queryCondition, true, clazz); } break; case "queryAllPublicConstructors": if (asBoolean(value, "queryAllPublicConstructors")) { - delegate.registerPublicConstructors(condition, true, clazz); + delegate.registerPublicConstructors(queryCondition, true, clazz); } break; case "queryAllDeclaredMethods": if (asBoolean(value, "queryAllDeclaredMethods")) { - delegate.registerDeclaredMethods(condition, true, clazz); + delegate.registerDeclaredMethods(queryCondition, true, clazz); } break; case "queryAllPublicMethods": if (asBoolean(value, "queryAllPublicMethods")) { - delegate.registerPublicMethods(condition, true, clazz); + delegate.registerPublicMethods(queryCondition, true, clazz); } break; case "unsafeAllocated": diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java index e2d037314212..01b9afcb58fc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java @@ -105,7 +105,7 @@ private void parseBundle(Object bundle) { EconomicMap resource = asMap(bundle, "Elements of 'bundles' list must be a bundle descriptor object"); checkAttributes(resource, "bundle descriptor object", Collections.singletonList("name"), Arrays.asList("locales", "classNames", "condition")); String basename = asString(resource.get("name")); - TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(resource)); + TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(resource, false)); if (!resolvedConfigurationCondition.isPresent()) { return; } @@ -146,7 +146,7 @@ private static Locale parseLocale(Object input) { private void parseStringEntry(Object data, String valueKey, BiConsumer resourceRegistry, String expectedType, String parentType) { EconomicMap resource = asMap(data, "Elements of " + parentType + " must be a " + expectedType); checkAttributes(resource, "resource and resource bundle descriptor object", Collections.singletonList(valueKey), Collections.singletonList(CONDITIONAL_KEY)); - TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(resource)); + TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(resource, false)); if (!resolvedConfigurationCondition.isPresent()) { return; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeCondition.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeCondition.java new file mode 100644 index 000000000000..e4a757cd7d15 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeCondition.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.util.Set; +import java.util.WeakHashMap; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.core.util.VMError; + +/** + * Represents a super-type for all metadata conditions that are checked at runtime. It is created + * from {@link ConfigurationCondition}s that represent the build-time conditions. + *

+ * {@link RuntimeCondition}s can be stored into the heap or encoded into the image for space + * reduction ({@link #getTypesForEncoding()}. All conditions are cached in the + * {@link #runtimeConditionCache} to save space in the image heap and hence they must implement + * {@link Object#equals(Object)} and {@link Object#hashCode()}. {@link RuntimeCondition} is most + * often used in groups that are stored in {@link RuntimeConditionSet}. + */ +public sealed interface RuntimeCondition permits TypeReachedCondition { + + WeakHashMap runtimeConditionCache = new WeakHashMap<>(); + + static RuntimeCondition create(ConfigurationCondition cnd) { + if (cnd.isAlwaysTrue() || !cnd.isRuntimeChecked()) { + throw VMError.shouldNotReachHere("We should never create run-time conditions from conditions that are always true at build time. Condition: " + cnd); + } + return createTypeReachedCondition(cnd.getType()); + } + + static RuntimeCondition createTypeReachedCondition(Class type) { + TypeReachedCondition typeReachedCondition = new TypeReachedCondition(type); + synchronized (runtimeConditionCache) { + if (runtimeConditionCache.containsKey(typeReachedCondition)) { + return runtimeConditionCache.get(typeReachedCondition); + } else { + runtimeConditionCache.put(typeReachedCondition, typeReachedCondition); + return typeReachedCondition; + } + } + } + + /** + * @return true if the condition has been satisfied at run time. + */ + boolean isSatisfied(); + + @Platforms(Platform.HOSTED_ONLY.class) + Set> getTypesForEncoding(); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java new file mode 100644 index 000000000000..67c7b8b3a3f5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.configure; + +import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TrackUnsatisfiedTypeReachedConditions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.LogUtils; + +/** + * Represents a group of {@link RuntimeCondition}s that guard a value. + *

+ * If any of the {@link #conditions} is satisfied then the whole set becomes also + * {@link #satisfied}. {@link RuntimeConditionSet}s can be created at build time + * {@link #createHosted(ConfigurationCondition...)} and stored to the image heap, or it can be + * encoded ({@link #getTypesForEncoding()} and later decoded at run time + * ({@link #createRuntime(Set)}. The current implementation does not cache {@link #conditions}, + * although this will be implemented in the future (GR-49526) + */ +public class RuntimeConditionSet { + + private RuntimeCondition[] conditions; + private boolean satisfied; + + @Platforms(Platform.HOSTED_ONLY.class) + public static RuntimeConditionSet createHosted(ConfigurationCondition... conditions) { + var conditionSet = new RuntimeConditionSet(Set.of()); + for (ConfigurationCondition condition : conditions) { + conditionSet.addCondition(condition); + } + return conditionSet; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static RuntimeConditionSet unmodifiableEmptySet() { + return UnmodifiableRuntimeConditionSet.UNMODIFIABLE_EMPTY_SET; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public synchronized void addCondition(ConfigurationCondition cnd) { + VMError.guarantee(cnd.isRuntimeChecked(), "Only runtime conditions can be added to the ConditionalRuntimeValue."); + if (satisfied) { + return; + } else if (cnd.isAlwaysTrue()) { + conditions = null; + satisfied = true; + return; + } + + RuntimeCondition newRuntimeCondition = RuntimeCondition.create(cnd); + Stream existingConditions = conditions == null ? Stream.empty() : Arrays.stream(conditions); + setConditions(Stream.concat(existingConditions, Stream.of(newRuntimeCondition)) + .collect(Collectors.toSet())); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public Set> getTypesForEncoding() { + if (conditions == null) { + return Set.of(); + } else { + Set> types = new HashSet<>(); + for (RuntimeCondition condition : conditions) { + types.addAll(condition.getTypesForEncoding()); + } + return types; + } + } + + public static RuntimeConditionSet createRuntime(Set conditions) { + return new RuntimeConditionSet(conditions); + } + + private RuntimeConditionSet(Set conditions) { + setConditions(conditions); + } + + private void setConditions(Set conditions) { + if (conditions.isEmpty()) { + this.conditions = null; + } else { + this.conditions = conditions.toArray(RuntimeCondition[]::new); + } + satisfied = false; + } + + /** + * Checks if any of the conditions has been satisfied. It caches the value in satisfied. This + * code can be concurrently executed, however there are no concurrency primitives used. The + * implementation relies on the fact that checking if a condition is satisfied is an idempotent + * operation. + * + * @return true if any of the elements is satisfied. + */ + public boolean satisfied() { + var result = false; + if (satisfied) { + result = true; + } else { + final var localConditions = conditions; + if (localConditions == null) { + result = true; + } else { + for (RuntimeCondition condition : localConditions) { + if (condition.isSatisfied()) { + conditions = null; + satisfied = result = true; + break; + } + } + } + } + + if (TrackUnsatisfiedTypeReachedConditions.getValue() && !result) { + LogUtils.info("Unsatisfied runtime conditions reachable at build-time: " + Arrays.toString(conditions)); + new Exception().printStackTrace(System.out); + return true; + } + + return result; + } + + @Override + public String toString() { + String conditionsString = this.conditions == null ? "[]" : Arrays.toString(this.conditions); + return conditionsString + " = " + satisfied; + } + + public static final class UnmodifiableRuntimeConditionSet extends RuntimeConditionSet { + private static final RuntimeConditionSet UNMODIFIABLE_EMPTY_SET = new UnmodifiableRuntimeConditionSet(Set.of()); + + private UnmodifiableRuntimeConditionSet(Set conditions) { + super(conditions); + } + + @Override + public synchronized void addCondition(ConfigurationCondition cnd) { + throw new UnsupportedOperationException("Can't add conditions to an unmodifiable set of conditions."); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java index 5bfca8dc53a0..c987d657382e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java @@ -100,15 +100,14 @@ private void parseSerializationDescriptorObject(EconomicMap data checkHasExactlyOneAttribute(data, "serialization descriptor object", List.of(TYPE_KEY, NAME_KEY)); } - UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data); - var condition = conditionResolver.resolveCondition(unresolvedCondition); - if (!condition.isPresent()) { + Optional targetSerializationClass = parseTypeOrName(data); + if (targetSerializationClass.isEmpty()) { return; } - Optional targetSerializationClass; - targetSerializationClass = parseType(data); - if (targetSerializationClass.isEmpty()) { + UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, targetSerializationClass.get().isType()); + var condition = conditionResolver.resolveCondition(unresolvedCondition); + if (!condition.isPresent()) { return; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/TypeReachedCondition.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/TypeReachedCondition.java new file mode 100644 index 000000000000..2242db7ec436 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/TypeReachedCondition.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.configure; + +import java.util.Objects; +import java.util.Set; + +import com.oracle.svm.core.hub.DynamicHub; + +/** + * This condition allows a runtime value to be access if {@link #type} is reached at run time. + */ +public final class TypeReachedCondition implements RuntimeCondition { + final Class type; + + TypeReachedCondition(Class type) { + this.type = type; + } + + @Override + public boolean isSatisfied() { + return DynamicHub.fromClass(type).isReached(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TypeReachedCondition that = (TypeReachedCondition) o; + return Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hashCode(type); + } + + @Override + public Set> getTypesForEncoding() { + return Set.of(type); + } + + @Override + public String toString() { + return type.getName(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java index b3e1566495a7..1910250a15d0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java @@ -26,9 +26,8 @@ import static com.oracle.svm.core.MissingRegistrationUtils.throwMissingRegistrationErrors; +import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; @@ -37,6 +36,7 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.core.configure.ConditionalRuntimeValue; +import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import com.oracle.svm.core.util.ImageHeapMap; @@ -65,54 +65,50 @@ public void registerClass(ConfigurationCondition condition, Class clazz) { if (PredefinedClassesSupport.isPredefined(clazz)) { return; // must be defined at runtime before it can be looked up } - String name = clazz.getName(); - ConditionalRuntimeValue exisingEntry = knownClasses.get(name); - Object currentValue = exisingEntry == null ? null : exisingEntry.getValueUnconditionally(); - - if (currentValue == null || // never seen - currentValue == NEGATIVE_QUERY || - currentValue == clazz) { - currentValue = clazz; - var cond = updateConditionalValue(exisingEntry, currentValue, condition); - knownClasses.put(name, cond); - } else if (currentValue instanceof Throwable) { // failed at linking time - var cond = updateConditionalValue(exisingEntry, currentValue, condition); - /* - * If the class has already been seen as throwing an error, we don't overwrite this - * error. Nevertheless, we have to update the set of conditionals to be correct. - */ - knownClasses.put(name, cond); - } else { - throw VMError.shouldNotReachHere(""" - Invalid Class.forName value for %s: %s - If the class is already registered as negative, it means that it exists but is not - accessible through the builder class loader, and it was already registered by name (as - negative query) before this point. In that case, we update the map to contain the actual - class. - """, name, currentValue); + synchronized (knownClasses) { + String name = clazz.getName(); + ConditionalRuntimeValue exisingEntry = knownClasses.get(name); + Object currentValue = exisingEntry == null ? null : exisingEntry.getValueUnconditionally(); + + if (currentValue == null || // never seen + currentValue == NEGATIVE_QUERY || + currentValue == clazz) { + currentValue = clazz; + var cond = updateConditionalValue(exisingEntry, currentValue, condition); + knownClasses.put(name, cond); + } else if (currentValue instanceof Throwable) { // failed at linking time + var cond = updateConditionalValue(exisingEntry, currentValue, condition); + /* + * If the class has already been seen as throwing an error, we don't overwrite this + * error. Nevertheless, we have to update the set of conditionals to be correct. + */ + knownClasses.put(name, cond); + } else { + throw VMError.shouldNotReachHere(""" + Invalid Class.forName value for %s: %s + If the class is already registered as negative, it means that it exists but is not + accessible through the builder class loader, and it was already registered by name (as + negative query) before this point. In that case, we update the map to contain the actual + class. + """, name, currentValue); + } } } - private static ConditionalRuntimeValue updateConditionalValue(ConditionalRuntimeValue existingConditionalValue, Object newValue, + public static ConditionalRuntimeValue updateConditionalValue(ConditionalRuntimeValue existingConditionalValue, Object newValue, ConfigurationCondition additionalCondition) { - Set> resConditions = Set.of(); - if (!additionalCondition.isAlwaysTrue()) { - Class conditionClass = additionalCondition.getType(); - if (existingConditionalValue != null) { - Set> conditions = existingConditionalValue.getConditions(); - resConditions = Stream.concat(conditions.stream(), Stream.of(conditionClass)) - .collect(Collectors.toUnmodifiableSet()); - } else { - resConditions = Set.of(conditionClass); - } + if (existingConditionalValue == null) { + return new ConditionalRuntimeValue<>(RuntimeConditionSet.createHosted(additionalCondition), newValue); + } else { + existingConditionalValue.getConditions().addCondition(additionalCondition); + existingConditionalValue.updateValue(newValue); + return existingConditionalValue; } - return new ConditionalRuntimeValue<>(resConditions, newValue); } @Platforms(Platform.HOSTED_ONLY.class) public void registerExceptionForClass(ConfigurationCondition condition, String className, Throwable t) { - Set> typeSet = condition.isAlwaysTrue() ? Set.of() : Set.of(condition.getType()); - knownClasses.put(className, new ConditionalRuntimeValue<>(typeSet, t)); + updateCondition(condition, className, t); } @Platforms(Platform.HOSTED_ONLY.class) @@ -121,8 +117,16 @@ public void registerNegativeQuery(ConfigurationCondition condition, String class * If the class is not accessible by the builder class loader, but was already registered * through registerClass(Class), we don't overwrite the actual class or exception. */ - Set> typeSet = condition.isAlwaysTrue() ? Set.of() : Set.of(condition.getType()); - knownClasses.putIfAbsent(className, new ConditionalRuntimeValue<>(typeSet, NEGATIVE_QUERY)); + updateCondition(condition, className, NEGATIVE_QUERY); + } + + private void updateCondition(ConfigurationCondition condition, String className, Object value) { + synchronized (knownClasses) { + var runtimeConditions = knownClasses.putIfAbsent(className, new ConditionalRuntimeValue<>(RuntimeConditionSet.createHosted(condition), value)); + if (runtimeConditions != null) { + runtimeConditions.getConditions().addCondition(condition); + } + } } public Class forNameOrNull(String className, ClassLoader classLoader) { @@ -142,7 +146,7 @@ private Class forName(String className, ClassLoader classLoader, boolean retu return null; } var conditional = knownClasses.get(className); - Object result = conditional == null ? null : conditional.getValue(cls -> DynamicHub.fromClass(cls).isReached()); + Object result = conditional == null ? null : conditional.getValue(); if (result == NEGATIVE_QUERY || className.endsWith("[]")) { /* Querying array classes with their "TypeName[]" name always throws */ result = new ClassNotFoundException(className); @@ -181,4 +185,14 @@ private Class forName(String className, ClassLoader classLoader, boolean retu public int count() { return knownClasses.size(); } + + public RuntimeConditionSet getConditionFor(Class jClass) { + Objects.requireNonNull(jClass); + ConditionalRuntimeValue conditionalClass = knownClasses.get(jClass.getName()); + if (conditionalClass == null) { + return RuntimeConditionSet.createRuntime(Set.of()); + } else { + return conditionalClass.getConditions(); + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index 919bf5b2fb3c..ad71a0c6a20a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -82,12 +82,12 @@ import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.nativeimage.impl.InternalPlatform; -import com.oracle.svm.core.BuildPhaseProvider.AfterCompilation; -import com.oracle.svm.core.BuildPhaseProvider.AfterHostedUniverse; -import com.oracle.svm.core.BuildPhaseProvider.CompileQueueFinished; import com.oracle.svm.core.RuntimeAssertionsSupport; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.BuildPhaseProvider.AfterCompilation; +import com.oracle.svm.core.BuildPhaseProvider.AfterHostedUniverse; +import com.oracle.svm.core.BuildPhaseProvider.CompileQueueFinished; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.annotate.InjectAccessors; @@ -100,6 +100,7 @@ import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.config.ObjectLayout; +import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.heap.UnknownObjectField; import com.oracle.svm.core.heap.UnknownPrimitiveField; import com.oracle.svm.core.jdk.JDK21OrEarlier; @@ -602,7 +603,7 @@ public void setReflectionMetadata(int fieldsEncodingIndex, int methodsEncodingIn } private void checkClassFlag(int mask, String methodName) { - if (throwMissingRegistrationErrors() && !isClassFlagSet(mask)) { + if (throwMissingRegistrationErrors() && !(isClassFlagSet(mask) && getConditions().satisfied())) { MissingReflectionRegistrationUtils.forBulkQuery(DynamicHub.toClass(this), methodName); } } @@ -1090,6 +1091,10 @@ private Field[] getFields() { return copyFields(privateGetPublicFields()); } + private RuntimeConditionSet getConditions() { + return ClassForNameSupport.singleton().getConditionFor(DynamicHub.toClass(this)); + } + @Substitute @CallerSensitive public Method[] getMethods() throws SecurityException { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java index e603ead67329..91740b51b360 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java @@ -53,6 +53,7 @@ import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Constructor; import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Field; import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Method; +import com.oracle.svm.core.reflect.target.Target_jdk_internal_reflect_MethodAccessor; import com.oracle.svm.core.util.VMError; import jdk.internal.reflect.FieldAccessor; @@ -265,8 +266,12 @@ private static SubstrateMethodAccessor asMethod(Target_java_lang_invoke_MemberNa return getMethodAccessor(method); } + @SuppressWarnings("DataFlowIssue") private static SubstrateMethodAccessor getMethodAccessor(Method method) { - return SubstrateUtil.cast(SubstrateUtil.cast(method, Target_java_lang_reflect_Method.class).acquireMethodAccessor(), SubstrateMethodAccessor.class); + Target_java_lang_reflect_Method internalMethod = SubstrateUtil.cast(method, Target_java_lang_reflect_Method.class); + Target_jdk_internal_reflect_MethodAccessor methodAccessor = internalMethod.methodAccessor; + var result = methodAccessor == null ? internalMethod.acquireMethodAccessor() : methodAccessor; + return SubstrateUtil.cast(result, SubstrateMethodAccessor.class); } private static SubstrateConstructorAccessor asConstructor(Target_java_lang_invoke_MemberName memberName) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java index 498bdf436af2..47337d528d4d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java @@ -136,7 +136,7 @@ private static String errorMessage(String failedAction, String elementDescriptor ERROR_EMPHASIS_INDENT + elementDescriptor + System.lineSeparator() + System.lineSeparator() + - " without it being registered for runtime reflection. Add " + elementDescriptor + " to the " + helpLink + " metadata to solve this problem. " + + "without it being registered for runtime reflection. Add " + elementDescriptor + " to the " + helpLink + " metadata to solve this problem. " + (note != null ? "Note: " + note + " " : "") + "See https://www.graalvm.org/latest/reference-manual/native-image/metadata/#" + helpLink + " for help."; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionObjectFactory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionObjectFactory.java index 02afb4c6bfb3..9238a7c06465 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionObjectFactory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionObjectFactory.java @@ -32,39 +32,46 @@ import java.lang.reflect.RecordComponent; import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.util.VMError; public final class ReflectionObjectFactory { public static final int FIELD_OFFSET_NONE = 0; - public static Field newField(Class declaringClass, String name, Class type, int modifiers, + public static Field newField(RuntimeConditionSet conditions, Class declaringClass, String name, Class type, int modifiers, boolean trustedFinal, String signature, byte[] annotations, int offset, String deletedReason, byte[] typeAnnotations) { Target_java_lang_reflect_Field field = new Target_java_lang_reflect_Field(); field.constructor(declaringClass, name, type, modifiers, trustedFinal, -1, signature, annotations); field.offset = offset; field.deletedReason = deletedReason; - SubstrateUtil.cast(field, Target_java_lang_reflect_AccessibleObject.class).typeAnnotations = typeAnnotations; + Target_java_lang_reflect_AccessibleObject accessibleObject = SubstrateUtil.cast(field, Target_java_lang_reflect_AccessibleObject.class); + accessibleObject.typeAnnotations = typeAnnotations; + accessibleObject.conditions = conditions; return SubstrateUtil.cast(field, Field.class); } - public static Method newMethod(Class declaringClass, String name, Class[] parameterTypes, Class returnType, Class[] exceptionTypes, int modifiers, + public static Method newMethod(RuntimeConditionSet conditions, Class declaringClass, String name, Class[] parameterTypes, Class returnType, Class[] exceptionTypes, int modifiers, String signature, byte[] annotations, byte[] parameterAnnotations, byte[] annotationDefault, Object accessor, byte[] rawParameters, byte[] typeAnnotations) { Target_java_lang_reflect_Method method = new Target_java_lang_reflect_Method(); method.constructor(declaringClass, name, parameterTypes, returnType, exceptionTypes, modifiers, -1, signature, annotations, parameterAnnotations, annotationDefault); - method.methodAccessor = (Target_jdk_internal_reflect_MethodAccessor) accessor; + method.methodAccessorFromMetadata = (Target_jdk_internal_reflect_MethodAccessor) accessor; SubstrateUtil.cast(method, Target_java_lang_reflect_Executable.class).rawParameters = rawParameters; - SubstrateUtil.cast(method, Target_java_lang_reflect_AccessibleObject.class).typeAnnotations = typeAnnotations; + Target_java_lang_reflect_AccessibleObject accessibleObject = SubstrateUtil.cast(method, Target_java_lang_reflect_AccessibleObject.class); + accessibleObject.typeAnnotations = typeAnnotations; + accessibleObject.conditions = conditions; return SubstrateUtil.cast(method, Method.class); } - public static Constructor newConstructor(Class declaringClass, Class[] parameterTypes, Class[] exceptionTypes, int modifiers, String signature, + public static Constructor newConstructor(RuntimeConditionSet conditions, Class declaringClass, Class[] parameterTypes, Class[] exceptionTypes, int modifiers, String signature, byte[] annotations, byte[] parameterAnnotations, Object accessor, byte[] rawParameters, byte[] typeAnnotations) { Target_java_lang_reflect_Constructor ctor = new Target_java_lang_reflect_Constructor(); ctor.constructor(declaringClass, parameterTypes, exceptionTypes, modifiers, -1, signature, annotations, parameterAnnotations); ctor.constructorAccessor = (Target_jdk_internal_reflect_ConstructorAccessor) accessor; SubstrateUtil.cast(ctor, Target_java_lang_reflect_Executable.class).rawParameters = rawParameters; - SubstrateUtil.cast(ctor, Target_java_lang_reflect_AccessibleObject.class).typeAnnotations = typeAnnotations; + Target_java_lang_reflect_AccessibleObject accessibleObject = SubstrateUtil.cast(ctor, Target_java_lang_reflect_AccessibleObject.class); + accessibleObject.typeAnnotations = typeAnnotations; + accessibleObject.conditions = conditions; return SubstrateUtil.cast(ctor, Constructor.class); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_AccessibleObject.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_AccessibleObject.java index a43e06f3206f..0c904ce39074 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_AccessibleObject.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_AccessibleObject.java @@ -32,12 +32,19 @@ import com.oracle.svm.core.annotate.Inject; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.configure.RuntimeConditionSet; @TargetClass(value = AccessibleObject.class) public final class Target_java_lang_reflect_AccessibleObject { @Alias // public boolean override; + /** + * For objects in image heap the conditions are always satisfied. + */ + @Inject @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = SatisfiedConditionComputer.class) // + public RuntimeConditionSet conditions; + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // volatile Object accessCheckCache; @@ -53,4 +60,11 @@ public Object transform(Object receiver, Object originalValue) { return ImageSingletons.lookup(EncodedRuntimeMetadataSupplier.class).getTypeAnnotationsEncoding((AccessibleObject) receiver); } } + + static class SatisfiedConditionComputer extends ReflectionMetadataComputer { + @Override + public Object transform(Object receiver, Object originalValue) { + return RuntimeConditionSet.unmodifiableEmptySet(); + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java index f7f33b5dd3a9..7c6f97266d33 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java @@ -34,11 +34,13 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Inject; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; +import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import sun.reflect.generics.repository.MethodRepository; @@ -60,7 +62,15 @@ public final class Target_java_lang_reflect_Method { @Alias // @RecomputeFieldValue(kind = Kind.Custom, declClass = ExecutableAccessorComputer.class) // - Target_jdk_internal_reflect_MethodAccessor methodAccessor; + public Target_jdk_internal_reflect_MethodAccessor methodAccessor; + + /** + * We need this indirection to use {@link #acquireMethodAccessor()} for checking if run-time + * conditions for this method are satisfied. + */ + @Inject // + @RecomputeFieldValue(kind = Kind.Reset) // + Target_jdk_internal_reflect_MethodAccessor methodAccessorFromMetadata; @Alias @TargetElement(name = CONSTRUCTOR_NAME) @@ -71,12 +81,18 @@ native void constructor(Class declaringClass, String name, Class[] paramet @Alias native Target_java_lang_reflect_Method copy(); + /** + * Encoded field, will fetch the value from {@link #methodAccessorFromMetadata} and check the + * conditions. + */ @Substitute public Target_jdk_internal_reflect_MethodAccessor acquireMethodAccessor() { - if (methodAccessor == null) { + assert methodAccessorFromMetadata != null || methodAccessor != null : "This method has likely be called without checking if methodAccessor is null so it is in inconsistent state."; + RuntimeConditionSet conditions = SubstrateUtil.cast(this, Target_java_lang_reflect_AccessibleObject.class).conditions; + if (methodAccessorFromMetadata == null || !conditions.satisfied()) { throw MissingReflectionRegistrationUtils.errorForQueriedOnlyExecutable(SubstrateUtil.cast(this, Executable.class)); } - return methodAccessor; + return methodAccessorFromMetadata; } static class AnnotationsComputer extends ReflectionMetadataComputer { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_ReflectAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_ReflectAccess.java index 8696746aa3f1..473c0541151b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_ReflectAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_ReflectAccess.java @@ -51,6 +51,7 @@ public Target_java_lang_reflect_Method copyMethod(Target_java_lang_reflect_Metho Target_java_lang_reflect_Method copy = method.copy(); copyExecutable(SubstrateUtil.cast(copy, Target_java_lang_reflect_Executable.class), SubstrateUtil.cast(method, Target_java_lang_reflect_Executable.class)); + copy.methodAccessorFromMetadata = method.methodAccessorFromMetadata; return copy; } @@ -84,5 +85,6 @@ static void copyExecutable(Target_java_lang_reflect_Executable copy, Target_java static void copyAccessibleObject(Target_java_lang_reflect_AccessibleObject copy, Target_java_lang_reflect_AccessibleObject accessibleObject) { copy.typeAnnotations = accessibleObject.typeAnnotations; + copy.conditions = accessibleObject.conditions; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java index 98ed7c9b61ee..5a04fcbce668 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java @@ -80,7 +80,8 @@ static long getFieldOffset(Target_java_lang_reflect_Field field) { throw new NullPointerException(); } int offset = field.root == null ? field.offset : field.root.offset; - if (offset <= 0) { + boolean conditionsSatisfied = SubstrateUtil.cast(field, Target_java_lang_reflect_AccessibleObject.class).conditions.satisfied(); + if (offset <= 0 || !conditionsSatisfied) { throw MissingReflectionRegistrationUtils.errorForQueriedOnlyField(SubstrateUtil.cast(field, Field.class)); } return offset; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java index f8bd664cacea..c116a3857ba5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java @@ -42,10 +42,11 @@ protected void registerConditionalConfiguration(ConfigurationCondition condition Objects.requireNonNull(consumer, "Cannot use null value as runnable for conditional configuration. Please ensure that you register a non-null runnable."); if (ConfigurationCondition.alwaysTrue().equals(condition)) { /* analysis optimization to include new types as early as possible */ - consumer.accept(condition); + consumer.accept(ConfigurationCondition.alwaysTrue()); } else { Collection handlers = pendingReachabilityHandlers.computeIfAbsent(condition.getType(), key -> new ConcurrentLinkedQueue<>()); - ConfigurationCondition runtimeCondition = condition.isRuntimeChecked() ? condition : ConfigurationCondition.alwaysTrue(); + ConfigurationCondition runtimeCondition; + runtimeCondition = condition.isRuntimeChecked() ? condition : ConfigurationCondition.alwaysTrue(); handlers.add(() -> consumer.accept(runtimeCondition)); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java index 8b1f10a59cc1..b4561f2cd4d6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java @@ -25,6 +25,7 @@ package com.oracle.svm.hosted.classinitialization; import static com.oracle.svm.core.SubstrateOptions.TraceObjectInstantiation; +import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TrackTypeReachedOnInterfaces; import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllUserSpaceTypesAsTrackedForTypeReached; import java.io.Serializable; @@ -57,6 +58,7 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.LinkAtBuildTimeSupport; +import com.oracle.svm.util.LogUtils; import jdk.graal.compiler.java.LambdaUtils; import jdk.internal.misc.Unsafe; @@ -492,6 +494,10 @@ private static void addAllInterfaces(Class clazz, EconomicSet> resul } public void addForTypeReachedTracking(Class clazz) { + if (TrackTypeReachedOnInterfaces.getValue() && clazz.isInterface() && metaAccess.lookupJavaType(clazz).declaresDefaultMethods()) { + LogUtils.info("Detected 'typeReached' on interface type without default methods: " + clazz); + } + if (!isAlwaysReached(clazz)) { UserError.guarantee(!configurationSealed, "It is not possible to register types for reachability tracking after the analysis has started."); typesRequiringReachability.add(clazz); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/ReflectionRuntimeMetadata.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/ReflectionRuntimeMetadata.java index a36a61cf6e70..a87e5f19ece0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/ReflectionRuntimeMetadata.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/ReflectionRuntimeMetadata.java @@ -26,6 +26,7 @@ import static com.oracle.svm.core.meta.SharedField.LOC_UNINITIALIZED; +import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.hosted.annotation.AnnotationMemberValue; import com.oracle.svm.hosted.annotation.AnnotationValue; import com.oracle.svm.hosted.annotation.TypeAnnotationValue; @@ -68,6 +69,7 @@ static class ClassMetadata extends AnnotatedElementMetadata { } static class AccessibleObjectMetadata extends AnnotatedElementMetadata { + final RuntimeConditionSet conditions; final boolean complete; final boolean negative; final JavaConstant heapObject; @@ -75,9 +77,11 @@ static class AccessibleObjectMetadata extends AnnotatedElementMetadata { final int modifiers; final String signature; - AccessibleObjectMetadata(boolean complete, boolean negative, JavaConstant heapObject, HostedType declaringType, int modifiers, String signature, AnnotationValue[] annotations, + AccessibleObjectMetadata(RuntimeConditionSet conditions, boolean complete, boolean negative, JavaConstant heapObject, HostedType declaringType, int modifiers, String signature, + AnnotationValue[] annotations, TypeAnnotationValue[] typeAnnotations) { super(annotations, typeAnnotations); + this.conditions = conditions; this.complete = complete; this.negative = negative; this.heapObject = heapObject; @@ -95,9 +99,10 @@ static class FieldMetadata extends AccessibleObjectMetadata { final int offset; final String deletedReason; - private FieldMetadata(boolean complete, boolean negative, boolean hiding, JavaConstant heapObject, HostedType declaringType, String name, HostedType type, int modifiers, boolean trustedFinal, + private FieldMetadata(RuntimeConditionSet conditions, boolean complete, boolean negative, boolean hiding, JavaConstant heapObject, HostedType declaringType, String name, HostedType type, + int modifiers, boolean trustedFinal, String signature, AnnotationValue[] annotations, TypeAnnotationValue[] typeAnnotations, int offset, String deletedReason) { - super(complete, negative, heapObject, declaringType, modifiers, signature, annotations, typeAnnotations); + super(conditions, complete, negative, heapObject, declaringType, modifiers, signature, annotations, typeAnnotations); this.hiding = hiding; this.name = name; this.type = type; @@ -107,24 +112,24 @@ private FieldMetadata(boolean complete, boolean negative, boolean hiding, JavaCo } /* Field registered for reflection */ - FieldMetadata(HostedType declaringType, String name, HostedType type, int modifiers, boolean trustedFinal, String signature, AnnotationValue[] annotations, + FieldMetadata(RuntimeConditionSet conditions, HostedType declaringType, String name, HostedType type, int modifiers, boolean trustedFinal, String signature, AnnotationValue[] annotations, TypeAnnotationValue[] typeAnnotations, int offset, String deletedReason) { - this(true, false, false, null, declaringType, name, type, modifiers, trustedFinal, signature, annotations, typeAnnotations, offset, deletedReason); + this(conditions, true, false, false, null, declaringType, name, type, modifiers, trustedFinal, signature, annotations, typeAnnotations, offset, deletedReason); } /* Field in heap */ - FieldMetadata(boolean registered, JavaConstant heapObject, AnnotationValue[] annotations, TypeAnnotationValue[] typeAnnotations) { - this(registered, false, false, heapObject, null, null, null, 0, false, null, annotations, typeAnnotations, LOC_UNINITIALIZED, null); + FieldMetadata(RuntimeConditionSet conditions, boolean registered, JavaConstant heapObject, AnnotationValue[] annotations, TypeAnnotationValue[] typeAnnotations) { + this(conditions, registered, false, false, heapObject, null, null, null, 0, false, null, annotations, typeAnnotations, LOC_UNINITIALIZED, null); } /* Hiding field */ - FieldMetadata(HostedType declaringType, String name, HostedType type, int modifiers) { - this(false, false, true, null, declaringType, name, type, modifiers, false, null, null, null, LOC_UNINITIALIZED, null); + FieldMetadata(RuntimeConditionSet conditions, HostedType declaringType, String name, HostedType type, int modifiers) { + this(conditions, false, false, true, null, declaringType, name, type, modifiers, false, null, null, null, LOC_UNINITIALIZED, null); } /* Reachable or negative query field */ - FieldMetadata(HostedType declaringType, String name, boolean negative) { - this(false, negative, false, null, declaringType, name, null, 0, false, null, null, null, LOC_UNINITIALIZED, null); + FieldMetadata(RuntimeConditionSet conditions, HostedType declaringType, String name, boolean negative) { + this(conditions, false, negative, false, null, declaringType, name, null, 0, false, null, null, null, LOC_UNINITIALIZED, null); } } @@ -135,10 +140,12 @@ static class ExecutableMetadata extends AccessibleObjectMetadata { final ReflectParameterMetadata[] reflectParameters; final JavaConstant accessor; - ExecutableMetadata(boolean complete, boolean negative, JavaConstant heapObject, HostedType declaringType, Object[] parameterTypes, int modifiers, HostedType[] exceptionTypes, String signature, + ExecutableMetadata(RuntimeConditionSet conditions, boolean complete, boolean negative, JavaConstant heapObject, HostedType declaringType, Object[] parameterTypes, int modifiers, + HostedType[] exceptionTypes, String signature, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters, JavaConstant accessor) { - super(complete, negative, heapObject, declaringType, modifiers, signature, annotations, typeAnnotations); + super(conditions, complete, negative, heapObject, declaringType, modifiers, signature, annotations, typeAnnotations); + this.parameterTypes = parameterTypes; this.exceptionTypes = exceptionTypes; this.parameterAnnotations = parameterAnnotations; @@ -153,10 +160,12 @@ static class MethodMetadata extends ExecutableMetadata { final HostedType returnType; final AnnotationMemberValue annotationDefault; - private MethodMetadata(boolean complete, boolean negative, boolean hiding, JavaConstant heapObject, HostedType declaringClass, String name, Object[] parameterTypes, int modifiers, + private MethodMetadata(RuntimeConditionSet conditions, boolean complete, boolean negative, boolean hiding, JavaConstant heapObject, HostedType declaringClass, String name, + Object[] parameterTypes, int modifiers, HostedType returnType, HostedType[] exceptionTypes, String signature, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, AnnotationMemberValue annotationDefault, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters, JavaConstant accessor) { - super(complete, negative, heapObject, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, reflectParameters, + super(conditions, complete, negative, heapObject, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, + reflectParameters, accessor); this.hiding = hiding; this.name = name; @@ -165,64 +174,72 @@ private MethodMetadata(boolean complete, boolean negative, boolean hiding, JavaC } /* Method registered for reflection */ - MethodMetadata(HostedType declaringClass, String name, HostedType[] parameterTypes, int modifiers, HostedType returnType, HostedType[] exceptionTypes, String signature, + MethodMetadata(RuntimeConditionSet conditions, HostedType declaringClass, String name, HostedType[] parameterTypes, int modifiers, HostedType returnType, HostedType[] exceptionTypes, + String signature, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, AnnotationMemberValue annotationDefault, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters, JavaConstant accessor) { - this(true, false, false, null, declaringClass, name, parameterTypes, modifiers, returnType, exceptionTypes, signature, annotations, parameterAnnotations, annotationDefault, + this(conditions, true, false, false, null, declaringClass, name, parameterTypes, modifiers, returnType, exceptionTypes, signature, annotations, parameterAnnotations, annotationDefault, typeAnnotations, reflectParameters, accessor); } /* Method in heap */ - MethodMetadata(boolean registered, JavaConstant heapObject, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, AnnotationMemberValue annotationDefault, + MethodMetadata(RuntimeConditionSet conditions, boolean registered, JavaConstant heapObject, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, + AnnotationMemberValue annotationDefault, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters) { - this(registered, false, false, heapObject, null, null, null, 0, null, null, null, annotations, parameterAnnotations, annotationDefault, typeAnnotations, reflectParameters, null); + this(conditions, registered, false, false, heapObject, null, null, null, 0, null, null, null, annotations, parameterAnnotations, annotationDefault, + typeAnnotations, + reflectParameters, null); } /* Hiding method */ - MethodMetadata(HostedType declaringClass, String name, HostedType[] parameterTypes, int modifiers, HostedType returnType) { - this(false, false, true, null, declaringClass, name, parameterTypes, modifiers, returnType, null, null, null, null, null, null, null, null); + MethodMetadata(RuntimeConditionSet conditions, HostedType declaringClass, String name, HostedType[] parameterTypes, int modifiers, HostedType returnType) { + this(conditions, false, false, true, null, declaringClass, name, parameterTypes, modifiers, returnType, null, null, null, null, null, null, null, null); } /* Reachable method */ - MethodMetadata(HostedType declaringClass, String name, String[] parameterTypeNames) { - this(false, false, false, null, declaringClass, name, parameterTypeNames, 0, null, null, null, null, null, null, null, null, null); + MethodMetadata(RuntimeConditionSet conditions, HostedType declaringClass, String name, String[] parameterTypeNames) { + this(conditions, false, false, false, null, declaringClass, name, parameterTypeNames, 0, null, null, null, null, null, null, null, null, null); } /* Negative query method */ - MethodMetadata(HostedType declaringClass, String name, HostedType[] parameterTypes) { - this(false, true, false, null, declaringClass, name, parameterTypes, 0, null, null, null, null, null, null, null, null, null); + MethodMetadata(RuntimeConditionSet conditions, HostedType declaringClass, String name, HostedType[] parameterTypes) { + this(conditions, false, true, false, null, declaringClass, name, parameterTypes, 0, null, null, null, null, null, null, null, null, null); } } static class ConstructorMetadata extends ExecutableMetadata { - private ConstructorMetadata(boolean complete, boolean negative, JavaConstant heapObject, HostedType declaringClass, Object[] parameterTypes, int modifiers, HostedType[] exceptionTypes, + private ConstructorMetadata(RuntimeConditionSet conditions, boolean complete, boolean negative, JavaConstant heapObject, HostedType declaringClass, Object[] parameterTypes, int modifiers, + HostedType[] exceptionTypes, String signature, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters, JavaConstant accessor) { - super(complete, negative, heapObject, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, reflectParameters, + super(conditions, complete, negative, heapObject, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, + reflectParameters, accessor); } /* Constructor registered for reflection */ - ConstructorMetadata(HostedType declaringClass, HostedType[] parameterTypes, int modifiers, HostedType[] exceptionTypes, String signature, AnnotationValue[] annotations, + ConstructorMetadata(RuntimeConditionSet conditions, HostedType declaringClass, HostedType[] parameterTypes, int modifiers, HostedType[] exceptionTypes, String signature, + AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters, JavaConstant accessor) { - this(true, false, null, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, reflectParameters, accessor); + this(conditions, true, false, null, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, reflectParameters, accessor); } /* Constructor in heap */ - ConstructorMetadata(boolean registered, JavaConstant heapObject, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, TypeAnnotationValue[] typeAnnotations, + ConstructorMetadata(RuntimeConditionSet conditions, boolean registered, JavaConstant heapObject, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, + TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters) { - this(registered, false, heapObject, null, null, 0, null, null, annotations, parameterAnnotations, typeAnnotations, reflectParameters, null); + this(conditions, registered, false, heapObject, null, null, 0, null, null, annotations, parameterAnnotations, typeAnnotations, reflectParameters, null); } /* Reachable constructor */ - ConstructorMetadata(HostedType declaringClass, String[] parameterTypeNames) { - this(false, false, null, declaringClass, parameterTypeNames, 0, null, null, null, null, null, null, null); + ConstructorMetadata(RuntimeConditionSet conditions, HostedType declaringClass, String[] parameterTypeNames) { + this(conditions, false, false, null, declaringClass, parameterTypeNames, 0, null, null, null, null, null, null, null); } /* Negative query constructor */ - ConstructorMetadata(HostedType declaringClass, HostedType[] parameterTypes) { - this(false, true, null, declaringClass, parameterTypes, 0, null, null, null, null, null, null, null); + ConstructorMetadata(RuntimeConditionSet conditions, HostedType declaringClass, HostedType[] parameterTypes) { + this(conditions, false, true, null, declaringClass, parameterTypes, 0, null, null, null, null, null, null, null); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java index b6943d16374a..d35dd7bbaec5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java @@ -79,6 +79,8 @@ import com.oracle.svm.core.code.CodeInfoEncoder; import com.oracle.svm.core.code.RuntimeMetadataDecoderImpl; import com.oracle.svm.core.code.RuntimeMetadataEncoding; +import com.oracle.svm.core.configure.ConditionalRuntimeValue; +import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.meta.SharedField; @@ -376,7 +378,8 @@ private static Class[] filterDeletedClasses(MetaAccessProvider metaAccess, Cl } @Override - public void addReflectionFieldMetadata(MetaAccessProvider metaAccess, HostedField hostedField, Field reflectField) { + public void addReflectionFieldMetadata(MetaAccessProvider metaAccess, HostedField hostedField, ConditionalRuntimeValue conditionalReflectField) { + Field reflectField = conditionalReflectField.getValueUnconditionally(); HostedType declaringType = hostedField.getDeclaringClass(); String name = hostedField.getName(); HostedType type = hostedField.getType(); @@ -387,8 +390,11 @@ public void addReflectionFieldMetadata(MetaAccessProvider metaAccess, HostedFiel int offset = hostedField.wrapped.isUnsafeAccessed() ? hostedField.getOffset() : SharedField.LOC_UNINITIALIZED; Delete deleteAnnotation = AnnotationAccess.getAnnotation(hostedField, Delete.class); String deletedReason = (deleteAnnotation != null) ? deleteAnnotation.value() : null; - + RuntimeConditionSet conditions = conditionalReflectField.getConditions(); /* Fill encoders with the necessary values. */ + for (Class conditionType : conditions.getTypesForEncoding()) { + encoders.classes.addObject(conditionType); + } encoders.memberNames.addObject(name); encoders.classes.addObject(type.getJavaClass()); encoders.otherStrings.addObject(signature); @@ -398,22 +404,27 @@ public void addReflectionFieldMetadata(MetaAccessProvider metaAccess, HostedFiel AnnotationValue[] annotations = registerAnnotationValues(analysisField); TypeAnnotationValue[] typeAnnotations = registerTypeAnnotationValues(analysisField); - registerField(declaringType, reflectField, new FieldMetadata(declaringType, name, type, modifiers, trustedFinal, signature, annotations, typeAnnotations, offset, deletedReason)); + registerField(declaringType, reflectField, new FieldMetadata(conditions, declaringType, name, type, modifiers, trustedFinal, signature, annotations, typeAnnotations, offset, deletedReason)); } @Override - public void addReflectionExecutableMetadata(MetaAccessProvider metaAccess, HostedMethod hostedMethod, Executable reflectMethod, Object accessor) { + public void addReflectionExecutableMetadata(MetaAccessProvider metaAccess, HostedMethod hostedMethod, ConditionalRuntimeValue conditionalMethod, Object accessor) { boolean isMethod = !hostedMethod.isConstructor(); HostedType declaringType = hostedMethod.getDeclaringClass(); String name = isMethod ? hostedMethod.getName() : null; HostedType[] parameterTypes = getParameterTypes(hostedMethod); /* Reflect method because substitution of Object.hashCode() is private */ + Executable reflectMethod = conditionalMethod.getValueUnconditionally(); + RuntimeConditionSet conditions = conditionalMethod.getConditions(); int modifiers = reflectMethod.getModifiers(); HostedType returnType = hostedMethod.getSignature().getReturnType(); HostedType[] exceptionTypes = getExceptionTypes(metaAccess, reflectMethod); String signature = getSignature(reflectMethod); /* Fill encoders with the necessary values. */ + for (Class type : conditions.getTypesForEncoding()) { + encoders.classes.addObject(type); + } if (isMethod) { encoders.memberNames.addObject(name); encoders.classes.addObject(returnType.getJavaClass()); @@ -439,11 +450,13 @@ public void addReflectionExecutableMetadata(MetaAccessProvider metaAccess, Hoste } if (isMethod) { - registerMethod(declaringType, reflectMethod, new MethodMetadata(declaringType, name, parameterTypes, modifiers, returnType, exceptionTypes, signature, annotations, parameterAnnotations, - annotationDefault, typeAnnotations, reflectParameters, accessorConstant)); + registerMethod(declaringType, reflectMethod, + new MethodMetadata(conditions, declaringType, name, parameterTypes, modifiers, returnType, exceptionTypes, signature, annotations, parameterAnnotations, + annotationDefault, typeAnnotations, reflectParameters, accessorConstant)); } else { - registerConstructor(declaringType, reflectMethod, new ConstructorMetadata(declaringType, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, - typeAnnotations, reflectParameters, accessorConstant)); + registerConstructor(declaringType, reflectMethod, + new ConstructorMetadata(conditions, declaringType, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, + typeAnnotations, reflectParameters, accessorConstant)); } } @@ -474,14 +487,16 @@ public void addHeapAccessibleObjectMetadata(MetaAccessProvider metaAccess, Wrapp addConstantObject(heapObjectConstant); AccessibleObjectMetadata metadata; + /* In-heap objects have an always satisfied condition */ + RuntimeConditionSet alwaysSatisfied = RuntimeConditionSet.unmodifiableEmptySet(); if (isMethod) { - metadata = new MethodMetadata(registered, heapObjectConstant, annotations, parameterAnnotations, annotationDefault, typeAnnotations, reflectParameters); + metadata = new MethodMetadata(alwaysSatisfied, registered, heapObjectConstant, annotations, parameterAnnotations, annotationDefault, typeAnnotations, reflectParameters); registerMethod((HostedType) metaAccess.lookupJavaType(((Method) object).getDeclaringClass()), holder, (MethodMetadata) metadata); } else if (isExecutable) { - metadata = new ConstructorMetadata(registered, heapObjectConstant, annotations, parameterAnnotations, typeAnnotations, reflectParameters); + metadata = new ConstructorMetadata(alwaysSatisfied, registered, heapObjectConstant, annotations, parameterAnnotations, typeAnnotations, reflectParameters); registerConstructor((HostedType) metaAccess.lookupJavaType(((Constructor) object).getDeclaringClass()), holder, (ConstructorMetadata) metadata); } else { - metadata = new FieldMetadata(registered, heapObjectConstant, annotations, typeAnnotations); + metadata = new FieldMetadata(alwaysSatisfied, registered, heapObjectConstant, annotations, typeAnnotations); registerField((HostedType) metaAccess.lookupJavaType(((Field) object).getDeclaringClass()), holder, (FieldMetadata) metadata); } heapData.add(metadata); @@ -559,7 +574,7 @@ public void addHidingFieldMetadata(AnalysisField analysisField, HostedType decla encoders.classes.addObject(type.getJavaClass()); addType(declaringType); - registerField(declaringType, analysisField, new FieldMetadata(declaringType, name, type, modifiers)); + registerField(declaringType, analysisField, new FieldMetadata(RuntimeConditionSet.createHosted(), declaringType, name, type, modifiers)); } @Override @@ -572,7 +587,7 @@ public void addHidingMethodMetadata(AnalysisMethod analysisMethod, HostedType de encoders.classes.addObject(returnType.getJavaClass()); addType(declaringType); - registerMethod(declaringType, analysisMethod, new MethodMetadata(declaringType, name, parameterTypes, modifiers, returnType)); + registerMethod(declaringType, analysisMethod, new MethodMetadata(RuntimeConditionSet.createHosted(), declaringType, name, parameterTypes, modifiers, returnType)); } @Override @@ -583,7 +598,7 @@ public void addReachableFieldMetadata(HostedField field) { /* Fill encoders with the necessary values. */ encoders.memberNames.addObject(name); - registerField(declaringType, field, new FieldMetadata(declaringType, name, false)); + registerField(declaringType, field, new FieldMetadata(RuntimeConditionSet.createHosted(), declaringType, name, false)); } @Override @@ -602,16 +617,16 @@ public void addReachableExecutableMetadata(HostedMethod executable) { } if (isMethod) { - registerMethod(declaringType, executable, new MethodMetadata(declaringType, name, parameterTypeNames)); + registerMethod(declaringType, executable, new MethodMetadata(RuntimeConditionSet.createHosted(), declaringType, name, parameterTypeNames)); } else { - registerConstructor(declaringType, executable, new ConstructorMetadata(declaringType, parameterTypeNames)); + registerConstructor(declaringType, executable, new ConstructorMetadata(RuntimeConditionSet.createHosted(), declaringType, parameterTypeNames)); } } @Override public void addNegativeFieldQueryMetadata(HostedType declaringClass, String fieldName) { encoders.memberNames.addObject(fieldName); - registerField(declaringClass, fieldName, new FieldMetadata(declaringClass, fieldName, true)); + registerField(declaringClass, fieldName, new FieldMetadata(RuntimeConditionSet.createHosted(), declaringClass, fieldName, true)); } @Override @@ -620,7 +635,7 @@ public void addNegativeMethodQueryMetadata(HostedType declaringClass, String met for (HostedType parameterType : parameterTypes) { encoders.classes.addObject(parameterType.getJavaClass()); } - registerMethod(declaringClass, Pair.create(methodName, parameterTypes), new MethodMetadata(declaringClass, methodName, parameterTypes)); + registerMethod(declaringClass, Pair.create(methodName, parameterTypes), new MethodMetadata(RuntimeConditionSet.createHosted(), declaringClass, methodName, parameterTypes)); } @Override @@ -628,7 +643,7 @@ public void addNegativeConstructorQueryMetadata(HostedType declaringClass, Hoste for (HostedType parameterType : parameterTypes) { encoders.classes.addObject(parameterType.getJavaClass()); } - registerConstructor(declaringClass, parameterTypes, new ConstructorMetadata(declaringClass, parameterTypes)); + registerConstructor(declaringClass, parameterTypes, new ConstructorMetadata(RuntimeConditionSet.createHosted(), declaringClass, parameterTypes)); } @Override @@ -843,6 +858,8 @@ private void encodeField(UnsafeArrayTypeWriter buf, FieldMetadata field) { modifiers |= field.hiding ? HIDING_FLAG_MASK : 0; modifiers |= field.negative ? NEGATIVE_FLAG_MASK : 0; buf.putUV(modifiers); + encodeConditions(buf, field.conditions); + if (field.heapObject != null) { encodeObject(buf, field.heapObject); } else { @@ -861,6 +878,10 @@ private void encodeField(UnsafeArrayTypeWriter buf, FieldMetadata field) { } } + private void encodeConditions(UnsafeArrayTypeWriter buf, RuntimeConditionSet conditions) { + encodeArray(buf, conditions.getTypesForEncoding().toArray(Class[]::new), t -> encodeType(buf, t)); + } + private void encodeExecutable(UnsafeArrayTypeWriter buf, ExecutableMetadata executable) { boolean isMethod = executable instanceof MethodMetadata; boolean isHiding = isMethod && ((MethodMetadata) executable).hiding; @@ -872,6 +893,9 @@ private void encodeExecutable(UnsafeArrayTypeWriter buf, ExecutableMetadata exec modifiers |= isHiding ? HIDING_FLAG_MASK : 0; modifiers |= executable.negative ? NEGATIVE_FLAG_MASK : 0; buf.putUV(modifiers); + + encodeConditions(buf, executable.conditions); + if (executable.heapObject != null) { encodeObject(buf, executable.heapObject); } else { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java index 27bae0c5ee6c..364efbcb9bc0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java @@ -73,11 +73,12 @@ import com.oracle.svm.core.code.CodeInfoQueryResult; import com.oracle.svm.core.code.CodeInfoTable; import com.oracle.svm.core.code.FrameInfoDecoder; -import com.oracle.svm.core.code.FrameInfoDecoder.ConstantAccess; import com.oracle.svm.core.code.FrameInfoEncoder; import com.oracle.svm.core.code.FrameInfoQueryResult; +import com.oracle.svm.core.code.FrameInfoDecoder.ConstantAccess; import com.oracle.svm.core.code.ImageCodeInfo.HostedImageCodeInfo; import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.configure.ConditionalRuntimeValue; import com.oracle.svm.core.deopt.DeoptEntryInfopoint; import com.oracle.svm.core.graal.code.SubstrateDataBuilder; import com.oracle.svm.core.meta.MethodPointer; @@ -371,8 +372,8 @@ protected void buildRuntimeMetadata(DebugContext debug, SnippetReflectionProvide Set includedFields = new HashSet<>(); Set includedMethods = new HashSet<>(); - Map configurationFields = reflectionSupport.getReflectionFields(); - Map configurationExecutables = reflectionSupport.getReflectionExecutables(); + Map> configurationFields = reflectionSupport.getReflectionFields(); + Map> configurationExecutables = reflectionSupport.getReflectionExecutables(); reflectionSupport.getHeapReflectionFields().forEach(((analysisField, reflectField) -> { if (includedFields.add(analysisField)) { @@ -443,11 +444,11 @@ protected void buildRuntimeMetadata(DebugContext debug, SnippetReflectionProvide } if (throwMissingRegistrationErrors()) { - reflectionSupport.getNegativeFieldQueries().forEach((analysisType, fieldNames) -> { + reflectionSupport.getNegativeFieldQueries().forEach((analysisType, fields) -> { HostedType hostedType = hUniverse.optionalLookup(analysisType); if (hostedType != null) { - for (String fieldName : fieldNames) { - runtimeMetadataEncoder.addNegativeFieldQueryMetadata(hostedType, fieldName); + for (String field : fields) { + runtimeMetadataEncoder.addNegativeFieldQueryMetadata(hostedType, field); } } }); @@ -791,9 +792,9 @@ protected boolean isDeoptEntry(ResolvedJavaMethod method, CompilationResult comp public interface RuntimeMetadataEncoder extends EncodedRuntimeMetadataSupplier { void addClassMetadata(MetaAccessProvider metaAccess, HostedType type, Class[] reflectionClasses); - void addReflectionFieldMetadata(MetaAccessProvider metaAccess, HostedField sharedField, Field reflectField); + void addReflectionFieldMetadata(MetaAccessProvider metaAccess, HostedField sharedField, ConditionalRuntimeValue reflectField); - void addReflectionExecutableMetadata(MetaAccessProvider metaAccess, HostedMethod sharedMethod, Executable reflectMethod, Object accessor); + void addReflectionExecutableMetadata(MetaAccessProvider metaAccess, HostedMethod sharedMethod, ConditionalRuntimeValue reflectMethod, Object accessor); void addHeapAccessibleObjectMetadata(MetaAccessProvider metaAccess, WrappedElement hostedElement, AccessibleObject object, boolean registered); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index e94f2ed66cb4..193172e7e443 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -78,14 +78,16 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.MissingRegistrationUtils; +import com.oracle.svm.core.configure.ConditionalRuntimeValue; +import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.hub.ClassForNameSupport; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.reflect.SubstrateAccessor; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ConditionalConfigurationRegistry; -import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.LinkAtBuildTimeSupport; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.annotation.AnnotationMemberValue; import com.oracle.svm.hosted.annotation.AnnotationValue; import com.oracle.svm.hosted.annotation.SubstrateAnnotationExtractor; @@ -104,6 +106,7 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl private AnalysisUniverse universe; private final SubstrateAnnotationExtractor annotationExtractor; private BeforeAnalysisAccessImpl analysisAccess; + private ClassForNameSupport classForNameSupport; private boolean sealed; @@ -116,9 +119,9 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl */ private final Map, Set>> innerClasses = new ConcurrentHashMap<>(); private final Map, Integer> enabledQueriesFlags = new ConcurrentHashMap<>(); - private final Map registeredFields = new ConcurrentHashMap<>(); + private final Map> registeredFields = new ConcurrentHashMap<>(); private final Set hidingFields = ConcurrentHashMap.newKeySet(); - private final Map registeredMethods = new ConcurrentHashMap<>(); + private final Map> registeredMethods = new ConcurrentHashMap<>(); private final Map methodAccessors = new ConcurrentHashMap<>(); private final Set hidingMethods = ConcurrentHashMap.newKeySet(); @@ -155,6 +158,7 @@ record ConditionalTask(ConfigurationCondition condition, Consumer() : null; + classForNameSupport = ClassForNameSupport.singleton(); } public void duringSetup(AnalysisMetaAccess analysisMetaAccess, AnalysisUniverse analysisUniverse) { @@ -195,6 +199,7 @@ public void register(ConfigurationCondition condition, boolean unsafeInstantiate @Override public void registerAllClassesQuery(ConfigurationCondition condition, Class clazz) { + guaranteeNotRuntimeConditionForQueries(condition, true); runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_CLASSES_FLAG); try { @@ -208,8 +213,17 @@ public void registerAllClassesQuery(ConfigurationCondition condition, Class c }); } + /** + * Runtime conditions can only be used with type, so they are not valid here. + */ + @SuppressWarnings("unused") + private static void guaranteeNotRuntimeConditionForQueries(ConfigurationCondition cnd, boolean queriedOnly) { + VMError.guarantee(!queriedOnly || cnd.isAlwaysTrue() || !cnd.isRuntimeChecked(), "Bulk queries can only be set with 'name' which does not allow run-time conditions."); + } + @Override public void registerAllDeclaredClassesQuery(ConfigurationCondition condition, Class clazz) { + guaranteeNotRuntimeConditionForQueries(condition, true); runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_DECLARED_CLASSES_FLAG); try { @@ -235,7 +249,7 @@ private void registerClass(ConfigurationCondition condition, Class clazz, boo } if (allowForName) { - ClassForNameSupport.singleton().registerClass(condition, clazz); + classForNameSupport.registerClass(condition, clazz); if (!MissingRegistrationUtils.throwMissingRegistrationErrors()) { /* @@ -259,7 +273,7 @@ private void registerClass(ConfigurationCondition condition, Class clazz, boo @Override public void registerClassLookupException(ConfigurationCondition condition, String typeName, Throwable t) { - runConditionalInAnalysisTask(condition, (cnd) -> ClassForNameSupport.singleton().registerExceptionForClass(cnd, typeName, t)); + runConditionalInAnalysisTask(condition, (cnd) -> classForNameSupport.registerExceptionForClass(cnd, typeName, t)); } @Override @@ -268,15 +282,16 @@ public void registerClassLookup(ConfigurationCondition condition, String typeNam try { registerClass(cnd, Class.forName(typeName, false, ClassLoader.getSystemClassLoader()), false, true); } catch (ClassNotFoundException e) { - ClassForNameSupport.singleton().registerNegativeQuery(cnd, typeName); + classForNameSupport.registerNegativeQuery(cnd, typeName); } catch (Throwable t) { - ClassForNameSupport.singleton().registerExceptionForClass(cnd, typeName, t); + classForNameSupport.registerExceptionForClass(cnd, typeName, t); } }); } @Override public void registerAllRecordComponentsQuery(ConfigurationCondition condition, Class clazz) { + guaranteeNotRuntimeConditionForQueries(condition, true); runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_RECORD_COMPONENTS_FLAG); registerRecordComponents(clazz); @@ -285,6 +300,7 @@ public void registerAllRecordComponentsQuery(ConfigurationCondition condition, C @Override public void registerAllPermittedSubclassesQuery(ConfigurationCondition condition, Class clazz) { + guaranteeNotRuntimeConditionForQueries(condition, true); runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_PERMITTED_SUBCLASSES_FLAG); if (clazz.isSealed()) { @@ -297,6 +313,7 @@ public void registerAllPermittedSubclassesQuery(ConfigurationCondition condition @Override public void registerAllNestMembersQuery(ConfigurationCondition condition, Class clazz) { + guaranteeNotRuntimeConditionForQueries(condition, true); runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_NEST_MEMBERS_FLAG); for (Class nestMember : clazz.getNestMembers()) { @@ -309,6 +326,7 @@ public void registerAllNestMembersQuery(ConfigurationCondition condition, Class< @Override public void registerAllSignersQuery(ConfigurationCondition condition, Class clazz) { + guaranteeNotRuntimeConditionForQueries(condition, true); runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_SIGNERS_FLAG); Object[] signers = clazz.getSigners(); @@ -323,17 +341,18 @@ public void registerAllSignersQuery(ConfigurationCondition condition, Class c @Override public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... executables) { requireNonNull(executables, "executable"); - runConditionalInAnalysisTask(condition, (cnd) -> registerMethods(queriedOnly, executables)); + runConditionalInAnalysisTask(condition, (cnd) -> registerMethods(cnd, queriedOnly, executables)); } @Override public void registerAllMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { + guaranteeNotRuntimeConditionForQueries(condition, queriedOnly); runConditionalInAnalysisTask(condition, (cnd) -> { for (Class current = clazz; current != null; current = current.getSuperclass()) { setQueryFlag(current, ALL_METHODS_FLAG); } try { - registerMethods(queriedOnly, clazz.getMethods()); + registerMethods(cnd, queriedOnly, clazz.getMethods()); } catch (LinkageError e) { registerLinkageError(clazz, e, methodLookupExceptions); } @@ -342,10 +361,11 @@ public void registerAllMethodsQuery(ConfigurationCondition condition, boolean qu @Override public void registerAllDeclaredMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { + guaranteeNotRuntimeConditionForQueries(condition, queriedOnly); runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_DECLARED_METHODS_FLAG); try { - registerMethods(queriedOnly, clazz.getDeclaredMethods()); + registerMethods(cnd, queriedOnly, clazz.getDeclaredMethods()); } catch (LinkageError e) { registerLinkageError(clazz, e, methodLookupExceptions); } @@ -354,12 +374,13 @@ public void registerAllDeclaredMethodsQuery(ConfigurationCondition condition, bo @Override public void registerAllConstructorsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { + guaranteeNotRuntimeConditionForQueries(condition, queriedOnly); runConditionalInAnalysisTask(condition, (cnd) -> { for (Class current = clazz; current != null; current = current.getSuperclass()) { setQueryFlag(current, ALL_CONSTRUCTORS_FLAG); } try { - registerMethods(queriedOnly, clazz.getConstructors()); + registerMethods(cnd, queriedOnly, clazz.getConstructors()); } catch (LinkageError e) { registerLinkageError(clazz, e, constructorLookupExceptions); } @@ -368,29 +389,37 @@ public void registerAllConstructorsQuery(ConfigurationCondition condition, boole @Override public void registerAllDeclaredConstructorsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { + guaranteeNotRuntimeConditionForQueries(condition, queriedOnly); runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_DECLARED_CONSTRUCTORS_FLAG); try { - registerMethods(queriedOnly, clazz.getDeclaredConstructors()); + registerMethods(cnd, queriedOnly, clazz.getDeclaredConstructors()); } catch (LinkageError e) { registerLinkageError(clazz, e, constructorLookupExceptions); } }); } - private void registerMethods(boolean queriedOnly, Executable[] reflectExecutables) { + private void registerMethods(ConfigurationCondition cnd, boolean queriedOnly, Executable[] reflectExecutables) { for (Executable reflectExecutable : reflectExecutables) { - registerMethod(queriedOnly, reflectExecutable); + registerMethod(cnd, queriedOnly, reflectExecutable); } } - private void registerMethod(boolean queriedOnly, Executable reflectExecutable) { + private void registerMethod(ConfigurationCondition cnd, boolean queriedOnly, Executable reflectExecutable) { if (SubstitutionReflectivityFilter.shouldExclude(reflectExecutable, metaAccess, universe)) { return; } AnalysisMethod analysisMethod = metaAccess.lookupJavaMethod(reflectExecutable); - if (registeredMethods.put(analysisMethod, reflectExecutable) == null) { + var exists = registeredMethods.containsKey(analysisMethod); + var conditionalValue = registeredMethods.computeIfAbsent(analysisMethod, (t) -> new ConditionalRuntimeValue<>(RuntimeConditionSet.createHosted(), reflectExecutable)); + if (!queriedOnly) { + /* queryOnly methods are conditioned by the type itself */ + conditionalValue.getConditions().addCondition(cnd); + } + + if (!exists) { registerTypesForMethod(analysisMethod, reflectExecutable); AnalysisType declaringType = analysisMethod.getDeclaringClass(); Class declaringClass = declaringType.getJavaClass(); @@ -431,9 +460,10 @@ private void registerMethod(boolean queriedOnly, Executable reflectExecutable) { @Override public void registerMethodLookup(ConfigurationCondition condition, Class declaringClass, String methodName, Class... parameterTypes) { + guaranteeNotRuntimeConditionForQueries(condition, true); runConditionalInAnalysisTask(condition, (cnd) -> { try { - registerMethod(true, declaringClass.getDeclaredMethod(methodName, parameterTypes)); + registerMethod(cnd, true, declaringClass.getDeclaredMethod(methodName, parameterTypes)); } catch (NoSuchMethodException e) { negativeMethodLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()) .add(new AnalysisMethod.Signature(methodName, metaAccess.lookupJavaTypes(parameterTypes))); @@ -443,9 +473,10 @@ public void registerMethodLookup(ConfigurationCondition condition, Class decl @Override public void registerConstructorLookup(ConfigurationCondition condition, Class declaringClass, Class... parameterTypes) { + guaranteeNotRuntimeConditionForQueries(condition, true); runConditionalInAnalysisTask(condition, (cnd) -> { try { - registerMethod(true, declaringClass.getDeclaredConstructor(parameterTypes)); + registerMethod(cnd, true, declaringClass.getDeclaredConstructor(parameterTypes)); } catch (NoSuchMethodException e) { negativeConstructorLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()) .add(metaAccess.lookupJavaTypes(parameterTypes)); @@ -456,7 +487,7 @@ public void registerConstructorLookup(ConfigurationCondition condition, Class @Override public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) { requireNonNull(fields, "field"); - runConditionalInAnalysisTask(condition, (cnd) -> registerFields(false, fields)); + runConditionalInAnalysisTask(condition, (cnd) -> registerFields(cnd, false, fields)); } @Override @@ -465,12 +496,13 @@ public void registerAllFieldsQuery(ConfigurationCondition condition, Class cl } private void registerAllFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { + guaranteeNotRuntimeConditionForQueries(condition, queriedOnly); runConditionalInAnalysisTask(condition, (cnd) -> { for (Class current = clazz; current != null; current = current.getSuperclass()) { setQueryFlag(current, ALL_FIELDS_FLAG); } try { - registerFields(queriedOnly, clazz.getFields()); + registerFields(cnd, queriedOnly, clazz.getFields()); } catch (LinkageError e) { registerLinkageError(clazz, e, fieldLookupExceptions); } @@ -486,26 +518,27 @@ private void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, bo runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_DECLARED_FIELDS_FLAG); try { - registerFields(queriedOnly, clazz.getDeclaredFields()); + registerFields(cnd, queriedOnly, clazz.getDeclaredFields()); } catch (LinkageError e) { registerLinkageError(clazz, e, fieldLookupExceptions); } }); } - private void registerFields(boolean queriedOnly, Field[] reflectFields) { + private void registerFields(ConfigurationCondition cnd, boolean queriedOnly, Field[] reflectFields) { for (Field reflectField : reflectFields) { - registerField(queriedOnly, reflectField); + registerField(cnd, queriedOnly, reflectField); } } - private void registerField(boolean queriedOnly, Field reflectField) { + private void registerField(ConfigurationCondition cnd, boolean queriedOnly, Field reflectField) { if (SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) { return; } AnalysisField analysisField = metaAccess.lookupJavaField(reflectField); - if (registeredFields.put(analysisField, reflectField) == null) { + + if (!registeredFields.containsKey(analysisField)) { registerTypesForField(analysisField, reflectField, true); AnalysisType declaringClass = analysisField.getDeclaringClass(); @@ -518,10 +551,13 @@ private void registerField(boolean queriedOnly, Field reflectField) { declaringClass.getJavaClass()); if (declaringClass.isAnnotation()) { - processAnnotationField(reflectField); + processAnnotationField(cnd, reflectField); } } + var cndValue = registeredFields.computeIfAbsent(analysisField, f -> new ConditionalRuntimeValue<>(RuntimeConditionSet.createHosted(), reflectField)); + cndValue.getConditions().addCondition(cnd); + /* * We need to run this even if the method has already been registered, in case it was only * registered as queried. @@ -535,8 +571,13 @@ private void registerField(boolean queriedOnly, Field reflectField) { public void registerFieldLookup(ConfigurationCondition condition, Class declaringClass, String fieldName) { runConditionalInAnalysisTask(condition, (cnd) -> { try { - registerField(false, declaringClass.getDeclaredField(fieldName)); + registerField(cnd, false, declaringClass.getDeclaredField(fieldName)); } catch (NoSuchFieldException e) { + /* + * This path is not possible to reach at runtime with run-time conditions as they + * can only be used with `type` which includes all fields so negative lookups are + * not necessary. + */ negativeFieldLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()).add(fieldName); } }); @@ -560,12 +601,11 @@ private void processAnnotationMethod(boolean queriedOnly, Method method) { } @SuppressWarnings("deprecation") - private void processAnnotationField(Field field) { + private void processAnnotationField(ConfigurationCondition cnd, Field field) { Class annotationClass = field.getDeclaringClass(); Class proxyClass = Proxy.getProxyClass(annotationClass.getClassLoader(), annotationClass); try { - var condition = ConfigurationCondition.create(proxyClass, true); - register(condition, false, proxyClass.getDeclaredField(field.getName())); + register(cnd, false, proxyClass.getDeclaredField(field.getName())); } catch (NoSuchFieldException e) { /* * The annotation member is not present in the proxy class so we don't add it. @@ -795,7 +835,7 @@ private void registerTypesForGenericSignature(Type type, int dimension) { /* * Reflection signature parsing will try to instantiate classes via Class.forName(). */ - ClassForNameSupport.singleton().registerClass(clazz); + classForNameSupport.registerClass(clazz); } else if (type instanceof TypeVariable) { /* Bounds are reified lazily. */ registerTypesForGenericSignature(queryGenericInfo(((TypeVariable) type)::getBounds), dimension); @@ -1010,13 +1050,13 @@ public int getEnabledReflectionQueries(Class clazz) { } @Override - public Map getReflectionFields() { + public Map> getReflectionFields() { assert sealed; return Collections.unmodifiableMap(registeredFields); } @Override - public Map getReflectionExecutables() { + public Map> getReflectionExecutables() { assert sealed; return Collections.unmodifiableMap(registeredMethods); } @@ -1068,7 +1108,7 @@ public void registerHeapReflectionField(Field reflectField, ScanReason reason) { if (heapFields.put(analysisField, reflectField) == null && !SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) { registerTypesForField(analysisField, reflectField, false); if (analysisField.getDeclaringClass().isAnnotation()) { - processAnnotationField(reflectField); + processAnnotationField(ConfigurationCondition.alwaysTrue(), reflectField); } } } @@ -1179,7 +1219,7 @@ private static String nullErrorMessage(String kind) { public static class TestBackdoor { public static void registerField(ReflectionDataBuilder reflectionDataBuilder, boolean queriedOnly, Field field) { - reflectionDataBuilder.runConditionalInAnalysisTask(ConfigurationCondition.alwaysTrue(), (cnd) -> reflectionDataBuilder.registerField(queriedOnly, field)); + reflectionDataBuilder.runConditionalInAnalysisTask(ConfigurationCondition.alwaysTrue(), (cnd) -> reflectionDataBuilder.registerField(cnd, queriedOnly, field)); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index 27d3c3c42631..530c1eefb631 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -31,7 +31,6 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -56,11 +55,13 @@ import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.ReflectionConfigurationParser; +import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; import com.oracle.svm.core.graal.meta.KnownOffsets; import com.oracle.svm.core.hub.ClassForNameSupport; +import com.oracle.svm.core.hub.ClassForNameSupportFeature; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.meta.SharedMethod; @@ -256,7 +257,7 @@ private MethodPointer asMethodPointer(ResolvedJavaMethod method) { @Override public List> getRequiredFeatures() { - return Collections.singletonList(DynamicProxyFeature.class); + return List.of(ClassForNameSupportFeature.class, DynamicProxyFeature.class); } @Override @@ -334,6 +335,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { /* Make sure array classes don't need to be registered for reflection. */ RuntimeReflection.register(Object.class.getDeclaredMethods()); + + access.registerAsInHeap(RuntimeConditionSet.UnmodifiableRuntimeConditionSet.class); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionHostedSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionHostedSupport.java index 95181691bc20..957952c7b4f8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionHostedSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionHostedSupport.java @@ -34,13 +34,14 @@ import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.configure.ConditionalRuntimeValue; public interface ReflectionHostedSupport { Map, Set>> getReflectionInnerClasses(); - Map getReflectionFields(); + Map> getReflectionFields(); - Map getReflectionExecutables(); + Map> getReflectionExecutables(); Object getAccessor(AnalysisMethod method); From afaa640254f0c5464a43824af557796e77fba029 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Fri, 17 May 2024 16:33:04 +0200 Subject: [PATCH 2/3] typeReached for proxies --- .../hosted/RuntimeProxyCreation.java | 5 ++- .../impl/RuntimeProxyCreationSupport.java | 4 +- .../core/configure/ConfigurationParser.java | 2 +- .../reflect/proxy/DynamicProxySupport.java | 23 +++++++---- .../svm/hosted/jdk/JmxCommonFeature.java | 38 +++++++++++-------- .../svm/hosted/jdk/JmxServerFeature.java | 4 +- .../hosted/reflect/proxy/ProxyRegistry.java | 2 +- 7 files changed, 46 insertions(+), 32 deletions(-) diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeProxyCreation.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeProxyCreation.java index 7018ed1b55f3..a2f4f7bb781e 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeProxyCreation.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeProxyCreation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -43,6 +43,7 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeProxyCreationSupport; /** @@ -61,7 +62,7 @@ public final class RuntimeProxyCreation { * @since 22.3 */ public static void register(Class... interfaces) { - ImageSingletons.lookup(RuntimeProxyCreationSupport.class).addProxyClass(interfaces); + ImageSingletons.lookup(RuntimeProxyCreationSupport.class).addProxyClass(ConfigurationCondition.alwaysTrue(), interfaces); } private RuntimeProxyCreation() { diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeProxyCreationSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeProxyCreationSupport.java index 1421e0513fca..f7ca149c3c3f 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeProxyCreationSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeProxyCreationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -41,5 +41,5 @@ package org.graalvm.nativeimage.impl; public interface RuntimeProxyCreationSupport { - void addProxyClass(Class... interfaces); + void addProxyClass(ConfigurationCondition condition, Class... interfaces); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java index e62e0a263913..fd689650bb95 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java @@ -231,7 +231,7 @@ protected UnresolvedConfigurationCondition parseCondition(EconomicMap proxyCache = ImageHeapMap.create(); + private final EconomicMap> proxyCache = ImageHeapMap.create(); @Platforms(Platform.HOSTED_ONLY.class) public DynamicProxySupport() { @@ -86,7 +90,8 @@ public DynamicProxySupport() { @Override @Platforms(Platform.HOSTED_ONLY.class) - public synchronized void addProxyClass(Class... interfaces) { + public synchronized void addProxyClass(ConfigurationCondition condition, Class... interfaces) { + VMError.guarantee(condition.isRuntimeChecked(), "The condition used must be a runtime condition."); /* * Make a defensive copy of the interfaces array to protect against the caller modifying the * array. @@ -95,8 +100,9 @@ public synchronized void addProxyClass(Class... interfaces) { ProxyCacheKey key = new ProxyCacheKey(intfs); if (!proxyCache.containsKey(key)) { - proxyCache.put(key, createProxyClass(intfs)); + proxyCache.put(key, new ConditionalRuntimeValue<>(RuntimeConditionSet.createHosted(), createProxyClass(intfs))); } + proxyCache.get(key).getConditions().addCondition(condition); } @Platforms(Platform.HOSTED_ONLY.class) @@ -168,14 +174,15 @@ private static ClassLoader getCommonClassLoaderOrFail(ClassLoader loader, Class< @Override public Class getProxyClass(ClassLoader loader, Class... interfaces) { ProxyCacheKey key = new ProxyCacheKey(interfaces); - Object clazzOrError = proxyCache.get(key); - if (clazzOrError == null) { + ConditionalRuntimeValue clazzOrError = proxyCache.get(key); + + if (clazzOrError == null || !clazzOrError.getConditions().satisfied()) { throw MissingReflectionRegistrationUtils.errorForProxy(interfaces); } - if (clazzOrError instanceof Throwable) { - throw new GraalError((Throwable) clazzOrError); + if (clazzOrError.getValue() instanceof Throwable) { + throw new GraalError((Throwable) clazzOrError.getValue()); } - Class clazz = (Class) clazzOrError; + Class clazz = (Class) clazzOrError.getValue(); if (!DynamicHub.fromClass(clazz).isLoaded()) { /* * NOTE: we might race with another thread in loading this proxy class. diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxCommonFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxCommonFeature.java index 3ad130a8841f..3ac2efba45ea 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxCommonFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxCommonFeature.java @@ -38,6 +38,7 @@ import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; import com.oracle.svm.core.jni.JNIRuntimeAccess; import com.oracle.svm.util.ReflectionUtil; +import org.graalvm.nativeimage.impl.ConfigurationCondition; @AutomaticallyRegisteredFeature public class JmxCommonFeature implements InternalFeature { @@ -157,22 +158,27 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { */ private static void configureProxy(BeforeAnalysisAccess access) { DynamicProxyRegistry dynamicProxySupport = ImageSingletons.lookup(DynamicProxyRegistry.class); - dynamicProxySupport.addProxyClass(access.findClassByName("com.sun.management.GarbageCollectorMXBean"), access.findClassByName("javax.management.NotificationEmitter")); - dynamicProxySupport.addProxyClass(access.findClassByName("com.sun.management.OperatingSystemMXBean")); - dynamicProxySupport.addProxyClass(access.findClassByName("com.sun.management.ThreadMXBean")); - dynamicProxySupport.addProxyClass(access.findClassByName("com.sun.management.UnixOperatingSystemMXBean")); - dynamicProxySupport.addProxyClass(access.findClassByName("java.lang.management.BufferPoolMXBean")); - dynamicProxySupport.addProxyClass(access.findClassByName("java.lang.management.ClassLoadingMXBean")); - dynamicProxySupport.addProxyClass(access.findClassByName("java.lang.management.CompilationMXBean")); - dynamicProxySupport.addProxyClass(access.findClassByName("java.lang.management.GarbageCollectorMXBean"), access.findClassByName("javax.management.NotificationEmitter")); - dynamicProxySupport.addProxyClass(access.findClassByName("java.lang.management.MemoryManagerMXBean"), access.findClassByName("javax.management.NotificationEmitter")); - dynamicProxySupport.addProxyClass(access.findClassByName("java.lang.management.MemoryManagerMXBean")); - dynamicProxySupport.addProxyClass(access.findClassByName("java.lang.management.MemoryPoolMXBean"), access.findClassByName("javax.management.NotificationEmitter")); - dynamicProxySupport.addProxyClass(access.findClassByName("java.lang.management.MemoryMXBean"), access.findClassByName("javax.management.NotificationEmitter")); - dynamicProxySupport.addProxyClass(access.findClassByName("java.lang.management.OperatingSystemMXBean")); - dynamicProxySupport.addProxyClass(access.findClassByName("java.lang.management.RuntimeMXBean")); - dynamicProxySupport.addProxyClass(access.findClassByName("java.lang.management.ThreadMXBean")); - dynamicProxySupport.addProxyClass(access.findClassByName("jdk.management.jfr.FlightRecorderMXBean"), + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("com.sun.management.GarbageCollectorMXBean"), + access.findClassByName("javax.management.NotificationEmitter")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("com.sun.management.OperatingSystemMXBean")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("com.sun.management.ThreadMXBean")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("com.sun.management.UnixOperatingSystemMXBean")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("java.lang.management.BufferPoolMXBean")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("java.lang.management.ClassLoadingMXBean")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("java.lang.management.CompilationMXBean")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("java.lang.management.GarbageCollectorMXBean"), + access.findClassByName("javax.management.NotificationEmitter")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("java.lang.management.MemoryManagerMXBean"), + access.findClassByName("javax.management.NotificationEmitter")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("java.lang.management.MemoryManagerMXBean")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("java.lang.management.MemoryPoolMXBean"), + access.findClassByName("javax.management.NotificationEmitter")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("java.lang.management.MemoryMXBean"), + access.findClassByName("javax.management.NotificationEmitter")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("java.lang.management.OperatingSystemMXBean")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("java.lang.management.RuntimeMXBean")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("java.lang.management.ThreadMXBean")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("jdk.management.jfr.FlightRecorderMXBean"), access.findClassByName("javax.management.NotificationEmitter")); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxServerFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxServerFeature.java index c358226241e7..fd43cdf1e10c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxServerFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxServerFeature.java @@ -89,10 +89,10 @@ private static void registerJMXAgentResources() { private static void configureProxy(BeforeAnalysisAccess access) { DynamicProxyRegistry dynamicProxySupport = ImageSingletons.lookup(DynamicProxyRegistry.class); - dynamicProxySupport.addProxyClass(access.findClassByName("java.rmi.Remote"), + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("java.rmi.Remote"), access.findClassByName("java.rmi.registry.Registry")); - dynamicProxySupport.addProxyClass(access.findClassByName("javax.management.remote.rmi.RMIServer")); + dynamicProxySupport.addProxyClass(ConfigurationCondition.alwaysTrue(), access.findClassByName("javax.management.remote.rmi.RMIServer")); } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/ProxyRegistry.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/ProxyRegistry.java index 1a896f12aae9..f2a2e1093e89 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/ProxyRegistry.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/ProxyRegistry.java @@ -49,7 +49,7 @@ public void accept(ConfigurationCondition condition, List proxies) { if (interfaces != null) { registerConditionalConfiguration(condition, (cnd) -> { /* The interfaces array can be empty. The java.lang.reflect.Proxy API allows it. */ - dynamicProxySupport.addProxyClass(interfaces); + dynamicProxySupport.addProxyClass(cnd, interfaces); }); } } From 01c61f036390140fce0619cd0fef094a5f6fe36d Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 23 May 2024 18:59:26 +0200 Subject: [PATCH 3/3] Remove abstractions over different conditions --- .../configure/config/TypeConfiguration.java | 2 +- .../core/code/RuntimeMetadataDecoderImpl.java | 9 +- .../core/configure/ConfigurationFiles.java | 7 +- .../core/configure/ConfigurationParser.java | 8 +- .../ConfigurationTypeDescriptor.java | 2 +- .../NamedConfigurationTypeDescriptor.java | 11 +- .../ProxyConfigurationTypeDescriptor.java | 2 +- .../ReflectionConfigurationParser.java | 8 +- .../svm/core/configure/RuntimeCondition.java | 76 ------------- .../core/configure/RuntimeConditionSet.java | 102 +++++++++++------- .../SerializationConfigurationParser.java | 6 +- .../core/configure/TypeReachedCondition.java | 75 ------------- .../svm/core/hub/ClassForNameSupport.java | 3 +- .../reflect/proxy/DynamicProxySupport.java | 2 +- ...et_java_lang_reflect_AccessibleObject.java | 9 +- .../ClassInitializationSupport.java | 2 +- .../code/RuntimeMetadataEncoderImpl.java | 16 +-- .../config/ConfigurationParserUtils.java | 4 +- .../hosted/reflect/ReflectionDataBuilder.java | 11 +- .../svm/hosted/reflect/ReflectionFeature.java | 3 - 20 files changed, 119 insertions(+), 239 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeCondition.java delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/TypeReachedCondition.java diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java index 54ceb576be6f..f95af6821f57 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java @@ -150,7 +150,7 @@ public void printJson(JsonWriter writer) throws IOException { @Override public ConfigurationParser createParser() { - return new ReflectionConfigurationParser<>(ConfigurationConditionResolver.identityResolver(), new ParserConfigurationAdapter(this), true, false); + return new ReflectionConfigurationParser<>(ConfigurationConditionResolver.identityResolver(), new ParserConfigurationAdapter(this), true, false, false); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeMetadataDecoderImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeMetadataDecoderImpl.java index 9b264865ddbf..283428b0b9a7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeMetadataDecoderImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeMetadataDecoderImpl.java @@ -33,8 +33,6 @@ import java.lang.reflect.Parameter; import java.lang.reflect.RecordComponent; import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; import java.util.function.Function; import org.graalvm.nativeimage.ImageSingletons; @@ -43,7 +41,6 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.c.NonmovableArrays; -import com.oracle.svm.core.configure.RuntimeCondition; import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.hub.DynamicHub; @@ -381,11 +378,7 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class declaringC private static RuntimeConditionSet decodeConditions(UnsafeArrayTypeReader buf) { var conditionTypes = decodeArray(buf, Class.class, i -> decodeType(buf)); - Set runtimeConditions = new HashSet<>(conditionTypes.length); - for (Class conditionType : conditionTypes) { - runtimeConditions.add(RuntimeCondition.createTypeReachedCondition(conditionType)); - } - return RuntimeConditionSet.createRuntime(runtimeConditions); + return RuntimeConditionSet.createDecoded(conditionTypes); } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java index 9e6d1cd313ad..328669446426 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java @@ -128,10 +128,13 @@ public static final class Options { @Option(help = "Testing flag: the typeReachable condition is treated as typeReached so the semantics of programs can change.")// public static final HostedOptionKey TreatAllTypeReachableConditionsAsTypeReached = new HostedOptionKey<>(false); - @Option(help = "Testing flag: the typeReached condition is always satisfied however it prints the stack traces where it would not be satisfied.")// + @Option(help = "Testing flag: the 'name' is treated as 'type' reflection configuration.")// + public static final HostedOptionKey TreatAllNameEntriesAsType = new HostedOptionKey<>(false); + + @Option(help = "Testing flag: the 'typeReached' condition is always satisfied however it prints the stack trace where it would not be satisfied.")// public static final HostedOptionKey TrackUnsatisfiedTypeReachedConditions = new HostedOptionKey<>(false); - @Option(help = "Testing flag: print typeReached conditions that are used on interfaces.")// + @Option(help = "Testing flag: print 'typeReached' conditions that are used on interfaces at build time.")// public static final HostedOptionKey TrackTypeReachedOnInterfaces = new HostedOptionKey<>(false); @Option(help = "Testing flag: every type is considered as it participates in a typeReachable condition.")// diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java index fd689650bb95..1b9ca1ff73de 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java @@ -215,7 +215,7 @@ protected UnresolvedConfigurationCondition parseCondition(EconomicMap parseTypeOrName(EconomicMap data) { + protected static Optional parseTypeOrName(EconomicMap data, boolean treatAllNameEntriesAsType) { Object typeObject = data.get(TYPE_KEY); Object name = data.get(NAME_KEY); if (typeObject != null) { return parseTypeContents(typeObject); } else if (name != null) { - return Optional.of(new NamedConfigurationTypeDescriptor(asString(name))); + return Optional.of(new NamedConfigurationTypeDescriptor(asString(name), treatAllNameEntriesAsType)); } else { throw failOnSchemaError("must have type or name specified for an element"); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java index 1793ba296201..b62249e7e57d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java @@ -73,5 +73,5 @@ static String checkQualifiedJavaName(String javaName) { return canonicalizeTypeName(javaName); } - boolean isType(); + boolean definedAsType(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java index 31137cae8876..9f1433dfbcbd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java @@ -30,20 +30,20 @@ import com.oracle.svm.core.util.json.JsonWriter; -public record NamedConfigurationTypeDescriptor(String name, boolean type) implements ConfigurationTypeDescriptor { +public record NamedConfigurationTypeDescriptor(String name, boolean definedAsType) implements ConfigurationTypeDescriptor { public NamedConfigurationTypeDescriptor(String name) { this(name, false); } - public NamedConfigurationTypeDescriptor(String name, boolean type) { + public NamedConfigurationTypeDescriptor(String name, boolean definedAsType) { this.name = ConfigurationTypeDescriptor.checkQualifiedJavaName(name); - this.type = type; + this.definedAsType = definedAsType; } @Override - public boolean isType() { - return type; + public boolean definedAsType() { + return definedAsType; } @Override @@ -74,5 +74,4 @@ public int compareTo(ConfigurationTypeDescriptor other) { public void printJson(JsonWriter writer) throws IOException { writer.quote(name); } - } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java index 72da3452124c..25ac6a024013 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java @@ -65,7 +65,7 @@ public int compareTo(ConfigurationTypeDescriptor other) { } @Override - public boolean isType() { + public boolean definedAsType() { return true; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java index 8dcaec3870d4..6927183e9d65 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java @@ -56,13 +56,15 @@ public final class ReflectionConfigurationParser extends ConfigurationPars "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, "queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods", "unsafeAllocated"); private final boolean printMissingElements; + private final boolean treatAllNameEntriesAsType; public ReflectionConfigurationParser(ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, - boolean printMissingElements) { + boolean printMissingElements, boolean treatAllNameEntriesAsType) { super(strictConfiguration); this.conditionResolver = conditionResolver; this.printMissingElements = printMissingElements; this.delegate = delegate; + this.treatAllNameEntriesAsType = treatAllNameEntriesAsType; } @Override @@ -80,11 +82,11 @@ private void parseClass(EconomicMap data) { checkAttributes(data, "reflection class descriptor object", Collections.emptyList(), OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS); checkHasExactlyOneAttribute(data, "reflection class descriptor object", List.of("name", "type")); - Optional type = parseTypeOrName(data); + Optional type = parseTypeOrName(data, treatAllNameEntriesAsType); if (type.isEmpty()) { return; } - boolean isType = type.get().isType(); + boolean isType = type.get().definedAsType(); UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, isType); TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeCondition.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeCondition.java deleted file mode 100644 index e4a757cd7d15..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeCondition.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.core.configure; - -import java.util.Set; -import java.util.WeakHashMap; - -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.impl.ConfigurationCondition; - -import com.oracle.svm.core.util.VMError; - -/** - * Represents a super-type for all metadata conditions that are checked at runtime. It is created - * from {@link ConfigurationCondition}s that represent the build-time conditions. - *

- * {@link RuntimeCondition}s can be stored into the heap or encoded into the image for space - * reduction ({@link #getTypesForEncoding()}. All conditions are cached in the - * {@link #runtimeConditionCache} to save space in the image heap and hence they must implement - * {@link Object#equals(Object)} and {@link Object#hashCode()}. {@link RuntimeCondition} is most - * often used in groups that are stored in {@link RuntimeConditionSet}. - */ -public sealed interface RuntimeCondition permits TypeReachedCondition { - - WeakHashMap runtimeConditionCache = new WeakHashMap<>(); - - static RuntimeCondition create(ConfigurationCondition cnd) { - if (cnd.isAlwaysTrue() || !cnd.isRuntimeChecked()) { - throw VMError.shouldNotReachHere("We should never create run-time conditions from conditions that are always true at build time. Condition: " + cnd); - } - return createTypeReachedCondition(cnd.getType()); - } - - static RuntimeCondition createTypeReachedCondition(Class type) { - TypeReachedCondition typeReachedCondition = new TypeReachedCondition(type); - synchronized (runtimeConditionCache) { - if (runtimeConditionCache.containsKey(typeReachedCondition)) { - return runtimeConditionCache.get(typeReachedCondition); - } else { - runtimeConditionCache.put(typeReachedCondition, typeReachedCondition); - return typeReachedCondition; - } - } - } - - /** - * @return true if the condition has been satisfied at run time. - */ - boolean isSatisfied(); - - @Platforms(Platform.HOSTED_ONLY.class) - Set> getTypesForEncoding(); -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java index 67c7b8b3a3f5..d403da2152bc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java @@ -30,43 +30,40 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.impl.ConfigurationCondition; +import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.LogUtils; /** - * Represents a group of {@link RuntimeCondition}s that guard a value. + * Represents a group of {@link #conditions} that guard a value. The conditions are encoded *

* If any of the {@link #conditions} is satisfied then the whole set becomes also * {@link #satisfied}. {@link RuntimeConditionSet}s can be created at build time - * {@link #createHosted(ConfigurationCondition...)} and stored to the image heap, or it can be - * encoded ({@link #getTypesForEncoding()} and later decoded at run time - * ({@link #createRuntime(Set)}. The current implementation does not cache {@link #conditions}, - * although this will be implemented in the future (GR-49526) + * {@link #createHosted(ConfigurationCondition)} and stored to the image heap, or it can be encoded + * ({@link #getTypesForEncoding()} and later decoded at run time ({@link #createDecoded(Object[])}. + * The current implementation does not cache {@link #conditions}, although this will be implemented + * in the future (GR-49526) */ public class RuntimeConditionSet { - private RuntimeCondition[] conditions; + private Object[] conditions; private boolean satisfied; @Platforms(Platform.HOSTED_ONLY.class) - public static RuntimeConditionSet createHosted(ConfigurationCondition... conditions) { - var conditionSet = new RuntimeConditionSet(Set.of()); - for (ConfigurationCondition condition : conditions) { - conditionSet.addCondition(condition); - } - return conditionSet; + public static RuntimeConditionSet emptySet() { + return new RuntimeConditionSet(new Object[0]); } @Platforms(Platform.HOSTED_ONLY.class) - public static RuntimeConditionSet unmodifiableEmptySet() { - return UnmodifiableRuntimeConditionSet.UNMODIFIABLE_EMPTY_SET; + public static RuntimeConditionSet createHosted(ConfigurationCondition condition) { + var conditionSet = new RuntimeConditionSet(new Object[0]); + conditionSet.addCondition(condition); + return conditionSet; } @Platforms(Platform.HOSTED_ONLY.class) @@ -80,10 +77,10 @@ public synchronized void addCondition(ConfigurationCondition cnd) { return; } - RuntimeCondition newRuntimeCondition = RuntimeCondition.create(cnd); - Stream existingConditions = conditions == null ? Stream.empty() : Arrays.stream(conditions); - setConditions(Stream.concat(existingConditions, Stream.of(newRuntimeCondition)) - .collect(Collectors.toSet())); + Object newRuntimeCondition = createRuntimeCondition(cnd); + Set existingConditions = conditions == null ? new HashSet<>() : new HashSet<>(Arrays.asList(conditions)); + existingConditions.add(newRuntimeCondition); + setConditions(existingConditions.toArray()); } @Platforms(Platform.HOSTED_ONLY.class) @@ -92,28 +89,19 @@ public Set> getTypesForEncoding() { return Set.of(); } else { Set> types = new HashSet<>(); - for (RuntimeCondition condition : conditions) { - types.addAll(condition.getTypesForEncoding()); + for (Object condition : conditions) { + types.addAll(getTypesForEncoding(condition)); } return types; } } - public static RuntimeConditionSet createRuntime(Set conditions) { - return new RuntimeConditionSet(conditions); - } - - private RuntimeConditionSet(Set conditions) { - setConditions(conditions); + public static RuntimeConditionSet unmodifiableEmptySet() { + return UnmodifiableRuntimeConditionSet.UNMODIFIABLE_EMPTY_SET; } - private void setConditions(Set conditions) { - if (conditions.isEmpty()) { - this.conditions = null; - } else { - this.conditions = conditions.toArray(RuntimeCondition[]::new); - } - satisfied = false; + public static RuntimeConditionSet createDecoded(Object[] conditions) { + return new RuntimeConditionSet(conditions); } /** @@ -133,8 +121,8 @@ public boolean satisfied() { if (localConditions == null) { result = true; } else { - for (RuntimeCondition condition : localConditions) { - if (condition.isSatisfied()) { + for (Object condition : localConditions) { + if (isSatisfied(condition)) { conditions = null; satisfied = result = true; break; @@ -158,10 +146,46 @@ public String toString() { return conditionsString + " = " + satisfied; } + private RuntimeConditionSet(Object[] conditions) { + setConditions(conditions); + } + + private void setConditions(Object[] conditions) { + if (conditions.length == 0) { + this.conditions = null; + } else { + this.conditions = conditions; + } + satisfied = false; + } + + private static Object createRuntimeCondition(ConfigurationCondition cnd) { + if (cnd.isAlwaysTrue() || !cnd.isRuntimeChecked()) { + throw VMError.shouldNotReachHere("We should never create run-time conditions from conditions that are always true at build time. Condition: " + cnd); + } + return cnd.getType(); + } + + private static boolean isSatisfied(Object condition) { + if (condition instanceof Class typeReachedCondition) { + return DynamicHub.fromClass(typeReachedCondition).isReached(); + } else { + throw VMError.shouldNotReachHere("Only typeReached condition is supported."); + } + } + + private static Set> getTypesForEncoding(Object condition) { + if (condition instanceof Class res) { + return Set.of(res); + } else { + throw VMError.shouldNotReachHere("Only typeReached condition is supported."); + } + } + public static final class UnmodifiableRuntimeConditionSet extends RuntimeConditionSet { - private static final RuntimeConditionSet UNMODIFIABLE_EMPTY_SET = new UnmodifiableRuntimeConditionSet(Set.of()); + private static final RuntimeConditionSet UNMODIFIABLE_EMPTY_SET = new UnmodifiableRuntimeConditionSet(new Object[0]); - private UnmodifiableRuntimeConditionSet(Set conditions) { + private UnmodifiableRuntimeConditionSet(Object[] conditions) { super(conditions); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java index c987d657382e..62e74e85188c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java @@ -47,12 +47,14 @@ public class SerializationConfigurationParser extends ConfigurationParser { private final ConfigurationConditionResolver conditionResolver; private final RuntimeSerializationSupport serializationSupport; private final ProxyConfigurationParser proxyConfigurationParser; + private final boolean treatAllNameEntriesAsType; public SerializationConfigurationParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { super(strictConfiguration); this.serializationSupport = serializationSupport; this.proxyConfigurationParser = new ProxyConfigurationParser<>(conditionResolver, strictConfiguration, serializationSupport::registerProxyClass); this.conditionResolver = conditionResolver; + this.treatAllNameEntriesAsType = false; } @Override @@ -100,12 +102,12 @@ private void parseSerializationDescriptorObject(EconomicMap data checkHasExactlyOneAttribute(data, "serialization descriptor object", List.of(TYPE_KEY, NAME_KEY)); } - Optional targetSerializationClass = parseTypeOrName(data); + Optional targetSerializationClass = parseTypeOrName(data, treatAllNameEntriesAsType); if (targetSerializationClass.isEmpty()) { return; } - UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, targetSerializationClass.get().isType()); + UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, targetSerializationClass.get().definedAsType()); var condition = conditionResolver.resolveCondition(unresolvedCondition); if (!condition.isPresent()) { return; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/TypeReachedCondition.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/TypeReachedCondition.java deleted file mode 100644 index 2242db7ec436..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/TypeReachedCondition.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.svm.core.configure; - -import java.util.Objects; -import java.util.Set; - -import com.oracle.svm.core.hub.DynamicHub; - -/** - * This condition allows a runtime value to be access if {@link #type} is reached at run time. - */ -public final class TypeReachedCondition implements RuntimeCondition { - final Class type; - - TypeReachedCondition(Class type) { - this.type = type; - } - - @Override - public boolean isSatisfied() { - return DynamicHub.fromClass(type).isReached(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - TypeReachedCondition that = (TypeReachedCondition) o; - return Objects.equals(type, that.type); - } - - @Override - public int hashCode() { - return Objects.hashCode(type); - } - - @Override - public Set> getTypesForEncoding() { - return Set.of(type); - } - - @Override - public String toString() { - return type.getName(); - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java index 1910250a15d0..2e3dccc75dad 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java @@ -27,7 +27,6 @@ import static com.oracle.svm.core.MissingRegistrationUtils.throwMissingRegistrationErrors; import java.util.Objects; -import java.util.Set; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; @@ -190,7 +189,7 @@ public RuntimeConditionSet getConditionFor(Class jClass) { Objects.requireNonNull(jClass); ConditionalRuntimeValue conditionalClass = knownClasses.get(jClass.getName()); if (conditionalClass == null) { - return RuntimeConditionSet.createRuntime(Set.of()); + return RuntimeConditionSet.unmodifiableEmptySet(); } else { return conditionalClass.getConditions(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java index a20aa5ed64cf..43d25b8661c8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java @@ -100,7 +100,7 @@ public synchronized void addProxyClass(ConfigurationCondition condition, Class(RuntimeConditionSet.createHosted(), createProxyClass(intfs))); + proxyCache.put(key, new ConditionalRuntimeValue<>(RuntimeConditionSet.emptySet(), createProxyClass(intfs))); } proxyCache.get(key).getConditions().addCondition(condition); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_AccessibleObject.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_AccessibleObject.java index 0c904ce39074..95bbfe1de8e4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_AccessibleObject.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_AccessibleObject.java @@ -33,6 +33,7 @@ import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.configure.RuntimeConditionSet; +import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; @TargetClass(value = AccessibleObject.class) public final class Target_java_lang_reflect_AccessibleObject { @@ -61,10 +62,16 @@ public Object transform(Object receiver, Object originalValue) { } } - static class SatisfiedConditionComputer extends ReflectionMetadataComputer { + static class SatisfiedConditionComputer implements FieldValueTransformerWithAvailability { @Override public Object transform(Object receiver, Object originalValue) { return RuntimeConditionSet.unmodifiableEmptySet(); } + + @Override + public final ValueAvailability valueAvailability() { + return ValueAvailability.BeforeAnalysis; + } + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java index b4561f2cd4d6..fc06c960ee33 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java @@ -494,7 +494,7 @@ private static void addAllInterfaces(Class clazz, EconomicSet> resul } public void addForTypeReachedTracking(Class clazz) { - if (TrackTypeReachedOnInterfaces.getValue() && clazz.isInterface() && metaAccess.lookupJavaType(clazz).declaresDefaultMethods()) { + if (TrackTypeReachedOnInterfaces.getValue() && clazz.isInterface() && !metaAccess.lookupJavaType(clazz).declaresDefaultMethods()) { LogUtils.info("Detected 'typeReached' on interface type without default methods: " + clazz); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java index d35dd7bbaec5..e6beea0422b5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java @@ -574,7 +574,7 @@ public void addHidingFieldMetadata(AnalysisField analysisField, HostedType decla encoders.classes.addObject(type.getJavaClass()); addType(declaringType); - registerField(declaringType, analysisField, new FieldMetadata(RuntimeConditionSet.createHosted(), declaringType, name, type, modifiers)); + registerField(declaringType, analysisField, new FieldMetadata(RuntimeConditionSet.emptySet(), declaringType, name, type, modifiers)); } @Override @@ -587,7 +587,7 @@ public void addHidingMethodMetadata(AnalysisMethod analysisMethod, HostedType de encoders.classes.addObject(returnType.getJavaClass()); addType(declaringType); - registerMethod(declaringType, analysisMethod, new MethodMetadata(RuntimeConditionSet.createHosted(), declaringType, name, parameterTypes, modifiers, returnType)); + registerMethod(declaringType, analysisMethod, new MethodMetadata(RuntimeConditionSet.emptySet(), declaringType, name, parameterTypes, modifiers, returnType)); } @Override @@ -598,7 +598,7 @@ public void addReachableFieldMetadata(HostedField field) { /* Fill encoders with the necessary values. */ encoders.memberNames.addObject(name); - registerField(declaringType, field, new FieldMetadata(RuntimeConditionSet.createHosted(), declaringType, name, false)); + registerField(declaringType, field, new FieldMetadata(RuntimeConditionSet.emptySet(), declaringType, name, false)); } @Override @@ -617,16 +617,16 @@ public void addReachableExecutableMetadata(HostedMethod executable) { } if (isMethod) { - registerMethod(declaringType, executable, new MethodMetadata(RuntimeConditionSet.createHosted(), declaringType, name, parameterTypeNames)); + registerMethod(declaringType, executable, new MethodMetadata(RuntimeConditionSet.emptySet(), declaringType, name, parameterTypeNames)); } else { - registerConstructor(declaringType, executable, new ConstructorMetadata(RuntimeConditionSet.createHosted(), declaringType, parameterTypeNames)); + registerConstructor(declaringType, executable, new ConstructorMetadata(RuntimeConditionSet.emptySet(), declaringType, parameterTypeNames)); } } @Override public void addNegativeFieldQueryMetadata(HostedType declaringClass, String fieldName) { encoders.memberNames.addObject(fieldName); - registerField(declaringClass, fieldName, new FieldMetadata(RuntimeConditionSet.createHosted(), declaringClass, fieldName, true)); + registerField(declaringClass, fieldName, new FieldMetadata(RuntimeConditionSet.emptySet(), declaringClass, fieldName, true)); } @Override @@ -635,7 +635,7 @@ public void addNegativeMethodQueryMetadata(HostedType declaringClass, String met for (HostedType parameterType : parameterTypes) { encoders.classes.addObject(parameterType.getJavaClass()); } - registerMethod(declaringClass, Pair.create(methodName, parameterTypes), new MethodMetadata(RuntimeConditionSet.createHosted(), declaringClass, methodName, parameterTypes)); + registerMethod(declaringClass, Pair.create(methodName, parameterTypes), new MethodMetadata(RuntimeConditionSet.emptySet(), declaringClass, methodName, parameterTypes)); } @Override @@ -643,7 +643,7 @@ public void addNegativeConstructorQueryMetadata(HostedType declaringClass, Hoste for (HostedType parameterType : parameterTypes) { encoders.classes.addObject(parameterType.getJavaClass()); } - registerConstructor(declaringClass, parameterTypes, new ConstructorMetadata(RuntimeConditionSet.createHosted(), declaringClass, parameterTypes)); + registerConstructor(declaringClass, parameterTypes, new ConstructorMetadata(RuntimeConditionSet.emptySet(), declaringClass, parameterTypes)); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java index 400dced8dab3..a3f777e2c98a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java @@ -53,6 +53,8 @@ import jdk.graal.compiler.util.json.JSONParserException; +import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllNameEntriesAsType; + public final class ConfigurationParserUtils { public static ReflectionConfigurationParser> create( @@ -60,7 +62,7 @@ public static ReflectionConfigurationParser> cr return new ReflectionConfigurationParser<>(conditionResolver, RegistryAdapter.create(registry, proxyRegistry, imageClassLoader), ConfigurationFiles.Options.StrictConfiguration.getValue(), - ConfigurationFiles.Options.WarnAboutMissingReflectionOrJNIMetadataElements.getValue()); + ConfigurationFiles.Options.WarnAboutMissingReflectionOrJNIMetadataElements.getValue(), TreatAllNameEntriesAsType.getValue()); } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index 193172e7e443..2584825ea35c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -37,6 +37,7 @@ import static com.oracle.svm.core.code.RuntimeMetadataDecoderImpl.ALL_PERMITTED_SUBCLASSES_FLAG; import static com.oracle.svm.core.code.RuntimeMetadataDecoderImpl.ALL_RECORD_COMPONENTS_FLAG; import static com.oracle.svm.core.code.RuntimeMetadataDecoderImpl.ALL_SIGNERS_FLAG; +import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllTypeReachableConditionsAsTypeReached; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Executable; @@ -86,8 +87,8 @@ import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ConditionalConfigurationRegistry; -import com.oracle.svm.hosted.LinkAtBuildTimeSupport; import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.hosted.LinkAtBuildTimeSupport; import com.oracle.svm.hosted.annotation.AnnotationMemberValue; import com.oracle.svm.hosted.annotation.AnnotationValue; import com.oracle.svm.hosted.annotation.SubstrateAnnotationExtractor; @@ -218,7 +219,9 @@ public void registerAllClassesQuery(ConfigurationCondition condition, Class c */ @SuppressWarnings("unused") private static void guaranteeNotRuntimeConditionForQueries(ConfigurationCondition cnd, boolean queriedOnly) { - VMError.guarantee(!queriedOnly || cnd.isAlwaysTrue() || !cnd.isRuntimeChecked(), "Bulk queries can only be set with 'name' which does not allow run-time conditions."); + if (!TreatAllTypeReachableConditionsAsTypeReached.getValue()) { + VMError.guarantee(!queriedOnly || cnd.isAlwaysTrue() || !cnd.isRuntimeChecked(), "Bulk queries can only be set with 'name' which does not allow run-time conditions."); + } } @Override @@ -413,7 +416,7 @@ private void registerMethod(ConfigurationCondition cnd, boolean queriedOnly, Exe AnalysisMethod analysisMethod = metaAccess.lookupJavaMethod(reflectExecutable); var exists = registeredMethods.containsKey(analysisMethod); - var conditionalValue = registeredMethods.computeIfAbsent(analysisMethod, (t) -> new ConditionalRuntimeValue<>(RuntimeConditionSet.createHosted(), reflectExecutable)); + var conditionalValue = registeredMethods.computeIfAbsent(analysisMethod, (t) -> new ConditionalRuntimeValue<>(RuntimeConditionSet.emptySet(), reflectExecutable)); if (!queriedOnly) { /* queryOnly methods are conditioned by the type itself */ conditionalValue.getConditions().addCondition(cnd); @@ -555,7 +558,7 @@ private void registerField(ConfigurationCondition cnd, boolean queriedOnly, Fiel } } - var cndValue = registeredFields.computeIfAbsent(analysisField, f -> new ConditionalRuntimeValue<>(RuntimeConditionSet.createHosted(), reflectField)); + var cndValue = registeredFields.computeIfAbsent(analysisField, f -> new ConditionalRuntimeValue<>(RuntimeConditionSet.emptySet(), reflectField)); cndValue.getConditions().addCondition(cnd); /* diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index 530c1eefb631..e0a42e10d187 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -55,7 +55,6 @@ import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.ReflectionConfigurationParser; -import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; @@ -335,8 +334,6 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { /* Make sure array classes don't need to be registered for reflection. */ RuntimeReflection.register(Object.class.getDeclaredMethods()); - - access.registerAsInHeap(RuntimeConditionSet.UnmodifiableRuntimeConditionSet.class); } @Override