From 88ce7783c236650f3241e43280379163a65260ea Mon Sep 17 00:00:00 2001 From: Matt D'Souza Date: Thu, 3 Apr 2025 15:30:37 -0400 Subject: [PATCH 1/5] 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 ffbdedc9d58e2e2ae75523af2a0c62b4de3c44f1 Mon Sep 17 00:00:00 2001 From: Matt D'Souza Date: Fri, 4 Apr 2025 16:20:59 -0400 Subject: [PATCH 2/5] 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 807931bccb9e..212b46d80d51 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 @@ -41,6 +41,7 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.core.configure.RuntimeConditionSet; +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; @@ -225,6 +226,9 @@ public Object getSerializationConstructorAccessor(Class rawDeclaringClass, Cl Object constructorAccessor = constructorAccessors.get(new SerializationLookupKey(declaringClass, targetConstructorClass)); if (constructorAccessor != null) { + if (MetadataTracer.Options.MetadataTracingSupport.getValue() && MetadataTracer.singleton().enabled()) { + MetadataTracer.singleton().traceSerializationType(declaringClass.getName()); + } return constructorAccessor; } else { String targetConstructorClassName = targetConstructorClass.getName(); From 0403fc3918ebae259dba0a0e887d09da909f2e36 Mon Sep 17 00:00:00 2001 From: Matt D'Souza Date: Mon, 21 Apr 2025 09:23:39 -0400 Subject: [PATCH 3/5] Trace reflective accesses --- .../snippets/SubstrateAllocationSnippets.java | 22 ++++++- .../svm/core/hub/ClassForNameSupport.java | 12 +++- .../com/oracle/svm/core/hub/DynamicHub.java | 58 +++++++++++++++++++ .../svm/core/metadata/MetadataTracer.java | 6 ++ 4 files changed, 96 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..50b6b1758881 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 @@ -30,6 +30,7 @@ import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.EXTREMELY_FAST_PATH_PROBABILITY; import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.FAST_PATH_PROBABILITY; import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.LIKELY_PROBABILITY; +import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.SLOW_PATH_PROBABILITY; import static jdk.graal.compiler.nodes.extended.BranchProbabilityNode.probability; import static jdk.graal.compiler.replacements.SnippetTemplate.DEFAULT_REPLACER; @@ -59,6 +60,7 @@ import com.oracle.svm.core.hub.RuntimeClassLoading; import com.oracle.svm.core.identityhashcode.IdentityHashCodeSupport; import com.oracle.svm.core.meta.SharedType; +import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.core.option.HostedOptionValues; import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import com.oracle.svm.core.snippets.SnippetRuntime; @@ -127,8 +129,11 @@ 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 TRACE_ARRAY_HUB = SnippetRuntime.findForeignCall(SubstrateAllocationSnippets.class, "traceArrayHubStub", 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}; + private static final SubstrateForeignCallDescriptor[] FOREIGN_CALLS = new SubstrateForeignCallDescriptor[]{NEW_MULTI_ARRAY, SLOW_PATH_HUB_OR_UNSAFE_INSTANTIATE_ERROR, TRACE_ARRAY_HUB, + ARRAY_HUB_ERROR}; public void registerForeignCalls(SubstrateForeignCallsProvider foreignCalls) { foreignCalls.register(FOREIGN_CALLS); @@ -396,6 +401,9 @@ private static DynamicHub getCheckedArrayHub(DynamicHub elementType) { if (probability(EXTREMELY_FAST_PATH_PROBABILITY, arrayHub != null)) { DynamicHub nonNullArrayHub = (DynamicHub) PiNode.piCastNonNull(arrayHub, SnippetAnchorNode.anchor()); if (probability(EXTREMELY_FAST_PATH_PROBABILITY, nonNullArrayHub.isInstantiated())) { + if (MetadataTracer.Options.MetadataTracingSupport.getValue()) { + callTraceArrayHubStub(TRACE_ARRAY_HUB, DynamicHub.toClass(elementType)); + } return nonNullArrayHub; } } @@ -405,6 +413,18 @@ private static DynamicHub getCheckedArrayHub(DynamicHub elementType) { throw UnreachableNode.unreachable(); } + @NodeIntrinsic(value = ForeignCallNode.class) + private static native void callTraceArrayHubStub(@ConstantNodeParameter ForeignCallDescriptor descriptor, Class elementType); + + /** Foreign call: {@link #TRACE_ARRAY_HUB}. */ + @SubstrateForeignCallTarget(stubCallingConvention = true) + private static void traceArrayHubStub(DynamicHub elementType) { + assert MetadataTracer.Options.MetadataTracingSupport.getValue(); + if (probability(SLOW_PATH_PROBABILITY, MetadataTracer.singleton().enabled())) { + MetadataTracer.singleton().traceReflectionType(elementType.getName()); + } + } + @NodeIntrinsic(value = ForeignCallNode.class) private static native void callArrayHubErrorStub(@ConstantNodeParameter ForeignCallDescriptor descriptor, Class elementType); 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 c5efadad75ae..f79fda3dd3bd 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 @@ -44,6 +44,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.MissingReflectionRegistrationUtils; import com.oracle.svm.core.util.ImageHeapMap; import com.oracle.svm.core.util.VMError; @@ -237,6 +238,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 */ @@ -296,7 +300,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 b040661220e3..62b8d8b6ce8b 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; @@ -89,6 +91,8 @@ 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; @@ -116,6 +120,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; @@ -698,6 +703,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); } @@ -720,6 +728,25 @@ 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()); + 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 { @@ -1286,6 +1313,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); @@ -1353,6 +1388,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); } @@ -1360,6 +1403,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)); } @@ -1830,6 +1886,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/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 b0a8a80f57169765a01be8124bb8ec32eaab80da Mon Sep 17 00:00:00 2001 From: Matt D'Souza Date: Mon, 7 Apr 2025 15:56:51 -0400 Subject: [PATCH 4/5] 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 4563cea8ab4c80d133271994473513c150e7e484 Mon Sep 17 00:00:00 2001 From: Matt D'Souza Date: Tue, 29 Apr 2025 15:42:10 -0400 Subject: [PATCH 5/5] 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 f79fda3dd3bd..eb22f3cf4b27 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 @@ -37,6 +37,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; @@ -302,7 +303,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 62b8d8b6ce8b..9006a6d655f2 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 @@ -730,6 +730,9 @@ private static boolean isClassFlagSet(int mask, ReflectionMetadata reflectionMet private void traceClassFlagQuery(int mask) { ConfigurationType type = MetadataTracer.singleton().traceReflectionType(getName()); + if (type == null) { + return; + } switch (mask) { case ALL_FIELDS_FLAG -> type.setAllPublicFields(ConfigurationMemberAccessibility.ACCESSED); case ALL_DECLARED_FIELDS_FLAG -> type.setAllDeclaredFields(ConfigurationMemberAccessibility.ACCESSED); @@ -1317,7 +1320,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()); } @@ -1392,7 +1397,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()));