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;
}
}