From c161ba0335ce51ed8b8beee37fce72c5dada7bb1 Mon Sep 17 00:00:00 2001 From: Matt D'Souza Date: Thu, 3 Apr 2025 15:30:37 -0400 Subject: [PATCH 1/6] Trace resource accesses --- .../src/com/oracle/svm/core/jdk/Resources.java | 8 ++++++++ .../jdk/localization/LocalizationSupport.java | 9 ++++++++- .../oracle/svm/core/metadata/MetadataTracer.java | 16 ++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) 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 3c1c5e164616..454412c9bc2b 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 @@ -71,6 +71,7 @@ import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonSupport; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton; +import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.core.util.ImageHeapMap; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.GlobUtils; @@ -385,6 +386,7 @@ public static ResourceStorageEntryBase getAtRuntime(Module module, String resour return null; } } + traceResourceAccess(resourceName, moduleName); if (!entry.getConditions().satisfied()) { return missingMetadata(resourceName, throwOnMissing); } @@ -414,6 +416,12 @@ public static ResourceStorageEntryBase getAtRuntime(Module module, String resour return unconditionalEntry; } + private static void traceResourceAccess(String resourceName, String moduleName) { + if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + MetadataTracer.singleton().traceResource(resourceName, moduleName); + } + } + private static ConditionalRuntimeValue getEntry(Module module, String canonicalResourceName) { for (var r : layeredSingletons()) { ConditionalRuntimeValue entry = r.resources.get(createStorageKey(module, canonicalResourceName)); 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 36d8bb4e6e21..470264065b6c 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 @@ -57,6 +57,7 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.jdk.Resources; +import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.core.util.ImageHeapMap; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; @@ -293,6 +294,12 @@ public boolean isRegisteredBundleLookup(String baseName, Locale locale, Object c /* Those cases will throw a NullPointerException before any lookup */ return true; } - return registeredBundles.containsKey(baseName) && registeredBundles.get(baseName).satisfied(); + 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/metadata/MetadataTracer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/metadata/MetadataTracer.java index 60a138fe0966..71922948a95a 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 @@ -33,6 +33,7 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; +import com.oracle.svm.configure.UnresolvedConfigurationCondition; import com.oracle.svm.configure.config.ConfigurationSet; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; @@ -72,6 +73,21 @@ public static MetadataTracer singleton() { return ImageSingletons.lookup(MetadataTracer.class); } + public boolean enabled() { + VMError.guarantee(Options.MetadataTracingSupport.getValue()); + return config != null; + } + + public void traceResource(String resourceName, String moduleName) { + assert enabled(); + config.getResourceConfiguration().addGlobPattern(UnresolvedConfigurationCondition.alwaysTrue(), resourceName, moduleName); + } + + public void traceResourceBundle(String baseName) { + assert enabled(); + config.getResourceConfiguration().addBundle(UnresolvedConfigurationCondition.alwaysTrue(), baseName, List.of()); + } + private static void initialize() { assert Options.MetadataTracingSupport.getValue(); MetadataTracer singleton = MetadataTracer.singleton(); From 7a14e3223f7724e660ecc9ce3a2bea50f552a344 Mon Sep 17 00:00:00 2001 From: Matt D'Souza Date: Fri, 4 Apr 2025 16:20:59 -0400 Subject: [PATCH 2/6] Trace serialization accesses --- .../com/oracle/svm/core/jdk/JavaIOSubstitutions.java | 12 ++++++++---- .../com/oracle/svm/core/metadata/MetadataTracer.java | 6 ++++++ .../core/reflect/serialize/SerializationSupport.java | 4 ++++ 3 files changed, 18 insertions(+), 4 deletions(-) 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 71a52c7ccff0..f9956c1f5ce6 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 @@ -35,8 +35,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; -import jdk.graal.compiler.java.LambdaUtils; - import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; @@ -45,8 +43,11 @@ import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.fieldvaluetransformer.NewInstanceFieldValueTransformer; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.core.reflect.serialize.MissingSerializationRegistrationUtils; +import jdk.graal.compiler.java.LambdaUtils; + @TargetClass(java.io.FileDescriptor.class) final class Target_java_io_FileDescriptor { @@ -68,8 +69,8 @@ static ObjectStreamClass lookup(Class cl, boolean all) { return null; } - if (Serializable.class.isAssignableFrom(cl)) { - if (!cl.isArray() && !DynamicHub.fromClass(cl).isRegisteredForSerialization()) { + if (Serializable.class.isAssignableFrom(cl) && !cl.isArray()) { + if (!DynamicHub.fromClass(cl).isRegisteredForSerialization()) { boolean isLambda = cl.getTypeName().contains(LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING); boolean isProxy = Proxy.isProxyClass(cl); if (isProxy || isLambda) { @@ -87,6 +88,9 @@ static ObjectStreamClass lookup(Class cl, boolean all) { MissingSerializationRegistrationUtils.missingSerializationRegistration(cl, "type " + cl.getTypeName()); } } + 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/metadata/MetadataTracer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/metadata/MetadataTracer.java index 71922948a95a..a2d6d288174f 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 @@ -33,6 +33,7 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; +import com.oracle.svm.configure.NamedConfigurationTypeDescriptor; import com.oracle.svm.configure.UnresolvedConfigurationCondition; import com.oracle.svm.configure.config.ConfigurationSet; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; @@ -88,6 +89,11 @@ public void traceResourceBundle(String baseName) { config.getResourceConfiguration().addBundle(UnresolvedConfigurationCondition.alwaysTrue(), baseName, List.of()); } + public void traceSerializationType(String className) { + assert enabled(); + config.getReflectionConfiguration().getOrCreateType(UnresolvedConfigurationCondition.alwaysTrue(), new NamedConfigurationTypeDescriptor(className)).setSerializable(); + } + private static void initialize() { assert Options.MetadataTracingSupport.getValue(); MetadataTracer singleton = MetadataTracer.singleton(); 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 866d3be261a0..8ca407aa2cd4 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 @@ -48,6 +48,7 @@ import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonSupport; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton; +import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.core.reflect.SubstrateConstructorAccessor; import com.oracle.svm.core.util.ImageHeapMap; import com.oracle.svm.core.util.VMError; @@ -269,6 +270,9 @@ public static Object getSerializationConstructorAccessor(Class serializationT 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; } } From 28385e06637d7d2159022625955b5a37b8028ca4 Mon Sep 17 00:00:00 2001 From: Matt D'Souza Date: Mon, 21 Apr 2025 09:23:39 -0400 Subject: [PATCH 3/6] Trace reflective accesses --- .../snippets/SubstrateAllocationSnippets.java | 1 + .../svm/core/hub/ClassForNameSupport.java | 12 +++- .../com/oracle/svm/core/hub/DynamicHub.java | 62 ++++++++++++++++++- .../jdk/JavaLangReflectSubstitutions.java | 4 ++ .../svm/core/metadata/MetadataTracer.java | 6 ++ 5 files changed, 83 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java index 01b5981adae9..53e4b8aff564 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java @@ -127,6 +127,7 @@ public class SubstrateAllocationSnippets extends AllocationSnippets { private static final SubstrateForeignCallDescriptor NEW_MULTI_ARRAY = SnippetRuntime.findForeignCall(SubstrateAllocationSnippets.class, "newMultiArrayStub", NO_SIDE_EFFECT); private static final SubstrateForeignCallDescriptor SLOW_PATH_HUB_OR_UNSAFE_INSTANTIATE_ERROR = SnippetRuntime.findForeignCall(SubstrateAllocationSnippets.class, "slowPathHubOrUnsafeInstantiationError", NO_SIDE_EFFECT); + private static final SubstrateForeignCallDescriptor ARRAY_HUB_ERROR = SnippetRuntime.findForeignCall(SubstrateAllocationSnippets.class, "arrayHubErrorStub", NO_SIDE_EFFECT); private static final SubstrateForeignCallDescriptor[] FOREIGN_CALLS = new SubstrateForeignCallDescriptor[]{NEW_MULTI_ARRAY, SLOW_PATH_HUB_OR_UNSAFE_INSTANTIATE_ERROR, ARRAY_HUB_ERROR}; 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 9e9412a89171..d90a89dd2035 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 @@ -47,6 +47,7 @@ import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonSupport; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton; +import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import com.oracle.svm.core.util.ImageHeapMap; @@ -330,6 +331,9 @@ private static Class forName(String className, ClassLoader classLoader, boole private Object forName0(String className, ClassLoader classLoader) { var conditional = knownClasses.get(className); + if (MetadataTracer.Options.MetadataTracingSupport.getValue() && conditional != null && MetadataTracer.singleton().enabled()) { + MetadataTracer.singleton().traceReflectionType(className); + } Object result = conditional == null ? null : conditional.getValue(); if (className.endsWith("[]")) { /* Querying array classes with their "TypeName[]" name always throws */ @@ -444,7 +448,13 @@ public static boolean canUnsafeInstantiateAsInstance(DynamicHub hub) { break; } } - return conditionSet != null && conditionSet.satisfied(); + if (conditionSet != null) { + if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + MetadataTracer.singleton().traceReflectionType(clazz.getName()).setUnsafeAllocated(); + } + return conditionSet.satisfied(); + } + return false; } @Override 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 f0061b6c3e67..a7fcbc8958e2 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 @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.hub; +import static com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility; +import static com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; import static com.oracle.svm.core.MissingRegistrationUtils.throwMissingRegistrationErrors; import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; import static com.oracle.svm.core.annotate.TargetElement.CONSTRUCTOR_NAME; @@ -84,19 +86,21 @@ import java.util.function.BiFunction; import java.util.function.IntFunction; -import com.oracle.svm.core.TrackDynamicAccessEnabled; import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.WordBase; +import com.oracle.svm.configure.config.ConfigurationType; +import com.oracle.svm.configure.config.SignatureUtil; import com.oracle.svm.core.BuildPhaseProvider.AfterHostedUniverse; import com.oracle.svm.core.BuildPhaseProvider.CompileQueueFinished; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.NeverInlineTrivial; import com.oracle.svm.core.RuntimeAssertionsSupport; import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.TrackDynamicAccessEnabled; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Delete; @@ -121,6 +125,7 @@ import com.oracle.svm.core.jdk.ProtectionDomainSupport; import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.meta.SharedType; +import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import com.oracle.svm.core.reflect.RuntimeMetadataDecoder; import com.oracle.svm.core.reflect.RuntimeMetadataDecoder.ConstructorDescriptor; @@ -704,6 +709,9 @@ private ReflectionMetadata reflectionMetadata() { } private void checkClassFlag(int mask, String methodName) { + if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + traceClassFlagQuery(mask); + } if (throwMissingRegistrationErrors() && !(isClassFlagSet(mask) && getConditions().satisfied())) { MissingReflectionRegistrationUtils.forBulkQuery(DynamicHub.toClass(this), methodName); } @@ -726,6 +734,27 @@ private static boolean isClassFlagSet(int mask, ReflectionMetadata reflectionMet return reflectionMetadata != null && (reflectionMetadata.classFlags & mask) != 0; } + private void traceClassFlagQuery(int mask) { + ConfigurationType type = MetadataTracer.singleton().traceReflectionType(getName()); + // TODO (GR-64765): We over-approximate member accessibility here because we don't trace + // accesses. Once we trace accesses, it will suffice to register the class for reflection. + switch (mask) { + case ALL_FIELDS_FLAG -> type.setAllPublicFields(ConfigurationMemberAccessibility.ACCESSED); + case ALL_DECLARED_FIELDS_FLAG -> type.setAllDeclaredFields(ConfigurationMemberAccessibility.ACCESSED); + case ALL_METHODS_FLAG -> type.setAllPublicMethods(ConfigurationMemberAccessibility.ACCESSED); + case ALL_DECLARED_METHODS_FLAG -> type.setAllDeclaredMethods(ConfigurationMemberAccessibility.ACCESSED); + case ALL_CONSTRUCTORS_FLAG -> type.setAllPublicConstructors(ConfigurationMemberAccessibility.ACCESSED); + case ALL_DECLARED_CONSTRUCTORS_FLAG -> type.setAllDeclaredConstructors(ConfigurationMemberAccessibility.ACCESSED); + case ALL_CLASSES_FLAG -> type.setAllPublicClasses(); + case ALL_DECLARED_CLASSES_FLAG -> type.setAllDeclaredClasses(); + case ALL_RECORD_COMPONENTS_FLAG -> type.setAllRecordComponents(); + case ALL_PERMITTED_SUBCLASSES_FLAG -> type.setAllPermittedSubclasses(); + case ALL_NEST_MEMBERS_FLAG -> type.setAllNestMembers(); + case ALL_SIGNERS_FLAG -> type.setAllSigners(); + default -> throw VMError.shouldNotReachHere("unknown class flag " + mask); + } + } + /** Executed at runtime. */ private static Object initEnumConstantsAtRuntime(Method values) { try { @@ -1292,6 +1321,14 @@ private void checkField(String fieldName, Field field, boolean publicOnly) throw */ throw new NoSuchFieldException(fieldName); } else { + if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + ConfigurationMemberDeclaration declaration = publicOnly ? ConfigurationMemberDeclaration.PRESENT : ConfigurationMemberDeclaration.DECLARED; + // register declaring type and field + ConfigurationType declaringType = MetadataTracer.singleton().traceReflectionType(field.getDeclaringClass().getName()); + declaringType.addField(fieldName, declaration, false); + // register receiver type + MetadataTracer.singleton().traceReflectionType(getName()); + } RuntimeMetadataDecoder decoder = ImageSingletons.lookup(RuntimeMetadataDecoder.class); int fieldModifiers = field.getModifiers(); boolean negative = decoder.isNegative(fieldModifiers); @@ -1359,6 +1396,14 @@ private boolean checkExecutableExists(String methodName, Class[] parameterTyp int methodModifiers = method.getModifiers(); boolean negative = decoder.isNegative(methodModifiers); boolean hiding = decoder.isHiding(methodModifiers); + if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + ConfigurationMemberDeclaration declaration = publicOnly ? ConfigurationMemberDeclaration.PRESENT : ConfigurationMemberDeclaration.DECLARED; + // register declaring type and method + ConfigurationType declaringType = MetadataTracer.singleton().traceReflectionType(method.getDeclaringClass().getName()); + declaringType.addMethod(methodName, toInternalSignature(parameterTypes), declaration); + // register receiver type + MetadataTracer.singleton().traceReflectionType(getName()); + } if (throwMissingErrors && hiding) { MissingReflectionRegistrationUtils.forMethod(clazz, methodName, parameterTypes); } @@ -1366,6 +1411,19 @@ private boolean checkExecutableExists(String methodName, Class[] parameterTyp } } + private static String toInternalSignature(Class[] classes) { + List names; + if (classes == null) { + names = List.of(); + } else { + names = new ArrayList<>(classes.length); + for (Class aClass : classes) { + names.add(aClass.getName()); + } + } + return SignatureUtil.toInternalSignature(names); + } + private boolean allElementsRegistered(boolean publicOnly, int allDeclaredElementsFlag, int allPublicElementsFlag) { return isClassFlagSet(allDeclaredElementsFlag) || (publicOnly && isClassFlagSet(allPublicElementsFlag)); } @@ -1882,6 +1940,8 @@ public DynamicHub arrayType() { } if (companion.arrayHub == null) { MissingReflectionRegistrationUtils.forClass(getTypeName() + "[]"); + } else if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + MetadataTracer.singleton().traceReflectionType(companion.arrayHub.getTypeName()); } return companion.arrayHub; } 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 7e5c2db054a0..652f1bcc738a 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 @@ -37,6 +37,7 @@ import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.LayoutEncoding; +import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import com.oracle.svm.core.snippets.KnownIntrinsics; @@ -386,6 +387,9 @@ 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()) { + 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/metadata/MetadataTracer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/metadata/MetadataTracer.java index a2d6d288174f..01428102c3bb 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 @@ -36,6 +36,7 @@ import com.oracle.svm.configure.NamedConfigurationTypeDescriptor; import com.oracle.svm.configure.UnresolvedConfigurationCondition; import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.config.ConfigurationType; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.jdk.RuntimeSupport; @@ -79,6 +80,11 @@ public boolean enabled() { return config != null; } + public ConfigurationType traceReflectionType(String className) { + assert enabled(); + return config.getReflectionConfiguration().getOrCreateType(UnresolvedConfigurationCondition.alwaysTrue(), new NamedConfigurationTypeDescriptor(className)); + } + public void traceResource(String resourceName, String moduleName) { assert enabled(); config.getResourceConfiguration().addGlobPattern(UnresolvedConfigurationCondition.alwaysTrue(), resourceName, moduleName); From 173b29deeb53cc685a1e52ba92b834765f099022 Mon Sep 17 00:00:00 2001 From: Matt D'Souza Date: Mon, 7 Apr 2025 15:56:51 -0400 Subject: [PATCH 4/6] Trace JNI accesses --- .../access/JNIAccessibleMethodDescriptor.java | 16 ++++++++++++++++ .../core/jni/access/JNIReflectionDictionary.java | 14 ++++++++++++++ .../oracle/svm/core/metadata/MetadataTracer.java | 7 +++++++ 3 files changed, 37 insertions(+) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleMethodDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleMethodDescriptor.java index 2bb1fe5a58a7..3daeaeb64bab 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleMethodDescriptor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleMethodDescriptor.java @@ -98,10 +98,19 @@ public boolean isClassInitializer() { return WRAPPED_CSTRING_EQUIVALENCE.equals(name, INITIALIZER_NAME); } + /** + * Returns the method name as a String. Can be used if the descriptor is known to be a String + * (i.e., it does not come from a JNI call); otherwise, use {@link #getNameConvertToString()}. + */ public String getName() { return (String) name; } + /** + * Returns the method signature as a String. Can be used if the descriptor is known to be a + * String (i.e., it does not come from a JNI call); otherwise, use + * {@link #getSignatureConvertToString()}. + */ public String getSignature() { return (String) signature; } @@ -113,6 +122,13 @@ public String getNameConvertToString() { return name.toString(); } + /** + * Performs a potentially costly conversion to string, only for slow paths. + */ + public String getSignatureConvertToString() { + return signature.toString(); + } + public String getSignatureWithoutReturnType() { String signatureString = signature.toString(); int parametersEnd = signatureString.lastIndexOf(')'); 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 c81efd2b99f5..f46da9dc7c5b 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 @@ -42,6 +42,8 @@ import org.graalvm.word.Pointer; import com.oracle.svm.configure.ClassNameSupport; +import com.oracle.svm.configure.config.ConfigurationMemberInfo; +import com.oracle.svm.configure.config.ConfigurationType; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.heap.Heap; @@ -53,6 +55,7 @@ import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton; import com.oracle.svm.core.log.Log; +import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.core.snippets.KnownIntrinsics; import com.oracle.svm.core.util.ImageHeapMap; import com.oracle.svm.core.util.Utf8.WrappedAsciiCString; @@ -187,6 +190,9 @@ public static Class getClassObjectByName(CharSequence name) { clazz = NEGATIVE_CLASS_LOOKUP; } } + if (MetadataTracer.Options.MetadataTracingSupport.getValue() && clazz != null && MetadataTracer.singleton().enabled()) { + MetadataTracer.singleton().traceJNIType(ClassNameSupport.jniNameToTypeName(name.toString())); + } clazz = checkClass(clazz, name); if (clazz != null) { return clazz.getClassObject(); @@ -279,6 +285,10 @@ 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()); + clazzType.addMethod(descriptor.getNameConvertToString(), descriptor.getSignatureConvertToString(), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); + } return method; } } @@ -334,6 +344,10 @@ 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()); + 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 01428102c3bb..33a0dd72f36f 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 @@ -85,6 +85,13 @@ public ConfigurationType traceReflectionType(String className) { return config.getReflectionConfiguration().getOrCreateType(UnresolvedConfigurationCondition.alwaysTrue(), new NamedConfigurationTypeDescriptor(className)); } + public ConfigurationType traceJNIType(String className) { + assert enabled(); + ConfigurationType result = traceReflectionType(className); + result.setJniAccessible(); + return result; + } + public void traceResource(String resourceName, String moduleName) { assert enabled(); config.getResourceConfiguration().addGlobPattern(UnresolvedConfigurationCondition.alwaysTrue(), resourceName, moduleName); From 499cdf21a91ede252e226bf3104c56fcfc3d2cf8 Mon Sep 17 00:00:00 2001 From: Matt D'Souza Date: Tue, 29 Apr 2025 15:42:10 -0400 Subject: [PATCH 5/6] Avoid tracing accesses triggered by shutdown hook --- .../svm/core/hub/ClassForNameSupport.java | 6 ++- .../com/oracle/svm/core/hub/DynamicHub.java | 11 +++- .../jni/access/JNIReflectionDictionary.java | 8 ++- .../svm/core/metadata/MetadataTracer.java | 54 ++++++++++++++++--- 4 files changed, 67 insertions(+), 12 deletions(-) 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 d90a89dd2035..0b3d1aa01534 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 @@ -39,6 +39,7 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.ClassNameSupport; +import com.oracle.svm.configure.config.ConfigurationType; import com.oracle.svm.core.configure.ConditionalRuntimeValue; import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; @@ -450,7 +451,10 @@ public static boolean canUnsafeInstantiateAsInstance(DynamicHub hub) { } if (conditionSet != null) { if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { - MetadataTracer.singleton().traceReflectionType(clazz.getName()).setUnsafeAllocated(); + ConfigurationType type = MetadataTracer.singleton().traceReflectionType(clazz.getName()); + if (type != null) { + type.setUnsafeAllocated(); + } } return conditionSet.satisfied(); } 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 a7fcbc8958e2..89f2656258e7 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 @@ -736,6 +736,9 @@ private static boolean isClassFlagSet(int mask, ReflectionMetadata reflectionMet private void traceClassFlagQuery(int mask) { ConfigurationType type = MetadataTracer.singleton().traceReflectionType(getName()); + if (type == null) { + return; + } // TODO (GR-64765): We over-approximate member accessibility here because we don't trace // accesses. Once we trace accesses, it will suffice to register the class for reflection. switch (mask) { @@ -1325,7 +1328,9 @@ private void checkField(String fieldName, Field field, boolean publicOnly) throw ConfigurationMemberDeclaration declaration = publicOnly ? ConfigurationMemberDeclaration.PRESENT : ConfigurationMemberDeclaration.DECLARED; // register declaring type and field ConfigurationType declaringType = MetadataTracer.singleton().traceReflectionType(field.getDeclaringClass().getName()); - declaringType.addField(fieldName, declaration, false); + if (declaringType != null) { + declaringType.addField(fieldName, declaration, false); + } // register receiver type MetadataTracer.singleton().traceReflectionType(getName()); } @@ -1400,7 +1405,9 @@ private boolean checkExecutableExists(String methodName, Class[] parameterTyp ConfigurationMemberDeclaration declaration = publicOnly ? ConfigurationMemberDeclaration.PRESENT : ConfigurationMemberDeclaration.DECLARED; // register declaring type and method ConfigurationType declaringType = MetadataTracer.singleton().traceReflectionType(method.getDeclaringClass().getName()); - declaringType.addMethod(methodName, toInternalSignature(parameterTypes), declaration); + if (declaringType != null) { + declaringType.addMethod(methodName, toInternalSignature(parameterTypes), declaration); + } // register receiver type MetadataTracer.singleton().traceReflectionType(getName()); } 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 f46da9dc7c5b..80da24ab3357 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 @@ -287,7 +287,9 @@ private static JNIAccessibleMethod getDeclaredMethod(Class classObject, JNIAc if (method != null) { if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { ConfigurationType clazzType = MetadataTracer.singleton().traceJNIType(classObject.getName()); - clazzType.addMethod(descriptor.getNameConvertToString(), descriptor.getSignatureConvertToString(), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); + if (clazzType != null) { + clazzType.addMethod(descriptor.getNameConvertToString(), descriptor.getSignatureConvertToString(), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); + } } return method; } @@ -346,7 +348,9 @@ private static JNIAccessibleField getDeclaredField(Class classObject, CharSeq if (field != null && (field.isStatic() == isStatic || field.isNegative())) { if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { ConfigurationType clazzType = MetadataTracer.singleton().traceJNIType(classObject.getName()); - clazzType.addField(name.toString(), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED, false); + 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 33a0dd72f36f..a7bc3aff0189 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 @@ -66,7 +66,7 @@ public static class Options { public static final RuntimeOptionKey RecordMetadata = new RuntimeOptionKey<>(""); } - private ConfigurationSet config; + private volatile ConfigurationSet config; private Path recordMetadataPath; @@ -75,36 +75,75 @@ public static MetadataTracer singleton() { return ImageSingletons.lookup(MetadataTracer.class); } + /** + * Returns whether tracing is enabled at run time (using {@code -XX:RecordMetadata=path}). + */ public boolean enabled() { VMError.guarantee(Options.MetadataTracingSupport.getValue()); - return config != null; + return recordMetadataPath != null; } + /** + * Marks the given type as reachable from reflection. + * + * @return the corresponding {@link ConfigurationType} or {@code null} if tracing is not active + * (e.g., during shutdown). + */ public ConfigurationType traceReflectionType(String className) { assert enabled(); - return config.getReflectionConfiguration().getOrCreateType(UnresolvedConfigurationCondition.alwaysTrue(), new NamedConfigurationTypeDescriptor(className)); + ConfigurationSet configurationSet = config; + if (configurationSet != null) { + return configurationSet.getReflectionConfiguration().getOrCreateType(UnresolvedConfigurationCondition.alwaysTrue(), new NamedConfigurationTypeDescriptor(className)); + } + return null; } + /** + * Marks the given type as reachable from JNI. + * + * @return the corresponding {@link ConfigurationType} or {@code null} if tracing is not active + * (e.g., during shutdown). + */ public ConfigurationType traceJNIType(String className) { assert enabled(); ConfigurationType result = traceReflectionType(className); - result.setJniAccessible(); + if (result != null) { + result.setJniAccessible(); + } return result; } + /** + * Marks the given resource within the given (optional) module as reachable. + */ public void traceResource(String resourceName, String moduleName) { assert enabled(); - config.getResourceConfiguration().addGlobPattern(UnresolvedConfigurationCondition.alwaysTrue(), resourceName, moduleName); + ConfigurationSet configurationSet = config; + if (configurationSet != null) { + configurationSet.getResourceConfiguration().addGlobPattern(UnresolvedConfigurationCondition.alwaysTrue(), resourceName, moduleName); + } } + /** + * Marks the given resource bundle within the given locale as reachable. + */ public void traceResourceBundle(String baseName) { assert enabled(); - config.getResourceConfiguration().addBundle(UnresolvedConfigurationCondition.alwaysTrue(), baseName, List.of()); + ConfigurationSet configurationSet = config; + if (configurationSet != null) { + configurationSet.getResourceConfiguration().addBundle(UnresolvedConfigurationCondition.alwaysTrue(), baseName, List.of()); + } } + /** + * Marks the given type as serializable. + */ public void traceSerializationType(String className) { assert enabled(); - config.getReflectionConfiguration().getOrCreateType(UnresolvedConfigurationCondition.alwaysTrue(), new NamedConfigurationTypeDescriptor(className)).setSerializable(); + ConfigurationSet configurationSet = config; + if (configurationSet != null) { + configurationSet.getReflectionConfiguration().getOrCreateType(UnresolvedConfigurationCondition.alwaysTrue(), new NamedConfigurationTypeDescriptor(className)).setSerializable(); + } } private static void initialize() { @@ -128,6 +167,7 @@ private static void shutdown() { assert Options.MetadataTracingSupport.getValue(); MetadataTracer singleton = MetadataTracer.singleton(); ConfigurationSet config = singleton.config; + singleton.config = null; // clear config so that shutdown events are not traced. if (config != null) { try { config.writeConfiguration(configFile -> singleton.recordMetadataPath.resolve(configFile.getFileName())); From daa26d729474e6e3e9c219cb29ae1d69142aa430 Mon Sep 17 00:00:00 2001 From: Matt D'Souza Date: Tue, 20 May 2025 15:27:28 -0400 Subject: [PATCH 6/6] Update RecordMetadata option to take comma-separated key-value pairs --- .../svm/core/metadata/MetadataTracer.java | 102 ++++++++++++++---- .../metadata/doc-files/RecordMetadataHelp.txt | 9 ++ 2 files changed, 90 insertions(+), 21 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/metadata/doc-files/RecordMetadataHelp.txt 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 a7bc3aff0189..9ab7eb7c22bb 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 @@ -28,7 +28,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; @@ -37,6 +40,7 @@ import com.oracle.svm.configure.UnresolvedConfigurationCondition; import com.oracle.svm.configure.config.ConfigurationSet; import com.oracle.svm.configure.config.ConfigurationType; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.jdk.RuntimeSupport; @@ -53,22 +57,24 @@ /** * Implements reachability metadata tracing during native image execution. Enabling * {@link Options#MetadataTracingSupport} at build time will generate code to trace all accesses of - * reachability metadata. When {@link Options#RecordMetadata} is specified at run time, the image - * will trace and emit metadata to the specified path. + * reachability metadata, and then the run-time option {@link Options#RecordMetadata} enables + * tracing. */ public final class MetadataTracer { public static class Options { - @Option(help = "Enables the run-time code to trace reachability metadata accesses in the produced native image by using -XX:RecordMetadata=.")// + @Option(help = "Generate an image that supports reachability metadata access tracing. " + + "When tracing is supported, use the -XX:RecordMetadata option to enable tracing at run time.")// public static final HostedOptionKey MetadataTracingSupport = new HostedOptionKey<>(false); - @Option(help = "The path of the directory to write traced metadata to. Metadata tracing is enabled only when this option is provided.")// - public static final RuntimeOptionKey RecordMetadata = new RuntimeOptionKey<>(""); + @Option(help = "file:doc-files/RecordMetadataHelp.txt")// + public static final RuntimeOptionKey RecordMetadata = new RuntimeOptionKey<>(null); } + + private RecordOptions options; private volatile ConfigurationSet config; - private Path recordMetadataPath; @Fold public static MetadataTracer singleton() { @@ -80,7 +86,7 @@ public static MetadataTracer singleton() { */ public boolean enabled() { VMError.guarantee(Options.MetadataTracingSupport.getValue()); - return recordMetadataPath != null; + return options != null; } /** @@ -146,20 +152,21 @@ public void traceSerializationType(String className) { } } - private static void initialize() { + private static void initialize(String recordMetadataValue) { assert Options.MetadataTracingSupport.getValue(); - MetadataTracer singleton = MetadataTracer.singleton(); - String recordMetadataValue = Options.RecordMetadata.getValue(); - if (recordMetadataValue.isEmpty()) { - throw new IllegalArgumentException("Empty path provided for " + Options.RecordMetadata.getName() + "."); - } - Path recordMetadataPath = Paths.get(recordMetadataValue); + + RecordOptions parsedOptions = RecordOptions.parse(recordMetadataValue); try { - Files.createDirectories(recordMetadataPath); + Files.createDirectories(parsedOptions.path()); } catch (IOException ex) { - throw new IllegalArgumentException("Exception occurred creating the output directory for tracing (" + recordMetadataPath + ")", ex); + throw new IllegalArgumentException("Exception occurred creating the output directory for tracing (" + parsedOptions.path() + ")", ex); } - singleton.recordMetadataPath = recordMetadataPath; + if (parsedOptions.mode() != RecordMode.DEFAULT) { + throw new IllegalArgumentException("Mode " + parsedOptions.mode() + " is not yet supported."); + } + + MetadataTracer singleton = MetadataTracer.singleton(); + singleton.options = parsedOptions; singleton.config = new ConfigurationSet(); } @@ -170,10 +177,10 @@ private static void shutdown() { singleton.config = null; // clear config so that shutdown events are not traced. if (config != null) { try { - config.writeConfiguration(configFile -> singleton.recordMetadataPath.resolve(configFile.getFileName())); + config.writeConfiguration(configFile -> singleton.options.path().resolve(configFile.getFileName())); } catch (IOException ex) { Log log = Log.log(); - log.string("Failed to write out reachability metadata to directory ").string(singleton.recordMetadataPath.toString()); + log.string("Failed to write out reachability metadata to directory ").string(singleton.options.path().toString()); log.string(":").string(ex.getMessage()); log.newline(); } @@ -187,7 +194,7 @@ static RuntimeSupport.Hook initializeMetadataTracingHook() { } VMError.guarantee(Options.MetadataTracingSupport.getValue()); if (Options.RecordMetadata.hasBeenSet()) { - initialize(); + initialize(Options.RecordMetadata.getValue()); } }; } @@ -217,12 +224,65 @@ static RuntimeSupport.Hook checkImproperOptionUsageHook() { throw new IllegalArgumentException( "The option " + Options.RecordMetadata.getName() + " can only be used if metadata tracing is enabled at build time (using " + hostedOptionCommandArgument + ")."); - } }; } } +enum RecordMode { + DEFAULT, + CONDITIONAL +} + +record RecordOptions(Path path, RecordMode mode) { + + static RecordOptions parse(String recordMetadataOptions) { + if (recordMetadataOptions.isEmpty()) { + throw parseError("Empty string provided for " + MetadataTracer.Options.RecordMetadata.getName() + "."); + } + + Map parsedOptions = new HashMap<>(); + Set allOptions = Set.of("path", "mode"); + for (String option : recordMetadataOptions.split(",")) { + String[] parts = SubstrateUtil.split(option, "=", 2); + if (parts.length != 2) { + throw badOptionError(option, "Option should be a key-value pair separated by '='."); + } else if (!allOptions.contains(parts[0])) { + throw badOptionError(option, "Option should be one of " + allOptions); + } else if (parsedOptions.containsKey(parts[0])) { + throw badOptionError(option, "Option was already specified with value " + parsedOptions.get(parts[0])); + } + parsedOptions.put(parts[0], parts[1]); + } + + String path = requiredOption(parsedOptions, "path"); + String mode = optionalOption(parsedOptions, "mode", "default"); + return new RecordOptions(Paths.get(path), RecordMode.valueOf(mode.toUpperCase())); + } + + private static IllegalArgumentException parseError(String message) { + return new IllegalArgumentException(message + " Sample usage: -XX:" + MetadataTracer.Options.RecordMetadata.getName() + "=path=[,mode=]"); + } + + private static IllegalArgumentException badOptionError(String option, String message) { + throw parseError("Bad option provided for " + MetadataTracer.Options.RecordMetadata.getName() + ": " + option + ". " + message); + } + + private static String requiredOption(Map options, String optionKey) { + if (options.containsKey(optionKey)) { + return options.get(optionKey); + } + throw parseError(MetadataTracer.Options.RecordMetadata.getName() + " missing required option '" + optionKey + "'"); + } + + private static String optionalOption(Map options, String optionKey, String defaultValue) { + if (options.containsKey(optionKey)) { + return options.get(optionKey); + } + return defaultValue; + } +} + @AutomaticallyRegisteredFeature class MetadataTracerFeature implements InternalFeature { @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/metadata/doc-files/RecordMetadataHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/metadata/doc-files/RecordMetadataHelp.txt new file mode 100644 index 000000000000..2fcf45acc323 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/metadata/doc-files/RecordMetadataHelp.txt @@ -0,0 +1,9 @@ +Enables metadata tracing at run time. This option is only supported if -H:MetadataTracingSupport is enabled when building the image. +The value of this option is a comma-separated list of arguments specified as key-value pairs. The following arguments are supported: + +- path= (required): Specifies the directory to write traced metadata to. +- mode= (optional): Specifies the tracing mode, which can be one of: + - default: Traces without conditions. + - conditional: Traces and infers conditions for each metadata element. An element (a type, resource, etc.) is only reachable in the image if the associated condition is satisfied, which can reduce image size and improve analysis results. + +Example usage: -H:RecordMetadata=path=tracing_output,mode=default \ No newline at end of file