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 4fa95db38751..4645e620f7e4 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 @@ -411,7 +411,7 @@ private Object forName0(String className, ClassLoader classLoader) { /* Invalid class names always throw, no need for reflection data */ return new ClassNotFoundException(className); } - if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + if (MetadataTracer.enabled()) { // NB: the early returns above ensure we do not trace calls with bad type args. MetadataTracer.singleton().traceReflectionType(className); } @@ -500,6 +500,12 @@ public static Throwable getSavedException(String className) { */ public static boolean canUnsafeInstantiateAsInstance(DynamicHub hub) { Class clazz = DynamicHub.toClass(hub); + if (MetadataTracer.enabled()) { + ConfigurationType type = MetadataTracer.singleton().traceReflectionType(clazz.getName()); + if (type != null) { + type.setUnsafeAllocated(); + } + } RuntimeConditionSet conditionSet = null; for (var singleton : layeredSingletons()) { conditionSet = singleton.unsafeInstantiatedClasses.get(clazz); @@ -508,12 +514,6 @@ public static boolean canUnsafeInstantiateAsInstance(DynamicHub hub) { } } if (conditionSet != null) { - if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { - ConfigurationType type = MetadataTracer.singleton().traceReflectionType(clazz.getName()); - if (type != null) { - type.setUnsafeAllocated(); - } - } return conditionSet.satisfied(); } return false; 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 01e67d488a22..a51aaf0ad548 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 @@ -709,7 +709,7 @@ private ReflectionMetadata reflectionMetadata() { } private void checkClassFlag(int mask, String methodName) { - if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + if (MetadataTracer.enabled()) { traceClassFlagQuery(mask); } if (throwMissingRegistrationErrors() && !(isClassFlagSet(mask) && getConditions().satisfied())) { @@ -1315,7 +1315,7 @@ private void checkField(String fieldName, Field field, boolean publicOnly) throw boolean throwMissingErrors = throwMissingRegistrationErrors(); Class clazz = DynamicHub.toClass(this); - if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + if (MetadataTracer.enabled()) { traceFieldLookup(fieldName, field, publicOnly); } @@ -1397,7 +1397,7 @@ private boolean checkExecutableExists(String methodName, Class[] parameterTyp boolean throwMissingErrors = throwMissingRegistrationErrors(); Class clazz = DynamicHub.toClass(this); - if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + if (MetadataTracer.enabled()) { traceMethodLookup(methodName, parameterTypes, method, publicOnly); } @@ -1973,14 +1973,19 @@ public DynamicHub arrayType() { if (toClass(this) == void.class) { throw new UnsupportedOperationException(new IllegalArgumentException()); } + if (MetadataTracer.enabled()) { + MetadataTracer.singleton().traceReflectionType(arrayTypeName()); + } if (companion.arrayHub == null) { - MissingReflectionRegistrationUtils.reportClassAccess(getTypeName() + "[]"); - } else if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { - MetadataTracer.singleton().traceReflectionType(companion.arrayHub.getTypeName()); + MissingReflectionRegistrationUtils.reportClassAccess(arrayTypeName()); } return companion.arrayHub; } + private String arrayTypeName() { + return getTypeName() + "[]"; + } + @KeepOriginal private native Class elementType(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaIOSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaIOSubstitutions.java index f76f1c26eddb..e25a80138486 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaIOSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaIOSubstitutions.java @@ -65,12 +65,12 @@ static ObjectStreamClass lookup(Class cl, boolean all) { } if (Serializable.class.isAssignableFrom(cl) && !cl.isArray()) { + if (MetadataTracer.enabled()) { + MetadataTracer.singleton().traceSerializationType(cl.getName()); + } if (!DynamicHub.fromClass(cl).isRegisteredForSerialization()) { MissingSerializationRegistrationUtils.reportSerialization(cl); } - if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { - MetadataTracer.singleton().traceSerializationType(cl.getName()); - } } return Target_java_io_ObjectStreamClass_Caches.localDescs0.get(cl); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangReflectSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangReflectSubstitutions.java index 63412998ef18..b4908275ecb4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangReflectSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangReflectSubstitutions.java @@ -387,7 +387,7 @@ private static void set(Object a, int index, Object value) { @Substitute private static Object newArray(Class componentType, int length) throws NegativeArraySizeException { - if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + if (MetadataTracer.enabled()) { MetadataTracer.singleton().traceReflectionType(componentType.arrayType().getName()); } return KnownIntrinsics.unvalidatedNewArray(componentType, length); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java index 78516e2b8645..b09020b23840 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java @@ -54,6 +54,7 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.impl.ConfigurationCondition; +import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.ClassLoaderSupport.ConditionWithOrigin; import com.oracle.svm.core.MissingRegistrationUtils; @@ -65,7 +66,6 @@ import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; -import com.oracle.svm.core.jdk.resources.MissingResourceRegistrationError; import com.oracle.svm.core.jdk.resources.MissingResourceRegistrationUtils; import com.oracle.svm.core.jdk.resources.ResourceExceptionEntry; import com.oracle.svm.core.jdk.resources.ResourceStorageEntry; @@ -428,54 +428,47 @@ private static boolean wasAlreadyInCanonicalForm(String resourceName, String can return resourceName.equals(canonicalResourceName) || removeTrailingSlash(resourceName).equals(canonicalResourceName); } - public static ResourceStorageEntryBase getAtRuntime(String name, boolean throwOnMissing) { - return getAtRuntime(null, name, throwOnMissing); + public static ResourceStorageEntryBase getAtRuntime(String name) { + return getAtRuntime(null, name, false); } /** - * If {@code throwOnMissing} is false, we have to distinguish an entry that was in the metadata - * from one that was not, so the caller can correctly throw the - * {@link MissingResourceRegistrationError}. This is needed because different modules can be - * tried on the same resource name, causing an unexpected exception if we throw directly. + * Looks up a resource from {@code module} with name {@code resourceName}. + *

+ * The {@code probe} parameter indicates whether the caller is probing for the existence of a + * resource. If {@code probe} is true, failed resource lookups return will not throw missing + * registration errors and may instead return {@link #MISSING_METADATA_MARKER}. + *

+ * Tracing note: When this method is used for probing, only successful metadata matches will be + * traced. If a probing result is {@link #MISSING_METADATA_MARKER}, the caller must explicitly + * trace the missing metadata. */ - public static ResourceStorageEntryBase getAtRuntime(Module module, String resourceName, boolean throwOnMissing) { + public static ResourceStorageEntryBase getAtRuntime(Module module, String resourceName, boolean probe) { VMError.guarantee(ImageInfo.inImageRuntimeCode(), "This function should be used only at runtime."); String canonicalResourceName = NativeImageResourcePathRepresentation.toCanonicalForm(resourceName); String moduleName = moduleName(module); ConditionalRuntimeValue entry = getEntry(module, canonicalResourceName); if (entry == null) { if (MissingRegistrationUtils.throwMissingRegistrationErrors()) { - for (var r : layeredSingletons()) { - MapCursor cursor = r.requestedPatterns.getEntries(); - while (cursor.advance()) { - RequestedPattern moduleResourcePair = cursor.getKey(); - if (Objects.equals(moduleName, moduleResourcePair.module) && - ((matchResource(moduleResourcePair.resource, resourceName) || matchResource(moduleResourcePair.resource, canonicalResourceName)) && - cursor.getValue().satisfied())) { - return null; - } - } - - String glob = GlobUtils.transformToTriePath(resourceName, moduleName); - String canonicalGlob = GlobUtils.transformToTriePath(canonicalResourceName, moduleName); - GlobTrieNode globsTrie = r.getResourcesTrieRoot(); - if (CompressedGlobTrie.match(globsTrie, glob) || - CompressedGlobTrie.match(globsTrie, canonicalGlob)) { - return null; - } - return missingMetadata(module, canonicalGlob, throwOnMissing); + if (missingResourceMatchesIncludePattern(resourceName, moduleName) || missingResourceMatchesIncludePattern(canonicalResourceName, moduleName)) { + // This resource name matches a pattern/glob from the provided metadata, but no + // resource with the name actually exists. Do not report missing metadata. + traceResource(resourceName, moduleName); + return null; } - - return missingMetadata(module, resourceName, throwOnMissing); + traceResourceMissingMetadata(resourceName, moduleName, probe); + return missingMetadata(module, resourceName, probe); } else { + // NB: Without exact reachability metadata, resource include patterns are not + // stored in the image heap, so we cannot reliably identify if the resource was + // included at build time. Assume it is missing. + traceResourceMissingMetadata(resourceName, moduleName, probe); return null; } } - if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { - MetadataTracer.singleton().traceResource(resourceName, moduleName); - } + traceResource(resourceName, moduleName); if (!entry.getConditions().satisfied()) { - return missingMetadata(module, resourceName, throwOnMissing); + return missingMetadata(module, resourceName, probe); } ResourceStorageEntryBase unconditionalEntry = entry.getValue(); @@ -503,6 +496,51 @@ public static ResourceStorageEntryBase getAtRuntime(Module module, String resour return unconditionalEntry; } + @AlwaysInline("tracing should fold away when disabled") + private static void traceResource(String resourceName, String moduleName) { + if (MetadataTracer.enabled()) { + MetadataTracer.singleton().traceResource(resourceName, moduleName); + } + } + + @AlwaysInline("tracing should fold away when disabled") + private static void traceResourceMissingMetadata(String resourceName, String moduleName) { + traceResourceMissingMetadata(resourceName, moduleName, false); + } + + @AlwaysInline("tracing should fold away when disabled") + private static void traceResourceMissingMetadata(String resourceName, String moduleName, boolean probe) { + if (MetadataTracer.enabled() && !probe) { + // Do not trace missing metadata for probing queries, otherwise we'll trace an entry for + // every module. The caller is responsible for tracing missing entries if it uses + // probing. + MetadataTracer.singleton().traceResource(resourceName, moduleName); + } + } + + /** + * Checks whether the given missing resource is matched by a pattern/glob registered at build + * time. In such a case, we should not report missing metadata. + */ + private static boolean missingResourceMatchesIncludePattern(String resourceName, String moduleName) { + VMError.guarantee(MissingRegistrationUtils.throwMissingRegistrationErrors(), "include patterns are only stored in the image with exact reachability metadata"); + String glob = GlobUtils.transformToTriePath(resourceName, moduleName); + for (var r : layeredSingletons()) { + MapCursor cursor = r.requestedPatterns.getEntries(); + while (cursor.advance()) { + RequestedPattern moduleResourcePair = cursor.getKey(); + if (Objects.equals(moduleName, moduleResourcePair.module) && matchResource(moduleResourcePair.resource, resourceName) && cursor.getValue().satisfied()) { + return true; + } + } + + if (CompressedGlobTrie.match(r.getResourcesTrieRoot(), glob)) { + return true; + } + } + return false; + } + private static ConditionalRuntimeValue getEntry(Module module, String canonicalResourceName) { for (var r : layeredSingletons()) { ConditionalRuntimeValue entry = r.resources.get(createStorageKey(module, canonicalResourceName)); @@ -513,8 +551,8 @@ private static ConditionalRuntimeValue getEntry(Module return null; } - private static ResourceStorageEntryBase missingMetadata(Module module, String resourceName, boolean throwOnMissing) { - if (throwOnMissing) { + private static ResourceStorageEntryBase missingMetadata(Module module, String resourceName, boolean probe) { + if (!probe) { MissingResourceRegistrationUtils.reportResourceAccess(module, resourceName); } return MISSING_METADATA_MARKER; @@ -544,42 +582,45 @@ public static URL createURL(Module module, String resourceName) { return urls.hasMoreElements() ? urls.nextElement() : null; } - public static InputStream createInputStream(String resourceName) { - return createInputStream(null, resourceName); - } - /* Avoid pulling in the URL class when only an InputStream is needed. */ public static InputStream createInputStream(Module module, String resourceName) { if (resourceName == null) { return null; } + ResourceStorageEntryBase entry = findResourceForInputStream(module, resourceName); + if (entry == MISSING_METADATA_MARKER) { + traceResourceMissingMetadata(resourceName, moduleName(module)); + MissingResourceRegistrationUtils.reportResourceAccess(module, resourceName); + return null; + } else if (entry == null) { + return null; + } + List data = entry.getData(); + return data.isEmpty() ? null : new ByteArrayInputStream(data.get(0)); + } - ResourceStorageEntryBase entry = getAtRuntime(module, resourceName, false); - boolean isInMetadata = entry != MISSING_METADATA_MARKER; - if (moduleName(module) == null && (entry == MISSING_METADATA_MARKER || entry == null)) { + private static ResourceStorageEntryBase findResourceForInputStream(Module module, String resourceName) { + ResourceStorageEntryBase result = getAtRuntime(module, resourceName, true); + if (moduleName(module) == null && (result == MISSING_METADATA_MARKER || result == null)) { /* * If module is not specified or is an unnamed module and entry was not found as * classpath-resource we have to search for the resource in all modules in the image. */ for (Module m : RuntimeModuleSupport.singleton().getBootLayer().modules()) { - entry = getAtRuntime(m, resourceName, false); + ResourceStorageEntryBase entry = getAtRuntime(m, resourceName, true); if (entry != MISSING_METADATA_MARKER) { - isInMetadata = true; - } - if (entry != null && entry != MISSING_METADATA_MARKER) { - break; + if (entry != null) { + // resource found + return entry; + } else { + // found a negative query. remember this result but keep trying in case some + // other module supplies an actual resource. + result = null; + } } } } - - if (!isInMetadata) { - MissingResourceRegistrationUtils.reportResourceAccess(module, resourceName); - } - if (entry == null || entry == MISSING_METADATA_MARKER) { - return null; - } - List data = entry.getData(); - return data.isEmpty() ? null : new ByteArrayInputStream(data.get(0)); + return result; } public static Enumeration createURLs(String resourceName) { @@ -595,23 +636,24 @@ public static Enumeration createURLs(Module module, String resourceName) { List resourcesURLs = new ArrayList<>(); String canonicalResourceName = NativeImageResourcePathRepresentation.toCanonicalForm(resourceName); - boolean shouldAppendTrailingSlash = hasTrailingSlash(resourceName); + if (hasTrailingSlash(resourceName)) { + canonicalResourceName += "/"; + } /* If moduleName was unspecified we have to consider all modules in the image */ if (moduleName(module) == null) { for (Module m : RuntimeModuleSupport.singleton().getBootLayer().modules()) { - ResourceStorageEntryBase entry = getAtRuntime(m, resourceName, false); - if (entry == MISSING_METADATA_MARKER) { - continue; + ResourceStorageEntryBase entry = getAtRuntime(m, resourceName, true); + if (entry != MISSING_METADATA_MARKER) { + missingMetadata = false; + addURLEntries(resourcesURLs, (ResourceStorageEntry) entry, m, canonicalResourceName); } - missingMetadata = false; - addURLEntries(resourcesURLs, (ResourceStorageEntry) entry, m, shouldAppendTrailingSlash ? canonicalResourceName + '/' : canonicalResourceName); } } - ResourceStorageEntryBase explicitEntry = getAtRuntime(module, resourceName, false); + ResourceStorageEntryBase explicitEntry = getAtRuntime(module, resourceName, true); if (explicitEntry != MISSING_METADATA_MARKER) { missingMetadata = false; - addURLEntries(resourcesURLs, (ResourceStorageEntry) explicitEntry, module, shouldAppendTrailingSlash ? canonicalResourceName + '/' : canonicalResourceName); + addURLEntries(resourcesURLs, (ResourceStorageEntry) explicitEntry, module, canonicalResourceName); } if (missingMetadata) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java index 470264065b6c..df909c382700 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java @@ -294,10 +294,10 @@ public boolean isRegisteredBundleLookup(String baseName, Locale locale, Object c /* Those cases will throw a NullPointerException before any lookup */ return true; } + if (MetadataTracer.enabled()) { + MetadataTracer.singleton().traceResourceBundle(baseName); + } if (registeredBundles.containsKey(baseName)) { - if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { - MetadataTracer.singleton().traceResourceBundle(baseName); - } return registeredBundles.get(baseName).satisfied(); } return false; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystem.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystem.java index 1a2513f21438..1b2b8361761b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystem.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystem.java @@ -574,7 +574,7 @@ IndexNode getInode(byte[] path) { IndexNode indexNode = inodes.get(IndexNode.keyOf(path)); if (indexNode == null && MissingRegistrationUtils.throwMissingRegistrationErrors()) { // Try to access the resource to see if the metadata is present - Resources.getAtRuntime(getString(path), true); + Resources.getAtRuntime(getString(path)); } return indexNode; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemUtil.java index c29c2ebdfa8f..c5bb353cc12b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemUtil.java @@ -36,7 +36,7 @@ private NativeImageResourceFileSystemUtil() { } public static byte[] getBytes(String resourceName, boolean readOnly) { - Object entry = Resources.getAtRuntime(resourceName, true); + Object entry = Resources.getAtRuntime(resourceName); if (entry == null) { return new byte[0]; } @@ -49,7 +49,7 @@ public static byte[] getBytes(String resourceName, boolean readOnly) { } public static int getSize(String resourceName) { - Object entry = Resources.getAtRuntime(resourceName, true); + Object entry = Resources.getAtRuntime(resourceName); if (entry == null) { return 0; } else { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceURLConnection.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceURLConnection.java index f5ee12c84b65..db815ac52a51 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceURLConnection.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceURLConnection.java @@ -74,7 +74,7 @@ public void connect() { String resourceName = urlPath.substring(1); Module module = hostNameOrNull != null ? ModuleLayer.boot().findModule(hostNameOrNull).orElse(null) : null; - Object entry = Resources.getAtRuntime(module, resourceName, true); + Object entry = Resources.getAtRuntime(module, resourceName, false); if (entry != null) { ResourceStorageEntry resourceStorageEntry = (ResourceStorageEntry) entry; List bytes = resourceStorageEntry.getData(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java index 3c4d68dd9369..3475435b548a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java @@ -184,7 +184,7 @@ public static Class getClassObjectByName(CharSequence name) { JNIAccessibleClass clazz = dictionary.classesByName.get(name); if (clazz == null && !ClassNameSupport.isValidJNIName(name.toString())) { clazz = NEGATIVE_CLASS_LOOKUP; - } else if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + } else if (MetadataTracer.enabled()) { // trace if class exists (positive query) or name is valid (negative query) MetadataTracer.singleton().traceJNIType(ClassNameSupport.jniNameToTypeName(name.toString())); } @@ -273,6 +273,12 @@ public static JNIMethodId getDeclaredMethodID(Class classObject, JNIAccessibl } private static JNIAccessibleMethod getDeclaredMethod(Class classObject, JNIAccessibleMethodDescriptor descriptor, String dumpLabel) { + if (MetadataTracer.enabled()) { + ConfigurationType clazzType = MetadataTracer.singleton().traceJNIType(classObject.getName()); + if (clazzType != null) { + clazzType.addMethod(descriptor.getNameConvertToString(), descriptor.getSignatureConvertToString(), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); + } + } boolean foundClass = false; for (var dictionary : layeredSingletons()) { JNIAccessibleClass clazz = dictionary.classesByClassObject.get(classObject); @@ -280,12 +286,6 @@ private static JNIAccessibleMethod getDeclaredMethod(Class classObject, JNIAc foundClass = true; JNIAccessibleMethod method = clazz.getMethod(descriptor); if (method != null) { - if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { - ConfigurationType clazzType = MetadataTracer.singleton().traceJNIType(classObject.getName()); - if (clazzType != null) { - clazzType.addMethod(descriptor.getNameConvertToString(), descriptor.getSignatureConvertToString(), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); - } - } return method; } } @@ -334,6 +334,12 @@ private static JNIAccessibleMethod checkMethod(JNIAccessibleMethod method, Class } private static JNIAccessibleField getDeclaredField(Class classObject, CharSequence name, boolean isStatic, String dumpLabel) { + if (MetadataTracer.enabled()) { + ConfigurationType clazzType = MetadataTracer.singleton().traceJNIType(classObject.getName()); + if (clazzType != null) { + clazzType.addField(name.toString(), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED, false); + } + } boolean foundClass = false; for (var dictionary : layeredSingletons()) { JNIAccessibleClass clazz = dictionary.classesByClassObject.get(classObject); @@ -341,12 +347,6 @@ private static JNIAccessibleField getDeclaredField(Class classObject, CharSeq foundClass = true; JNIAccessibleField field = clazz.getField(name); if (field != null && (field.isStatic() == isStatic || field.isNegative())) { - if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { - ConfigurationType clazzType = MetadataTracer.singleton().traceJNIType(classObject.getName()); - if (clazzType != null) { - clazzType.addField(name.toString(), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED, false); - } - } return field; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/metadata/MetadataTracer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/metadata/MetadataTracer.java index 6184da4aab92..8ecfa8e3b006 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/metadata/MetadataTracer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/metadata/MetadataTracer.java @@ -44,6 +44,7 @@ import com.oracle.svm.configure.config.ConfigurationFileCollection; import com.oracle.svm.configure.config.ConfigurationSet; import com.oracle.svm.configure.config.ConfigurationType; +import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; @@ -56,7 +57,6 @@ import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.util.VMError; -import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.options.Option; import jdk.graal.compiler.options.OptionStability; @@ -97,15 +97,32 @@ public static class Options { */ private volatile ConfigurationSet config; - @Fold + /** + * Returns the singleton object, which is only available if tracing is enabled at build time. + *

+ * We use {@code @AlwaysInline} and not {@code @Fold} because the latter eagerly evaluates the + * method, which fails when the singleton is unavailable. + */ + @AlwaysInline("avoid null check on singleton") public static MetadataTracer singleton() { return ImageSingletons.lookup(MetadataTracer.class); } + /** + * Returns whether tracing is enabled. Tracing code should be guarded by this condition. + *

+ * This condition is force-inlined so that when tracing support is not included at build time + * the condition folds to false and the tracing code itself will fold away. + */ + @AlwaysInline("tracing should fold away when disabled") + public static boolean enabled() { + return Options.MetadataTracingSupport.getValue() && singleton().enabledAtRunTime(); + } + /** * Returns whether tracing is enabled at run time (using {@code -XX:RecordMetadata}). */ - public boolean enabled() { + private boolean enabledAtRunTime() { VMError.guarantee(Options.MetadataTracingSupport.getValue()); return options != null; } @@ -140,7 +157,7 @@ public void traceProxyType(List interfaceNames) { } private ConfigurationType traceReflectionTypeImpl(ConfigurationTypeDescriptor typeDescriptor) { - assert enabled(); + assert enabledAtRunTime(); ConfigurationSet configurationSet = getConfigurationSetForTracing(); if (configurationSet != null) { return configurationSet.getReflectionConfiguration().getOrCreateType(UnresolvedConfigurationCondition.alwaysTrue(), typeDescriptor); @@ -155,7 +172,7 @@ private ConfigurationType traceReflectionTypeImpl(ConfigurationTypeDescriptor ty * (e.g., during shutdown). */ public ConfigurationType traceJNIType(String className) { - assert enabled(); + assert enabledAtRunTime(); ConfigurationType result = traceReflectionType(className); if (result != null) { result.setJniAccessible(); @@ -164,10 +181,11 @@ public ConfigurationType traceJNIType(String className) { } /** - * Marks the given resource within the given (optional) module as reachable. + * Marks the given resource within the given (optional) module as reachable. Use this method to + * trace resource lookups covered by image metadata (including negative queries). */ public void traceResource(String resourceName, String moduleName) { - assert enabled(); + assert enabledAtRunTime(); ConfigurationSet configurationSet = getConfigurationSetForTracing(); if (configurationSet != null) { configurationSet.getResourceConfiguration().addGlobPattern(UnresolvedConfigurationCondition.alwaysTrue(), resourceName, moduleName); @@ -178,7 +196,7 @@ public void traceResource(String resourceName, String moduleName) { * Marks the given resource bundle within the given locale as reachable. */ public void traceResourceBundle(String baseName) { - assert enabled(); + assert enabledAtRunTime(); ConfigurationSet configurationSet = getConfigurationSetForTracing(); if (configurationSet != null) { configurationSet.getResourceConfiguration().addBundle(UnresolvedConfigurationCondition.alwaysTrue(), baseName, List.of()); @@ -189,7 +207,7 @@ public void traceResourceBundle(String baseName) { * Marks the given type as serializable. */ public void traceSerializationType(String className) { - assert enabled(); + assert enabledAtRunTime(); ConfigurationType result = traceReflectionType(className); if (result != null) { result.setSerializable(); 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 09fe40650cd9..1803b16d38d6 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 @@ -191,7 +191,7 @@ private static ClassLoader getCommonClassLoaderOrFail(ClassLoader loader, Class< @Override public Class getProxyClass(ClassLoader loader, Class... interfaces) { - if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + if (MetadataTracer.enabled()) { List interfaceNames = new ArrayList<>(interfaces.length); for (Class iface : interfaces) { interfaceNames.add(iface.getName()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java index ed17222dff95..63f927793152 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java @@ -267,12 +267,12 @@ public static Object getSerializationConstructorAccessor(Class serializationT return constructorAccessor; } } else { + if (MetadataTracer.enabled()) { + MetadataTracer.singleton().traceSerializationType(declaringClass.getName()); + } for (var singleton : layeredSingletons()) { Object constructorAccessor = singleton.getSerializationConstructorAccessor0(declaringClass, targetConstructorClass); if (constructorAccessor != null) { - if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { - MetadataTracer.singleton().traceSerializationType(declaringClass.getName()); - } return constructorAccessor; } }