diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/common/util/FrequencyEncoder.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/common/util/FrequencyEncoder.java index 2692e494ad44..113326208025 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/common/util/FrequencyEncoder.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/core/common/util/FrequencyEncoder.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.Equivalence; @@ -71,6 +72,14 @@ public static FrequencyEncoder createEqualityEncoder() { return new FrequencyEncoder<>(EconomicMap.create(Equivalence.DEFAULT)); } + /* + * Creates an encoder that uses {@link Object#equals(Object)} object equality and calls the + * provided verifier before adding an element. + */ + public static FrequencyEncoder createVerifyingEqualityEncoder(Consumer verifier) { + return new VerifyingFrequencyEncoder<>(EconomicMap.create(Equivalence.DEFAULT), verifier); + } + protected FrequencyEncoder(EconomicMap> map) { this.map = map; } @@ -159,4 +168,19 @@ public T[] encodeAll(T[] allObjects) { encoded = true; return allObjects; } + + static class VerifyingFrequencyEncoder extends FrequencyEncoder { + private final Consumer verifier; + + VerifyingFrequencyEncoder(EconomicMap> map, Consumer verifier) { + super(map); + this.verifier = verifier; + } + + @Override + public boolean addObject(T object) { + verifier.accept(object); + return super.addObject(object); + } + } } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java index 5d17d6ac3fb3..14c74345636e 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java @@ -370,6 +370,10 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } } + if (tracer != null) { + tracer.traceTrackReflectionMetadata(trackReflectionMetadata); + } + try { BreakpointInterceptor.onLoad(jvmti, callbacks, tracer, this, interceptedStateSupplier, experimentalClassLoaderSupport, experimentalClassDefineSupport, experimentalUnsafeAllocationSupport, trackReflectionMetadata); diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/core/Tracer.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/core/Tracer.java index 01669919c4be..7f84ac9343c9 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/core/Tracer.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/core/Tracer.java @@ -74,6 +74,14 @@ public void tracePhaseChange(String phase) { traceEntry(entry); } + public void traceTrackReflectionMetadata(boolean trackReflectionMetadata) { + EconomicMap entry = EconomicMap.create(); + entry.put("tracer", "meta"); + entry.put("event", "track_reflection_metadata"); + entry.put("track", trackReflectionMetadata); + traceEntry(entry); + } + /** * Trace a call to a function or method. {@link Object} arguments are represented as strings by * calling {@link Object#toString()} on them unless they are {@link #EXPLICIT_NULL}, diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java index d81c90485d36..2d6be7200447 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java @@ -150,11 +150,13 @@ private static void doTestExpectedMissingTypes(TypeConfiguration typeConfig) { private static void doTestTypeFlags(TypeConfiguration typeConfig) { ConfigurationType flagTestHasDeclaredType = getConfigTypeOrFail(typeConfig, "FlagTestC"); - Assert.assertTrue(ConfigurationType.TestBackdoor.haveAllDeclaredClasses(flagTestHasDeclaredType) || ConfigurationType.TestBackdoor.haveAllDeclaredFields(flagTestHasDeclaredType) || + Assert.assertTrue(ConfigurationType.TestBackdoor.haveAllDeclaredClasses(flagTestHasDeclaredType) || + ConfigurationType.TestBackdoor.getAllDeclaredFields(flagTestHasDeclaredType) == ConfigurationMemberAccessibility.ACCESSED || ConfigurationType.TestBackdoor.getAllDeclaredConstructors(flagTestHasDeclaredType) == ConfigurationMemberAccessibility.ACCESSED); ConfigurationType flagTestHasPublicType = getConfigTypeOrFail(typeConfig, "FlagTestD"); - Assert.assertTrue(ConfigurationType.TestBackdoor.haveAllPublicClasses(flagTestHasPublicType) || ConfigurationType.TestBackdoor.haveAllPublicFields(flagTestHasPublicType) || + Assert.assertTrue(ConfigurationType.TestBackdoor.haveAllPublicClasses(flagTestHasPublicType) || + ConfigurationType.TestBackdoor.getAllPublicFields(flagTestHasPublicType) == ConfigurationMemberAccessibility.ACCESSED || ConfigurationType.TestBackdoor.getAllPublicConstructors(flagTestHasPublicType) == ConfigurationMemberAccessibility.ACCESSED); } @@ -281,13 +283,13 @@ void setFlags(ConfigurationType config) { config.setAllDeclaredClasses(); config.setAllDeclaredConstructors(ConfigurationMemberAccessibility.ACCESSED); config.setAllDeclaredMethods(ConfigurationMemberAccessibility.ACCESSED); - config.setAllDeclaredFields(); + config.setAllDeclaredFields(ConfigurationMemberAccessibility.ACCESSED); } if (methodKind.equals(ConfigurationMemberDeclaration.PUBLIC) || methodKind.equals(ConfigurationMemberDeclaration.DECLARED_AND_PUBLIC)) { config.setAllPublicClasses(); config.setAllPublicConstructors(ConfigurationMemberAccessibility.ACCESSED); config.setAllPublicMethods(ConfigurationMemberAccessibility.ACCESSED); - config.setAllPublicFields(); + config.setAllPublicFields(ConfigurationMemberAccessibility.ACCESSED); } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java index 0bb5e209c31f..a4830aabf2ed 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java @@ -98,8 +98,8 @@ static ConfigurationType copyAndMerge(ConfigurationType type, ConfigurationType private boolean allNestMembers; private boolean allSigners; private boolean allPublicClasses; - private boolean allDeclaredFields; - private boolean allPublicFields; + private ConfigurationMemberAccessibility allDeclaredFieldsAccess = ConfigurationMemberAccessibility.NONE; + private ConfigurationMemberAccessibility allPublicFieldsAccess = ConfigurationMemberAccessibility.NONE; private boolean unsafeAllocated; private ConfigurationMemberAccessibility allDeclaredMethodsAccess = ConfigurationMemberAccessibility.NONE; private ConfigurationMemberAccessibility allPublicMethodsAccess = ConfigurationMemberAccessibility.NONE; @@ -109,8 +109,8 @@ static ConfigurationType copyAndMerge(ConfigurationType type, ConfigurationType public ConfigurationType(UnresolvedConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean includeAllElements) { this.condition = condition; this.typeDescriptor = typeDescriptor; - allDeclaredClasses = allPublicClasses = allDeclaredFields = allPublicFields = allRecordComponents = allPermittedSubclasses = allNestMembers = allSigners = includeAllElements; - allDeclaredMethodsAccess = allPublicMethodsAccess = allDeclaredConstructorsAccess = allPublicConstructorsAccess = includeAllElements + allDeclaredClasses = allPublicClasses = allRecordComponents = allPermittedSubclasses = allNestMembers = allSigners = includeAllElements; + allDeclaredFieldsAccess = allPublicFieldsAccess = allDeclaredMethodsAccess = allPublicMethodsAccess = allDeclaredConstructorsAccess = allPublicConstructorsAccess = includeAllElements ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.NONE; } @@ -158,15 +158,15 @@ private void mergeFieldsFrom(ConfigurationType other) { }); } } - maybeRemoveFields(allDeclaredFields, allPublicFields); + maybeRemoveFields(allDeclaredFieldsAccess, allPublicFieldsAccess); } - private void maybeRemoveFields(boolean hasAllDeclaredFields, boolean hasAllPublicFields) { - if (hasAllDeclaredFields) { - removeFields(ConfigurationMemberDeclaration.DECLARED); + private void maybeRemoveFields(ConfigurationMemberAccessibility hasAllDeclaredFields, ConfigurationMemberAccessibility hasAllPublicFields) { + if (hasAllDeclaredFields != ConfigurationMemberAccessibility.NONE) { + removeFields(ConfigurationMemberDeclaration.DECLARED, hasAllDeclaredFields); } - if (hasAllPublicFields) { - removeFields(ConfigurationMemberDeclaration.PUBLIC); + if (hasAllPublicFields != ConfigurationMemberAccessibility.NONE) { + removeFields(ConfigurationMemberDeclaration.PUBLIC, hasAllPublicFields); } } @@ -249,7 +249,7 @@ private void removeFlags(ConfigurationType other) { } private void removeFields(ConfigurationType other) { - maybeRemoveFields(allDeclaredFields || other.allDeclaredFields, allPublicFields || other.allPublicFields); + maybeRemoveFields(allDeclaredFieldsAccess.combine(other.allDeclaredFieldsAccess), allPublicFieldsAccess.combine(other.allPublicFieldsAccess)); if (fields != null && other.fields != null) { for (Map.Entry fieldInfoEntry : other.fields.entrySet()) { fields.computeIfPresent(fieldInfoEntry.getKey(), (key, value) -> value.newWithDifferencesFrom(fieldInfoEntry.getValue())); @@ -279,8 +279,8 @@ private void setFlagsFromOther(ConfigurationType other, BiPredicate { FieldInfo fieldInfo = map.get(name); if (fieldInfo != null && !fieldInfo.isFinalButWritable()) { @@ -319,8 +321,8 @@ public synchronized void addField(String name, ConfigurationMemberDeclaration de fields = new HashMap<>(); } fields.compute(name, (k, v) -> (v != null) - ? FieldInfo.get(v.getKind().intersect(declaration), v.isFinalButWritable() || finalButWritable) - : FieldInfo.get(declaration, finalButWritable)); + ? FieldInfo.get(v.getKind().intersect(ConfigurationMemberInfo.get(declaration, accessibility)), v.isFinalButWritable() || finalButWritable) + : FieldInfo.get(declaration, accessibility, finalButWritable)); } public void addMethodsWithName(String name, ConfigurationMemberDeclaration declaration, ConfigurationMemberAccessibility accessibility) { @@ -402,14 +404,18 @@ public void setUnsafeAllocated() { this.unsafeAllocated = true; } - public synchronized void setAllDeclaredFields() { - allDeclaredFields = true; - removeFields(ConfigurationMemberDeclaration.DECLARED); + public synchronized void setAllDeclaredFields(ConfigurationMemberAccessibility accessibility) { + if (!allDeclaredFieldsAccess.includes(accessibility)) { + allDeclaredFieldsAccess = accessibility; + removeFields(ConfigurationMemberDeclaration.DECLARED, allDeclaredFieldsAccess); + } } - public synchronized void setAllPublicFields() { - allPublicFields = true; - removeFields(ConfigurationMemberDeclaration.PUBLIC); + public synchronized void setAllPublicFields(ConfigurationMemberAccessibility accessibility) { + if (!allPublicFieldsAccess.includes(accessibility)) { + allPublicFieldsAccess = accessibility; + removeFields(ConfigurationMemberDeclaration.PUBLIC, allDeclaredFieldsAccess); + } } public synchronized void setAllDeclaredMethods(ConfigurationMemberAccessibility accessibility) { @@ -444,27 +450,26 @@ public synchronized void setAllPublicConstructors(ConfigurationMemberAccessibili public synchronized void printJson(JsonWriter writer) throws IOException { writer.append('{').indent().newline(); ConfigurationConditionPrintable.printConditionAttribute(condition, writer); - /* GR-50385: Flip boolean entries below when "type" includes them by default. */ writer.quote("type").append(":"); typeDescriptor.printJson(writer); - optionallyPrintJsonBoolean(writer, allDeclaredFields, "allDeclaredFields"); - optionallyPrintJsonBoolean(writer, allPublicFields, "allPublicFields"); - optionallyPrintJsonBoolean(writer, allDeclaredMethodsAccess == ConfigurationMemberAccessibility.ACCESSED, "allDeclaredMethods"); - optionallyPrintJsonBoolean(writer, allPublicMethodsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicMethods"); - optionallyPrintJsonBoolean(writer, allDeclaredConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allDeclaredConstructors"); - optionallyPrintJsonBoolean(writer, allPublicConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicConstructors"); - optionallyPrintJsonBoolean(writer, allDeclaredClasses, "allDeclaredClasses"); - optionallyPrintJsonBoolean(writer, allRecordComponents, "allRecordComponents"); - optionallyPrintJsonBoolean(writer, allPermittedSubclasses, "allPermittedSubclasses"); - optionallyPrintJsonBoolean(writer, allNestMembers, "allNestMembers"); - optionallyPrintJsonBoolean(writer, allSigners, "allSigners"); - optionallyPrintJsonBoolean(writer, allPublicClasses, "allPublicClasses"); - optionallyPrintJsonBoolean(writer, allDeclaredMethodsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllDeclaredMethods"); - optionallyPrintJsonBoolean(writer, allPublicMethodsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllPublicMethods"); - optionallyPrintJsonBoolean(writer, allDeclaredConstructorsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllDeclaredConstructors"); - optionallyPrintJsonBoolean(writer, allPublicConstructorsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllPublicConstructors"); - optionallyPrintJsonBoolean(writer, unsafeAllocated, "unsafeAllocated"); + printJsonBooleanIfSet(writer, allDeclaredFieldsAccess == ConfigurationMemberAccessibility.ACCESSED, "allDeclaredFields"); + printJsonBooleanIfSet(writer, allPublicFieldsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicFields"); + printJsonBooleanIfSet(writer, allDeclaredMethodsAccess == ConfigurationMemberAccessibility.ACCESSED, "allDeclaredMethods"); + printJsonBooleanIfSet(writer, allPublicMethodsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicMethods"); + printJsonBooleanIfSet(writer, allDeclaredConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allDeclaredConstructors"); + printJsonBooleanIfSet(writer, allPublicConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicConstructors"); + printJsonBooleanIfNotSet(writer, allDeclaredClasses, "allDeclaredClasses"); + printJsonBooleanIfNotSet(writer, allRecordComponents, "allRecordComponents"); + printJsonBooleanIfNotSet(writer, allPermittedSubclasses, "allPermittedSubclasses"); + printJsonBooleanIfNotSet(writer, allNestMembers, "allNestMembers"); + printJsonBooleanIfNotSet(writer, allSigners, "allSigners"); + printJsonBooleanIfNotSet(writer, allPublicClasses, "allPublicClasses"); + printJsonBooleanIfNotSet(writer, allDeclaredMethodsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllDeclaredMethods"); + printJsonBooleanIfNotSet(writer, allPublicMethodsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllPublicMethods"); + printJsonBooleanIfNotSet(writer, allDeclaredConstructorsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllDeclaredConstructors"); + printJsonBooleanIfNotSet(writer, allPublicConstructorsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllPublicConstructors"); + printJsonBooleanIfSet(writer, unsafeAllocated, "unsafeAllocated"); if (fields != null) { writer.append(',').newline().quote("fields").append(':'); @@ -504,14 +509,25 @@ private static void printField(Map.Entry entry, JsonWriter w) w.append('}'); } - private static void optionallyPrintJsonBoolean(JsonWriter writer, boolean predicate, String attribute) throws IOException { + private static void printJsonBooleanIfNotSet(JsonWriter writer, boolean predicate, String attribute) throws IOException { + if (!predicate) { + printJsonBoolean(writer, predicate, attribute); + } + } + + private static void printJsonBooleanIfSet(JsonWriter writer, boolean predicate, String attribute) throws IOException { if (predicate) { - writer.append(',').newline().quote(attribute).append(":true"); + printJsonBoolean(writer, predicate, attribute); } } - private void removeFields(ConfigurationMemberDeclaration declaration) { - fields = maybeRemove(fields, map -> map.values().removeIf(v -> declaration.includes(v.getKind()))); + private static void printJsonBoolean(JsonWriter writer, boolean value, String attribute) throws IOException { + writer.append(',').newline().quote(attribute).append(":").append(Boolean.toString(value)); + } + + private void removeFields(ConfigurationMemberDeclaration declaration, ConfigurationMemberAccessibility accessibility) { + ConfigurationMemberInfo kind = ConfigurationMemberInfo.get(declaration, accessibility); + fields = maybeRemove(fields, map -> map.entrySet().removeIf(entry -> kind.includes(entry.getValue().getKind()))); } private void removeMethods(ConfigurationMemberDeclaration declaration, ConfigurationMemberAccessibility accessibility, boolean constructors) { @@ -547,12 +563,12 @@ public static FieldInfo getFieldInfoIfPresent(ConfigurationType type, String fie return (type.fields == null) ? null : type.fields.get(fieldName); } - public static boolean haveAllDeclaredFields(ConfigurationType self) { - return self.allDeclaredFields; + public static ConfigurationMemberAccessibility getAllDeclaredFields(ConfigurationType self) { + return self.allDeclaredFieldsAccess; } - public static boolean haveAllPublicFields(ConfigurationType type) { - return type.allPublicFields; + public static ConfigurationMemberAccessibility getAllPublicFields(ConfigurationType type) { + return type.allPublicFieldsAccess; } public static boolean haveAllDeclaredClasses(ConfigurationType type) { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/FieldInfo.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/FieldInfo.java index 0ae58761bc60..936d2cf73ad8 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/FieldInfo.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/FieldInfo.java @@ -24,30 +24,38 @@ */ package com.oracle.svm.configure.config; +import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility; import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; public final class FieldInfo { - private static final FieldInfo[] FINAL_NOT_WRITABLE_CACHE; + private static final FieldInfo[][] FINAL_NOT_WRITABLE_CACHE; static { - ConfigurationMemberDeclaration[] values = ConfigurationMemberDeclaration.values(); - FINAL_NOT_WRITABLE_CACHE = new FieldInfo[values.length]; - for (ConfigurationMemberDeclaration value : values) { - FINAL_NOT_WRITABLE_CACHE[value.ordinal()] = new FieldInfo(value, false); + ConfigurationMemberDeclaration[] declarations = ConfigurationMemberDeclaration.values(); + ConfigurationMemberAccessibility[] accessibilities = ConfigurationMemberAccessibility.values(); + FINAL_NOT_WRITABLE_CACHE = new FieldInfo[declarations.length][accessibilities.length]; + for (ConfigurationMemberDeclaration declaration : declarations) { + for (ConfigurationMemberAccessibility accessibility : accessibilities) { + FINAL_NOT_WRITABLE_CACHE[declaration.ordinal()][accessibility.ordinal()] = new FieldInfo(declaration, accessibility, false); + } } } - static FieldInfo get(ConfigurationMemberDeclaration kind, boolean finalButWritable) { + static FieldInfo get(ConfigurationMemberInfo kind, boolean finalButWritable) { + return get(kind.getDeclaration(), kind.getAccessibility(), finalButWritable); + } + + static FieldInfo get(ConfigurationMemberDeclaration declaration, ConfigurationMemberAccessibility accessibility, boolean finalButWritable) { if (finalButWritable) { // assumed to be rare - return new FieldInfo(kind, finalButWritable); + return new FieldInfo(declaration, accessibility, finalButWritable); } - return FINAL_NOT_WRITABLE_CACHE[kind.ordinal()]; + return FINAL_NOT_WRITABLE_CACHE[declaration.ordinal()][accessibility.ordinal()]; } - private final ConfigurationMemberDeclaration kind; + private final ConfigurationMemberInfo kind; private final boolean finalButWritable; - private FieldInfo(ConfigurationMemberDeclaration kind, boolean finalButWritable) { - this.kind = kind; + private FieldInfo(ConfigurationMemberDeclaration declaration, ConfigurationMemberAccessibility accessibility, boolean finalButWritable) { + this.kind = ConfigurationMemberInfo.get(declaration, accessibility); this.finalButWritable = finalButWritable; } @@ -74,7 +82,7 @@ public FieldInfo newIntersectedWith(FieldInfo other) { return get(kind, newFinalButWritable); } - public ConfigurationMemberDeclaration getKind() { + public ConfigurationMemberInfo getKind() { return kind; } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java index 0f119a041824..c653e9206892 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java @@ -44,9 +44,13 @@ public ParserConfigurationAdapter(TypeConfiguration configuration) { } @Override - public TypeResult resolveType(UnresolvedConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives, boolean includeAllElements) { + public TypeResult resolveType(UnresolvedConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives) { ConfigurationType type = configuration.get(condition, typeDescriptor); - ConfigurationType result = type != null ? type : new ConfigurationType(condition, typeDescriptor, includeAllElements); + /* + * The type is not immediately set with all elements included. These are added afterwards + * when parsing the correspondind fields to check for overriding values + */ + ConfigurationType result = type != null ? type : new ConfigurationType(condition, typeDescriptor, false); return TypeResult.forType(typeDescriptor.toString(), result); } @@ -134,15 +138,15 @@ public void registerSigners(UnresolvedConfigurationCondition condition, Configur } @Override - public void registerPublicFields(UnresolvedConfigurationCondition condition, ConfigurationType type) { + public void registerPublicFields(UnresolvedConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { VMError.guarantee(condition.equals(type.getCondition()), "condition is already a part of the type"); - type.setAllPublicFields(); + type.setAllPublicFields(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerDeclaredFields(UnresolvedConfigurationCondition condition, ConfigurationType type) { + public void registerDeclaredFields(UnresolvedConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { VMError.guarantee(condition.equals(type.getCondition()), "condition is already a part of the type"); - type.setAllDeclaredFields(); + type.setAllDeclaredFields(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override 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 3ece69bf7589..2aaca642d75b 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 @@ -124,7 +124,7 @@ public ConfigurationType getOrCreateType(UnresolvedConfigurationCondition condit } public ConfigurationType getOrCreateType(UnresolvedConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor) { - return types.computeIfAbsent(new ConditionalElement<>(condition, typeDescriptor), p -> new ConfigurationType(p.condition(), p.element(), false)); + return types.computeIfAbsent(new ConditionalElement<>(condition, typeDescriptor), p -> new ConfigurationType(p.condition(), p.element(), true)); } @Override diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java index bc9b8e3ffa43..e81dfdad9763 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java @@ -49,11 +49,16 @@ class ReflectionProcessor extends AbstractProcessor { private final AccessAdvisor advisor; + private boolean trackReflectionMetadata = true; ReflectionProcessor(AccessAdvisor advisor) { this.advisor = advisor; } + public void setTrackReflectionMetadata(boolean trackReflectionMetadata) { + this.trackReflectionMetadata = trackReflectionMetadata; + } + @Override @SuppressWarnings("fallthrough") public void processEntry(EconomicMap entry, ConfigurationSet configurationSet) { @@ -111,15 +116,15 @@ public void processEntry(EconomicMap entry, ConfigurationSet configur } } ConfigurationMemberDeclaration declaration = ConfigurationMemberDeclaration.PUBLIC; - ConfigurationMemberAccessibility accessibility = Boolean.TRUE.equals(entry.get("result")) ? ConfigurationMemberAccessibility.ACCESSED : ConfigurationMemberAccessibility.QUERIED; + ConfigurationMemberAccessibility accessibility = ConfigurationMemberAccessibility.QUERIED; ConfigurationTypeDescriptor clazzOrDeclaringClass = entry.containsKey("declaring_class") ? descriptorForClass(entry.get("declaring_class")) : clazz; switch (function) { case "getDeclaredFields": { - configuration.getOrCreateType(condition, clazz).setAllDeclaredFields(); + configuration.getOrCreateType(condition, clazz).setAllDeclaredFields(ConfigurationMemberAccessibility.ACCESSED); break; } case "getFields": { - configuration.getOrCreateType(condition, clazz).setAllPublicFields(); + configuration.getOrCreateType(condition, clazz).setAllPublicFields(ConfigurationMemberAccessibility.ACCESSED); break; } @@ -189,10 +194,10 @@ public void processEntry(EconomicMap entry, ConfigurationSet configur declaration = "getDeclaredMethod".equals(function) ? ConfigurationMemberDeclaration.DECLARED : ConfigurationMemberDeclaration.PRESENT; // fall through case "getMethod": { - accessibility = (accessibility == ConfigurationMemberAccessibility.ACCESSED || function.equals("invokeMethod") || function.equals("findMethodHandle")) + expectSize(args, 2); + accessibility = (!trackReflectionMetadata || function.equals("invokeMethod") || function.equals("findMethodHandle")) ? ConfigurationMemberAccessibility.ACCESSED : ConfigurationMemberAccessibility.QUERIED; - expectSize(args, 2); String name = (String) args.get(0); List parameterTypes = (List) args.get(1); if (parameterTypes == null) { // tolerated and equivalent to no parameter types @@ -211,7 +216,7 @@ public void processEntry(EconomicMap entry, ConfigurationSet configur declaration = "getDeclaredConstructor".equals(function) ? ConfigurationMemberDeclaration.DECLARED : ConfigurationMemberDeclaration.PRESENT; // fall through case "getConstructor": { - accessibility = (accessibility == ConfigurationMemberAccessibility.ACCESSED || function.equals("invokeConstructor") || function.equals("findConstructorHandle")) + accessibility = (!trackReflectionMetadata || function.equals("invokeConstructor") || function.equals("findConstructorHandle")) ? ConfigurationMemberAccessibility.ACCESSED : ConfigurationMemberAccessibility.QUERIED; List parameterTypes = singleElement(args); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java index d4c43accb071..25a2cb3e423c 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java @@ -77,6 +77,8 @@ public void processEntry(EconomicMap entry, ConfigurationSet configur setInLivePhase(entry.get("phase").equals("live")); } else if (event.equals("initialization")) { // not needed for now, but contains version for breaking changes + } else if (event.equals("track_reflection_metadata")) { + reflectionProcessor.setTrackReflectionMetadata((boolean) entry.get("track")); } else { LogUtils.warning("Unknown meta event, ignoring: " + event); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java index 101c0b82b36e..789a94ba7260 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java @@ -29,6 +29,7 @@ import java.util.BitSet; import java.util.Objects; import java.util.TreeMap; +import java.util.function.Consumer; import java.util.function.IntFunction; import java.util.stream.Stream; @@ -146,7 +147,7 @@ public record Member(ResolvedJavaMethod method, Class clazz, String name, Str private final FrequencyEncoder methods; private Member[] encodedMethods; - public Encoders(boolean imageCode) { + public Encoders(boolean imageCode, Consumer> classVerifier) { this.objectConstants = FrequencyEncoder.createEqualityEncoder(); /* @@ -154,7 +155,7 @@ public Encoders(boolean imageCode) { * reference only image methods via method ids. */ assert imageCode == SubstrateUtil.HOSTED; - this.classes = imageCode ? FrequencyEncoder.createEqualityEncoder() : null; + this.classes = imageCode ? FrequencyEncoder.createVerifyingEqualityEncoder(classVerifier) : null; this.memberNames = imageCode ? FrequencyEncoder.createEqualityEncoder() : null; this.methods = imageCode ? FrequencyEncoder.createEqualityEncoder() : null; this.otherStrings = imageCode ? FrequencyEncoder.createEqualityEncoder() : null; 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 64b90e1ca446..15b0278d8a9d 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 @@ -86,6 +86,11 @@ private void parseClass(EconomicMap data) { if (type.isEmpty()) { return; } + /* + * Classes registered using the old ("name") syntax requires elements (fields, methods, + * constructors, ...) to be registered for runtime queries, whereas the new ("type") syntax + * automatically registers all elements as queried. + */ boolean isType = type.get().definedAsType(); UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, isType); @@ -99,7 +104,7 @@ private void parseClass(EconomicMap data) { * allow getDeclaredMethods() and similar bulk queries at run time. */ C condition = conditionResult.get(); - TypeResult result = delegate.resolveType(condition, type.get(), true, false); + TypeResult result = delegate.resolveType(condition, type.get(), true); if (!result.isPresent()) { handleMissingElement("Could not resolve " + type.get() + " for reflection configuration.", result.getException()); return; @@ -109,97 +114,33 @@ private void parseClass(EconomicMap data) { T clazz = result.get(); delegate.registerType(conditionResult.get(), clazz); + registerIfNecessary(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, clazz)); + registerIfNecessary(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, clazz)); + registerIfNecessary(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, clazz)); + registerIfNecessary(data, false, clazz, "allPublicMethods", () -> delegate.registerPublicMethods(condition, false, clazz)); + registerIfNecessary(data, false, clazz, "allDeclaredFields", () -> delegate.registerDeclaredFields(condition, false, clazz)); + registerIfNecessary(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, clazz)); + registerIfNecessary(data, isType, clazz, "allDeclaredClasses", () -> delegate.registerDeclaredClasses(queryCondition, clazz)); + registerIfNecessary(data, isType, clazz, "allRecordComponents", () -> delegate.registerRecordComponents(queryCondition, clazz)); + registerIfNecessary(data, isType, clazz, "allPermittedSubclasses", () -> delegate.registerPermittedSubclasses(queryCondition, clazz)); + registerIfNecessary(data, isType, clazz, "allNestMembers", () -> delegate.registerNestMembers(queryCondition, clazz)); + registerIfNecessary(data, isType, clazz, "allSigners", () -> delegate.registerSigners(queryCondition, clazz)); + registerIfNecessary(data, isType, clazz, "allPublicClasses", () -> delegate.registerPublicClasses(queryCondition, clazz)); + registerIfNecessary(data, isType, clazz, "queryAllDeclaredConstructors", () -> delegate.registerDeclaredConstructors(queryCondition, true, clazz)); + registerIfNecessary(data, isType, clazz, "queryAllPublicConstructors", () -> delegate.registerPublicConstructors(queryCondition, true, clazz)); + registerIfNecessary(data, isType, clazz, "queryAllDeclaredMethods", () -> delegate.registerDeclaredMethods(queryCondition, true, clazz)); + registerIfNecessary(data, isType, clazz, "queryAllPublicMethods", () -> delegate.registerPublicMethods(queryCondition, true, clazz)); + if (isType) { + delegate.registerDeclaredFields(queryCondition, true, clazz); + delegate.registerPublicFields(queryCondition, true, clazz); + } + registerIfNecessary(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz)); MapCursor cursor = data.getEntries(); while (cursor.advance()) { String name = cursor.getKey(); Object value = cursor.getValue(); try { switch (name) { - case "allDeclaredConstructors": - if (asBoolean(value, "allDeclaredConstructors")) { - delegate.registerDeclaredConstructors(condition, false, clazz); - } - break; - case "allPublicConstructors": - if (asBoolean(value, "allPublicConstructors")) { - delegate.registerPublicConstructors(condition, false, clazz); - } - break; - case "allDeclaredMethods": - if (asBoolean(value, "allDeclaredMethods")) { - delegate.registerDeclaredMethods(condition, false, clazz); - } - break; - case "allPublicMethods": - if (asBoolean(value, "allPublicMethods")) { - delegate.registerPublicMethods(condition, false, clazz); - } - break; - case "allDeclaredFields": - if (asBoolean(value, "allDeclaredFields")) { - delegate.registerDeclaredFields(condition, clazz); - } - break; - case "allPublicFields": - if (asBoolean(value, "allPublicFields")) { - delegate.registerPublicFields(condition, clazz); - } - break; - case "allDeclaredClasses": - if (asBoolean(value, "allDeclaredClasses")) { - delegate.registerDeclaredClasses(queryCondition, clazz); - } - break; - case "allRecordComponents": - if (asBoolean(value, "allRecordComponents")) { - delegate.registerRecordComponents(queryCondition, clazz); - } - break; - case "allPermittedSubclasses": - if (asBoolean(value, "allPermittedSubclasses")) { - delegate.registerPermittedSubclasses(queryCondition, clazz); - } - break; - case "allNestMembers": - if (asBoolean(value, "allNestMembers")) { - delegate.registerNestMembers(queryCondition, clazz); - } - break; - case "allSigners": - if (asBoolean(value, "allSigners")) { - delegate.registerSigners(queryCondition, clazz); - } - break; - case "allPublicClasses": - if (asBoolean(value, "allPublicClasses")) { - delegate.registerPublicClasses(queryCondition, clazz); - } - break; - case "queryAllDeclaredConstructors": - if (asBoolean(value, "queryAllDeclaredConstructors")) { - delegate.registerDeclaredConstructors(queryCondition, true, clazz); - } - break; - case "queryAllPublicConstructors": - if (asBoolean(value, "queryAllPublicConstructors")) { - delegate.registerPublicConstructors(queryCondition, true, clazz); - } - break; - case "queryAllDeclaredMethods": - if (asBoolean(value, "queryAllDeclaredMethods")) { - delegate.registerDeclaredMethods(queryCondition, true, clazz); - } - break; - case "queryAllPublicMethods": - if (asBoolean(value, "queryAllPublicMethods")) { - delegate.registerPublicMethods(queryCondition, true, clazz); - } - break; - case "unsafeAllocated": - if (asBoolean(value, "unsafeAllocated")) { - delegate.registerUnsafeAllocated(condition, clazz); - } - break; case "methods": parseMethods(condition, false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); break; @@ -216,6 +157,16 @@ private void parseClass(EconomicMap data) { } } + private void registerIfNecessary(EconomicMap data, boolean defaultValue, T clazz, String propertyName, Runnable register) { + if (data.containsKey(propertyName) ? asBoolean(data.get(propertyName), propertyName) : defaultValue) { + try { + register.run(); + } catch (LinkageError e) { + handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + propertyName + " for reflection.", e); + } + } + } + private void parseFields(C condition, List fields, T clazz) { for (Object field : fields) { parseField(condition, asMap(field, "Elements of 'fields' array must be field descriptor objects"), clazz); @@ -288,7 +239,7 @@ private List parseMethodParameters(T clazz, String methodName, List t List result = new ArrayList<>(); for (Object type : types) { String typeName = asString(type, "types"); - TypeResult typeResult = delegate.resolveType(conditionResolver.alwaysTrue(), new NamedConfigurationTypeDescriptor(typeName), true, false); + TypeResult typeResult = delegate.resolveType(conditionResolver.alwaysTrue(), new NamedConfigurationTypeDescriptor(typeName), true); if (!typeResult.isPresent()) { handleMissingElement("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", typeResult.getException()); return null; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java index 25547387027c..748262afabb7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java @@ -30,7 +30,7 @@ public interface ReflectionConfigurationParserDelegate { - TypeResult resolveType(C condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives, boolean includeAllElements); + TypeResult resolveType(C condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives); void registerType(C condition, T type); @@ -46,9 +46,9 @@ public interface ReflectionConfigurationParserDelegate { void registerSigners(C condition, T type); - void registerPublicFields(C condition, T type); + void registerPublicFields(C condition, boolean queriedOnly, T type); - void registerDeclaredFields(C condition, T type); + void registerDeclaredFields(C condition, boolean queriedOnly, T type); void registerPublicMethods(C condition, boolean queriedOnly, T type); diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/RuntimeCodeInstaller.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/RuntimeCodeInstaller.java index 996de5099e78..e9d907a5fbbf 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/RuntimeCodeInstaller.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/RuntimeCodeInstaller.java @@ -266,7 +266,7 @@ private void patchDirectObjectConstants(ObjectConstantsHolder objectConstants, C } private void createCodeChunkInfos(CodeInfo runtimeMethodInfo, ReferenceAdjuster adjuster) { - CodeInfoEncoder codeInfoEncoder = new CodeInfoEncoder(new RuntimeFrameInfoCustomization(), new CodeInfoEncoder.Encoders(false)); + CodeInfoEncoder codeInfoEncoder = new CodeInfoEncoder(new RuntimeFrameInfoCustomization(), new CodeInfoEncoder.Encoders(false, null)); codeInfoEncoder.addMethod(method, compilation, 0, compilation.getTargetCodeSize()); codeInfoEncoder.encodeAllAndInstall(runtimeMethodInfo, adjuster); 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 8f8def1a6575..a8b46a661640 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 @@ -73,8 +73,10 @@ import com.oracle.graal.pointsto.infrastructure.WrappedElement; import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.code.CodeInfoEncoder; import com.oracle.svm.core.code.RuntimeMetadataDecoderImpl; @@ -100,6 +102,7 @@ import com.oracle.svm.hosted.reflect.ReflectionDataBuilder; import com.oracle.svm.hosted.reflect.ReflectionHostedSupport; import com.oracle.svm.hosted.substitute.DeletedElementException; +import com.oracle.svm.hosted.substitute.SubstitutionReflectivityFilter; import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; @@ -365,13 +368,16 @@ private static Class[] filterDeletedClasses(MetaAccessProvider metaAccess, Cl if (classes == null) { return null; } + AnalysisMetaAccess aMetaAccess = (AnalysisMetaAccess) ((HostedMetaAccess) metaAccess).getWrapped(); Set> reachableClasses = new HashSet<>(); for (Class clazz : classes) { try { - metaAccess.lookupJavaType(clazz); - reachableClasses.add(clazz); - } catch (DeletedElementException dee) { - // class has been deleted -> ignore + if (!SubstitutionReflectivityFilter.shouldExclude(clazz, aMetaAccess, aMetaAccess.getUniverse())) { + metaAccess.lookupJavaType(clazz); + reachableClasses.add(clazz); + } + } catch (DeletedElementException | AnalysisError.TypeNotFoundError e) { + // class has been deleted or otherwise deemed unreachable by the analysis -> ignore } } return reachableClasses.toArray(new Class[0]); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java index f29eb05d5f5d..44089b176abd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.List; +import com.oracle.svm.hosted.reflect.ReflectionDataBuilder; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; @@ -58,8 +59,8 @@ public void registerType(ConfigurationCondition condition, Class type) { } @Override - public TypeResult> resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives, boolean includeAllElements) { - TypeResult> result = super.resolveType(condition, typeDescriptor, allowPrimitives, includeAllElements); + public TypeResult> resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives) { + TypeResult> result = super.resolveType(condition, typeDescriptor, allowPrimitives); if (!result.isPresent() && typeDescriptor instanceof NamedConfigurationTypeDescriptor namedDescriptor) { Throwable classLookupException = result.getException(); if (classLookupException instanceof LinkageError) { @@ -102,13 +103,13 @@ public void registerSigners(ConfigurationCondition condition, Class type) { } @Override - public void registerPublicFields(ConfigurationCondition condition, Class type) { - reflectionSupport.registerAllFieldsQuery(condition, type); + public void registerPublicFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { + ((ReflectionDataBuilder) reflectionSupport).registerAllFieldsQuery(condition, queriedOnly, type); } @Override - public void registerDeclaredFields(ConfigurationCondition condition, Class type) { - reflectionSupport.registerAllDeclaredFieldsQuery(condition, type); + public void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { + ((ReflectionDataBuilder) reflectionSupport).registerAllDeclaredFieldsQuery(condition, queriedOnly, type); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java index d8c484c14143..94e914232d06 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java @@ -70,7 +70,7 @@ public void registerType(ConfigurationCondition condition, Class type) { } @Override - public TypeResult> resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives, boolean includeAllElements) { + public TypeResult> resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives) { switch (typeDescriptor.getDescriptorType()) { case NAMED -> { return resolveNamedType(((NamedConfigurationTypeDescriptor) typeDescriptor), allowPrimitives); @@ -134,13 +134,17 @@ public void registerSigners(ConfigurationCondition condition, Class type) { } @Override - public void registerPublicFields(ConfigurationCondition condition, Class type) { - registry.register(condition, false, type.getFields()); + public void registerPublicFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { + if (!queriedOnly) { + registry.register(condition, false, type.getFields()); + } } @Override - public void registerDeclaredFields(ConfigurationCondition condition, Class type) { - registry.register(condition, false, type.getDeclaredFields()); + public void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { + if (!queriedOnly) { + registry.register(condition, false, type.getDeclaredFields()); + } } @Override 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 f5a64aa0b36e..bb3fb94df36f 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 @@ -327,7 +327,11 @@ public Object asObject(JavaConstant constant) { protected void buildRuntimeMetadata(DebugContext debug, SnippetReflectionProvider snippetReflection, CFunctionPointer firstMethod, UnsignedWord codeSize) { // Build run-time metadata. HostedFrameInfoCustomization frameInfoCustomization = new HostedFrameInfoCustomization(); - CodeInfoEncoder.Encoders encoders = new CodeInfoEncoder.Encoders(true); + CodeInfoEncoder.Encoders encoders = new CodeInfoEncoder.Encoders(true, clazz -> { + if (clazz != null && !imageHeap.hMetaAccess.optionalLookupJavaType(clazz).isPresent()) { + throw VMError.shouldNotReachHere("Type added to the runtime metadata without being seen by the analysis: %s", clazz); + } + }); HostedConstantAccess hostedConstantAccess = new HostedConstantAccess(snippetReflection); CodeInfoEncoder codeInfoEncoder = new CodeInfoEncoder(frameInfoCustomization, encoders, hostedConstantAccess); DeadlockWatchdog watchdog = ImageSingletons.lookup(DeadlockWatchdog.class); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index 48c2b3d403dd..530311c86e8a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -220,7 +220,9 @@ public void register(ConfigurationCondition condition, boolean unsafeAllocated, public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... executables) { requireNonNull(executables, "executable"); abortIfSealed(); - registerConditionalConfiguration(condition, (cnd) -> newMethods.addAll(Arrays.asList(executables))); + if (!queriedOnly) { + registerConditionalConfiguration(condition, (cnd) -> newMethods.addAll(Arrays.asList(executables))); + } } @Override 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 96b0d2475890..cceba8eb62f9 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 @@ -53,6 +53,7 @@ import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -205,7 +206,10 @@ public void registerAllClassesQuery(ConfigurationCondition condition, Class c setQueryFlag(clazz, ALL_CLASSES_FLAG); try { for (Class innerClass : clazz.getClasses()) { - innerClasses.computeIfAbsent(innerClass.getDeclaringClass(), c -> ConcurrentHashMap.newKeySet()).add(innerClass); + if (innerClass.getDeclaringClass() != null) { + /* Malformed inner classes can have no declaring class */ + innerClasses.computeIfAbsent(innerClass.getDeclaringClass(), c -> ConcurrentHashMap.newKeySet()).add(innerClass); + } registerClass(cnd, innerClass, false, !MissingRegistrationUtils.throwMissingRegistrationErrors()); } } catch (LinkageError e) { @@ -498,7 +502,7 @@ public void registerAllFieldsQuery(ConfigurationCondition condition, Class cl registerAllFieldsQuery(condition, false, clazz); } - private void registerAllFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { + public void registerAllFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { guaranteeNotRuntimeConditionForQueries(condition, queriedOnly); runConditionalInAnalysisTask(condition, (cnd) -> { for (Class current = clazz; current != null; current = current.getSuperclass()) { @@ -517,7 +521,7 @@ public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Cla registerAllDeclaredFieldsQuery(condition, false, clazz); } - private void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { + public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_DECLARED_FIELDS_FLAG); try { @@ -559,7 +563,10 @@ private void registerField(ConfigurationCondition cnd, boolean queriedOnly, Fiel } var cndValue = registeredFields.computeIfAbsent(analysisField, f -> new ConditionalRuntimeValue<>(RuntimeConditionSet.emptySet(), reflectField)); - cndValue.getConditions().addCondition(cnd); + if (!queriedOnly) { + /* queryOnly methods are conditioned by the type itself */ + cndValue.getConditions().addCondition(cnd); + } /* * We need to run this even if the method has already been registered, in case it was only @@ -752,6 +759,11 @@ private void registerTypesForField(AnalysisField analysisField, Field reflectFie analysisField.registerAsUnsafeAccessed("is registered for reflection."); } + /* + * In some rare cases, the type of the field can be absent from its generic signature. + */ + metaAccess.lookupJavaType(reflectField.getType()).registerAsReachable("Is present in a Field object reconstructed by reflection"); + /* * The generic signature is parsed at run time, so we need to make all the types necessary * for parsing also available at run time. @@ -766,6 +778,15 @@ private void registerTypesForField(AnalysisField analysisField, Field reflectFie } private void registerTypesForMethod(AnalysisMethod analysisMethod, Executable reflectExecutable) { + /* + * In some rare cases, the parameter, exception or return types of the method can be absent + * from their generic signature. + */ + Arrays.stream(reflectExecutable.getParameterTypes()).forEach(clazz -> metaAccess.lookupJavaType(clazz).registerAsReachable("Is present in an Executable object reconstructed by reflection")); + Arrays.stream(reflectExecutable.getExceptionTypes()).forEach(clazz -> metaAccess.lookupJavaType(clazz).registerAsReachable("Is present in an Executable object reconstructed by reflection")); + if (reflectExecutable instanceof Method method) { + metaAccess.lookupJavaType(method.getReturnType()).registerAsReachable("Is present in a Method object reconstructed by reflection"); + } /* * The generic signature is parsed at run time, so we need to make all the types necessary * for parsing also available at run time.