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 bdb16556f983..3c060e86d00c 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 @@ -25,22 +25,31 @@ package com.oracle.svm.core.hub; import org.graalvm.collections.EconomicMap; +import org.graalvm.compiler.options.Option; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.util.ExitStatus; import com.oracle.svm.core.util.ImageHeapMap; +import com.oracle.svm.core.util.VMError; @AutomaticallyRegisteredImageSingleton public final class ClassForNameSupport { + public static class Options { + @Option(help = "Enable termination caused by missing metadata.")// + public static final HostedOptionKey ExitOnUnknownClassLoadingFailure = new HostedOptionKey<>(false); + } + static ClassForNameSupport singleton() { return ImageSingletons.lookup(ClassForNameSupport.class); } /** The map used to collect registered classes. */ - private final EconomicMap> knownClasses = ImageHeapMap.create(); + private final EconomicMap knownClasses = ImageHeapMap.create(); @Platforms(Platform.HOSTED_ONLY.class) public static void registerClass(Class clazz) { @@ -48,18 +57,63 @@ public static void registerClass(Class clazz) { if (PredefinedClassesSupport.isPredefined(clazz)) { return; // must be defined at runtime before it can be looked up } - singleton().knownClasses.put(clazz.getName(), clazz); + String name = clazz.getName(); + VMError.guarantee(!singleton().knownClasses.containsKey(name) || singleton().knownClasses.get(name) == clazz); + singleton().knownClasses.put(name, clazz); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static void registerExceptionForClass(String className, Throwable t) { + singleton().knownClasses.put(className, t); } public static Class forNameOrNull(String className, ClassLoader classLoader) { + try { + return forName(className, classLoader, true); + } catch (ClassNotFoundException e) { + throw VMError.shouldNotReachHere("ClassForNameSupport.forNameOrNull should not throw", e); + } + } + + public static Class forName(String className, ClassLoader classLoader) throws ClassNotFoundException { + return forName(className, classLoader, false); + } + + private static Class forName(String className, ClassLoader classLoader, boolean returnNullOnException) throws ClassNotFoundException { if (className == null) { return null; } - Class result = singleton().knownClasses.get(className); + Object result = singleton().knownClasses.get(className); if (result == null) { result = PredefinedClassesSupport.getLoadedForNameOrNull(className, classLoader); } // Note: for non-predefined classes, we (currently) don't need to check the provided loader - return result; + // TODO rewrite stack traces (GR-42813) + if (result instanceof Class) { + return (Class) result; + } else if (returnNullOnException && (result instanceof Throwable || result == null)) { + return null; + } else if (result == null) { + if (ClassForNameSupport.Options.ExitOnUnknownClassLoadingFailure.getValue()) { + terminateUnconfigured(className); + } + throw new ClassNotFoundException(className); + } else if (result instanceof Error) { + throw (Error) result; + } else if (result instanceof ClassNotFoundException) { + throw (ClassNotFoundException) result; + } else { + throw VMError.shouldNotReachHere("Class.forName result should be Class, ClassNotFoundException or Error: " + result); + } + } + + public static int count() { + return singleton().knownClasses.size(); + } + + private static void terminateUnconfigured(String className) { + System.out.println("Missing metadata error: Unable to process Class.forName invocation for class name " + className); + new ClassNotFoundException(className).printStackTrace(); + System.exit(ExitStatus.MISSING_METADATA.getValue()); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassLoadingExceptionSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassLoadingExceptionSupport.java deleted file mode 100644 index bc9f8b20712d..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassLoadingExceptionSupport.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2022, 2022, 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.hub; - -import org.graalvm.collections.EconomicMap; -import org.graalvm.compiler.options.Option; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; - -import com.oracle.svm.core.option.HostedOptionKey; -import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; -import com.oracle.svm.core.util.ImageHeapMap; - -@AutomaticallyRegisteredImageSingleton -public class ClassLoadingExceptionSupport { - - /** The exit code used when terminating abruptly due to missing metadata. */ - private static final int EXIT_CODE = 172; - - public static class Options { - @Option(help = "Enable termination caused by missing metadata.")// - public static final HostedOptionKey ExitOnUnknownClassLoadingFailure = new HostedOptionKey<>(false); - } - - static ClassLoadingExceptionSupport singleton() { - return ImageSingletons.lookup(ClassLoadingExceptionSupport.class); - } - - /** The map used to collect registered problematic classes. */ - private final EconomicMap inaccessibleClasses = ImageHeapMap.create(); - - @Platforms(Platform.HOSTED_ONLY.class) - public static void registerClass(String className, Throwable t) { - singleton().inaccessibleClasses.put(className, t); - } - - public static Throwable getExceptionForClass(String className, Throwable original) { - Throwable t = singleton().inaccessibleClasses.get(className); - if (t == null && Options.ExitOnUnknownClassLoadingFailure.getValue()) { - terminateUnconfigured(className); - } - if (t == null || t.getClass() == original.getClass()) { - return original; - } - return t; - } - - private static void terminateUnconfigured(String className) { - System.err.println("Missing metadata error: Unable to process Class.forName invocation for class name " + className); - System.exit(EXIT_CODE); - } -} 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 3cd384ea800a..91d982c57b71 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 @@ -84,6 +84,9 @@ import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.reflect.ReflectionMetadataDecoder; +import com.oracle.svm.core.reflect.ReflectionMetadataDecoder.ConstructorDescriptor; +import com.oracle.svm.core.reflect.ReflectionMetadataDecoder.FieldDescriptor; +import com.oracle.svm.core.reflect.ReflectionMetadataDecoder.MethodDescriptor; import com.oracle.svm.core.reflect.Target_java_lang_reflect_RecordComponent; import com.oracle.svm.core.reflect.Target_jdk_internal_reflect_ConstantPool; import com.oracle.svm.core.util.LazyFinalReference; @@ -1148,12 +1151,15 @@ private static Class forName(String name, boolean initialize, ClassLoader loa if (name == null) { throw new NullPointerException(); } - Class result = ClassForNameSupport.forNameOrNull(name, loader); - if (result == null && loader != null && PredefinedClassesSupport.hasBytecodeClasses()) { - result = loader.loadClass(name); // may throw - } - if (result == null) { - throw ClassLoadingExceptionSupport.getExceptionForClass(name, new ClassNotFoundException(name)); + Class result; + try { + result = ClassForNameSupport.forName(name, loader); + } catch (ClassNotFoundException e) { + if (loader != null && PredefinedClassesSupport.hasBytecodeClasses()) { + result = loader.loadClass(name); // may throw + } else { + throw e; + } } if (initialize) { DynamicHub.fromClass(result).ensureInitialized(); @@ -1419,7 +1425,7 @@ private Field[] getDeclaredFields0(boolean publicOnly) { if (reflectionMetadata == null || reflectionMetadata.fieldsEncodingIndex == NO_DATA) { return new Field[0]; } - return ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseFields(this, reflectionMetadata.fieldsEncodingIndex, publicOnly, true); + return ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseFields(this, reflectionMetadata.fieldsEncodingIndex, publicOnly); } @Substitute @@ -1427,7 +1433,7 @@ private Method[] getDeclaredMethods0(boolean publicOnly) { if (reflectionMetadata == null || reflectionMetadata.methodsEncodingIndex == NO_DATA) { return new Method[0]; } - return ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseMethods(this, reflectionMetadata.methodsEncodingIndex, publicOnly, true); + return ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseMethods(this, reflectionMetadata.methodsEncodingIndex, publicOnly); } @Substitute @@ -1435,7 +1441,7 @@ private Constructor[] getDeclaredConstructors0(boolean publicOnly) { if (reflectionMetadata == null || reflectionMetadata.constructorsEncodingIndex == NO_DATA) { return new Constructor[0]; } - return ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseConstructors(this, reflectionMetadata.constructorsEncodingIndex, publicOnly, true); + return ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseConstructors(this, reflectionMetadata.constructorsEncodingIndex, publicOnly); } @Substitute @@ -1670,25 +1676,25 @@ private ReflectionMetadata(int fieldsEncodingIndex, int methodsEncodingIndex, in } } - public Field[] getReachableFields() { + public FieldDescriptor[] getReachableFields() { if (reflectionMetadata == null || reflectionMetadata.fieldsEncodingIndex == NO_DATA) { - return new Field[0]; + return new FieldDescriptor[0]; } - return ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseFields(this, reflectionMetadata.fieldsEncodingIndex, false, false); + return ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseReachableFields(this, reflectionMetadata.fieldsEncodingIndex); } - public Method[] getReachableMethods() { + public MethodDescriptor[] getReachableMethods() { if (reflectionMetadata == null || reflectionMetadata.methodsEncodingIndex == NO_DATA) { - return new Method[0]; + return new MethodDescriptor[0]; } - return ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseMethods(this, reflectionMetadata.methodsEncodingIndex, false, false); + return ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseReachableMethods(this, reflectionMetadata.methodsEncodingIndex); } - public Constructor[] getReachableConstructors() { + public ConstructorDescriptor[] getReachableConstructors() { if (reflectionMetadata == null || reflectionMetadata.constructorsEncodingIndex == NO_DATA) { - return new Constructor[0]; + return new ConstructorDescriptor[0]; } - return ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseConstructors(this, reflectionMetadata.constructorsEncodingIndex, false, false); + return ImageSingletons.lookup(ReflectionMetadataDecoder.class).parseReachableConstructors(this, reflectionMetadata.constructorsEncodingIndex); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionMetadataDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionMetadataDecoder.java index 538bfc4fe0b4..8e25cced7a0d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionMetadataDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionMetadataDecoder.java @@ -35,11 +35,17 @@ public interface ReflectionMetadataDecoder { int NO_DATA = -1; - Field[] parseFields(DynamicHub declaringType, int index, boolean publicOnly, boolean reflectOnly); + Field[] parseFields(DynamicHub declaringType, int index, boolean publicOnly); - Method[] parseMethods(DynamicHub declaringType, int index, boolean publicOnly, boolean reflectOnly); + FieldDescriptor[] parseReachableFields(DynamicHub declaringType, int index); - Constructor[] parseConstructors(DynamicHub declaringType, int index, boolean publicOnly, boolean reflectOnly); + Method[] parseMethods(DynamicHub declaringType, int index, boolean publicOnly); + + MethodDescriptor[] parseReachableMethods(DynamicHub declaringType, int index); + + Constructor[] parseConstructors(DynamicHub declaringType, int index, boolean publicOnly); + + ConstructorDescriptor[] parseReachableConstructors(DynamicHub declaringType, int index); Class[] parseClasses(int index); @@ -54,4 +60,81 @@ public interface ReflectionMetadataDecoder { boolean isHiding(int modifiers); long getMetadataByteLength(); + + class ElementDescriptor { + private final Class declaringClass; + + public ElementDescriptor(Class declaringClass) { + this.declaringClass = declaringClass; + } + + public Class getDeclaringClass() { + return declaringClass; + } + + protected static String[] getParameterTypeNames(Class[] parameterTypes) { + String[] parameterTypeNames = new String[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; ++i) { + parameterTypeNames[i] = parameterTypes[i].getTypeName(); + } + return parameterTypeNames; + } + } + + class FieldDescriptor extends ElementDescriptor { + public final String name; + + public FieldDescriptor(Class declaringClass, String name) { + super(declaringClass); + this.name = name; + } + + public FieldDescriptor(Field field) { + this(field.getDeclaringClass(), field.getName()); + } + + public String getName() { + return name; + } + } + + class MethodDescriptor extends ElementDescriptor { + public final String name; + public final String[] parameterTypeNames; + + public MethodDescriptor(Class declaringClass, String name, String[] parameterTypeNames) { + super(declaringClass); + this.name = name; + this.parameterTypeNames = parameterTypeNames; + } + + public MethodDescriptor(Method method) { + this(method.getDeclaringClass(), method.getName(), getParameterTypeNames(method.getParameterTypes())); + } + + public String getName() { + return name; + } + + public String[] getParameterTypeNames() { + return parameterTypeNames; + } + } + + class ConstructorDescriptor extends ElementDescriptor { + public final String[] parameterTypeNames; + + public ConstructorDescriptor(Class declaringClass, String[] parameterTypeNames) { + super(declaringClass); + this.parameterTypeNames = parameterTypeNames; + } + + public ConstructorDescriptor(Constructor constructor) { + this(constructor.getDeclaringClass(), getParameterTypeNames(constructor.getParameterTypes())); + } + + public String[] getParameterTypeNames() { + return parameterTypeNames; + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java index a63ffb4e5f99..a462417fd40e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java @@ -86,10 +86,17 @@ static byte[] getEncoding() { * */ @Override - public Field[] parseFields(DynamicHub declaringType, int index, boolean publicOnly, boolean reflectOnly) { + public Field[] parseFields(DynamicHub declaringType, int index, boolean publicOnly) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); CodeInfo codeInfo = CodeInfoTable.getImageCodeInfo(); - return decodeArray(reader, Field.class, (i) -> decodeField(reader, codeInfo, DynamicHub.toClass(declaringType), publicOnly, reflectOnly)); + return decodeArray(reader, Field.class, (i) -> (Field) decodeField(reader, codeInfo, DynamicHub.toClass(declaringType), publicOnly, true)); + } + + @Override + public FieldDescriptor[] parseReachableFields(DynamicHub declaringType, int index) { + UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); + CodeInfo codeInfo = CodeInfoTable.getImageCodeInfo(); + return decodeArray(reader, FieldDescriptor.class, (i) -> (FieldDescriptor) decodeField(reader, codeInfo, DynamicHub.toClass(declaringType), false, false)); } /** @@ -100,10 +107,17 @@ public Field[] parseFields(DynamicHub declaringType, int index, boolean publicOn * */ @Override - public Method[] parseMethods(DynamicHub declaringType, int index, boolean publicOnly, boolean reflectOnly) { + public Method[] parseMethods(DynamicHub declaringType, int index, boolean publicOnly) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); CodeInfo codeInfo = CodeInfoTable.getImageCodeInfo(); - return decodeArray(reader, Method.class, (i) -> decodeMethod(reader, codeInfo, DynamicHub.toClass(declaringType), publicOnly, reflectOnly)); + return decodeArray(reader, Method.class, (i) -> (Method) decodeExecutable(reader, codeInfo, DynamicHub.toClass(declaringType), publicOnly, true, true)); + } + + @Override + public MethodDescriptor[] parseReachableMethods(DynamicHub declaringType, int index) { + UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); + CodeInfo codeInfo = CodeInfoTable.getImageCodeInfo(); + return decodeArray(reader, MethodDescriptor.class, (i) -> (MethodDescriptor) decodeExecutable(reader, codeInfo, DynamicHub.toClass(declaringType), false, false, true)); } /** @@ -114,10 +128,17 @@ public Method[] parseMethods(DynamicHub declaringType, int index, boolean public * */ @Override - public Constructor[] parseConstructors(DynamicHub declaringType, int index, boolean publicOnly, boolean reflectOnly) { + public Constructor[] parseConstructors(DynamicHub declaringType, int index, boolean publicOnly) { + UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); + CodeInfo codeInfo = CodeInfoTable.getImageCodeInfo(); + return decodeArray(reader, Constructor.class, (i) -> (Constructor) decodeExecutable(reader, codeInfo, DynamicHub.toClass(declaringType), publicOnly, true, false)); + } + + @Override + public ConstructorDescriptor[] parseReachableConstructors(DynamicHub declaringType, int index) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); CodeInfo codeInfo = CodeInfoTable.getImageCodeInfo(); - return decodeArray(reader, Constructor.class, (i) -> decodeConstructor(reader, codeInfo, DynamicHub.toClass(declaringType), publicOnly, reflectOnly)); + return decodeArray(reader, ConstructorDescriptor.class, (i) -> (ConstructorDescriptor) decodeExecutable(reader, codeInfo, DynamicHub.toClass(declaringType), false, false, false)); } /** @@ -261,7 +282,7 @@ private static void decodeAndThrowError(int index) throws * } * */ - private static Field decodeField(UnsafeArrayTypeReader buf, CodeInfo info, Class declaringClass, boolean publicOnly, boolean reflectOnly) { + private static Object decodeField(UnsafeArrayTypeReader buf, CodeInfo info, Class declaringClass, boolean publicOnly, boolean reflectOnly) { int modifiers = buf.getUVInt(); boolean inHeap = (modifiers & IN_HEAP_FLAG_MASK) != 0; boolean complete = (modifiers & COMPLETE_FLAG_MASK) != 0; @@ -270,10 +291,11 @@ private static Field decodeField(UnsafeArrayTypeReader buf, CodeInfo info, Class if (publicOnly && !Modifier.isPublic(field.getModifiers())) { return null; } - if (reflectOnly && !complete) { - return null; + if (reflectOnly) { + return complete ? field : null; + } else { + return new FieldDescriptor(field); } - return field; } boolean hiding = (modifiers & HIDING_FLAG_MASK) != 0; assert !(complete && hiding); @@ -290,6 +312,9 @@ private static Field decodeField(UnsafeArrayTypeReader buf, CodeInfo info, Class */ return null; } + if (!reflectOnly) { + return new FieldDescriptor(declaringClass, name); + } Target_java_lang_reflect_Field field = new Target_java_lang_reflect_Field(); if (JavaVersionUtil.JAVA_SPEC >= 17) { field.constructorJDK17OrLater(declaringClass, name, type, modifiers, false, -1, null, null); @@ -317,7 +342,8 @@ private static Field decodeField(UnsafeArrayTypeReader buf, CodeInfo info, Class field.offset = offset; field.deletedReason = deletedReason; SubstrateUtil.cast(field, Target_java_lang_reflect_AccessibleObject.class).typeAnnotations = typeAnnotations; - return SubstrateUtil.cast(field, Field.class); + Field reflectField = SubstrateUtil.cast(field, Field.class); + return reflectOnly ? reflectField : new FieldDescriptor(reflectField); } /** @@ -368,12 +394,7 @@ private static Field decodeField(UnsafeArrayTypeReader buf, CodeInfo info, Class * ClassIndex[] parameterTypes * } * - */ - private static Method decodeMethod(UnsafeArrayTypeReader buf, CodeInfo info, Class declaringClass, boolean publicOnly, boolean reflectOnly) { - return (Method) decodeExecutable(buf, info, declaringClass, publicOnly, reflectOnly, true); - } - - /** + * * Complete constructor encoding. * *
@@ -407,11 +428,7 @@ private static Method decodeMethod(UnsafeArrayTypeReader buf, CodeInfo info, Cla
      * }
      * 
*/ - private static Constructor decodeConstructor(UnsafeArrayTypeReader buf, CodeInfo info, Class declaringClass, boolean publicOnly, boolean reflectOnly) { - return (Constructor) decodeExecutable(buf, info, declaringClass, publicOnly, reflectOnly, false); - } - - private static Executable decodeExecutable(UnsafeArrayTypeReader buf, CodeInfo info, Class declaringClass, boolean publicOnly, boolean reflectOnly, boolean isMethod) { + private static Object decodeExecutable(UnsafeArrayTypeReader buf, CodeInfo info, Class declaringClass, boolean publicOnly, boolean reflectOnly, boolean isMethod) { int modifiers = buf.getUVInt(); boolean inHeap = (modifiers & IN_HEAP_FLAG_MASK) != 0; boolean complete = (modifiers & COMPLETE_FLAG_MASK) != 0; @@ -420,17 +437,29 @@ private static Executable decodeExecutable(UnsafeArrayTypeReader buf, CodeInfo i if (publicOnly && !Modifier.isPublic(executable.getModifiers())) { return null; } - if (reflectOnly && !complete) { - return null; + if (reflectOnly) { + return complete ? executable : null; + } else { + if (isMethod) { + Method method = (Method) executable; + return new MethodDescriptor(method); + } else { + Constructor constructor = (Constructor) executable; + return new ConstructorDescriptor(constructor); + } } - return executable; } boolean hiding = (modifiers & HIDING_FLAG_MASK) != 0; assert !(complete && hiding); modifiers &= ~COMPLETE_FLAG_MASK; String name = isMethod ? decodeName(buf, info) : null; - Class[] parameterTypes = decodeArray(buf, Class.class, (i) -> decodeType(buf, info)); + Object[] parameterTypes; + if (complete || hiding) { + parameterTypes = decodeArray(buf, Class.class, (i) -> decodeType(buf, info)); + } else { + parameterTypes = decodeArray(buf, String.class, (i) -> decodeName(buf, info)); + } Class returnType = isMethod && (complete || hiding) ? decodeType(buf, info) : null; if (!complete) { if (reflectOnly != hiding) { @@ -442,12 +471,18 @@ private static Executable decodeExecutable(UnsafeArrayTypeReader buf, CodeInfo i return null; } if (isMethod) { + if (!reflectOnly) { + return new MethodDescriptor(declaringClass, name, (String[]) parameterTypes); + } Target_java_lang_reflect_Method method = new Target_java_lang_reflect_Method(); - method.constructor(declaringClass, name, parameterTypes, returnType, null, modifiers, -1, null, null, null, null); + method.constructor(declaringClass, name, (Class[]) parameterTypes, returnType, null, modifiers, -1, null, null, null, null); return SubstrateUtil.cast(method, Executable.class); } else { + if (!reflectOnly) { + return new ConstructorDescriptor(declaringClass, (String[]) parameterTypes); + } Target_java_lang_reflect_Constructor constructor = new Target_java_lang_reflect_Constructor(); - constructor.constructor(declaringClass, parameterTypes, null, modifiers, -1, null, null, null); + constructor.constructor(declaringClass, (Class[]) parameterTypes, null, modifiers, -1, null, null, null); return SubstrateUtil.cast(constructor, Executable.class); } } @@ -466,13 +501,19 @@ private static Executable decodeExecutable(UnsafeArrayTypeReader buf, CodeInfo i Target_java_lang_reflect_Executable executable; if (isMethod) { Target_java_lang_reflect_Method method = new Target_java_lang_reflect_Method(); - method.constructor(declaringClass, name, parameterTypes, returnType, exceptionTypes, modifiers, -1, signature, annotations, parameterAnnotations, annotationDefault); + method.constructor(declaringClass, name, (Class[]) parameterTypes, returnType, exceptionTypes, modifiers, -1, signature, annotations, parameterAnnotations, annotationDefault); method.methodAccessor = (Target_jdk_internal_reflect_MethodAccessor) accessor; + if (!reflectOnly) { + return new MethodDescriptor(SubstrateUtil.cast(method, Method.class)); + } executable = SubstrateUtil.cast(method, Target_java_lang_reflect_Executable.class); } else { Target_java_lang_reflect_Constructor constructor = new Target_java_lang_reflect_Constructor(); - constructor.constructor(declaringClass, parameterTypes, exceptionTypes, modifiers, -1, signature, annotations, parameterAnnotations); + constructor.constructor(declaringClass, (Class[]) parameterTypes, exceptionTypes, modifiers, -1, signature, annotations, parameterAnnotations); constructor.constructorAccessor = (Target_jdk_internal_reflect_ConstructorAccessor) accessor; + if (!reflectOnly) { + return new ConstructorDescriptor(SubstrateUtil.cast(constructor, Constructor.class)); + } executable = SubstrateUtil.cast(constructor, Target_java_lang_reflect_Executable.class); } executable.rawParameters = reflectParameters; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ExitStatus.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ExitStatus.java index 8bcd127db84f..1d23b2aa56c9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ExitStatus.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ExitStatus.java @@ -31,7 +31,8 @@ public enum ExitStatus { BUILDER_INTERRUPT_WITHOUT_REASON(3), DRIVER_ERROR(20), DRIVER_TO_BUILDER_ERROR(21), - WATCHDOG_EXIT(30); + WATCHDOG_EXIT(30), + MISSING_METADATA(172); public static ExitStatus of(int status) { for (ExitStatus s : values()) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java index 1ee6e315c3d2..dab90726dfbf 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java @@ -24,10 +24,10 @@ */ package com.oracle.svm.hosted; -import java.util.ArrayList; -import java.util.List; +import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.impl.ConfigurationCondition; @@ -35,20 +35,20 @@ import com.oracle.svm.core.TypeResult; public abstract class ConditionalConfigurationRegistry { - private final Map> pendingReachabilityHandlers = new ConcurrentHashMap<>(); + private final Map> pendingReachabilityHandlers = new ConcurrentHashMap<>(); protected void registerConditionalConfiguration(ConfigurationCondition condition, Runnable runnable) { if (ConfigurationCondition.alwaysTrue().equals(condition)) { /* analysis optimization to include new types as early as possible */ runnable.run(); } else { - List handlers = pendingReachabilityHandlers.computeIfAbsent(condition.getTypeName(), key -> new ArrayList<>()); + Collection handlers = pendingReachabilityHandlers.computeIfAbsent(condition.getTypeName(), key -> new ConcurrentLinkedQueue<>()); handlers.add(runnable); } } public void flushConditionalConfiguration(Feature.BeforeAnalysisAccess b) { - for (Map.Entry> reachabilityEntry : pendingReachabilityHandlers.entrySet()) { + for (Map.Entry> reachabilityEntry : pendingReachabilityHandlers.entrySet()) { TypeResult> typeResult = ((FeatureImpl.BeforeAnalysisAccessImpl) b).getImageClassLoader().findClass(reachabilityEntry.getKey()); b.registerReachabilityHandler(access -> reachabilityEntry.getValue().forEach(Runnable::run), typeResult.get()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index e51774403b9d..a8825fd344c1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -74,14 +74,15 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.VM; import com.oracle.svm.core.code.CodeInfoTable; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.hub.ClassForNameSupport; import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.jdk.resources.ResourceStorageEntry; import com.oracle.svm.core.meta.SubstrateObjectConstant; import com.oracle.svm.core.option.HostedOptionValues; import com.oracle.svm.core.reflect.ReflectionMetadataDecoder; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ProgressReporterJsonHelper.AnalysisResults; import com.oracle.svm.hosted.ProgressReporterJsonHelper.GeneralInfo; @@ -364,8 +365,8 @@ private void printAnalysisStatistics(AnalysisUniverse universe, Collection>> resolveType(ConfigurationConditi TypeResult> clazz = classLoader.findClass(name, allowPrimitives); if (!clazz.isPresent()) { Throwable classLookupException = clazz.getException(); - if (classLookupException instanceof LinkageError || ClassLoadingExceptionSupport.Options.ExitOnUnknownClassLoadingFailure.getValue()) { + if (classLookupException instanceof LinkageError || ClassForNameSupport.Options.ExitOnUnknownClassLoadingFailure.getValue()) { registry.registerClassLookupException(condition, typeName, classLookupException); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java index 3e5ea754174d..74c6a0698225 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.hosted.heap; -import java.lang.reflect.AccessibleObject; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.util.function.Consumer; @@ -148,10 +147,12 @@ protected void rescanEconomicMap(EconomicMap map) { protected void onObjectReachable(ImageHeapConstant imageHeapConstant, ScanReason reason) { super.onObjectReachable(imageHeapConstant, reason); - if (metaAccess.isInstanceOf(imageHeapConstant, Field.class) || metaAccess.isInstanceOf(imageHeapConstant, Executable.class)) { - reflectionSupport.registerHeapReflectionObject((AccessibleObject) SubstrateObjectConstant.asObject(imageHeapConstant.getHostedObject())); + if (metaAccess.isInstanceOf(imageHeapConstant, Field.class)) { + reflectionSupport.registerHeapReflectionField((Field) SubstrateObjectConstant.asObject(imageHeapConstant.getHostedObject()), reason); + } else if (metaAccess.isInstanceOf(imageHeapConstant, Executable.class)) { + reflectionSupport.registerHeapReflectionExecutable((Executable) SubstrateObjectConstant.asObject(imageHeapConstant.getHostedObject()), reason); } else if (metaAccess.isInstanceOf(imageHeapConstant, DynamicHub.class)) { - reflectionSupport.registerHeapDynamicHub(SubstrateObjectConstant.asObject(imageHeapConstant.getHostedObject())); + reflectionSupport.registerHeapDynamicHub(SubstrateObjectConstant.asObject(imageHeapConstant.getHostedObject()), reason); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapVerifier.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapVerifier.java index e049deed020b..1c40f1f8a69c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapVerifier.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapVerifier.java @@ -35,7 +35,6 @@ import com.oracle.graal.pointsto.util.CompletionExecutor; import com.oracle.svm.core.meta.SubstrateObjectConstant; import com.oracle.svm.hosted.SVMHost; -import com.oracle.svm.hosted.reflect.ReflectionHostedSupport; import jdk.vm.ci.meta.JavaConstant; @@ -62,8 +61,7 @@ public boolean requireAnalysisIteration(CompletionExecutor executor) throws Inte * */ private static boolean imageStateModified() { - return ImageSingletons.lookup(ReflectionHostedSupport.class).requiresProcessing() || - ImageSingletons.lookup(ImageHeapMapFeature.class).imageHeapMapNeedsUpdate(); + return ImageSingletons.lookup(ImageHeapMapFeature.class).imageHeapMapNeedsUpdate(); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java index 52d17823f508..1e42425986c8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java @@ -268,41 +268,41 @@ public void buildRuntimeMetadata(CFunctionPointer firstMethod, UnsignedWord code Set includedFields = new HashSet<>(); Set includedMethods = new HashSet<>(); - Set configurationFields = reflectionSupport.getReflectionFields(); - Set configurationExecutables = reflectionSupport.getReflectionExecutables(); - - for (AccessibleObject object : reflectionSupport.getHeapReflectionObjects()) { - if (object instanceof Field) { - HostedField hostedField = hMetaAccess.lookupJavaField((Field) object); - if (!includedFields.contains(hostedField)) { - reflectionMetadataEncoder.addHeapAccessibleObjectMetadata(hMetaAccess, hostedField, object, configurationFields.contains(object)); - includedFields.add(hostedField); - } - } else if (object instanceof Executable) { - HostedMethod hostedMethod = hMetaAccess.lookupJavaMethod((Executable) object); - if (!includedMethods.contains(hostedMethod)) { - reflectionMetadataEncoder.addHeapAccessibleObjectMetadata(hMetaAccess, hostedMethod, object, configurationExecutables.contains(object)); - includedMethods.add(hostedMethod); - } + Map configurationFields = reflectionSupport.getReflectionFields(); + Map configurationExecutables = reflectionSupport.getReflectionExecutables(); + + reflectionSupport.getHeapReflectionFields().forEach(((analysisField, reflectField) -> { + HostedField hostedField = hUniverse.lookup(analysisField); + if (!includedFields.contains(hostedField)) { + reflectionMetadataEncoder.addHeapAccessibleObjectMetadata(hMetaAccess, hostedField, reflectField, configurationFields.containsKey(analysisField)); + includedFields.add(hostedField); } - } + })); - for (Field reflectField : configurationFields) { - HostedField field = hMetaAccess.lookupJavaField(reflectField); - if (!includedFields.contains(field)) { - reflectionMetadataEncoder.addReflectionFieldMetadata(hMetaAccess, field, reflectField); - includedFields.add(field); + reflectionSupport.getHeapReflectionExecutables().forEach(((analysisMethod, reflectMethod) -> { + HostedMethod hostedMethod = hUniverse.lookup(analysisMethod); + if (!includedMethods.contains(hostedMethod)) { + reflectionMetadataEncoder.addHeapAccessibleObjectMetadata(hMetaAccess, hostedMethod, reflectMethod, configurationExecutables.containsKey(analysisMethod)); + includedMethods.add(hostedMethod); } - } + })); + + configurationFields.forEach(((analysisField, reflectField) -> { + HostedField hostedField = hUniverse.lookup(analysisField); + if (!includedFields.contains(hostedField)) { + reflectionMetadataEncoder.addReflectionFieldMetadata(hMetaAccess, hostedField, reflectField); + includedFields.add(hostedField); + } + })); - for (Executable reflectMethod : configurationExecutables) { - HostedMethod method = hMetaAccess.lookupJavaMethod(reflectMethod); + configurationExecutables.forEach(((analysisMethod, reflectMethod) -> { + HostedMethod method = hUniverse.lookup(analysisMethod); if (!includedMethods.contains(method)) { - Object accessor = reflectionSupport.getAccessor(reflectMethod); + Object accessor = reflectionSupport.getAccessor(analysisMethod); reflectionMetadataEncoder.addReflectionExecutableMetadata(hMetaAccess, method, reflectMethod, accessor); includedMethods.add(method); } - } + })); for (Object field : reflectionSupport.getHidingReflectionFields()) { AnalysisField hidingField = (AnalysisField) field; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index 6d515abc92ff..963dfedbcbd6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -24,24 +24,21 @@ */ package com.oracle.svm.hosted.reflect; -import java.lang.reflect.AccessibleObject; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.MalformedParameterizedTypeException; -import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Proxy; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -51,135 +48,201 @@ import java.util.stream.Collectors; import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; import org.graalvm.nativeimage.hosted.RuntimeProxyCreation; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; +import com.oracle.graal.pointsto.ObjectScanner.ScanReason; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; -import com.oracle.graal.pointsto.meta.AnalysisElement; import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.svm.core.SubstrateOptions; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.hub.ClassForNameSupport; -import com.oracle.svm.core.hub.ClassLoadingExceptionSupport; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.jdk.RecordSupport; import com.oracle.svm.core.reflect.SubstrateAccessor; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ConditionalConfigurationRegistry; -import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.annotation.AnnotationMemberValue; -import com.oracle.svm.hosted.annotation.AnnotationSubstitutionType; import com.oracle.svm.hosted.annotation.AnnotationValue; import com.oracle.svm.hosted.annotation.SubstrateAnnotationExtractor; import com.oracle.svm.hosted.annotation.TypeAnnotationValue; import com.oracle.svm.hosted.substitute.SubstitutionReflectivityFilter; import com.oracle.svm.util.ReflectionUtil; -import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.Signature; import sun.reflect.annotation.ExceptionProxy; public class ReflectionDataBuilder extends ConditionalConfigurationRegistry implements RuntimeReflectionSupport, ReflectionHostedSupport { - - private final Set> modifiedClasses = ConcurrentHashMap.newKeySet(); + private AnalysisMetaAccess metaAccess; + private AnalysisUniverse universe; + private final SubstrateAnnotationExtractor annotationExtractor; + private BeforeAnalysisAccessImpl analysisAccess; private boolean sealed; - private final Set> reflectionClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); - private final Map inaccessibleClasses = new ConcurrentHashMap<>(); - private final Set> unsafeInstantiatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); - private final Map reflectionMethods = new ConcurrentHashMap<>(); - private final Map methodAccessors = new ConcurrentHashMap<>(); - private final Set reflectionFields = Collections.newSetFromMap(new ConcurrentHashMap<>()); - private final Set hidingFields = ConcurrentHashMap.newKeySet(); - private final Set hidingMethods = ConcurrentHashMap.newKeySet(); - private final Map registeredMethods = new ConcurrentHashMap<>(); - private final Map registeredFields = new ConcurrentHashMap<>(); + // Reflection data private final Map, Object[]> registeredRecordComponents = new ConcurrentHashMap<>(); - private final Set heapDynamicHubs = ConcurrentHashMap.newKeySet(); - private final Set heapReflectionObjects = ConcurrentHashMap.newKeySet(); private final Map, Set>> innerClasses = new ConcurrentHashMap<>(); + private final Map registeredFields = new ConcurrentHashMap<>(); + private final Set hidingFields = ConcurrentHashMap.newKeySet(); + private final Map registeredMethods = new ConcurrentHashMap<>(); + private final Map methodAccessors = new ConcurrentHashMap<>(); + private final Set hidingMethods = ConcurrentHashMap.newKeySet(); - private final Map processedTypes = new HashMap<>(); - private final Set processedDynamicHubs = new HashSet<>(); - private final Map> processedHidingFields = new HashMap<>(); - private final Map> processedHidingMethods = new HashMap<>(); - private final Map processedHeapReflectionObjects = new ConcurrentHashMap<>(); + // Heap reflection data + private final Set heapDynamicHubs = ConcurrentHashMap.newKeySet(); + private final Map heapFields = new ConcurrentHashMap<>(); + private final Map heapMethods = new ConcurrentHashMap<>(); - /* Keep track of annotation interface members to include in proxy classes */ - private final Map, Set> annotationMembers = new HashMap<>(); + // Intermediate bookkeeping + private final Map> processedTypes = new ConcurrentHashMap<>(); + private final Map, Set> pendingRecordClasses = new ConcurrentHashMap<>(); + // Annotations handling private final Map filteredAnnotations = new ConcurrentHashMap<>(); private final Map filteredParameterAnnotations = new ConcurrentHashMap<>(); private final Map filteredTypeAnnotations = new ConcurrentHashMap<>(); - private final SubstrateAnnotationExtractor annotationExtractor; - ReflectionDataBuilder(SubstrateAnnotationExtractor annotationExtractor) { this.annotationExtractor = annotationExtractor; } + public void duringSetup(AnalysisMetaAccess analysisMetaAccess, AnalysisUniverse analysisUniverse) { + this.metaAccess = analysisMetaAccess; + this.universe = analysisUniverse; + } + + public void beforeAnalysis(BeforeAnalysisAccessImpl beforeAnalysisAccess) { + this.analysisAccess = beforeAnalysisAccess; + } + @Override public void register(ConfigurationCondition condition, boolean unsafeInstantiated, Class clazz) { checkNotSealed(); - registerConditionalConfiguration(condition, () -> { - if (unsafeInstantiated) { - unsafeInstantiatedClasses.add(clazz); - } - if (reflectionClasses.add(clazz)) { - modifiedClasses.add(clazz); + registerConditionalConfiguration(condition, () -> universe.getBigbang().postTask(debug -> registerClass(clazz, unsafeInstantiated))); + } + + private void registerClass(Class clazz, boolean unsafeInstantiated) { + if (shouldExcludeClass(clazz)) { + return; + } + + AnalysisType type = metaAccess.lookupJavaType(clazz); + type.registerAsReachable("Is registered for reflection."); + if (unsafeInstantiated) { + type.registerAsAllocated("Is registered for reflection."); + } + + ClassForNameSupport.registerClass(clazz); + + try { + if (clazz.getEnclosingClass() != null) { + innerClasses.computeIfAbsent(metaAccess.lookupJavaType(clazz.getEnclosingClass()).getJavaClass(), (enclosingType) -> ConcurrentHashMap.newKeySet()).add(clazz); } - }); + } catch (LinkageError e) { + reportLinkingErrors(clazz, List.of(e)); + } } @Override public void registerClassLookupException(ConfigurationCondition condition, String typeName, Throwable t) { checkNotSealed(); - registerConditionalConfiguration(condition, () -> { - inaccessibleClasses.put(typeName, t); - }); + registerConditionalConfiguration(condition, () -> ClassForNameSupport.registerExceptionForClass(typeName, t)); } @Override - public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... methods) { + public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... executables) { checkNotSealed(); - registerConditionalConfiguration(condition, () -> registerMethods(queriedOnly, methods)); - } - - private void registerMethods(boolean queriedOnly, Executable[] methods) { - for (Executable method : methods) { - ExecutableAccessibility oldValue; - ExecutableAccessibility newValue; - do { - newValue = queriedOnly ? ExecutableAccessibility.QueriedOnly : ExecutableAccessibility.Accessed; - oldValue = reflectionMethods.get(method); - if (oldValue != null) { - newValue = ExecutableAccessibility.max(oldValue, newValue); - } - } while (oldValue == null ? reflectionMethods.putIfAbsent(method, newValue) != null : !reflectionMethods.replace(method, oldValue, newValue)); - if (oldValue != newValue) { - modifiedClasses.add(method.getDeclaringClass()); + registerConditionalConfiguration(condition, () -> { + for (Executable executable : executables) { + universe.getBigbang().postTask(debug -> registerMethod(queriedOnly, executable)); } + }); + } + + private void registerMethod(boolean queriedOnly, Executable reflectExecutable) { + if (SubstitutionReflectivityFilter.shouldExclude(reflectExecutable, metaAccess, universe)) { + return; + } + + AnalysisMethod analysisMethod = metaAccess.lookupJavaMethod(reflectExecutable); + if (registeredMethods.put(analysisMethod, reflectExecutable) == null) { + registerTypesForMethod(analysisMethod, reflectExecutable); + AnalysisType declaringType = analysisMethod.getDeclaringClass(); + Class declaringClass = declaringType.getJavaClass(); + + /* + * The image needs to know about subtypes shadowing methods registered for reflection to + * ensure the correctness of run-time reflection queries. + */ + analysisAccess.registerSubtypeReachabilityHandler((access, subType) -> { + universe.getBigbang().postTask(debug -> checkHidingMethod(analysisMethod, metaAccess.lookupJavaType(subType))); + }, declaringClass); + + if (RecordSupport.singleton().isRecord(declaringClass)) { + pendingRecordClasses.computeIfPresent(declaringClass, (clazz, unregisteredAccessors) -> { + if (unregisteredAccessors.remove(reflectExecutable) && unregisteredAccessors.isEmpty()) { + registerRecordComponents(declaringClass); + } + return unregisteredAccessors; + }); + } + + if (declaringType.isAnnotation() && !analysisMethod.isConstructor()) { + processAnnotationMethod(queriedOnly, (Method) reflectExecutable); + } + } + + /* + * We need to run this even if the method has already been registered, in case it was only + * registered as queried. + */ + if (!queriedOnly) { + methodAccessors.computeIfAbsent(analysisMethod, aMethod -> { + SubstrateAccessor accessor = ImageSingletons.lookup(ReflectionFeature.class).getOrCreateAccessor(reflectExecutable); + universe.getHeapScanner().rescanObject(accessor); + return accessor; + }); } } @Override public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) { checkNotSealed(); - registerConditionalConfiguration(condition, () -> registerFields(fields)); + registerConditionalConfiguration(condition, () -> { + for (Field field : fields) { + universe.getBigbang().postTask(debug -> registerField(field)); + } + }); } - private void registerFields(Field[] fields) { - // Unsafe and write accesses are always enabled for fields because accessors use Unsafe. - for (Field field : fields) { - if (reflectionFields.add(field)) { - modifiedClasses.add(field.getDeclaringClass()); + private void registerField(Field reflectField) { + if (SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) { + return; + } + + AnalysisField analysisField = metaAccess.lookupJavaField(reflectField); + if (registeredFields.put(analysisField, reflectField) == null) { + registerTypesForField(analysisField, reflectField); + AnalysisType declaringClass = analysisField.getDeclaringClass(); + + /* + * The image needs to know about subtypes shadowing fields registered for reflection to + * ensure the correctness of run-time reflection queries. + */ + analysisAccess.registerSubtypeReachabilityHandler((access, subType) -> { + universe.getBigbang().postTask(debug -> checkHidingField(analysisField, metaAccess.lookupJavaType(subType))); + }, declaringClass.getJavaClass()); + + if (declaringClass.isAnnotation()) { + processAnnotationField(reflectField); } } } @@ -190,246 +253,117 @@ private void checkNotSealed() { } } - protected void duringAnalysis(DuringAnalysisAccess a) { - DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; - processAnnotationProxyTypes(access); - processRegisteredElements(access); - processMethodMetadata(access); + /* + * Proxy classes for annotations present the annotation default methods and fields as their own. + */ + @SuppressWarnings("deprecation") + private void processAnnotationMethod(boolean queriedOnly, Method method) { + Class annotationClass = method.getDeclaringClass(); + Class proxyClass = Proxy.getProxyClass(annotationClass.getClassLoader(), annotationClass); + try { + register(ConfigurationCondition.create(proxyClass.getTypeName()), queriedOnly, proxyClass.getDeclaredMethod(method.getName(), method.getParameterTypes())); + } catch (NoSuchMethodException e) { + /* + * The annotation member is not present in the proxy class so we don't add it. + */ + } } - private void processAnnotationProxyTypes(DuringAnalysisAccessImpl access) { - for (AnalysisType type : access.getUniverse().getTypes()) { - if (type.getWrappedWithoutResolve() instanceof AnnotationSubstitutionType) { - /* - * Proxy classes for annotations present the annotation default methods and fields - * as their own. - */ - ResolvedJavaType annotationType = ((AnnotationSubstitutionType) type.getWrappedWithoutResolve()).getAnnotationInterfaceType(); - Class annotationClass = access.getUniverse().lookup(annotationType).getJavaClass(); - if (!annotationMembers.containsKey(annotationClass)) { - processClass(access, annotationClass); - } - for (Member member : annotationMembers.get(annotationClass)) { - try { - Class annotationProxyClass = type.getJavaClass(); - if (member instanceof Field) { - Field field = (Field) member; - register(ConfigurationCondition.alwaysTrue(), false, annotationProxyClass.getDeclaredField(field.getName())); - } else if (member instanceof Method) { - Method method = (Method) member; - register(ConfigurationCondition.alwaysTrue(), false, annotationProxyClass.getDeclaredMethod(method.getName(), method.getParameterTypes())); - } - } catch (NoSuchFieldException | NoSuchMethodException e) { - /* - * The annotation member is not present in the proxy class so we don't add - * it. - */ - } - } - } + @SuppressWarnings("deprecation") + private void processAnnotationField(Field field) { + Class annotationClass = field.getDeclaringClass(); + Class proxyClass = Proxy.getProxyClass(annotationClass.getClassLoader(), annotationClass); + try { + register(ConfigurationCondition.create(proxyClass.getTypeName()), false, proxyClass.getDeclaredField(field.getName())); + } catch (NoSuchFieldException e) { + /* + * The annotation member is not present in the proxy class so we don't add it. + */ } } /** - * See {@link ReflectionMetadataEncoderImpl} for details. + * @see ReflectionHostedSupport#getHidingReflectionFields() */ - protected void processMethodMetadata(DuringAnalysisAccessImpl access) { - for (DynamicHub hub : heapDynamicHubs) { - if (!processedDynamicHubs.contains(hub)) { - AnalysisType type = access.getHostVM().lookupType(hub); - if (!SubstitutionReflectivityFilter.shouldExclude(type.getJavaClass(), access.getMetaAccess(), access.getUniverse())) { - registerTypesForClass(access, type, type.getJavaClass()); - processedDynamicHubs.add(hub); - } - } - } - for (Field reflectField : reflectionFields) { - if (!registeredFields.containsKey(reflectField) && !SubstitutionReflectivityFilter.shouldExclude(reflectField, access.getMetaAccess(), access.getUniverse())) { - AnalysisField analysisField = access.getMetaAccess().lookupJavaField(reflectField); - access.requireAnalysisIteration(); - registerTypesForField(access, analysisField, reflectField); - registeredFields.put(reflectField, analysisField); - } - } - for (AnalysisField registeredField : registeredFields.values()) { - registerHidingSubTypeFields(access, registeredField, registeredField.getDeclaringClass()); - } - for (Executable method : reflectionMethods.keySet()) { - if (SubstitutionReflectivityFilter.shouldExclude(method, access.getMetaAccess(), access.getUniverse())) { - continue; - } - if (!registeredMethods.containsKey(method)) { - AnalysisMethod analysisMethod = access.getMetaAccess().lookupJavaMethod(method); - access.requireAnalysisIteration(); - registerTypesForMethod(access, analysisMethod, method); - registeredMethods.put(method, analysisMethod); - } - if (reflectionMethods.get(method) == ExecutableAccessibility.Accessed) { - /* - * We must also generate the accessor for a method that was registered as queried - * and then registered again as accessed - */ - SubstrateAccessor accessor = ImageSingletons.lookup(ReflectionFeature.class).getOrCreateAccessor(method); - if (methodAccessors.putIfAbsent(method, accessor) == null) { - access.rescanObject(accessor); - } - } - } - for (AnalysisMethod registeredMethod : registeredMethods.values()) { - registerHidingSubTypeMethods(access, registeredMethod, registeredMethod.getDeclaringClass()); - } - for (AccessibleObject object : heapReflectionObjects) { - if (!processedHeapReflectionObjects.containsKey(object)) { - AnalysisElement analysisElement = null; - if (object instanceof Field) { - Field field = (Field) object; - if (!SubstitutionReflectivityFilter.shouldExclude(field, access.getMetaAccess(), access.getUniverse())) { - analysisElement = access.getMetaAccess().lookupJavaField(field); - access.requireAnalysisIteration(); - registerTypesForField(access, (AnalysisField) analysisElement, field); - } - } else if (object instanceof Executable) { - Executable executable = (Executable) object; - if (!SubstitutionReflectivityFilter.shouldExclude(executable, access.getMetaAccess(), access.getUniverse())) { - analysisElement = access.getMetaAccess().lookupJavaMethod(executable); - access.requireAnalysisIteration(); - registerTypesForMethod(access, (AnalysisMethod) analysisElement, executable); - } - } - if (analysisElement != null) { - processedHeapReflectionObjects.put(object, analysisElement); - } - } - } - for (AnalysisElement processedObject : processedHeapReflectionObjects.values()) { - if (processedObject instanceof AnalysisField) { - AnalysisField analysisField = (AnalysisField) processedObject; - registerHidingSubTypeFields(access, analysisField, analysisField.getDeclaringClass()); - } else if (processedObject instanceof AnalysisMethod) { - AnalysisMethod analysisMethod = (AnalysisMethod) processedObject; - registerHidingSubTypeMethods(access, analysisMethod, analysisMethod.getDeclaringClass()); - } - } - if (SubstrateOptions.IncludeMethodData.getValue()) { - for (AnalysisField field : access.getUniverse().getFields()) { - if (field.isAccessed()) { - registerTypesForReachableField(access, field); - } - } - for (AnalysisMethod method : access.getUniverse().getMethods()) { - if (method.isReachable() && !method.isIntrinsicMethod()) { - registerTypesForReachableMethod(access, method); - } - } - } - } - - private void registerHidingSubTypeFields(DuringAnalysisAccess access, AnalysisField field, AnalysisType type) { - if (!type.equals(field.getDeclaringClass()) && type.isReachable()) { - if (!processedHidingFields.containsKey(field) || !processedHidingFields.get(field).contains(type)) { - processedHidingFields.computeIfAbsent(field, m -> ConcurrentHashMap.newKeySet()).add(type); - try { - AnalysisField[] subClassFields = field.isStatic() ? type.getStaticFields() : type.getInstanceFields(false); - for (AnalysisField subclassField : subClassFields) { - if (subclassField.getName().equals(field.getName())) { - hidingFields.add(subclassField); - } - } - /* - * Lookup can lead to the creation of new AnalysisField objects, so we need to - * run another analysis iteration. - */ - access.requireAnalysisIteration(); - - } catch (UnsupportedFeatureException | LinkageError e) { - /* - * A field that is not supposed to end up in the image is considered as being - * absent for reflection purposes. - */ + private void checkHidingField(AnalysisField field, AnalysisType subtype) { + try { + AnalysisField[] subClassFields = field.isStatic() ? subtype.getStaticFields() : subtype.getInstanceFields(false); + for (AnalysisField subclassField : subClassFields) { + if (subclassField.getName().equals(field.getName())) { + hidingFields.add(subclassField); } } - } - for (AnalysisType subType : type.getSubTypes()) { - if (!subType.equals(type)) { - registerHidingSubTypeFields(access, field, subType); - } + } catch (UnsupportedFeatureException | LinkageError e) { + /* + * A field that is not supposed to end up in the image is considered as being absent for + * reflection purposes. + */ } } - private void registerHidingSubTypeMethods(DuringAnalysisAccess access, AnalysisMethod method, AnalysisType type) { - if (!type.equals(method.getDeclaringClass()) && type.isReachable()) { - if (!processedHidingMethods.containsKey(method) || !processedHidingMethods.get(method).contains(type)) { - processedHidingMethods.computeIfAbsent(method, m -> ConcurrentHashMap.newKeySet()).add(type); - try { - /* - * Using findMethod here which uses getDeclaredMethods internally, instead of - * resolveConcreteMethods which gives different results in at least two - * scenarios: - * - * 1) When resolving a static method, resolveConcreteMethods does not return a - * subclass method with the same signature, since they are actually fully - * distinct methods. However these methods need to be included in the hiding - * list because them showing up in a reflection query would be wrong. - * - * 2) When resolving an interface method from an abstract class, - * resolveConcreteMethods returns an undeclared method with the abstract - * subclass as declaring class, which is not the reflection API behavior. - */ - AnalysisMethod subClassMethod = type.findMethod(method.getName(), method.getSignature()); - if (subClassMethod != null) { - hidingMethods.add(subClassMethod); - } - /* - * findMethod can lead to the creation of new AnalysisMethod, so we need to run - * another analysis iteration. - */ - access.requireAnalysisIteration(); - - } catch (UnsupportedFeatureException | LinkageError e) { - /* - * A method that is not supposed to end up in the image is considered as being - * absent for reflection purposes. - */ - } - } - } - for (AnalysisType subType : type.getSubTypes()) { - if (!subType.equals(type)) { - registerHidingSubTypeMethods(access, method, subType); + /** + * Using {@link AnalysisType#findMethod(String, Signature)} here which uses + * {@link Class#getDeclaredMethods()} internally, instead of + * {@link AnalysisType#resolveConcreteMethod(ResolvedJavaMethod)} which gives different results + * in at least two scenarios: + * + * 1) When resolving a static method, resolveConcreteMethod does not return a subclass method + * with the same signature, since they are actually fully distinct methods. However, these + * methods need to be included in the hiding list because them showing up in a reflection query + * would be wrong. + * + * 2) When resolving an interface method from an abstract class, resolveConcreteMethod returns + * an undeclared method with the abstract subclass as declaring class, which is not the + * reflection API behavior. + * + * @see ReflectionHostedSupport#getHidingReflectionMethods() + */ + private void checkHidingMethod(AnalysisMethod method, AnalysisType subtype) { + try { + AnalysisMethod subClassMethod = subtype.findMethod(method.getName(), method.getSignature()); + if (subClassMethod != null) { + hidingMethods.add(subClassMethod); } + } catch (UnsupportedFeatureException | LinkageError e) { + /* + * A method that is not supposed to end up in the image is considered as being absent + * for reflection purposes. + */ } } - private void registerTypesForClass(DuringAnalysisAccessImpl access, AnalysisType analysisType, Class clazz) { + private void registerTypesForClass(AnalysisType analysisType, Class clazz) { /* * The generic signature is parsed at run time, so we need to make all the types necessary * for parsing also available at run time. */ - registerTypesForGenericSignature(access, queryGenericInfo(clazz::getTypeParameters)); - registerTypesForGenericSignature(access, queryGenericInfo(clazz::getGenericSuperclass)); - registerTypesForGenericSignature(access, queryGenericInfo(clazz::getGenericInterfaces)); + registerTypesForGenericSignature(queryGenericInfo(clazz::getTypeParameters)); + registerTypesForGenericSignature(queryGenericInfo(clazz::getGenericSuperclass)); + registerTypesForGenericSignature(queryGenericInfo(clazz::getGenericInterfaces)); - registerTypesForEnclosingMethodInfo(access, clazz); + registerTypesForEnclosingMethodInfo(clazz); + maybeRegisterRecordComponents(clazz); - Object[] recordComponents = buildRecordComponents(clazz, access); - if (recordComponents != null) { - for (Object recordComponent : recordComponents) { - registerTypesForRecordComponent(access, recordComponent); - } - registeredRecordComponents.put(clazz, recordComponents); + registerTypesForAnnotations(analysisType); + registerTypesForTypeAnnotations(analysisType); + } + + private void registerRecordComponents(Class clazz) { + Object[] recordComponents = RecordSupport.singleton().getRecordComponents(clazz); + for (Object recordComponent : recordComponents) { + registerTypesForRecordComponent(recordComponent); } - registerTypesForAnnotations(access, analysisType); - registerTypesForTypeAnnotations(access, analysisType); + registeredRecordComponents.put(clazz, recordComponents); } - private void registerTypesForEnclosingMethodInfo(DuringAnalysisAccessImpl access, Class clazz) { + private void registerTypesForEnclosingMethodInfo(Class clazz) { Object[] enclosingMethodInfo = getEnclosingMethodInfo(clazz); if (enclosingMethodInfo == null) { return; /* Nothing to do. */ } /* Ensure the class stored in the enclosing method info is available at run time. */ - makeAnalysisTypeReachable(access, access.getMetaAccess().lookupJavaType((Class) enclosingMethodInfo[0])); + metaAccess.lookupJavaType((Class) enclosingMethodInfo[0]).registerAsReachable("Is used by the enclosing method info of an element registered for reflection."); Executable enclosingMethodOrConstructor; try { @@ -446,7 +380,6 @@ private void registerTypesForEnclosingMethodInfo(DuringAnalysisAccessImpl access if (enclosingMethodOrConstructor != null) { /* Make the metadata for the enclosing method or constructor available at run time. */ RuntimeReflection.registerAsQueried(enclosingMethodOrConstructor); - access.requireAnalysisIteration(); } } @@ -469,87 +402,71 @@ private Object[] getEnclosingMethodInfo(Class clazz) { } } - private void registerTypesForField(DuringAnalysisAccessImpl access, AnalysisField analysisField, Field reflectField) { + private void registerTypesForField(AnalysisField analysisField, Field reflectField) { /* * Reflection accessors use Unsafe, so ensure that all reflectively accessible fields are * registered as unsafe-accessible, whether they have been explicitly registered or their * Field object is reachable in the image heap. */ - access.registerAsUnsafeAccessed(analysisField, "is registered for reflection"); + analysisField.registerAsUnsafeAccessed("is registered for reflection."); /* * The generic signature is parsed at run time, so we need to make all the types necessary * for parsing also available at run time. */ - registerTypesForGenericSignature(access, queryGenericInfo(reflectField::getGenericType)); + registerTypesForGenericSignature(queryGenericInfo(reflectField::getGenericType)); /* * Enable runtime instantiation of annotations */ - registerTypesForAnnotations(access, analysisField); - registerTypesForTypeAnnotations(access, analysisField); + registerTypesForAnnotations(analysisField); + registerTypesForTypeAnnotations(analysisField); } - private void registerTypesForMethod(DuringAnalysisAccessImpl access, AnalysisMethod analysisMethod, Executable reflectMethod) { + private void registerTypesForMethod(AnalysisMethod analysisMethod, Executable reflectExecutable) { /* * The generic signature is parsed at run time, so we need to make all the types necessary * for parsing also available at run time. */ - registerTypesForGenericSignature(access, queryGenericInfo(reflectMethod::getTypeParameters)); - registerTypesForGenericSignature(access, queryGenericInfo(reflectMethod::getGenericParameterTypes)); - registerTypesForGenericSignature(access, queryGenericInfo(reflectMethod::getGenericExceptionTypes)); + registerTypesForGenericSignature(queryGenericInfo(reflectExecutable::getTypeParameters)); + registerTypesForGenericSignature(queryGenericInfo(reflectExecutable::getGenericParameterTypes)); + registerTypesForGenericSignature(queryGenericInfo(reflectExecutable::getGenericExceptionTypes)); if (!analysisMethod.isConstructor()) { - registerTypesForGenericSignature(access, queryGenericInfo(((Method) reflectMethod)::getGenericReturnType)); + registerTypesForGenericSignature(queryGenericInfo(((Method) reflectExecutable)::getGenericReturnType)); } /* * Enable runtime instantiation of annotations */ - registerTypesForAnnotations(access, analysisMethod); - registerTypesForParameterAnnotations(access, analysisMethod); - registerTypesForTypeAnnotations(access, analysisMethod); + registerTypesForAnnotations(analysisMethod); + registerTypesForParameterAnnotations(analysisMethod); + registerTypesForTypeAnnotations(analysisMethod); if (!analysisMethod.isConstructor()) { - registerTypesForAnnotationDefault(access, analysisMethod); - } - } - - private static void registerTypesForReachableField(DuringAnalysisAccessImpl access, AnalysisField analysisField) { - makeAnalysisTypeReachable(access, analysisField.getDeclaringClass()); - } - - private static void registerTypesForReachableMethod(DuringAnalysisAccessImpl access, AnalysisMethod analysisMethod) { - makeAnalysisTypeReachable(access, analysisMethod.getDeclaringClass()); - for (JavaType paramType : analysisMethod.toParameterTypes()) { - makeAnalysisTypeReachable(access, (AnalysisType) paramType); + registerTypesForAnnotationDefault(analysisMethod); } } - private void registerTypesForGenericSignature(DuringAnalysisAccessImpl access, Type[] types) { + private void registerTypesForGenericSignature(Type[] types) { if (types != null) { for (Type type : types) { - registerTypesForGenericSignature(access, type); + registerTypesForGenericSignature(type); } } } - private void registerTypesForGenericSignature(DuringAnalysisAccessImpl access, Type type) { - registerTypesForGenericSignature(access, type, 0); + private void registerTypesForGenericSignature(Type type) { + registerTypesForGenericSignature(type, 0); } /* * We need the dimension argument to keep track of how deep in the stack of GenericArrayType * instances we are so we register the correct array type once we get to the leaf Class object. */ - private void registerTypesForGenericSignature(DuringAnalysisAccessImpl access, Type type, int dimension) { - if (type == null) { - return; - } - + private void registerTypesForGenericSignature(Type type, int dimension) { try { - if (processedTypes.getOrDefault(type, -1) >= dimension) { - return; /* Already processed. */ + if (type == null || !processedTypes.computeIfAbsent(type, t -> ConcurrentHashMap.newKeySet()).add(dimension)) { + return; } - processedTypes.put(type, dimension); } catch (MalformedParameterizedTypeException | TypeNotPresentException | LinkageError e) { /* * Hash code computation can trigger an exception if a type in wildcard bounds is @@ -558,104 +475,111 @@ private void registerTypesForGenericSignature(DuringAnalysisAccessImpl access, T */ } - if (type instanceof Class && !shouldExcludeClass(access, (Class) type)) { + if (type instanceof Class) { Class clazz = (Class) type; + if (shouldExcludeClass(clazz)) { + return; + } + if (dimension > 0) { /* * We only need to register the array type here, since it is the one that gets * stored in the heap. The component type will be registered elsewhere if needed. */ - makeAnalysisTypeReachable(access, access.getMetaAccess().lookupJavaType(clazz).getArrayClass(dimension)); + metaAccess.lookupJavaType(clazz).getArrayClass(dimension).registerAsReachable("Is used by generic signature of element registered for reflection."); } - /* Generic signature parsing will try to instantiate classes via Class.forName(). */ + + /* + * Reflection signature parsing will try to instantiate classes via Class.forName(). + */ ClassForNameSupport.registerClass(clazz); } else if (type instanceof TypeVariable) { /* Bounds are reified lazily. */ - registerTypesForGenericSignature(access, queryGenericInfo(((TypeVariable) type)::getBounds)); + registerTypesForGenericSignature(queryGenericInfo(((TypeVariable) type)::getBounds)); } else if (type instanceof GenericArrayType) { - registerTypesForGenericSignature(access, ((GenericArrayType) type).getGenericComponentType(), dimension + 1); + registerTypesForGenericSignature(((GenericArrayType) type).getGenericComponentType(), dimension + 1); } else if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; - registerTypesForGenericSignature(access, parameterizedType.getActualTypeArguments()); - registerTypesForGenericSignature(access, parameterizedType.getRawType(), dimension); - registerTypesForGenericSignature(access, parameterizedType.getOwnerType()); + registerTypesForGenericSignature(parameterizedType.getActualTypeArguments()); + registerTypesForGenericSignature(parameterizedType.getRawType(), dimension); + registerTypesForGenericSignature(parameterizedType.getOwnerType()); } else if (type instanceof WildcardType) { /* Bounds are reified lazily. */ WildcardType wildcardType = (WildcardType) type; - registerTypesForGenericSignature(access, queryGenericInfo(wildcardType::getLowerBounds)); - registerTypesForGenericSignature(access, queryGenericInfo(wildcardType::getUpperBounds)); + registerTypesForGenericSignature(queryGenericInfo(wildcardType::getLowerBounds)); + registerTypesForGenericSignature(queryGenericInfo(wildcardType::getUpperBounds)); } } - private void registerTypesForRecordComponent(DuringAnalysisAccessImpl access, Object recordComponent) { - registerTypesForAnnotations(access, (AnnotatedElement) recordComponent); - registerTypesForTypeAnnotations(access, (AnnotatedElement) recordComponent); + private void registerTypesForRecordComponent(Object recordComponent) { + registerTypesForAnnotations((AnnotatedElement) recordComponent); + registerTypesForTypeAnnotations((AnnotatedElement) recordComponent); } - private void registerTypesForAnnotations(DuringAnalysisAccessImpl access, AnnotatedElement annotatedElement) { + private void registerTypesForAnnotations(AnnotatedElement annotatedElement) { if (annotatedElement != null) { - filteredAnnotations.computeIfAbsent(annotatedElement, element -> { + if (!filteredAnnotations.containsKey(annotatedElement)) { List includedAnnotations = new ArrayList<>(); - for (AnnotationValue annotation : annotationExtractor.getDeclaredAnnotationData(element)) { - if (includeAnnotation(access, annotation)) { + for (AnnotationValue annotation : annotationExtractor.getDeclaredAnnotationData(annotatedElement)) { + if (includeAnnotation(annotation)) { includedAnnotations.add(annotation); - registerTypes(access, annotation.getTypes()); + registerTypes(annotation.getTypes()); } } - return includedAnnotations.toArray(new AnnotationValue[0]); - }); + filteredAnnotations.put(annotatedElement, includedAnnotations.toArray(new AnnotationValue[0])); + } } } - private void registerTypesForParameterAnnotations(DuringAnalysisAccessImpl access, AnalysisMethod executable) { - if (executable != null) { - filteredParameterAnnotations.computeIfAbsent(executable, element -> { - AnnotationValue[][] parameterAnnotations = annotationExtractor.getParameterAnnotationData(element); + private void registerTypesForParameterAnnotations(AnalysisMethod method) { + if (method != null) { + if (!filteredParameterAnnotations.containsKey(method)) { + AnnotationValue[][] parameterAnnotations = annotationExtractor.getParameterAnnotationData(method); AnnotationValue[][] includedParameterAnnotations = new AnnotationValue[parameterAnnotations.length][]; for (int i = 0; i < includedParameterAnnotations.length; ++i) { AnnotationValue[] annotations = parameterAnnotations[i]; List includedAnnotations = new ArrayList<>(); for (AnnotationValue annotation : annotations) { - if (includeAnnotation(access, annotation)) { + if (includeAnnotation(annotation)) { includedAnnotations.add(annotation); - registerTypes(access, annotation.getTypes()); + registerTypes(annotation.getTypes()); } } includedParameterAnnotations[i] = includedAnnotations.toArray(new AnnotationValue[0]); } - return includedParameterAnnotations; - }); + filteredParameterAnnotations.put(method, includedParameterAnnotations); + } } } - private void registerTypesForTypeAnnotations(DuringAnalysisAccessImpl access, AnnotatedElement annotatedElement) { + private void registerTypesForTypeAnnotations(AnnotatedElement annotatedElement) { if (annotatedElement != null) { - filteredTypeAnnotations.computeIfAbsent(annotatedElement, element -> { + if (!filteredTypeAnnotations.containsKey(annotatedElement)) { List includedTypeAnnotations = new ArrayList<>(); - for (TypeAnnotationValue typeAnnotation : annotationExtractor.getTypeAnnotationData(element)) { - if (includeAnnotation(access, typeAnnotation.getAnnotationData())) { + for (TypeAnnotationValue typeAnnotation : annotationExtractor.getTypeAnnotationData(annotatedElement)) { + if (includeAnnotation(typeAnnotation.getAnnotationData())) { includedTypeAnnotations.add(typeAnnotation); - registerTypes(access, typeAnnotation.getAnnotationData().getTypes()); + registerTypes(typeAnnotation.getAnnotationData().getTypes()); } } - return includedTypeAnnotations.toArray(new TypeAnnotationValue[0]); - }); + filteredTypeAnnotations.put(annotatedElement, includedTypeAnnotations.toArray(new TypeAnnotationValue[0])); + } } } - private void registerTypesForAnnotationDefault(DuringAnalysisAccessImpl access, AnalysisMethod method) { + private void registerTypesForAnnotationDefault(AnalysisMethod method) { AnnotationMemberValue annotationDefault = annotationExtractor.getAnnotationDefaultData(method); if (annotationDefault != null) { - registerTypes(access, annotationDefault.getTypes()); + registerTypes(annotationDefault.getTypes()); } } - private static boolean includeAnnotation(DuringAnalysisAccessImpl access, AnnotationValue annotationValue) { + private boolean includeAnnotation(AnnotationValue annotationValue) { if (annotationValue == null) { return false; } for (Class type : annotationValue.getTypes()) { - if (type == null || SubstitutionReflectivityFilter.shouldExclude(type, access.getMetaAccess(), access.getUniverse())) { + if (type == null || SubstitutionReflectivityFilter.shouldExclude(type, metaAccess, universe)) { return false; } } @@ -663,10 +587,10 @@ private static boolean includeAnnotation(DuringAnalysisAccessImpl access, Annota } @SuppressWarnings("cast") - private static void registerTypes(DuringAnalysisAccessImpl access, Collection> types) { + private void registerTypes(Collection> types) { for (Class type : types) { - AnalysisType analysisType = access.getMetaAccess().lookupJavaType(type); - makeAnalysisTypeReachable(access, analysisType); + AnalysisType analysisType = metaAccess.lookupJavaType(type); + analysisType.registerAsReachable("Is used by annotation of element registered for reflection."); if (type.isAnnotation()) { RuntimeProxyCreation.register(type); } @@ -679,81 +603,11 @@ private static void registerTypes(DuringAnalysisAccessImpl access, Collection clazz : modifiedClasses) { - processClass(access, clazz); - } - modifiedClasses.clear(); - access.requireAnalysisIteration(); - } - - if (!inaccessibleClasses.isEmpty()) { - inaccessibleClasses.forEach(ClassLoadingExceptionSupport::registerClass); - inaccessibleClasses.clear(); - access.requireAnalysisIteration(); - } - } - - private static boolean shouldExcludeClass(DuringAnalysisAccessImpl access, Class clazz) { + private boolean shouldExcludeClass(Class clazz) { if (clazz.isPrimitive()) { return true; // primitives cannot be looked up by name and have no methods or fields } - return SubstitutionReflectivityFilter.shouldExclude(clazz, access.getMetaAccess(), access.getUniverse()); - } - - private void processClass(DuringAnalysisAccessImpl access, Class clazz) { - if (shouldExcludeClass(access, clazz)) { - return; - } - - AnalysisType type = access.getMetaAccess().lookupJavaType(clazz); - /* - * Make sure the class is registered as reachable before its fields are accessed below to - * build the reflection metadata. - */ - type.registerAsReachable("is registered for reflection"); - if (unsafeInstantiatedClasses.contains(clazz)) { - type.registerAsAllocated("Is registered for reflection."); - } - - if (reflectionClasses.contains(clazz)) { - ClassForNameSupport.registerClass(clazz); - - try { - if (clazz.getEnclosingClass() != null) { - innerClasses.computeIfAbsent(access.getMetaAccess().lookupJavaType(clazz.getEnclosingClass()).getJavaClass(), (enclosingType) -> ConcurrentHashMap.newKeySet()).add(clazz); - } - } catch (LinkageError e) { - reportLinkingErrors(clazz, List.of(e)); - } - } - - if (type.isAnnotation()) { - /* - * Cache the annotation members to allow proxy classes seen later to include those in - * their own reflection data - */ - Set members = new HashSet<>(); - for (Field field : reflectionFields) { - if (field.getDeclaringClass().equals(clazz) && !SubstitutionReflectivityFilter.shouldExclude(field, access.getMetaAccess(), access.getUniverse())) { - members.add(field); - } - } - for (Executable executable : reflectionMethods.keySet()) { - if (executable.getDeclaringClass().equals(clazz) && !SubstitutionReflectivityFilter.shouldExclude(executable, access.getMetaAccess(), access.getUniverse())) { - members.add(executable); - } - } - annotationMembers.put(clazz, members); - access.requireAnalysisIteration(); /* Need the proxy class to see the added members */ - } + return SubstitutionReflectivityFilter.shouldExclude(clazz, metaAccess, universe); } private static T queryGenericInfo(Callable callable) { @@ -767,10 +621,10 @@ private static T queryGenericInfo(Callable callable) { } } - private Object[] buildRecordComponents(Class clazz, DuringAnalysisAccessImpl access) { + private void maybeRegisterRecordComponents(Class clazz) { RecordSupport support = RecordSupport.singleton(); if (!support.isRecord(clazz)) { - return null; + return; } /* @@ -784,13 +638,20 @@ private Object[] buildRecordComponents(Class clazz, DuringAnalysisAccessImpl * components in that case will throw an exception at image run time, see * DynamicHub.getRecordComponents0(). */ - Method[] allMethods = support.getRecordComponentAccessorMethods(clazz); - for (Method method : allMethods) { - if (!reflectionMethods.containsKey(method) || SubstitutionReflectivityFilter.shouldExclude(method, access.getMetaAccess(), access.getUniverse())) { - return null; + Method[] accessors = support.getRecordComponentAccessorMethods(clazz); + Set unregisteredAccessors = ConcurrentHashMap.newKeySet(); + for (Method accessor : accessors) { + if (SubstitutionReflectivityFilter.shouldExclude(accessor, metaAccess, universe)) { + return; } + unregisteredAccessors.add(accessor); + } + pendingRecordClasses.put(clazz, unregisteredAccessors); + + unregisteredAccessors.removeIf(accessor -> registeredMethods.containsKey(metaAccess.lookupJavaMethod(accessor))); + if (unregisteredAccessors.isEmpty()) { + registerRecordComponents(clazz); } - return support.getRecordComponents(clazz); } private static void reportLinkingErrors(Class clazz, List errors) { @@ -804,14 +665,8 @@ private static void reportLinkingErrors(Class clazz, List errors) protected void afterAnalysis() { sealed = true; - if (!modifiedClasses.isEmpty()) { - throw UserError.abort("Registration of classes, methods, and fields for reflective access during analysis must set DuringAnalysisAccess.requireAnalysisIteration()."); - } - } - - @Override - public boolean requiresProcessing() { - return !modifiedClasses.isEmpty(); + processedTypes.clear(); + pendingRecordClasses.clear(); } @Override @@ -821,19 +676,19 @@ public Map, Set>> getReflectionInnerClasses() { } @Override - public Set getReflectionFields() { + public Map getReflectionFields() { assert sealed; - return Collections.unmodifiableSet(registeredFields.keySet()); + return Collections.unmodifiableMap(registeredFields); } @Override - public Set getReflectionExecutables() { + public Map getReflectionExecutables() { assert sealed; - return Collections.unmodifiableSet(registeredMethods.keySet()); + return Collections.unmodifiableMap(registeredMethods); } @Override - public Object getAccessor(Executable method) { + public Object getAccessor(AnalysisMethod method) { assert sealed; return methodAccessors.get(method); } @@ -857,9 +712,16 @@ public Object[] getRecordComponents(Class type) { } @Override - public void registerHeapDynamicHub(Object hub) { + public void registerHeapDynamicHub(Object object, ScanReason reason) { assert !sealed; - heapDynamicHubs.add((DynamicHub) hub); + DynamicHub hub = (DynamicHub) object; + Class javaClass = hub.getHostedJavaClass(); + if (SubstitutionReflectivityFilter.shouldExclude(javaClass, metaAccess, universe)) { + throw UserError.abort("Found forbidden " + javaClass + " in the Native Image heap. The class was added with the following reason: " + reason); + } + if (heapDynamicHubs.add(hub)) { + registerTypesForClass(metaAccess.lookupJavaType(javaClass), javaClass); + } } @Override @@ -869,15 +731,45 @@ public Set getHeapDynamicHubs() { } @Override - public void registerHeapReflectionObject(AccessibleObject object) { + public void registerHeapReflectionField(Field reflectField, ScanReason reason) { assert !sealed; - heapReflectionObjects.add(object); + if (SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) { + throw UserError.abort("Found forbidden field " + reflectField + " in the Native Image heap. The field was added with the following reason: " + reason); + } + AnalysisField analysisField = metaAccess.lookupJavaField(reflectField); + if (heapFields.put(analysisField, reflectField) == null) { + registerTypesForField(analysisField, reflectField); + if (analysisField.getDeclaringClass().isAnnotation()) { + processAnnotationField(reflectField); + } + } } @Override - public Set getHeapReflectionObjects() { + public void registerHeapReflectionExecutable(Executable reflectExecutable, ScanReason reason) { + assert !sealed; + if (SubstitutionReflectivityFilter.shouldExclude(reflectExecutable, metaAccess, universe)) { + throw UserError.abort("Found forbidden method " + reflectExecutable + " in the Native Image heap. The method was added with the following reason: " + reason); + } + AnalysisMethod analysisMethod = metaAccess.lookupJavaMethod(reflectExecutable); + if (heapMethods.put(analysisMethod, reflectExecutable) == null) { + registerTypesForMethod(analysisMethod, reflectExecutable); + if (reflectExecutable instanceof Method && reflectExecutable.getDeclaringClass().isAnnotation()) { + processAnnotationMethod(false, (Method) reflectExecutable); + } + } + } + + @Override + public Map getHeapReflectionFields() { + assert sealed; + return Collections.unmodifiableMap(heapFields); + } + + @Override + public Map getHeapReflectionExecutables() { assert sealed; - return Collections.unmodifiableSet(heapReflectionObjects); + return Collections.unmodifiableMap(heapMethods); } private static final AnnotationValue[] NO_ANNOTATIONS = new AnnotationValue[0]; @@ -905,11 +797,6 @@ public AnnotationMemberValue getAnnotationDefaultData(AnnotatedElement element) return annotationExtractor.getAnnotationDefaultData(element); } - @Override - public int getReflectionClassesCount() { - return reflectionClasses.size(); - } - @Override public int getReflectionMethodsCount() { return registeredMethods.size(); @@ -919,13 +806,4 @@ public int getReflectionMethodsCount() { public int getReflectionFieldsCount() { return registeredFields.size(); } - - private enum ExecutableAccessibility { - QueriedOnly, - Accessed; - - static ExecutableAccessibility max(ExecutableAccessibility a, ExecutableAccessibility b) { - return a == Accessed || b == Accessed ? Accessed : QueriedOnly; - } - } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index 5cb0c7717053..2ca41a57e06c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -258,6 +258,7 @@ public void afterRegistration(AfterRegistrationAccess access) { public void duringSetup(DuringSetupAccess a) { DuringSetupAccessImpl access = (DuringSetupAccessImpl) a; aUniverse = access.getUniverse(); + reflectionData.duringSetup(access.getMetaAccess(), aUniverse); ReflectionConfigurationParser>> parser = ConfigurationParserUtils.create(reflectionData, access.getImageClassLoader()); loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, access.getImageClassLoader(), "reflection", @@ -271,18 +272,23 @@ public void duringSetup(DuringSetupAccess a) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { analysisAccess = (FeatureImpl.BeforeAnalysisAccessImpl) access; + reflectionData.beforeAnalysis(analysisAccess); /* duplicated to reduce the number of analysis iterations */ reflectionData.flushConditionalConfiguration(access); - /* Make sure array classes don't need to be registered for reflection. */ - RuntimeReflection.register(Object[].class.getMethods()); + /* + * This has to be registered before registering methods below since this causes the analysis + * to see SubstrateMethodAccessor.vtableOffset before we register the transformer. + */ access.registerFieldValueTransformer(ReflectionUtil.lookupField(SubstrateMethodAccessor.class, "vtableOffset"), new ComputeVTableOffset()); + + /* Make sure array classes don't need to be registered for reflection. */ + RuntimeReflection.register(Object[].class.getMethods()); } @Override public void duringAnalysis(DuringAnalysisAccess access) { reflectionData.flushConditionalConfiguration(access); - reflectionData.duringAnalysis(access); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionHostedSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionHostedSupport.java index a2f476e77d6e..2ceebd93ae47 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionHostedSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionHostedSupport.java @@ -24,44 +24,51 @@ */ package com.oracle.svm.hosted.reflect; -import java.lang.reflect.AccessibleObject; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.util.Map; import java.util.Set; +import com.oracle.graal.pointsto.ObjectScanner.ScanReason; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMethod; + public interface ReflectionHostedSupport { Map, Set>> getReflectionInnerClasses(); - Set getReflectionFields(); + Map getReflectionFields(); - Set getReflectionExecutables(); + Map getReflectionExecutables(); - Object getAccessor(Executable method); + Object getAccessor(AnalysisMethod method); - /* - * Returns the methods and fields that shadow a superclass element registered for reflection, to - * be excluded from reflection queries. + /** + * Returns the fields that shadow a superclass element registered for reflection, to be excluded + * from reflection queries. */ Set getHidingReflectionFields(); + /** + * Returns the methods that shadow a superclass element registered for reflection, to be + * excluded from reflection queries. + */ Set getHidingReflectionMethods(); Object[] getRecordComponents(Class type); - void registerHeapDynamicHub(Object hub); + void registerHeapDynamicHub(Object hub, ScanReason reason); Set getHeapDynamicHubs(); - void registerHeapReflectionObject(AccessibleObject object); + void registerHeapReflectionField(Field field, ScanReason reason); - Set getHeapReflectionObjects(); + void registerHeapReflectionExecutable(Executable executable, ScanReason reason); - int getReflectionClassesCount(); + Map getHeapReflectionFields(); + + Map getHeapReflectionExecutables(); int getReflectionMethodsCount(); int getReflectionFieldsCount(); - - boolean requiresProcessing(); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadata.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadata.java index 7eafa8bd3256..c4b6179cd07c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadata.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadata.java @@ -123,13 +123,13 @@ private FieldMetadata(boolean complete, boolean hiding, JavaConstant heapObject, } static class ExecutableMetadata extends AccessibleObjectMetadata { - final HostedType[] parameterTypes; + final Object[] parameterTypes; final HostedType[] exceptionTypes; final AnnotationValue[][] parameterAnnotations; final ReflectParameterMetadata[] reflectParameters; final JavaConstant accessor; - ExecutableMetadata(boolean complete, JavaConstant heapObject, HostedType declaringType, HostedType[] parameterTypes, int modifiers, HostedType[] exceptionTypes, String signature, + ExecutableMetadata(boolean complete, JavaConstant heapObject, HostedType declaringType, Object[] parameterTypes, int modifiers, HostedType[] exceptionTypes, String signature, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters, JavaConstant accessor) { super(complete, heapObject, declaringType, modifiers, signature, annotations, typeAnnotations); @@ -147,7 +147,7 @@ static class MethodMetadata extends ExecutableMetadata { final HostedType returnType; final AnnotationMemberValue annotationDefault; - private MethodMetadata(boolean complete, boolean hiding, JavaConstant heapObject, HostedType declaringClass, String name, HostedType[] parameterTypes, int modifiers, HostedType returnType, + private MethodMetadata(boolean complete, boolean hiding, JavaConstant heapObject, HostedType declaringClass, String name, Object[] parameterTypes, int modifiers, HostedType returnType, HostedType[] exceptionTypes, String signature, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, AnnotationMemberValue annotationDefault, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters, JavaConstant accessor) { super(complete, heapObject, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, reflectParameters, accessor); @@ -177,14 +177,14 @@ private MethodMetadata(boolean complete, boolean hiding, JavaConstant heapObject } /* Reachable method */ - MethodMetadata(HostedType declaringClass, String name, HostedType[] parameterTypes) { - this(false, false, null, declaringClass, name, parameterTypes, 0, null, null, null, null, null, null, null, null, null); + MethodMetadata(HostedType declaringClass, String name, String[] parameterTypeNames) { + this(false, false, null, declaringClass, name, parameterTypeNames, 0, null, null, null, null, null, null, null, null, null); } } static class ConstructorMetadata extends ExecutableMetadata { - private ConstructorMetadata(boolean complete, JavaConstant heapObject, HostedType declaringClass, HostedType[] parameterTypes, int modifiers, HostedType[] exceptionTypes, String signature, + private ConstructorMetadata(boolean complete, JavaConstant heapObject, HostedType declaringClass, Object[] parameterTypes, int modifiers, HostedType[] exceptionTypes, String signature, AnnotationValue[] annotations, AnnotationValue[][] parameterAnnotations, TypeAnnotationValue[] typeAnnotations, ReflectParameterMetadata[] reflectParameters, JavaConstant accessor) { super(complete, heapObject, declaringClass, parameterTypes, modifiers, exceptionTypes, signature, annotations, parameterAnnotations, typeAnnotations, reflectParameters, accessor); @@ -203,8 +203,8 @@ private ConstructorMetadata(boolean complete, JavaConstant heapObject, HostedTyp } /* Reachable constructor */ - ConstructorMetadata(HostedType declaringClass, HostedType[] parameterTypes) { - this(false, null, declaringClass, parameterTypes, 0, null, null, null, null, null, null, null); + ConstructorMetadata(HostedType declaringClass, String[] parameterTypeNames) { + this(false, null, declaringClass, parameterTypeNames, 0, null, null, null, null, null, null, null); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadataEncoderImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadataEncoderImpl.java index ed5a97000c33..3d0fb0336e68 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadataEncoderImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionMetadataEncoderImpl.java @@ -157,13 +157,19 @@ public ReflectionMetadataEncoderImpl(CodeInfoEncoder.Encoders encoders) { this.dataBuilder = (ReflectionDataBuilder) ImageSingletons.lookup(RuntimeReflectionSupport.class); } + private void addType(HostedType type) { + if (type.getWrapped().isReachable() && sortedTypes.add(type)) { + encoders.sourceClasses.addObject(type.getJavaClass()); + } + } + private void registerClass(HostedType type, ClassMetadata metadata) { - sortedTypes.add(type); + addType(type); classData.put(type, metadata); } private void registerField(HostedType declaringType, Object field, FieldMetadata metadata) { - sortedTypes.add(declaringType); + addType(declaringType); FieldMetadata oldData = fieldData.computeIfAbsent(declaringType, t -> new HashMap<>()).put(field, metadata); assert oldData == null; } @@ -175,7 +181,7 @@ private FieldMetadata[] getFields(HostedType declaringType) { } private void registerMethod(HostedType declaringType, Object method, MethodMetadata metadata) { - sortedTypes.add(declaringType); + addType(declaringType); MethodMetadata oldData = methodData.computeIfAbsent(declaringType, t -> new HashMap<>()).put(method, metadata); assert oldData == null; } @@ -187,7 +193,7 @@ private MethodMetadata[] getMethods(HostedType declaringType) { } private void registerConstructor(HostedType declaringType, Object constructor, ConstructorMetadata metadata) { - sortedTypes.add(declaringType); + addType(declaringType); ConstructorMetadata oldData = constructorData.computeIfAbsent(declaringType, t -> new HashMap<>()).put(constructor, metadata); assert oldData == null; } @@ -517,7 +523,7 @@ public void addHidingFieldMetadata(AnalysisField analysisField, HostedType decla encoders.sourceMethodNames.addObject(name); encoders.sourceClasses.addObject(type.getJavaClass()); - sortedTypes.add(declaringType); + addType(declaringType); registerField(declaringType, analysisField, new FieldMetadata(declaringType, name, type, modifiers)); } @@ -530,7 +536,7 @@ public void addHidingMethodMetadata(AnalysisMethod analysisMethod, HostedType de } encoders.sourceClasses.addObject(returnType.getJavaClass()); - sortedTypes.add(declaringType); + addType(declaringType); registerMethod(declaringType, analysisMethod, new MethodMetadata(declaringType, name, parameterTypes, modifiers, returnType)); } @@ -550,20 +556,20 @@ public void addReachableExecutableMetadata(HostedMethod executable) { boolean isMethod = !executable.isConstructor(); HostedType declaringType = executable.getDeclaringClass(); String name = isMethod ? executable.getName() : null; - HostedType[] parameterTypes = getParameterTypes(executable); + String[] parameterTypeNames = getParameterTypeNames(executable); /* Fill encoders with the necessary values. */ if (isMethod) { encoders.sourceMethodNames.addObject(name); } - for (HostedType parameterType : parameterTypes) { - encoders.sourceClasses.addObject(parameterType.getJavaClass()); + for (String parameterTypeName : parameterTypeNames) { + encoders.sourceMethodNames.addObject(parameterTypeName); } if (isMethod) { - registerMethod(declaringType, executable, new MethodMetadata(declaringType, name, parameterTypes)); + registerMethod(declaringType, executable, new MethodMetadata(declaringType, name, parameterTypeNames)); } else { - registerConstructor(declaringType, executable, new ConstructorMetadata(declaringType, parameterTypes)); + registerConstructor(declaringType, executable, new ConstructorMetadata(declaringType, parameterTypeNames)); } } @@ -575,6 +581,14 @@ private static HostedType[] getParameterTypes(HostedMethod method) { return parameterTypes; } + private static String[] getParameterTypeNames(HostedMethod method) { + String[] parameterTypeNames = new String[method.getSignature().getParameterCount(false)]; + for (int i = 0; i < parameterTypeNames.length; ++i) { + parameterTypeNames[i] = method.getSignature().getParameterType(i, null).toJavaName(); + } + return parameterTypeNames; + } + private static HostedType[] getExceptionTypes(MetaAccessProvider metaAccess, Executable reflectMethod) { Class[] exceptionClasses = reflectMethod.getExceptionTypes(); HostedType[] exceptionTypes = new HostedType[exceptionClasses.length]; @@ -822,7 +836,11 @@ private void encodeExecutable(UnsafeArrayTypeWriter buf, ExecutableMetadata exec if (isMethod) { encodeName(buf, ((MethodMetadata) executable).name); } - encodeArray(buf, executable.parameterTypes, parameterType -> encodeType(buf, parameterType)); + if (executable.complete || isHiding) { + encodeArray(buf, (HostedType[]) executable.parameterTypes, parameterType -> encodeType(buf, parameterType)); + } else { + encodeArray(buf, (String[]) executable.parameterTypes, parameterTypeName -> encodeName(buf, parameterTypeName)); + } if (isMethod && (executable.complete || isHiding)) { encodeType(buf, ((MethodMetadata) executable).returnType); }