diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/TraceFileWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/TraceFileWriter.java index e706a5e1e07f..980ab82864b5 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/TraceFileWriter.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/TraceFileWriter.java @@ -31,7 +31,7 @@ import com.oracle.svm.agent.tracing.core.Tracer; import com.oracle.svm.agent.tracing.core.TracingResultWriter; -import com.oracle.svm.configure.trace.JsonFileWriter; +import com.oracle.svm.configure.JsonFileWriter; public class TraceFileWriter extends Tracer implements TracingResultWriter { private final JsonFileWriter jsonFileWriter; diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JsonFileWriter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/JsonFileWriter.java similarity index 96% rename from substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JsonFileWriter.java rename to substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/JsonFileWriter.java index b06c6994df68..4402b93f4fd4 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JsonFileWriter.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/JsonFileWriter.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.configure.trace; +package com.oracle.svm.configure; import java.io.BufferedWriter; import java.io.Closeable; @@ -97,6 +97,10 @@ private static void printArray(JsonWriter json, Object[] array) throws IOExcepti } private static void printValue(JsonWriter json, Object value) throws IOException { + if (value instanceof JsonPrintable printable) { + printable.printJson(json); + return; + } Object s = null; if (value instanceof byte[]) { s = Base64.getEncoder().encodeToString((byte[]) value); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java index d3dc983a342f..1c6fd5452482 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java @@ -465,10 +465,18 @@ public synchronized void setAllPublicConstructors(ConfigurationMemberAccessibili } } + public synchronized boolean isSerializable() { + return serializable; + } + public synchronized void setSerializable() { serializable = true; } + public synchronized boolean isJniAccessible() { + return typeJniAccessible; + } + public synchronized void setJniAccessible() { typeJniAccessible = true; } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SignatureUtil.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SignatureUtil.java index 7e4ee9489823..d5ae7c5ff423 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SignatureUtil.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SignatureUtil.java @@ -80,6 +80,19 @@ public static String toInternalSignature(List parameterTypes) { return sb.append(')').toString(); } + public static String toInternalSignature(Class[] parameters) { + List names; + if (parameters == null) { + names = List.of(); + } else { + names = new ArrayList<>(parameters.length); + for (Class parameter : parameters) { + names.add(parameter.getName()); + } + } + return toInternalSignature(names); + } + public static String toInternalClassName(String qualifiedForNameString) { assert qualifiedForNameString.indexOf('/') == -1 : "Requires qualified Java name, not internal representation"; assert !qualifiedForNameString.endsWith("[]") : "Requires Class.forName syntax, for example '[Ljava.lang.String;'"; diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java index d5e263c1457f..5601fe4dd397 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java @@ -31,6 +31,7 @@ import org.graalvm.collections.EconomicMap; +import com.oracle.svm.configure.JsonFileWriter; import com.oracle.svm.configure.filters.ConfigurationFilter; import com.oracle.svm.configure.filters.HierarchyFilterNode; 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 1154ca22ba89..3db63d93410a 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,7 +47,6 @@ 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; @@ -414,7 +413,7 @@ private Object forName0(String className, ClassLoader classLoader) { } if (MetadataTracer.enabled()) { // NB: the early returns above ensure we do not trace calls with bad type args. - MetadataTracer.singleton().traceReflectionType(className); + MetadataTracer.singleton().traceReflectionType(ClassNameSupport.reflectionNameToTypeName(className)); } return result == NEGATIVE_QUERY ? new ClassNotFoundException(className) : result; } @@ -502,10 +501,7 @@ 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(); - } + MetadataTracer.singleton().traceUnsafeAllocatedType(clazz); } RuntimeConditionSet conditionSet = null; for (var singleton : layeredSingletons()) { 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 50dbe3050222..123bad962417 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,7 +24,6 @@ */ 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; @@ -91,7 +90,6 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -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; @@ -761,7 +759,7 @@ private ReflectionMetadata reflectionMetadata() { private void checkClassFlag(int mask, String methodName) { if (MetadataTracer.enabled()) { - traceClassFlagQuery(mask); + MetadataTracer.singleton().traceReflectionType(toClass(this)); } if (throwMissingRegistrationErrors() && !(isClassFlagSet(mask) && getConditions().satisfied())) { MissingReflectionRegistrationUtils.reportClassQuery(DynamicHub.toClass(this), methodName); @@ -785,30 +783,6 @@ 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()); - 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) { - 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 { @@ -1396,19 +1370,13 @@ private void checkField(String fieldName, Field field, boolean publicOnly) throw private void traceFieldLookup(String fieldName, Field field, boolean publicOnly) { ConfigurationMemberDeclaration declaration = publicOnly ? ConfigurationMemberDeclaration.PRESENT : ConfigurationMemberDeclaration.DECLARED; if (field != null) { - // register declaring type and field - ConfigurationType declaringType = MetadataTracer.singleton().traceReflectionType(field.getDeclaringClass().getName()); - if (declaringType != null) { - declaringType.addField(fieldName, declaration, false); - } + // register declaring type (registers all fields for lookup) + MetadataTracer.singleton().traceReflectionType(field.getDeclaringClass()); // register receiver type - MetadataTracer.singleton().traceReflectionType(getName()); + MetadataTracer.singleton().traceReflectionType(toClass(this)); } else { // register receiver type and negative field query - ConfigurationType receiverType = MetadataTracer.singleton().traceReflectionType(getName()); - if (receiverType != null) { - receiverType.addField(fieldName, declaration, false); - } + MetadataTracer.singleton().traceFieldAccess(toClass(this), fieldName, declaration); } } @@ -1481,35 +1449,16 @@ private boolean checkExecutableExists(String methodName, Class[] parameterTyp private void traceMethodLookup(String methodName, Class[] parameterTypes, Executable method, boolean publicOnly) { ConfigurationMemberDeclaration declaration = publicOnly ? ConfigurationMemberDeclaration.PRESENT : ConfigurationMemberDeclaration.DECLARED; if (method != null) { - // register declaring type and method - ConfigurationType declaringType = MetadataTracer.singleton().traceReflectionType(method.getDeclaringClass().getName()); - if (declaringType != null) { - declaringType.addMethod(methodName, toInternalSignature(parameterTypes), declaration); - } + // register declaring type (registers all methods for lookup) + MetadataTracer.singleton().traceReflectionType(method.getDeclaringClass()); // register receiver type - MetadataTracer.singleton().traceReflectionType(getName()); + MetadataTracer.singleton().traceReflectionType(toClass(this)); } else { // register receiver type and negative method query - ConfigurationType receiverType = MetadataTracer.singleton().traceReflectionType(getName()); - if (receiverType != null) { - receiverType.addMethod(methodName, toInternalSignature(parameterTypes), declaration, ConfigurationMemberAccessibility.QUERIED); - } + MetadataTracer.singleton().traceMethodAccess(toClass(this), methodName, SignatureUtil.toInternalSignature(parameterTypes), declaration); } } - 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)); } @@ -2025,18 +1974,14 @@ public DynamicHub arrayType() { throw new UnsupportedOperationException(new IllegalArgumentException()); } if (MetadataTracer.enabled()) { - MetadataTracer.singleton().traceReflectionType(arrayTypeName()); + MetadataTracer.singleton().traceReflectionArrayType(toClass(this)); } if (companion.arrayHub == null) { - MissingReflectionRegistrationUtils.reportClassAccess(arrayTypeName()); + MissingReflectionRegistrationUtils.reportClassAccess(getTypeName() + "[]"); } 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 e25a80138486..3e0b3275ade9 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 @@ -66,7 +66,7 @@ static ObjectStreamClass lookup(Class cl, boolean all) { if (Serializable.class.isAssignableFrom(cl) && !cl.isArray()) { if (MetadataTracer.enabled()) { - MetadataTracer.singleton().traceSerializationType(cl.getName()); + MetadataTracer.singleton().traceSerializationType(cl); } if (!DynamicHub.fromClass(cl).isRegisteredForSerialization()) { MissingSerializationRegistrationUtils.reportSerialization(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 b4908275ecb4..206209695c1c 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 @@ -388,7 +388,7 @@ private static void set(Object a, int index, Object value) { private static Object newArray(Class componentType, int length) throws NegativeArraySizeException { if (MetadataTracer.enabled()) { - MetadataTracer.singleton().traceReflectionType(componentType.arrayType().getName()); + MetadataTracer.singleton().traceReflectionArrayType(componentType); } return KnownIntrinsics.unvalidatedNewArray(componentType, length); } 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 3475435b548a..9d7e0bcedf20 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 @@ -43,7 +43,6 @@ 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; @@ -274,10 +273,9 @@ 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); - } + MetadataTracer.singleton().traceJNIType(classObject); + MetadataTracer.singleton().traceMethodAccess(classObject, descriptor.getNameConvertToString(), descriptor.getSignatureConvertToString(), + ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); } boolean foundClass = false; for (var dictionary : layeredSingletons()) { @@ -335,10 +333,8 @@ 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); - } + MetadataTracer.singleton().traceJNIType(classObject); + MetadataTracer.singleton().traceFieldAccess(classObject, name.toString(), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); } boolean foundClass = false; for (var dictionary : layeredSingletons()) { 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 8ecfa8e3b006..0930d040b092 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,20 +28,24 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; import com.oracle.svm.configure.ConfigurationTypeDescriptor; +import com.oracle.svm.configure.JsonFileWriter; import com.oracle.svm.configure.NamedConfigurationTypeDescriptor; import com.oracle.svm.configure.ProxyConfigurationTypeDescriptor; import com.oracle.svm.configure.UnresolvedConfigurationCondition; import com.oracle.svm.configure.config.ConfigurationFileCollection; +import com.oracle.svm.configure.config.ConfigurationMemberInfo; import com.oracle.svm.configure.config.ConfigurationSet; import com.oracle.svm.configure.config.ConfigurationType; import com.oracle.svm.core.AlwaysInline; @@ -63,33 +67,37 @@ /** * 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, and then the run-time option {@link Options#RecordMetadata} enables + * reachability metadata, and then the run-time option {@link Options#TraceMetadata} enables * tracing. */ public final class MetadataTracer { public static class Options { @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.")// + "When tracing is supported, use the -XX:TraceMetadata option to enable tracing at run time.")// public static final HostedOptionKey MetadataTracingSupport = new HostedOptionKey<>(false); - static final String RECORD_METADATA_HELP = """ + static final String TRACE_METADATA_HELP = """ Enables metadata tracing at run time. This option is only supported if -H:+MetadataTracingSupport is set 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. - merge= (optional): Specifies whether to merge or overwrite metadata with existing files at the output path (default: true). + - debug-log= (optional): Specifies a path to write debug output to. This option is meant for debugging; the option name and its + output format may change at any time. Example usage: - -H:RecordMetadata=path=trace_output_directory - -H:RecordMetadata=path=trace_output_directory,merge=false + -H:TraceMetadata=path=trace_output_directory + -H:TraceMetadata=path=trace_output_directory,merge=false """; - @Option(help = RECORD_METADATA_HELP, stability = OptionStability.EXPERIMENTAL)// - public static final RuntimeOptionKey RecordMetadata = new RuntimeOptionKey<>(null); + @Option(help = TRACE_METADATA_HELP, stability = OptionStability.EXPERIMENTAL)// + public static final RuntimeOptionKey TraceMetadata = new RuntimeOptionKey<>(null); } - private RecordOptions options; + private TraceOptions options; + private JsonFileWriter debugWriter; + private final ThreadLocal disableTracingReason = new ThreadLocal<>(); /** * The configuration set to trace with. Do not read this field directly when tracing; instead @@ -108,6 +116,32 @@ public static MetadataTracer singleton() { return ImageSingletons.lookup(MetadataTracer.class); } + private void initialize(TraceOptions parsedOptions) { + this.options = parsedOptions; + this.debugWriter = initializeDebugWriter(parsedOptions); + this.config = initializeConfigurationSet(parsedOptions); + } + + private void shutdown() { + ConfigurationSet finalConfig = this.config; + this.config = null; // clear config so that shutdown actions are not traced. + + if (finalConfig != null) { + try { + finalConfig.writeConfiguration(configFile -> this.options.path().resolve(configFile.getFileName())); + } catch (IOException ex) { + Log log = Log.log(); + log.string("Failed to write out reachability metadata to directory ").string(this.options.path().toString()); + log.string(":").string(ex.getMessage()); + log.newline(); + } + } + + if (debugWriter != null) { + debugWriter.close(); + } + } + /** * Returns whether tracing is enabled. Tracing code should be guarded by this condition. *

@@ -120,7 +154,7 @@ public static boolean enabled() { } /** - * Returns whether tracing is enabled at run time (using {@code -XX:RecordMetadata}). + * Returns whether tracing is enabled at run time (using {@code -XX:TraceMetadata}). */ private boolean enabledAtRunTime() { VMError.guarantee(Options.MetadataTracingSupport.getValue()); @@ -132,52 +166,133 @@ private boolean enabledAtRunTime() { * performed for some reason. */ private ConfigurationSet getConfigurationSetForTracing() { - if (VMOperation.isInProgress()) { - // Do not trace during VM operations. + if (disableTracingReason.get() != null || VMOperation.isInProgress()) { + // Do not trace when tracing is disabled or during VM operations. return null; } return config; } + /** + * Marks the type with the given name as reachable from reflection. + */ + public void traceReflectionType(String typeName) { + traceReflectionTypeImpl(new NamedConfigurationTypeDescriptor(typeName)); + } + /** * 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) { - return traceReflectionTypeImpl(new NamedConfigurationTypeDescriptor(className)); + public void traceReflectionType(Class clazz) { + traceReflectionTypeImpl(ConfigurationTypeDescriptor.fromClass(clazz)); + } + + public void traceReflectionArrayType(Class componentClazz) { + ConfigurationTypeDescriptor typeDescriptor = ConfigurationTypeDescriptor.fromClass(componentClazz); + if (typeDescriptor instanceof NamedConfigurationTypeDescriptor(String name)) { + traceReflectionType(name + "[]"); + } else { + debug("array type not registered for reflection (component type is not a named type)", typeDescriptor); + } + } + + /** + * Marks the given field as accessible from reflection. + */ + public void traceFieldAccess(Class declaringClass, String fieldName, ConfigurationMemberInfo.ConfigurationMemberDeclaration declaration) { + ConfigurationTypeDescriptor typeDescriptor = ConfigurationTypeDescriptor.fromClass(declaringClass); + ConfigurationType type = traceReflectionTypeImpl(typeDescriptor); + if (type != null) { + debugField(typeDescriptor, fieldName); + type.addField(fieldName, declaration, false); + } + } + + /** + * Marks the given method as accessible from reflection. + */ + public void traceMethodAccess(Class declaringClass, String methodName, String internalSignature, ConfigurationMemberInfo.ConfigurationMemberDeclaration declaration) { + ConfigurationTypeDescriptor typeDescriptor = ConfigurationTypeDescriptor.fromClass(declaringClass); + ConfigurationType type = traceReflectionTypeImpl(typeDescriptor); + if (type != null) { + debugMethod(typeDescriptor, methodName, internalSignature); + type.addMethod(methodName, internalSignature, declaration, ConfigurationMemberInfo.ConfigurationMemberAccessibility.ACCESSED); + } + } + + /** + * Marks the given type as unsafely allocated. + */ + public void traceUnsafeAllocatedType(Class clazz) { + ConfigurationTypeDescriptor typeDescriptor = ConfigurationTypeDescriptor.fromClass(clazz); + ConfigurationType type = traceReflectionTypeImpl(typeDescriptor); + if (type != null) { + debug("type marked as unsafely allocated", clazz.getTypeName()); + type.setUnsafeAllocated(); + } } /** * Marks the given proxy type as reachable from reflection. */ - public void traceProxyType(List interfaceNames) { - traceReflectionTypeImpl(new ProxyConfigurationTypeDescriptor(interfaceNames)); + public void traceProxyType(Class[] interfaces) { + List interfaceNames = Arrays.stream(interfaces).map(Class::getTypeName).toList(); + ProxyConfigurationTypeDescriptor descriptor = new ProxyConfigurationTypeDescriptor(interfaceNames); + traceReflectionTypeImpl(descriptor); } private ConfigurationType traceReflectionTypeImpl(ConfigurationTypeDescriptor typeDescriptor) { assert enabledAtRunTime(); + if (isInternal(typeDescriptor)) { + debug("type not registered for reflection (uses an internal interface)", typeDescriptor); + return null; + } ConfigurationSet configurationSet = getConfigurationSetForTracing(); if (configurationSet != null) { + debugReflectionType(typeDescriptor, configurationSet); return configurationSet.getReflectionConfiguration().getOrCreateType(UnresolvedConfigurationCondition.alwaysTrue(), typeDescriptor); } return null; } + private static boolean isInternal(ConfigurationTypeDescriptor typeDescriptor) { + if (typeDescriptor instanceof NamedConfigurationTypeDescriptor(String name)) { + return isInternal(name); + } else if (typeDescriptor instanceof ProxyConfigurationTypeDescriptor proxyType) { + for (String interfaceName : proxyType.interfaceNames()) { + if (isInternal(interfaceName)) { + return true; + } + } + } + return false; + } + + private static boolean isInternal(String typeName) { + return typeName.startsWith("com.oracle.svm.core"); + } + + /** + * Marks the type with the given name as reachable from JNI. + */ + public void traceJNIType(String typeName) { + traceJNITypeImpl(new NamedConfigurationTypeDescriptor(typeName)); + } + /** * 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) { + public void traceJNIType(Class clazz) { + traceJNITypeImpl(ConfigurationTypeDescriptor.fromClass(clazz)); + } + + private void traceJNITypeImpl(ConfigurationTypeDescriptor typeDescriptor) { assert enabledAtRunTime(); - ConfigurationType result = traceReflectionType(className); - if (result != null) { - result.setJniAccessible(); + ConfigurationType type = traceReflectionTypeImpl(typeDescriptor); + if (type != null && !type.isJniAccessible()) { + debug("type registered for jni", typeDescriptor); + type.setJniAccessible(); } - return result; } /** @@ -188,6 +303,7 @@ public void traceResource(String resourceName, String moduleName) { assert enabledAtRunTime(); ConfigurationSet configurationSet = getConfigurationSetForTracing(); if (configurationSet != null) { + debugResourceGlob(resourceName, moduleName); configurationSet.getResourceConfiguration().addGlobPattern(UnresolvedConfigurationCondition.alwaysTrue(), resourceName, moduleName); } } @@ -199,6 +315,7 @@ public void traceResourceBundle(String baseName) { assert enabledAtRunTime(); ConfigurationSet configurationSet = getConfigurationSetForTracing(); if (configurationSet != null) { + debug("resource bundle registered", baseName); configurationSet.getResourceConfiguration().addBundle(UnresolvedConfigurationCondition.alwaysTrue(), baseName, List.of()); } } @@ -206,30 +323,161 @@ public void traceResourceBundle(String baseName) { /** * Marks the given type as serializable. */ - public void traceSerializationType(String className) { + public void traceSerializationType(Class clazz) { assert enabledAtRunTime(); - ConfigurationType result = traceReflectionType(className); - if (result != null) { + ConfigurationTypeDescriptor typeDescriptor = ConfigurationTypeDescriptor.fromClass(clazz); + ConfigurationType result = traceReflectionTypeImpl(typeDescriptor); + if (result != null && !result.isSerializable()) { + debug("type registered for serialization", typeDescriptor); result.setSerializable(); } } - private static void initialize(String recordMetadataValue) { + /** + * Main entrypoint for debug logging. Emits a JSON object to the debug log with the given + * message and element. + */ + @SuppressWarnings("try") + private void debug(String message, Object element) { + if (debugWriter == null) { + return; + } + assert enabledAtRunTime(); + try (var ignored = new DisableTracingImpl("debug logging")) { + EconomicMap entry = EconomicMap.create(); + entry.put("message", message); + entry.put("element", element); + entry.put("stacktrace", debugStackTrace()); + debugWriter.printObject(entry); + } + } + + private static StackTraceElement[] debugStackTrace() { + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + // Trim the prefix containing "getStackTrace" and the various "debug" methods. + int i = 0; + while (i < trace.length && (trace[i].getMethodName().contains("getStackTrace") || trace[i].getMethodName().startsWith("debug"))) { + i++; + } + return Arrays.copyOfRange(trace, i, trace.length); + } + + /** + * Debug helper for resource globs. Avoids glob name computations if debug logging is disabled. + */ + private void debugResourceGlob(String resourceName, String moduleName) { + if (debugWriter == null) { + return; + } + String element = (moduleName == null) ? resourceName : String.format("%s:%s", moduleName, resourceName); + debug("resource glob registered", element); + } + + /** + * Debug helper for reflective type accesses. Avoids "type is already registered" check if debug + * logging is disabled. + */ + private void debugReflectionType(ConfigurationTypeDescriptor typeDescriptor, ConfigurationSet configurationSet) { + if (debugWriter == null) { + return; + } + if (configurationSet.getReflectionConfiguration().get(UnresolvedConfigurationCondition.alwaysTrue(), typeDescriptor) == null) { + debug("type registered for reflection", typeDescriptor); + } + } + + /** + * Debug helper for fields. Avoids field name computations if debug logging is disabled. + */ + private void debugField(ConfigurationTypeDescriptor typeDescriptor, String fieldName) { + if (debugWriter == null) { + return; + } + debug("field registered for reflection", typeDescriptor + "." + fieldName); + } + + /** + * Debug helper for methods. Avoids method name computations if debug logging is disabled. + */ + private void debugMethod(ConfigurationTypeDescriptor typeDescriptor, String methodName, String internalSignature) { + if (debugWriter == null) { + return; + } + debug("method registered for reflection", typeDescriptor + "." + methodName + internalSignature); + } + + /** + * Disables tracing on the current thread from instantiation until {@link #close}. + */ + public sealed interface DisableTracing extends AutoCloseable { + @Override + void close(); + } + + private final class DisableTracingImpl implements DisableTracing { + final String oldReason; + + private DisableTracingImpl(String reason) { + this.oldReason = disableTracingReason.get(); + disableTracingReason.set(reason); + } + + @Override + public void close() { + disableTracingReason.set(oldReason); + } + } + + private static final class DisableTracingNoOp implements DisableTracing { + private static final DisableTracingNoOp INSTANCE = new DisableTracingNoOp(); + + @Override + public void close() { + // do nothing + } + } + + /** + * Disables tracing on the current thread from instantiation until {@link DisableTracing#close}. + * Should be used in a try-with-resources block. + */ + public static DisableTracing disableTracing(String reason) { + if (Options.MetadataTracingSupport.getValue() && singleton().enabledAtRunTime()) { + return singleton().new DisableTracingImpl(reason); + } else { + // Fallback implementation when tracing is not enabled at build time. + return DisableTracingNoOp.INSTANCE; + } + } + + private static void initializeSingleton(String recordMetadataValue) { assert Options.MetadataTracingSupport.getValue(); + MetadataTracer.singleton().initialize(TraceOptions.parse(recordMetadataValue)); + } - RecordOptions parsedOptions = RecordOptions.parse(recordMetadataValue); + private static JsonFileWriter initializeDebugWriter(TraceOptions options) { + if (options.debugLog() == null) { + return null; + } try { - Files.createDirectories(parsedOptions.path()); + Path parentDir = options.debugLog().getParent(); + if (parentDir == null) { + throw new IllegalArgumentException("Invalid debug-log path '" + options.debugLog() + "'."); + } + Files.createDirectories(parentDir); + return new JsonFileWriter(options.debugLog()); } catch (IOException ex) { - throw new IllegalArgumentException("Exception occurred creating the output directory for tracing (" + parsedOptions.path() + ")", ex); + throw new IllegalArgumentException("Exception occurred preparing the debug log file (" + options.debugLog() + ")", ex); } - - MetadataTracer singleton = MetadataTracer.singleton(); - singleton.options = parsedOptions; - singleton.config = initializeConfigurationSet(parsedOptions); } - private static ConfigurationSet initializeConfigurationSet(RecordOptions options) { + private static ConfigurationSet initializeConfigurationSet(TraceOptions options) { + try { + Files.createDirectories(options.path()); + } catch (IOException ex) { + throw new IllegalArgumentException("Exception occurred creating the output directory for tracing (" + options.path() + ")", ex); + } + if (options.merge() && Files.exists(options.path())) { ConfigurationFileCollection mergeConfigs = new ConfigurationFileCollection(); mergeConfigs.addDirectory(options.path()); @@ -245,21 +493,9 @@ private static ConfigurationSet initializeConfigurationSet(RecordOptions options return new ConfigurationSet(); } - private static void shutdown() { + private static void shutdownSingleton() { 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.options.path().resolve(configFile.getFileName())); - } catch (IOException ex) { - Log log = Log.log(); - log.string("Failed to write out reachability metadata to directory ").string(singleton.options.path().toString()); - log.string(":").string(ex.getMessage()); - log.newline(); - } - } + MetadataTracer.singleton().shutdown(); } static RuntimeSupport.Hook initializeMetadataTracingHook() { @@ -268,8 +504,8 @@ static RuntimeSupport.Hook initializeMetadataTracingHook() { return; } VMError.guarantee(Options.MetadataTracingSupport.getValue()); - if (Options.RecordMetadata.hasBeenSet()) { - initialize(Options.RecordMetadata.getValue()); + if (Options.TraceMetadata.hasBeenSet()) { + initializeSingleton(Options.TraceMetadata.getValue()); } }; } @@ -280,8 +516,8 @@ static RuntimeSupport.Hook shutDownMetadataTracingHook() { return; } VMError.guarantee(Options.MetadataTracingSupport.getValue()); - if (Options.RecordMetadata.hasBeenSet()) { - shutdown(); + if (Options.TraceMetadata.hasBeenSet()) { + shutdownSingleton(); } }; } @@ -295,29 +531,29 @@ static RuntimeSupport.Hook checkImproperOptionUsageHook() { return; } VMError.guarantee(!Options.MetadataTracingSupport.getValue()); - if (Options.RecordMetadata.hasBeenSet()) { + if (Options.TraceMetadata.hasBeenSet()) { throw new IllegalArgumentException( - "The option " + Options.RecordMetadata.getName() + " can only be used if metadata tracing is enabled at build time (using " + + "The option " + Options.TraceMetadata.getName() + " can only be used if metadata tracing is enabled at build time (using " + hostedOptionCommandArgument + ")."); } }; } } -record RecordOptions(Path path, boolean merge) { +record TraceOptions(Path path, boolean merge, Path debugLog) { private static final int ARGUMENT_PARTS = 2; - static RecordOptions parse(String recordMetadataValue) { - if (recordMetadataValue.isEmpty()) { - throw printHelp("Option " + MetadataTracer.Options.RecordMetadata.getName() + " cannot be empty."); - } else if (recordMetadataValue.equals("help")) { - throw printHelp("Option " + MetadataTracer.Options.RecordMetadata.getName() + " value is 'help'. Printing a description and aborting."); + static TraceOptions parse(String traceMetadataValue) { + if (traceMetadataValue.isEmpty()) { + throw printHelp("Option " + MetadataTracer.Options.TraceMetadata.getName() + " cannot be empty."); + } else if (traceMetadataValue.equals("help")) { + throw printHelp("Option " + MetadataTracer.Options.TraceMetadata.getName() + " value is 'help'. Printing a description and aborting."); } Map parsedArguments = new HashMap<>(); - Set allArguments = new LinkedHashSet<>(List.of("path", "merge")); - for (String argument : recordMetadataValue.split(",")) { + Set allArguments = new LinkedHashSet<>(List.of("path", "merge", "debug-log")); + for (String argument : traceMetadataValue.split(",")) { String[] parts = SubstrateUtil.split(argument, "=", ARGUMENT_PARTS); if (parts.length != ARGUMENT_PARTS) { throw badArgumentError(argument, "Argument should be a key-value pair separated by '='"); @@ -331,9 +567,10 @@ static RecordOptions parse(String recordMetadataValue) { parsedArguments.put(parts[0], parts[1]); } - String path = requiredArgument(parsedArguments, "path", IDENTITY_PARSER); + Path path = requiredArgument(parsedArguments, "path", PATH_PARSER); boolean merge = optionalArgument(parsedArguments, "merge", true, BOOLEAN_PARSER); - return new RecordOptions(Paths.get(path), merge); + Path debugLog = optionalArgument(parsedArguments, "debug-log", null, PATH_PARSER); + return new TraceOptions(path, merge, debugLog); } private static IllegalArgumentException printHelp(String errorMessage) { @@ -343,15 +580,15 @@ private static IllegalArgumentException printHelp(String errorMessage) { %s description: %s - """.formatted(errorMessage, MetadataTracer.Options.RecordMetadata.getName(), MetadataTracer.Options.RECORD_METADATA_HELP)); + """.formatted(errorMessage, MetadataTracer.Options.TraceMetadata.getName(), MetadataTracer.Options.TRACE_METADATA_HELP)); } private static IllegalArgumentException parseError(String message) { - return new IllegalArgumentException(message + ". For more information (including usage examples), pass 'help' as an argument to " + MetadataTracer.Options.RecordMetadata.getName() + "."); + return new IllegalArgumentException(message + ". For more information (including usage examples), pass 'help' as an argument to " + MetadataTracer.Options.TraceMetadata.getName() + "."); } private static IllegalArgumentException badArgumentError(String argument, String message) { - throw parseError("Bad argument provided for " + MetadataTracer.Options.RecordMetadata.getName() + ": '" + argument + "'. " + message); + throw parseError("Bad argument provided for " + MetadataTracer.Options.TraceMetadata.getName() + ": '" + argument + "'. " + message); } private static IllegalArgumentException badArgumentValueError(String argumentKey, String argumentValue, String message) { @@ -362,7 +599,7 @@ private interface ArgumentParser { T parse(String argumentKey, String argumentValue); } - private static final ArgumentParser IDENTITY_PARSER = ((argumentKey, argumentValue) -> argumentValue); + private static final ArgumentParser PATH_PARSER = ((argumentKey, argumentValue) -> Paths.get(argumentValue).toAbsolutePath()); private static final ArgumentParser BOOLEAN_PARSER = ((argumentKey, argumentValue) -> switch (argumentValue) { case "true" -> true; case "false" -> false; @@ -373,7 +610,7 @@ private static T requiredArgument(Map arguments, String key, if (arguments.containsKey(key)) { return parser.parse(key, arguments.get(key)); } - throw parseError(MetadataTracer.Options.RecordMetadata.getName() + " missing required argument '" + key + "'"); + throw parseError(MetadataTracer.Options.TraceMetadata.getName() + " missing required argument '" + key + "'"); } private static T optionalArgument(Map options, String key, T defaultValue, ArgumentParser parser) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java index 5142f84a9951..3a400f53cc02 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java @@ -52,6 +52,7 @@ import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; +import com.oracle.svm.core.reflect.UnsafeFieldUtil; import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Field; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; @@ -134,11 +135,7 @@ private static long objectFieldOffset(Target_java_lang_invoke_MemberName self) { if (self.intrinsic != null) { return -1L; } - int offset = SubstrateUtil.cast(self.reflectAccess, Target_java_lang_reflect_Field.class).offset; - if (offset == -1) { - throw unsupportedFeature("Trying to access field " + self.reflectAccess + " without registering it as unsafe accessed."); - } - return offset; + return UnsafeFieldUtil.getFieldOffset(SubstrateUtil.cast(self.reflectAccess, Target_java_lang_reflect_Field.class)); } @Substitute @@ -153,11 +150,7 @@ private static long staticFieldOffset(Target_java_lang_invoke_MemberName self) { if (self.intrinsic != null) { return -1L; } - int offset = SubstrateUtil.cast(self.reflectAccess, Target_java_lang_reflect_Field.class).offset; - if (offset == -1) { - throw unsupportedFeature("Trying to access field " + self.reflectAccess + " without registering it as unsafe accessed."); - } - return offset; + return UnsafeFieldUtil.getFieldOffset(SubstrateUtil.cast(self.reflectAccess, Target_java_lang_reflect_Field.class)); } @Substitute diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/UnsafeFieldUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/UnsafeFieldUtil.java new file mode 100644 index 000000000000..7ffb0b264eda --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/UnsafeFieldUtil.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.reflect; + +import java.lang.reflect.Field; + +import com.oracle.svm.configure.config.ConfigurationMemberInfo; +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.metadata.MetadataTracer; +import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_AccessibleObject; +import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Field; + +public class UnsafeFieldUtil { + public static long getFieldOffset(Target_java_lang_reflect_Field field) { + if (field == null) { + throw new NullPointerException(); + } + if (MetadataTracer.enabled()) { + traceFieldAccess(SubstrateUtil.cast(field, Field.class)); + } + int offset = field.root == null ? field.offset : field.root.offset; + boolean conditionsSatisfied = SubstrateUtil.cast(field, Target_java_lang_reflect_AccessibleObject.class).conditions.satisfied(); + if (offset <= 0 || !conditionsSatisfied) { + throw MissingReflectionRegistrationUtils.reportAccessedField(SubstrateUtil.cast(field, Field.class)); + } + return offset; + } + + private static void traceFieldAccess(Field f) { + MetadataTracer.singleton().traceFieldAccess(f.getDeclaringClass(), f.getName(), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); + } +} 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 1803b16d38d6..119999247d61 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 @@ -26,10 +26,8 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; -import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; -import java.util.List; import java.util.regex.Pattern; import org.graalvm.collections.EconomicMap; @@ -192,11 +190,7 @@ private static ClassLoader getCommonClassLoaderOrFail(ClassLoader loader, Class< @Override public Class getProxyClass(ClassLoader loader, Class... interfaces) { if (MetadataTracer.enabled()) { - List interfaceNames = new ArrayList<>(interfaces.length); - for (Class iface : interfaces) { - interfaceNames.add(iface.getName()); - } - MetadataTracer.singleton().traceProxyType(interfaceNames); + MetadataTracer.singleton().traceProxyType(interfaces); } ProxyCacheKey key = new ProxyCacheKey(interfaces); 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 63f927793152..7518aa1b25e4 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 @@ -268,7 +268,7 @@ public static Object getSerializationConstructorAccessor(Class serializationT } } else { if (MetadataTracer.enabled()) { - MetadataTracer.singleton().traceSerializationType(declaringClass.getName()); + MetadataTracer.singleton().traceSerializationType(declaringClass); } for (var singleton : layeredSingletons()) { Object constructorAccessor = singleton.getSerializationConstructorAccessor0(declaringClass, targetConstructorClass); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionObjectFactory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionObjectFactory.java index 845281586bcf..1c6f5b084026 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionObjectFactory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionObjectFactory.java @@ -68,7 +68,7 @@ public static Constructor newConstructor(RuntimeConditionSet conditions, Clas byte[] annotations, byte[] parameterAnnotations, Object accessor, byte[] rawParameters, byte[] typeAnnotations) { Target_java_lang_reflect_Constructor ctor = new Target_java_lang_reflect_Constructor(); ctor.constructor(declaringClass, parameterTypes, exceptionTypes, modifiers, -1, signature, annotations, parameterAnnotations); - ctor.constructorAccessor = (Target_jdk_internal_reflect_ConstructorAccessor) accessor; + ctor.constructorAccessorFromMetadata = (Target_jdk_internal_reflect_ConstructorAccessor) accessor; SubstrateUtil.cast(ctor, Target_java_lang_reflect_Executable.class).rawParameters = rawParameters; Target_java_lang_reflect_AccessibleObject accessibleObject = SubstrateUtil.cast(ctor, Target_java_lang_reflect_AccessibleObject.class); accessibleObject.typeAnnotations = typeAnnotations; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Constructor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Constructor.java index ed343a2fa14b..9984c682ec29 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Constructor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Constructor.java @@ -32,13 +32,17 @@ import org.graalvm.nativeimage.ImageSingletons; +import com.oracle.svm.configure.config.ConfigurationMemberInfo; +import com.oracle.svm.configure.config.SignatureUtil; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Inject; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; +import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import sun.reflect.generics.repository.ConstructorRepository; @@ -59,6 +63,14 @@ public final class Target_java_lang_reflect_Constructor { @RecomputeFieldValue(kind = Kind.Custom, declClass = ExecutableAccessorComputer.class) // Target_jdk_internal_reflect_ConstructorAccessor constructorAccessor; + /** + * We need this indirection to use {@link #acquireConstructorAccessor()} for checking if + * run-time conditions for this method are satisfied. + */ + @Inject // + @RecomputeFieldValue(kind = Kind.Reset) // + Target_jdk_internal_reflect_ConstructorAccessor constructorAccessorFromMetadata; + @Alias @TargetElement(name = CONSTRUCTOR_NAME) @SuppressWarnings("hiding") @@ -70,10 +82,13 @@ native void constructor(Class declaringClass, Class[] parameterTypes, Clas @Substitute public Target_jdk_internal_reflect_ConstructorAccessor acquireConstructorAccessor() { - if (constructorAccessor == null) { + if (MetadataTracer.enabled()) { + ConstructorUtil.traceConstructorAccess(SubstrateUtil.cast(this, Executable.class)); + } + if (constructorAccessorFromMetadata == null) { throw MissingReflectionRegistrationUtils.reportInvokedExecutable(SubstrateUtil.cast(this, Executable.class)); } - return constructorAccessor; + return constructorAccessorFromMetadata; } static class AnnotationsComputer extends ReflectionMetadataComputer { @@ -90,3 +105,11 @@ public Object transform(Object receiver, Object originalValue) { } } } + +class ConstructorUtil { + + static void traceConstructorAccess(Executable ctor) { + MetadataTracer.singleton().traceMethodAccess(ctor.getDeclaringClass(), CONSTRUCTOR_NAME, SignatureUtil.toInternalSignature(ctor.getParameterTypes()), + ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Field.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Field.java index 07d59a3d3933..f206a7424fe2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Field.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Field.java @@ -86,7 +86,7 @@ public final class Target_java_lang_reflect_Field { boolean override; @Alias // - Target_java_lang_reflect_Field root; + public Target_java_lang_reflect_Field root; @Alias native Target_java_lang_reflect_Field copy(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java index c91c717e2941..a4e51e883e0a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java @@ -35,6 +35,8 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.FieldValueTransformer; +import com.oracle.svm.configure.config.ConfigurationMemberInfo; +import com.oracle.svm.configure.config.SignatureUtil; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Inject; @@ -48,6 +50,7 @@ import com.oracle.svm.core.imagelayer.DynamicImageLayerInfo; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; +import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import jdk.internal.reflect.ConstantPool; @@ -134,6 +137,9 @@ native void constructor(Class declaringClass, String name, Class[] paramet @Substitute public Target_jdk_internal_reflect_MethodAccessor acquireMethodAccessor() { RuntimeConditionSet conditions = SubstrateUtil.cast(this, Target_java_lang_reflect_AccessibleObject.class).conditions; + if (MetadataTracer.enabled()) { + MethodUtil.traceMethodAccess(SubstrateUtil.cast(this, Executable.class)); + } if (methodAccessorFromMetadata == null || !conditions.satisfied()) { throw MissingReflectionRegistrationUtils.reportInvokedExecutable(SubstrateUtil.cast(this, Executable.class)); } @@ -212,3 +218,10 @@ public Object transform(Object receiver, Object originalValue) { } } } + +class MethodUtil { + static void traceMethodAccess(Executable meth) { + MetadataTracer.singleton().traceMethodAccess(meth.getDeclaringClass(), meth.getName(), SignatureUtil.toInternalSignature(meth.getParameterTypes()), + ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_ReflectAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_ReflectAccess.java index 473c0541151b..14943853f0e6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_ReflectAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_ReflectAccess.java @@ -72,6 +72,7 @@ public Target_java_lang_reflect_Constructor copyConstructor(Target_java_lang_ref Target_java_lang_reflect_Constructor copy = constructor.copy(); copyExecutable(SubstrateUtil.cast(copy, Target_java_lang_reflect_Executable.class), SubstrateUtil.cast(constructor, Target_java_lang_reflect_Executable.class)); + copy.constructorAccessorFromMetadata = constructor.constructorAccessorFromMetadata; return copy; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java index 13529271683f..19f81eb85dd9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java @@ -33,7 +33,7 @@ import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; -import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; +import com.oracle.svm.core.reflect.UnsafeFieldUtil; @TargetClass(className = "jdk.internal.misc.Unsafe") @SuppressWarnings({"static-method"}) @@ -41,12 +41,12 @@ public final class Target_jdk_internal_misc_Unsafe_Reflection { @Substitute public long objectFieldOffset(Target_java_lang_reflect_Field field) { - return UnsafeUtil.getFieldOffset(field); + return UnsafeFieldUtil.getFieldOffset(field); } @Substitute public long staticFieldOffset(Target_java_lang_reflect_Field field) { - return UnsafeUtil.getFieldOffset(field); + return UnsafeFieldUtil.getFieldOffset(field); } @Substitute @@ -76,18 +76,3 @@ public long objectFieldOffset(Class c, String name) { } } } - -class UnsafeUtil { - static long getFieldOffset(Target_java_lang_reflect_Field field) { - if (field == null) { - throw new NullPointerException(); - } - int offset = field.root == null ? field.offset : field.root.offset; - boolean conditionsSatisfied = SubstrateUtil.cast(field, Target_java_lang_reflect_AccessibleObject.class).conditions.satisfied(); - if (offset <= 0 || !conditionsSatisfied) { - throw MissingReflectionRegistrationUtils.reportAccessedField(SubstrateUtil.cast(field, Field.class)); - } - return offset; - } - -}