From 35ac872c5c3b1ede6ae063a7320cda2829b8c64f Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Tue, 13 Jul 2021 18:16:04 +0200 Subject: [PATCH] WIP --- .../nativeimage/impl/ReflectionRegistry.java | 4 + .../svm/agent/BreakpointInterceptor.java | 60 +++++- .../oracle/svm/agent/NativeImageAgent.java | 6 +- .../agent/NativeImageAgentJNIHandleSet.java | 3 + .../configure/config/ConfigurationType.java | 88 +++++++- .../config/ParserConfigurationAdapter.java | 64 ++++-- .../configure/trace/ReflectionProcessor.java | 15 +- .../com/oracle/svm/core/SubstrateOptions.java | 4 + .../oracle/svm/core/code/CodeInfoAccess.java | 32 ++- .../oracle/svm/core/code/CodeInfoEncoder.java | 200 +++++++++++++++++- .../oracle/svm/core/code/CodeInfoImpl.java | 12 ++ .../oracle/svm/core/code/CodeInfoTable.java | 52 ++++- .../svm/core/code/FrameInfoEncoder.java | 73 ++----- .../oracle/svm/core/code/ImageCodeInfo.java | 24 +++ .../svm/core/code/RuntimeCodeInfoAccess.java | 2 + .../ReflectionConfigurationParser.java | 42 ++-- ...ReflectionConfigurationParserDelegate.java | 16 +- .../com/oracle/svm/core/hub/DynamicHub.java | 118 ++++++++++- .../svm/core/hub/DynamicHubCompanion.java | 16 ++ .../config/ReflectionRegistryAdapter.java | 42 ++-- .../hosted/image/NativeImageCodeCache.java | 19 +- .../hosted/ExecutableAccessorComputer.java | 4 +- .../reflect/hosted/ReflectionDataBuilder.java | 16 ++ .../Target_java_lang_reflect_Method.java | 2 +- 24 files changed, 765 insertions(+), 149 deletions(-) diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java index 8c568a4ac1019..bf62b521c60d8 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java @@ -50,4 +50,8 @@ public interface ReflectionRegistry { void register(boolean finalIsWritable, Field... fields); + @SuppressWarnings("unused") + default void registerAsQueried(Executable... methods) { + // TODO unimplemented/shouldNotReachHere? + } } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 22cf2f454c20b..2019dd39d2722 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -136,6 +136,9 @@ final class BreakpointInterceptor { /** Enables experimental support for class definitions via {@code ClassLoader.defineClass}. */ private static boolean experimentalClassDefineSupport = false; + /** Enables tracking of reflection queries for fine-tuned configuration. */ + private static boolean trackReflectionMetadata = false; + /** * Locations in methods where explicit calls to {@code ClassLoader.loadClass} have been found. */ @@ -367,6 +370,31 @@ private static boolean getEnclosingMethod(JNIEnvironment jni, Breakpoint bp, Int return true; } + private static boolean invokeMethod(JNIEnvironment jni, Breakpoint bp, InterceptedState state) { + JNIObjectHandle callerClass = state.getDirectCallerClass(); + JNIObjectHandle self = getObjectArgument(0); + + JNIObjectHandle declaring = Support.callObjectMethod(jni, self, agent.handles().javaLangReflectMemberGetDeclaringClass); + if (clearException(jni)) { + declaring = nullHandle(); + } + + JNIObjectHandle nameHandle = Support.callObjectMethod(jni, self, agent.handles().javaLangReflectMemberGetName); + if (clearException(jni)) { + nameHandle = nullHandle(); + } + String name = fromJniString(jni, nameHandle); + + JNIObjectHandle paramTypesHandle = Support.callObjectMethod(jni, self, agent.handles().javaLangReflectMethodGetParameterTypes); + if (clearException(jni)) { + paramTypesHandle = nullHandle(); + } + Object paramTypes = getClassArrayNames(jni, paramTypesHandle); + + traceBreakpoint(jni, declaring, declaring, callerClass, bp.specification.methodName, self.notEqual(nullHandle()), state.getFullStackTraceOrNull(), name, paramTypes); + return true; + } + private static boolean newInstance(JNIEnvironment jni, Breakpoint bp, InterceptedState state) { JNIObjectHandle callerClass = state.getDirectCallerClass(); JNIMethodId result = nullPointer(); @@ -1068,12 +1096,13 @@ private static void onClassFileLoadHook(@SuppressWarnings("unused") JvmtiEnv jvm public static void onLoad(JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, Tracer writer, NativeImageAgent nativeImageTracingAgent, Supplier currentThreadJavaStackAccessSupplier, - boolean exptlClassLoaderSupport, boolean exptlClassDefineSupport) { + boolean exptlClassLoaderSupport, boolean exptlClassDefineSupport, boolean trackReflectionData) { BreakpointInterceptor.tracer = writer; BreakpointInterceptor.agent = nativeImageTracingAgent; BreakpointInterceptor.interceptedStateSupplier = currentThreadJavaStackAccessSupplier; BreakpointInterceptor.experimentalClassLoaderSupport = exptlClassLoaderSupport; BreakpointInterceptor.experimentalClassDefineSupport = exptlClassDefineSupport; + BreakpointInterceptor.trackReflectionMetadata = trackReflectionData; JvmtiCapabilities capabilities = UnmanagedMemory.calloc(SizeOf.get(JvmtiCapabilities.class)); check(jvmti.getFunctions().GetCapabilities().invoke(jvmti, capabilities)); @@ -1122,7 +1151,13 @@ public static void onVMInit(JvmtiEnv jvmti, JNIEnvironment jni) { JNIObjectHandle lastClass = nullHandle(); String lastClassName = null; - for (BreakpointSpecification br : BREAKPOINT_SPECIFICATIONS) { + BreakpointSpecification[] breakpointSpecifications = BREAKPOINT_SPECIFICATIONS; + if (trackReflectionMetadata) { + breakpointSpecifications = new BreakpointSpecification[BREAKPOINT_SPECIFICATIONS.length + REFLECTION_QUERIES_BREAKPOINT_SPECIFICATIONS.length]; + System.arraycopy(BREAKPOINT_SPECIFICATIONS, 0, breakpointSpecifications, 0, BREAKPOINT_SPECIFICATIONS.length); + System.arraycopy(REFLECTION_QUERIES_BREAKPOINT_SPECIFICATIONS, 0, breakpointSpecifications, BREAKPOINT_SPECIFICATIONS.length, REFLECTION_QUERIES_BREAKPOINT_SPECIFICATIONS.length); + } + for (BreakpointSpecification br : breakpointSpecifications) { JNIObjectHandle clazz = nullHandle(); if (lastClassName != null && lastClassName.equals(br.className)) { clazz = lastClass; @@ -1245,24 +1280,17 @@ private interface BreakpointHandler { brk("java/lang/Class", "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;", BreakpointInterceptor::forName), brk("java/lang/Class", "getFields", "()[Ljava/lang/reflect/Field;", BreakpointInterceptor::getFields), - brk("java/lang/Class", "getMethods", "()[Ljava/lang/reflect/Method;", BreakpointInterceptor::getMethods), - brk("java/lang/Class", "getConstructors", "()[Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructors), brk("java/lang/Class", "getClasses", "()[Ljava/lang/Class;", BreakpointInterceptor::getClasses), brk("java/lang/Class", "getDeclaredFields", "()[Ljava/lang/reflect/Field;", BreakpointInterceptor::getDeclaredFields), - brk("java/lang/Class", "getDeclaredMethods", "()[Ljava/lang/reflect/Method;", BreakpointInterceptor::getDeclaredMethods), - brk("java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getDeclaredConstructors), brk("java/lang/Class", "getDeclaredClasses", "()[Ljava/lang/Class;", BreakpointInterceptor::getDeclaredClasses), brk("java/lang/Class", "getField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;", BreakpointInterceptor::getField), brk("java/lang/Class", "getDeclaredField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;", BreakpointInterceptor::getDeclaredField), - brk("java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", BreakpointInterceptor::getMethod), - brk("java/lang/Class", "getConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructor), - brk("java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", BreakpointInterceptor::getDeclaredMethod), - brk("java/lang/Class", "getDeclaredConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructor), brk("java/lang/Class", "getEnclosingMethod", "()Ljava/lang/reflect/Method;", BreakpointInterceptor::getEnclosingMethod), brk("java/lang/Class", "getEnclosingConstructor", "()Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getEnclosingMethod), + brk("java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", BreakpointInterceptor::invokeMethod), brk("java/lang/Class", "newInstance", "()Ljava/lang/Object;", BreakpointInterceptor::newInstance), brk("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;I)Ljava/lang/Object;", BreakpointInterceptor::newArrayInstance), brk("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;[I)Ljava/lang/Object;", BreakpointInterceptor::newArrayInstanceMulti), @@ -1353,6 +1381,18 @@ private interface BreakpointHandler { private static final BreakpointSpecification CLASSLOADER_LOAD_CLASS_BREAKPOINT_SPECIFICATION = optionalBrk("java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", BreakpointInterceptor::loadClass); + private static final BreakpointSpecification[] REFLECTION_QUERIES_BREAKPOINT_SPECIFICATIONS = { + brk("java/lang/Class", "getMethods", "()[Ljava/lang/reflect/Method;", BreakpointInterceptor::getMethods), + brk("java/lang/Class", "getConstructors", "()[Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructors), + brk("java/lang/Class", "getDeclaredMethods", "()[Ljava/lang/reflect/Method;", BreakpointInterceptor::getDeclaredMethods), + brk("java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getDeclaredConstructors), + + brk("java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", BreakpointInterceptor::getMethod), + brk("java/lang/Class", "getConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructor), + brk("java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", BreakpointInterceptor::getDeclaredMethod), + brk("java/lang/Class", "getDeclaredConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructor), + }; + private static BreakpointSpecification brk(String className, String methodName, String signature, BreakpointHandler handler) { return new BreakpointSpecification(className, methodName, signature, handler, false); } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java index 2cec4b292ba7b..38d868ba05f8e 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java @@ -121,6 +121,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c boolean configurationWithOrigins = false; int configWritePeriod = -1; // in seconds int configWritePeriodInitialDelay = 1; // in seconds + boolean trackReflectionMetadata = false; String[] tokens = !options.isEmpty() ? options.split(",") : new String[0]; for (String token : tokens) { @@ -189,6 +190,8 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c build = Boolean.parseBoolean(getTokenValue(token)); } else if (token.equals("experimental-configuration-with-origins")) { configurationWithOrigins = true; + } else if (token.equals("track-reflection-metadata")) { + trackReflectionMetadata = true; } else { return usage(1, "unknown option: '" + token + "'."); } @@ -306,7 +309,8 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } try { - BreakpointInterceptor.onLoad(jvmti, callbacks, tracer, this, interceptedStateSupplier, experimentalClassLoaderSupport, experimentalClassDefineSupport); + BreakpointInterceptor.onLoad(jvmti, callbacks, tracer, this, interceptedStateSupplier, + experimentalClassLoaderSupport, experimentalClassDefineSupport, trackReflectionMetadata); } catch (Throwable t) { return error(3, t.toString()); } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index c7945fdc5b075..7ae55a48b1fd8 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -42,6 +42,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIMethodId javaLangReflectMemberGetName; final JNIMethodId javaLangReflectMemberGetDeclaringClass; + final JNIMethodId javaLangReflectMethodGetParameterTypes; final JNIMethodId javaUtilEnumerationHasMoreElements; @@ -76,8 +77,10 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { javaLangClassGetName = getMethodId(env, javaLangClass, "getName", "()Ljava/lang/String;", false); JNIObjectHandle javaLangReflectMember = findClass(env, "java/lang/reflect/Member"); + JNIObjectHandle javaLangReflectMethod = findClass(env, "java/lang/reflect/Method"); javaLangReflectMemberGetName = getMethodId(env, javaLangReflectMember, "getName", "()Ljava/lang/String;", false); javaLangReflectMemberGetDeclaringClass = getMethodId(env, javaLangReflectMember, "getDeclaringClass", "()Ljava/lang/Class;", false); + javaLangReflectMethodGetParameterTypes = getMethodId(env, javaLangReflectMethod, "getParameterTypes", "()[Ljava/lang/Class;", false); JNIObjectHandle javaUtilEnumeration = findClass(env, "java/util/Enumeration"); javaUtilEnumerationHasMoreElements = getMethodId(env, javaUtilEnumeration, "hasMoreElements", "()Z", false); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java index 2f1ee8fb9ebea..86497f0dcf1da 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java @@ -40,6 +40,7 @@ public class ConfigurationType implements JsonPrintable { private Map fields; private Map methods; + private Map queriedMethods; private boolean allDeclaredClasses; private boolean allPublicClasses; @@ -49,6 +50,10 @@ public class ConfigurationType implements JsonPrintable { private boolean allPublicMethods; private boolean allDeclaredConstructors; private boolean allPublicConstructors; + private boolean queryAllDeclaredMethods; + private boolean queryAllPublicMethods; + private boolean queryAllDeclaredConstructors; + private boolean queryAllPublicConstructors; public ConfigurationType(String qualifiedJavaName) { assert qualifiedJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation"; @@ -213,27 +218,41 @@ public void addMethodsWithName(String name, ConfigurationMemberKind memberKind) addMethod(name, null, memberKind); } + public void addQueriedMethodsWithName(String name, ConfigurationMemberKind memberKind) { + addQueriedMethod(name, null, memberKind); + } + public void addMethod(String name, String internalSignature, ConfigurationMemberKind memberKind) { + methods = addMethodToMap(name, internalSignature, memberKind, methods); + } + + public void addQueriedMethod(String name, String internalSignature, ConfigurationMemberKind memberKind) { + queriedMethods = addMethodToMap(name, internalSignature, memberKind, queriedMethods); + } + + private Map addMethodToMap(String name, String internalSignature, ConfigurationMemberKind memberKind, Map originalMethodsMap) { boolean matchesAllSignatures = (internalSignature == null); + Map methodsMap = originalMethodsMap; if (ConfigurationMethod.isConstructorName(name) ? (haveAllDeclaredConstructors() || (memberKind.includes(ConfigurationMemberKind.PUBLIC) && haveAllPublicConstructors())) : ((memberKind.includes(ConfigurationMemberKind.DECLARED) && haveAllDeclaredMethods()) || (memberKind.includes(ConfigurationMemberKind.PUBLIC) && haveAllPublicMethods()))) { if (!matchesAllSignatures) { - methods = maybeRemove(methods, map -> map.remove(new ConfigurationMethod(name, internalSignature))); + methodsMap = maybeRemove(methodsMap, map -> map.remove(new ConfigurationMethod(name, internalSignature))); } - return; + return methodsMap; } - if (methods == null) { - methods = new HashMap<>(); + if (methodsMap == null) { + methodsMap = new HashMap<>(); } ConfigurationMethod method = new ConfigurationMethod(name, internalSignature); - if (matchesAllSignatures) { // remove any methods that the new entry matches - methods.compute(method, (k, v) -> v != null ? memberKind.union(v) : memberKind); - methods = maybeRemove(methods, map -> map.entrySet().removeIf(entry -> name.equals(entry.getKey().getName()) && + if (matchesAllSignatures) { // remove any methodsMap that the new entry matches + methodsMap.compute(method, (k, v) -> v != null ? memberKind.union(v) : memberKind); + methodsMap = maybeRemove(methodsMap, map -> map.entrySet().removeIf(entry -> name.equals(entry.getKey().getName()) && memberKind.includes(entry.getValue()) && !method.equals(entry.getKey()))); } else { - methods.compute(method, (k, v) -> v != null ? memberKind.intersect(v) : memberKind); + methodsMap.compute(method, (k, v) -> v != null ? memberKind.intersect(v) : memberKind); } - assert methods.containsKey(method); + assert methodsMap.containsKey(method); + return methodsMap; } public ConfigurationMemberKind getMethodKindIfPresent(ConfigurationMethod method) { @@ -314,6 +333,42 @@ public void setAllPublicConstructors() { removeMethods(ConfigurationMemberKind.PUBLIC, true); } + public boolean haveQueryAllDeclaredMethods() { + return queryAllDeclaredMethods; + } + + public boolean haveQueryAllPublicMethods() { + return queryAllPublicMethods; + } + + public void setQueryAllDeclaredMethods() { + this.queryAllDeclaredMethods = true; + removeQueriedMethods(ConfigurationMemberKind.DECLARED, false); + } + + public void setQueryAllPublicMethods() { + this.queryAllPublicMethods = true; + removeQueriedMethods(ConfigurationMemberKind.PUBLIC, false); + } + + public boolean haveQueryAllDeclaredConstructors() { + return queryAllDeclaredConstructors; + } + + public boolean haveQueryAllPublicConstructors() { + return queryAllPublicConstructors; + } + + public void setQueryAllDeclaredConstructors() { + this.queryAllDeclaredConstructors = true; + removeQueriedMethods(ConfigurationMemberKind.DECLARED, true); + } + + public void setQueryAllPublicConstructors() { + this.queryAllPublicConstructors = true; + removeQueriedMethods(ConfigurationMemberKind.PUBLIC, true); + } + @Override public void printJson(JsonWriter writer) throws IOException { writer.append('{').indent().newline(); @@ -326,6 +381,10 @@ public void printJson(JsonWriter writer) throws IOException { optionallyPrintJsonBoolean(writer, haveAllPublicConstructors(), "allPublicConstructors"); optionallyPrintJsonBoolean(writer, haveAllDeclaredClasses(), "allDeclaredClasses"); optionallyPrintJsonBoolean(writer, haveAllPublicClasses(), "allPublicClasses"); + optionallyPrintJsonBoolean(writer, haveQueryAllDeclaredMethods(), "queryAllDeclaredMethods"); + optionallyPrintJsonBoolean(writer, haveQueryAllPublicMethods(), "queryAllPublicMethods"); + optionallyPrintJsonBoolean(writer, haveQueryAllDeclaredConstructors(), "queryAllDeclaredConstructors"); + optionallyPrintJsonBoolean(writer, haveQueryAllPublicConstructors(), "queryAllPublicConstructors"); if (fields != null) { writer.append(',').newline().quote("fields").append(':'); JsonPrinter.printCollection(writer, fields.entrySet(), Map.Entry.comparingByKey(), ConfigurationType::printField); @@ -337,6 +396,13 @@ public void printJson(JsonWriter writer) throws IOException { Comparator.comparing(ConfigurationMethod::getName).thenComparing(Comparator.nullsFirst(Comparator.comparing(ConfigurationMethod::getInternalSignature))), JsonPrintable::printJson); } + if (queriedMethods != null) { + writer.append(',').newline().quote("queriedMethods").append(':'); + JsonPrinter.printCollection(writer, + queriedMethods.keySet(), + Comparator.comparing(ConfigurationMethod::getName).thenComparing(Comparator.nullsFirst(Comparator.comparing(ConfigurationMethod::getInternalSignature))), + JsonPrintable::printJson); + } writer.unindent().newline(); writer.append('}'); } @@ -363,6 +429,10 @@ private void removeMethods(ConfigurationMemberKind memberKind, boolean construct methods = maybeRemove(methods, map -> map.entrySet().removeIf(entry -> entry.getKey().isConstructor() == constructors && memberKind.includes(entry.getValue()))); } + private void removeQueriedMethods(ConfigurationMemberKind memberKind, boolean constructors) { + queriedMethods = maybeRemove(queriedMethods, map -> map.entrySet().removeIf(entry -> entry.getKey().isConstructor() == constructors && memberKind.includes(entry.getValue()))); + } + private static Map maybeRemove(Map fromMap, Consumer> action) { Map map = fromMap; if (map != null) { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java index e96635fc227b8..a175717632bec 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java @@ -55,25 +55,41 @@ public void registerField(ConfigurationType type, String fieldName, boolean fina } @Override - public boolean registerAllMethodsWithName(ConfigurationType type, String methodName) { - type.addMethodsWithName(methodName, ConfigurationMemberKind.PRESENT); + public boolean registerAllMethodsWithName(ConfigurationType type, String methodName, boolean queriedOnly) { + if (queriedOnly) { + type.addQueriedMethodsWithName(methodName, ConfigurationMemberKind.PRESENT); + } else { + type.addMethodsWithName(methodName, ConfigurationMemberKind.PRESENT); + } return true; } @Override - public boolean registerAllConstructors(ConfigurationType type) { - type.addMethodsWithName(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMemberKind.PRESENT); + public boolean registerAllConstructors(ConfigurationType type, boolean queriedOnly) { + if (queriedOnly) { + type.addQueriedMethodsWithName(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMemberKind.PRESENT); + } else { + type.addMethodsWithName(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMemberKind.PRESENT); + } return true; } @Override - public void registerMethod(ConfigurationType type, String methodName, List methodParameterTypes) { - type.addMethod(methodName, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberKind.PRESENT); + public void registerMethod(ConfigurationType type, String methodName, List methodParameterTypes, boolean queriedOnly) { + if (queriedOnly) { + type.addQueriedMethod(methodName, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberKind.PRESENT); + } else { + type.addMethod(methodName, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberKind.PRESENT); + } } @Override - public void registerConstructor(ConfigurationType type, List methodParameterTypes) { - type.addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberKind.PRESENT); + public void registerConstructor(ConfigurationType type, List methodParameterTypes, boolean queriedOnly) { + if (queriedOnly) { + type.addQueriedMethod(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberKind.PRESENT); + } else { + type.addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberKind.PRESENT); + } } @Override @@ -97,23 +113,39 @@ public void registerDeclaredFields(ConfigurationType type) { } @Override - public void registerPublicMethods(ConfigurationType type) { - type.setAllPublicMethods(); + public void registerPublicMethods(ConfigurationType type, boolean queriedOnly) { + if (queriedOnly) { + type.setQueryAllPublicMethods(); + } else { + type.setAllPublicMethods(); + } } @Override - public void registerDeclaredMethods(ConfigurationType type) { - type.setAllDeclaredMethods(); + public void registerDeclaredMethods(ConfigurationType type, boolean queriedOnly) { + if (queriedOnly) { + type.setQueryAllDeclaredMethods(); + } else { + type.setAllDeclaredMethods(); + } } @Override - public void registerPublicConstructors(ConfigurationType type) { - type.setAllPublicConstructors(); + public void registerPublicConstructors(ConfigurationType type, boolean queriedOnly) { + if (queriedOnly) { + type.setQueryAllPublicConstructors(); + } else { + type.setAllPublicConstructors(); + } } @Override - public void registerDeclaredConstructors(ConfigurationType type) { - type.setAllDeclaredConstructors(); + public void registerDeclaredConstructors(ConfigurationType type, boolean queriedOnly) { + if (queriedOnly) { + type.setQueryAllDeclaredConstructors(); + } else { + type.setAllDeclaredConstructors(); + } } @Override diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java index d97a41bc69e14..5f0d85739d6b1 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java @@ -173,14 +173,19 @@ public void processEntry(Map entry) { case "findMethodHandle": memberKind = "findMethodHandle".equals(function) ? ConfigurationMemberKind.PRESENT : ConfigurationMemberKind.DECLARED; // fall through - case "getMethod": { + case "getMethod": + case "invoke": { expectSize(args, 2); String name = (String) args.get(0); List parameterTypes = (List) args.get(1); if (parameterTypes == null) { // tolerated and equivalent to no parameter types parameterTypes = Collections.emptyList(); } - configuration.getOrCreateType(clazzOrDeclaringClass).addMethod(name, SignatureUtil.toInternalSignature(parameterTypes), memberKind); + if (function.equals("invoke") || function.equals("findMethodHandle")) { + configuration.getOrCreateType(clazzOrDeclaringClass).addMethod(name, SignatureUtil.toInternalSignature(parameterTypes), memberKind); + } else { + configuration.getOrCreateType(clazzOrDeclaringClass).addQueriedMethod(name, SignatureUtil.toInternalSignature(parameterTypes), memberKind); + } if (!clazzOrDeclaringClass.equals(clazz)) { configuration.getOrCreateType(clazz); } @@ -198,7 +203,11 @@ public void processEntry(Map entry) { } String signature = SignatureUtil.toInternalSignature(parameterTypes); assert clazz.equals(clazzOrDeclaringClass) : "Constructor can only be accessed via declaring class"; - configuration.getOrCreateType(clazzOrDeclaringClass).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, signature, memberKind); + if (function.equals("findConstructorHandle")) { + configuration.getOrCreateType(clazzOrDeclaringClass).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, signature, memberKind); + } else { + configuration.getOrCreateType(clazzOrDeclaringClass).addQueriedMethod(ConfigurationMethod.CONSTRUCTOR_NAME, signature, memberKind); + } break; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 1c61cbd5ec585..1987502ec04c8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -577,4 +577,8 @@ public ReportingSupport(String reportingPath) { this.reportsPath = reportingPath; } } + + @APIOption(name = "configure-reflection-metadata")// + @Option(help = "Include reflection metadata for all methods")// + public static final HostedOptionKey ConfigureReflectionMetadata = new HostedOptionKey<>(false); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java index 0ba6f44f1948b..cc8a9355cdb0c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java @@ -191,6 +191,8 @@ public static UnsignedWord getNativeMetadataSize(CodeInfo info) { .add(NonmovableArrays.byteSizeOf(impl.getCodeInfoIndex())) .add(NonmovableArrays.byteSizeOf(impl.getCodeInfoEncodings())) .add(NonmovableArrays.byteSizeOf(impl.getStackReferenceMapEncoding())) + .add(NonmovableArrays.byteSizeOf(impl.getMethodReflectionMetadataEncoding())) + .add(NonmovableArrays.byteSizeOf(impl.getMethodReflectionMetadataIndexEncoding())) .add(NonmovableArrays.byteSizeOf(impl.getFrameInfoEncodings())) .add(NonmovableArrays.byteSizeOf(impl.getFrameInfoObjectConstants())) .add(NonmovableArrays.byteSizeOf(impl.getFrameInfoSourceClasses())) @@ -261,6 +263,14 @@ public static NonmovableArray getStackReferenceMapEncoding(CodeInfo info) return cast(info).getStackReferenceMapEncoding(); } + public static NonmovableArray getMethodReflectionMetadataEncoding(CodeInfo info) { + return cast(info).getMethodReflectionMetadataEncoding(); + } + + public static NonmovableArray getMethodReflectionMetadataIndexEncoding(CodeInfo info) { + return cast(info).getMethodReflectionMetadataIndexEncoding(); + } + public static long lookupStackReferenceMapIndex(CodeInfo info, long ip) { return CodeInfoDecoder.lookupStackReferenceMapIndex(info, ip); } @@ -274,25 +284,33 @@ public static void lookupCodeInfo(CodeInfo info, long ip, SimpleCodeInfoQueryRes } @Uninterruptible(reason = "Nonmovable object arrays are not visible to GC until installed.") - public static void setFrameInfo(CodeInfo info, NonmovableArray encodings, NonmovableObjectArray objectConstants, - NonmovableObjectArray> sourceClasses, NonmovableObjectArray sourceMethodNames, NonmovableObjectArray names) { + public static void setFrameInfo(CodeInfo info, NonmovableArray encodings) { CodeInfoImpl impl = cast(info); impl.setFrameInfoEncodings(encodings); - impl.setFrameInfoObjectConstants(objectConstants); - impl.setFrameInfoSourceClasses(sourceClasses); - impl.setFrameInfoSourceMethodNames(sourceMethodNames); - impl.setFrameInfoNames(names); if (!SubstrateUtil.HOSTED) { // notify the GC about the frame metadata that is now live Heap.getHeap().getRuntimeCodeInfoGCSupport().registerFrameMetadata(impl); } } - public static void setCodeInfo(CodeInfo info, NonmovableArray index, NonmovableArray encodings, NonmovableArray referenceMapEncoding) { + public static void setCodeInfo(CodeInfo info, NonmovableArray index, NonmovableArray encodings, NonmovableArray referenceMapEncoding, + NonmovableArray methodDataEncoding, NonmovableArray methodDataIndexEncoding) { CodeInfoImpl impl = cast(info); impl.setCodeInfoIndex(index); impl.setCodeInfoEncodings(encodings); impl.setStackReferenceMapEncoding(referenceMapEncoding); + impl.setMethodReflectionMetadataEncoding(methodDataEncoding); + impl.setMethodReflectionMetadataIndexEncoding(methodDataIndexEncoding); + } + + @Uninterruptible(reason = "called from uninterruptible code.") + public static void setEncodings(CodeInfo info, NonmovableObjectArray objectConstants, + NonmovableObjectArray> sourceClasses, NonmovableObjectArray sourceMethodNames, NonmovableObjectArray names) { + CodeInfoImpl impl = cast(info); + impl.setFrameInfoObjectConstants(objectConstants); + impl.setFrameInfoSourceClasses(sourceClasses); + impl.setFrameInfoSourceMethodNames(sourceMethodNames); + impl.setFrameInfoNames(names); } public static Log log(CodeInfo info, Log log) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java index cf7e1bea64837..46da389879014 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java @@ -26,11 +26,18 @@ import static com.oracle.svm.core.util.VMError.shouldNotReachHere; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.BitSet; +import java.util.List; +import java.util.Map; import java.util.TreeMap; import org.graalvm.compiler.code.CompilationResult; import org.graalvm.compiler.core.common.NumUtil; +import org.graalvm.compiler.core.common.util.FrequencyEncoder; import org.graalvm.compiler.core.common.util.TypeConversion; import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; import org.graalvm.compiler.options.Option; @@ -41,8 +48,10 @@ import com.oracle.svm.core.CalleeSavedRegisters; import com.oracle.svm.core.ReservedRegisters; +import com.oracle.svm.core.annotate.Uninterruptible; import com.oracle.svm.core.c.NonmovableArray; import com.oracle.svm.core.c.NonmovableArrays; +import com.oracle.svm.core.c.NonmovableObjectArray; import com.oracle.svm.core.code.FrameInfoQueryResult.ValueInfo; import com.oracle.svm.core.code.FrameInfoQueryResult.ValueType; import com.oracle.svm.core.config.ConfigurationValues; @@ -97,6 +106,52 @@ public static class Counters { final Counter virtualObjectsCount = new Counter(group, "Number of virtual objects", "Number of virtual objects encoded"); } + public static final class Encoders { + final FrequencyEncoder objectConstants; + final FrequencyEncoder> sourceClasses; + final FrequencyEncoder sourceMethodNames; + final FrequencyEncoder names; + + private Encoders() { + this.objectConstants = FrequencyEncoder.createEqualityEncoder(); + this.sourceClasses = FrequencyEncoder.createEqualityEncoder(); + this.sourceMethodNames = FrequencyEncoder.createEqualityEncoder(); + if (FrameInfoDecoder.encodeDebugNames() || FrameInfoDecoder.encodeSourceReferences()) { + this.names = FrequencyEncoder.createEqualityEncoder(); + } else { + this.names = null; + } + } + + private void encodeAllAndInstall(CodeInfo target, ReferenceAdjuster adjuster) { + JavaConstant[] encodedJavaConstants = objectConstants.encodeAll(new JavaConstant[objectConstants.getLength()]); + Class[] sourceClassesArray = null; + String[] sourceMethodNamesArray = null; + String[] namesArray = null; + final boolean encodeDebugNames = FrameInfoDecoder.encodeDebugNames(); + if (encodeDebugNames || FrameInfoDecoder.encodeSourceReferences()) { + sourceClassesArray = sourceClasses.encodeAll(new Class[sourceClasses.getLength()]); + sourceMethodNamesArray = sourceMethodNames.encodeAll(new String[sourceMethodNames.getLength()]); + } + if (encodeDebugNames) { + namesArray = names.encodeAll(new String[names.getLength()]); + } + install(target, encodedJavaConstants, sourceClassesArray, sourceMethodNamesArray, namesArray, adjuster); + } + + @Uninterruptible(reason = "Nonmovable object arrays are not visible to GC until installed in target.") + private static void install(CodeInfo target, JavaConstant[] objectConstantsArray, Class[] sourceClassesArray, + String[] sourceMethodNamesArray, String[] namesArray, ReferenceAdjuster adjuster) { + + NonmovableObjectArray frameInfoObjectConstants = adjuster.copyOfObjectConstantArray(objectConstantsArray); + NonmovableObjectArray> frameInfoSourceClasses = (sourceClassesArray != null) ? adjuster.copyOfObjectArray(sourceClassesArray) : NonmovableArrays.nullArray(); + NonmovableObjectArray frameInfoSourceMethodNames = (sourceMethodNamesArray != null) ? adjuster.copyOfObjectArray(sourceMethodNamesArray) : NonmovableArrays.nullArray(); + NonmovableObjectArray frameInfoNames = (namesArray != null) ? adjuster.copyOfObjectArray(namesArray) : NonmovableArrays.nullArray(); + + CodeInfoAccess.setEncodings(target, frameInfoObjectConstants, frameInfoSourceClasses, frameInfoSourceMethodNames, frameInfoNames); + } + } + static class IPData { protected long ip; protected int frameSizeEncoding; @@ -107,16 +162,32 @@ static class IPData { protected IPData next; } + public static class MethodData { + public Class declaringClass; + public String name; + public Class[] paramTypes; + public Class returnType; + public Class[] exceptionTypes; + public int modifiers; +// protected byte[] annotations; + } + private final TreeMap entries; + private final Encoders encoders; private final FrameInfoEncoder frameInfoEncoder; + private final TreeMap methodData; private NonmovableArray codeInfoIndex; private NonmovableArray codeInfoEncodings; private NonmovableArray referenceMapEncoding; + private NonmovableArray methodDataEncoding; + private NonmovableArray methodDataIndexEncoding; public CodeInfoEncoder(FrameInfoEncoder.Customization frameInfoCustomization) { this.entries = new TreeMap<>(); - this.frameInfoEncoder = new FrameInfoEncoder(frameInfoCustomization); + this.encoders = new Encoders(); + this.frameInfoEncoder = new FrameInfoEncoder(frameInfoCustomization, encoders); + this.methodData = new TreeMap<>(); } public static int getEntryOffset(Infopoint infopoint) { @@ -131,6 +202,10 @@ public static int getEntryOffset(Infopoint infopoint) { return -1; } + public void addClass(Class clazz) { + encoders.sourceClasses.addObject(clazz); + } + public void addMethod(SharedMethod method, CompilationResult compilation, int compilationOffset) { int totalFrameSize = compilation.getTotalFrameSize(); boolean isEntryPoint = method.isEntryPoint(); @@ -173,6 +248,39 @@ public void addMethod(SharedMethod method, CompilationResult compilation, int co ImageSingletons.lookup(Counters.class).codeSize.add(compilation.getTargetCodeSize()); } + public void addMethodMetadata(Class clazz, long id) { + List data = new ArrayList<>(); + try { + for (Method m : clazz.getDeclaredMethods()) { + data.add(getMethodData(m)); + } + } catch (Throwable t) { + // ignore + } + + try { + for (Constructor c : clazz.getDeclaredConstructors()) { + data.add(getMethodData(c)); + } + } catch (Throwable t) { + // ignore + } + methodData.put(id, data.toArray(new MethodData[0])); + } + + private MethodData getMethodData(Executable m) { + MethodData data = new MethodData(); + data.declaringClass = m.getDeclaringClass(); + encoders.sourceClasses.addObject(data.declaringClass); + data.name = m instanceof Constructor ? "" : m.getName(); + encoders.sourceMethodNames.addObject(data.name); + data.modifiers = m.getModifiers(); + data.paramTypes = m.getParameterTypes(); + data.returnType = (m instanceof Method) ? ((Method) m).getReturnType() : null; + data.exceptionTypes = m.getExceptionTypes(); + return data; + } + private IPData makeEntry(long ip) { IPData result = entries.get(ip); if (result == null) { @@ -184,15 +292,17 @@ private IPData makeEntry(long ip) { } public void encodeAllAndInstall(CodeInfo target, ReferenceAdjuster adjuster) { + encoders.encodeAllAndInstall(target, adjuster); encodeReferenceMaps(); - frameInfoEncoder.encodeAllAndInstall(target, adjuster); + frameInfoEncoder.encodeAllAndInstall(target); + encodeMethodMetadata(); encodeIPData(); install(target); } private void install(CodeInfo target) { - CodeInfoAccess.setCodeInfo(target, codeInfoIndex, codeInfoEncodings, referenceMapEncoding); + CodeInfoAccess.setCodeInfo(target, codeInfoIndex, codeInfoEncodings, referenceMapEncoding, methodDataEncoding, methodDataIndexEncoding); } private void encodeReferenceMaps() { @@ -206,6 +316,90 @@ private void encodeReferenceMaps() { } } + private void encodeMethodMetadata() { + UnsafeArrayTypeWriter dataEncodingBuffer = UnsafeArrayTypeWriter.create(ByteArrayReader.supportsUnalignedMemoryAccess()); + UnsafeArrayTypeWriter indexEncodingBuffer = UnsafeArrayTypeWriter.create(ByteArrayReader.supportsUnalignedMemoryAccess()); + long lastTypeID = -1; + for (Map.Entry entry : methodData.entrySet()) { + long typeID = entry.getKey(); + assert typeID > lastTypeID; + lastTypeID++; + while (lastTypeID < typeID) { + indexEncodingBuffer.putU4(0); + lastTypeID++; + } + long index = dataEncodingBuffer.getBytesWritten(); + indexEncodingBuffer.putU4(index); + MethodData[] classData = filterMethods(entry.getValue()); + dataEncodingBuffer.putUV(classData.length); + for (MethodData data : classData) { + final int declaringClassIndex = encoders.sourceClasses.getIndex(data.declaringClass); + dataEncodingBuffer.putSV(declaringClassIndex); + final int nameIndex = encoders.sourceMethodNames.getIndex(data.name); + dataEncodingBuffer.putSV(nameIndex); + dataEncodingBuffer.putUV(data.modifiers); + dataEncodingBuffer.putUV(data.paramTypes.length); + for (Class paramClazz : data.paramTypes) { + try { // TODO condition + final int classIndex = encoders.sourceClasses.getIndex(paramClazz); + dataEncodingBuffer.putSV(classIndex); + } catch (Throwable t) { + dataEncodingBuffer.putSV(-1); + } + } + try { + final int classIndex = encoders.sourceClasses.getIndex(data.returnType); + dataEncodingBuffer.putSV(classIndex); + } catch (Throwable t) { + dataEncodingBuffer.putSV(-1); + } + dataEncodingBuffer.putUV(data.exceptionTypes.length); + for (Class exceptionClazz : data.exceptionTypes) { + try { + final int classIndex = encoders.sourceClasses.getIndex(exceptionClazz); + dataEncodingBuffer.putSV(classIndex); + } catch (Throwable t) { + dataEncodingBuffer.putSV(-1); + } + } +// if (cur.sourceMethodAnnotations != null) { +// for (byte b : cur.sourceMethodAnnotations) { +// encodingBuffer.putU1(b); // TODO replace indices (here?) +// } +// } + } + } + methodDataEncoding = NonmovableArrays.createByteArray(TypeConversion.asS4(dataEncodingBuffer.getBytesWritten())); + dataEncodingBuffer.toByteBuffer(NonmovableArrays.asByteBuffer(methodDataEncoding)); + methodDataIndexEncoding = NonmovableArrays.createByteArray(TypeConversion.asS4(indexEncodingBuffer.getBytesWritten())); + indexEncodingBuffer.toByteBuffer(NonmovableArrays.asByteBuffer(methodDataIndexEncoding)); + } + + private MethodData[] filterMethods(MethodData[] methods) { + List newMethods = new ArrayList<>(); + for (MethodData data : methods) { + boolean include = true; + for (Class paramType : data.paramTypes) { + try { + encoders.sourceClasses.getIndex(paramType); // TODO cleanly + } catch (Throwable t) { + include = false; + } + } + for (Class paramType : data.exceptionTypes) { + try { + encoders.sourceClasses.getIndex(paramType); // TODO cleanly + } catch (Throwable t) { + include = false; + } + } + if (include) { + newMethods.add(data); + } + } + return newMethods.toArray(new MethodData[0]); + } + /** * Inverse of {@link CodeInfoDecoder#decodeTotalFrameSize} and * {@link CodeInfoDecoder#decodeMethodStart}. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoImpl.java index a96da5602940f..49bfe52b5efb9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoImpl.java @@ -146,6 +146,18 @@ interface CodeInfoImpl extends CodeInfo { @RawField void setStackReferenceMapEncoding(NonmovableArray referenceMapEncoding); + @RawField + NonmovableArray getMethodReflectionMetadataEncoding(); + + @RawField + void setMethodReflectionMetadataEncoding(NonmovableArray methodDataEncoding); + + @RawField + NonmovableArray getMethodReflectionMetadataIndexEncoding(); + + @RawField + void setMethodReflectionMetadataIndexEncoding(NonmovableArray methodDataIndexEncoding); + @RawField void setCodeSize(UnsignedWord codeSize); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java index 17d54ca21a0e6..32845004fa0a3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java @@ -27,7 +27,6 @@ import java.util.Arrays; import java.util.List; -import com.oracle.svm.core.heap.ReferenceMapIndex; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.options.Option; import org.graalvm.nativeimage.ImageSingletons; @@ -46,12 +45,14 @@ import com.oracle.svm.core.deopt.SubstrateInstalledCode; import com.oracle.svm.core.heap.CodeReferenceMapDecoder; import com.oracle.svm.core.heap.ObjectReferenceVisitor; +import com.oracle.svm.core.heap.ReferenceMapIndex; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.util.Counter; import com.oracle.svm.core.util.CounterFeature; +import com.oracle.svm.core.util.NonmovableByteArrayTypeReader; import com.oracle.svm.core.util.VMError; import jdk.vm.ci.code.InstalledCode; @@ -156,6 +157,53 @@ public static RuntimeException reportNoReferenceMap(Pointer sp, CodePointer ip, throw VMError.shouldNotReachHere("No reference map information found"); } + public static CodeInfoEncoder.MethodData[] getMethodMetadata(CodeInfo info, long typeID) { + NonmovableArray index = CodeInfoAccess.getMethodReflectionMetadataIndexEncoding(info); + NonmovableByteArrayTypeReader indexReader = new NonmovableByteArrayTypeReader(index, 4 * typeID); + int offset = (int) indexReader.getU4(); + NonmovableArray data = CodeInfoAccess.getMethodReflectionMetadataEncoding(info); + NonmovableByteArrayTypeReader dataReader = new NonmovableByteArrayTypeReader(data, offset); + + int methodCount = dataReader.getUVInt(); + CodeInfoEncoder.MethodData[] methods = new CodeInfoEncoder.MethodData[methodCount]; + for (int i = 0; i < methodCount; ++i) { + CodeInfoEncoder.MethodData methodData = new CodeInfoEncoder.MethodData(); + int declaringClassIndex = dataReader.getSVInt(); + methodData.declaringClass = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceClasses(info), declaringClassIndex); + int nameIndex = dataReader.getSVInt(); + methodData.name = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceMethodNames(info), nameIndex); + methodData.modifiers = dataReader.getUVInt(); + int paramCount = dataReader.getUVInt(); + methodData.paramTypes = new Class[paramCount]; + for (int j = 0; j < paramCount; ++j) { + int paramTypeIndex = dataReader.getSVInt(); + if (paramTypeIndex == -1) { + methodData.paramTypes[j] = null; + } else { + methodData.paramTypes[j] = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceClasses(info), paramTypeIndex); + } + } + int returnTypeIndex = dataReader.getSVInt(); + if (returnTypeIndex == -1) { + methodData.returnType = null; + } else { + methodData.returnType = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceClasses(info), returnTypeIndex); + } + int exceptionCount = dataReader.getUVInt(); + methodData.exceptionTypes = new Class[exceptionCount]; + for (int j = 0; j < exceptionCount; ++j) { + int exceptionTypeIndex = dataReader.getSVInt(); + if (exceptionTypeIndex == -1) { + methodData.exceptionTypes[j] = null; + } else { + methodData.exceptionTypes[j] = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceClasses(info), exceptionTypeIndex); + } + } + methods[i] = methodData; + } + return methods; + } + /** * Retrieves the {@link InstalledCode} that contains the provided instruction pointer. Returns * {@code null} if the instruction pointer is not within a runtime compile method. @@ -289,6 +337,8 @@ public void afterCompilation(AfterCompilationAccess config) { config.registerAsImmutable(imageInfo.codeInfoIndex); config.registerAsImmutable(imageInfo.codeInfoEncodings); config.registerAsImmutable(imageInfo.referenceMapEncoding); + config.registerAsImmutable(imageInfo.methodDataEncoding); + config.registerAsImmutable(imageInfo.methodDataIndexEncoding); config.registerAsImmutable(imageInfo.frameInfoEncodings); config.registerAsImmutable(imageInfo.frameInfoObjectConstants); config.registerAsImmutable(imageInfo.frameInfoSourceClasses); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java index cacd1ced21e45..cc5eb40a8a18e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java @@ -24,15 +24,12 @@ */ package com.oracle.svm.core.code; -import static com.oracle.svm.core.util.VMError.shouldNotReachHere; - import java.util.ArrayList; import java.util.BitSet; import java.util.List; import java.util.Objects; import org.graalvm.compiler.core.common.LIRKind; -import org.graalvm.compiler.core.common.util.FrequencyEncoder; import org.graalvm.compiler.core.common.util.TypeConversion; import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; import org.graalvm.nativeimage.ImageSingletons; @@ -42,8 +39,8 @@ import com.oracle.svm.core.annotate.Uninterruptible; import com.oracle.svm.core.c.NonmovableArray; import com.oracle.svm.core.c.NonmovableArrays; -import com.oracle.svm.core.c.NonmovableObjectArray; import com.oracle.svm.core.code.CodeInfoEncoder.Counters; +import com.oracle.svm.core.code.CodeInfoEncoder.Encoders; import com.oracle.svm.core.code.FrameInfoQueryResult.ValueInfo; import com.oracle.svm.core.code.FrameInfoQueryResult.ValueType; import com.oracle.svm.core.config.ConfigurationValues; @@ -180,24 +177,12 @@ static class FrameData { private final Customization customization; private final List allDebugInfos; - private final FrequencyEncoder objectConstants; - private final FrequencyEncoder> sourceClasses; - private final FrequencyEncoder sourceMethodNames; - private final FrequencyEncoder names; + private final Encoders encoders; - protected FrameInfoEncoder(Customization customization) { + protected FrameInfoEncoder(Customization customization, Encoders encoders) { this.customization = customization; + this.encoders = encoders; this.allDebugInfos = new ArrayList<>(); - this.objectConstants = FrequencyEncoder.createEqualityEncoder(); - if (FrameInfoDecoder.encodeDebugNames() || FrameInfoDecoder.encodeSourceReferences()) { - this.sourceClasses = FrequencyEncoder.createEqualityEncoder(); - this.sourceMethodNames = FrequencyEncoder.createEqualityEncoder(); - this.names = FrequencyEncoder.createEqualityEncoder(); - } else { - this.sourceClasses = null; - this.sourceMethodNames = null; - this.names = null; - } } protected FrameData addDebugInfo(ResolvedJavaMethod method, Infopoint infopoint, int totalFrameSize) { @@ -223,15 +208,15 @@ protected FrameData addDebugInfo(ResolvedJavaMethod method, Infopoint infopoint, } for (FrameInfoQueryResult cur = data.frame; cur != null; cur = cur.caller) { - sourceClasses.addObject(cur.sourceClass); - sourceMethodNames.addObject(cur.sourceMethodName); + encoders.sourceClasses.addObject(cur.sourceClass); + encoders.sourceMethodNames.addObject(cur.sourceMethodName); if (encodeDebugNames) { for (ValueInfo valueInfo : cur.valueInfos) { if (valueInfo.name == null) { valueInfo.name = ""; } - names.addObject(valueInfo.name); + encoders.names.addObject(valueInfo.name); } } } @@ -280,7 +265,7 @@ private FrameInfoQueryResult addFrame(FrameData data, BytecodeFrame frame, boole SharedMethod method = (SharedMethod) frame.getMethod(); if (customization.shouldStoreMethod()) { result.deoptMethod = method; - objectConstants.addObject(SubstrateObjectConstant.forObject(method)); + encoders.objectConstants.addObject(SubstrateObjectConstant.forObject(method)); } result.deoptMethodOffset = method.getDeoptOffsetInImage(); @@ -383,7 +368,7 @@ private ValueInfo makeValueInfo(FrameData data, JavaKind kind, JavaValue v, bool * Collect all Object constants, which will be stored in a separate Object[] * array so that the GC can visit them. */ - objectConstants.addObject(constant); + encoders.objectConstants.addObject(constant); } } ImageSingletons.lookup(Counters.class).constantValueCount.inc(); @@ -394,7 +379,7 @@ private ValueInfo makeValueInfo(FrameData data, JavaKind kind, JavaValue v, bool result.data = virtualObject.getId(); makeVirtualObject(data, virtualObject, isDeoptEntry); } else { - throw shouldNotReachHere(); + throw VMError.shouldNotReachHere(); } return result; } @@ -576,34 +561,14 @@ private static int computeOffset(ArrayList valueInfos, int startIndex return result; } - protected void encodeAllAndInstall(CodeInfo target, ReferenceAdjuster adjuster) { - JavaConstant[] encodedJavaConstants = objectConstants.encodeAll(new JavaConstant[objectConstants.getLength()]); - Class[] sourceClassesArray = null; - String[] sourceMethodNamesArray = null; - String[] namesArray = null; - final boolean encodeDebugNames = FrameInfoDecoder.encodeDebugNames(); - if (encodeDebugNames || FrameInfoDecoder.encodeSourceReferences()) { - sourceClassesArray = sourceClasses.encodeAll(new Class[sourceClasses.getLength()]); - sourceMethodNamesArray = sourceMethodNames.encodeAll(new String[sourceMethodNames.getLength()]); - } - if (encodeDebugNames) { - namesArray = names.encodeAll(new String[names.getLength()]); - } + protected void encodeAllAndInstall(CodeInfo target) { NonmovableArray frameInfoEncodings = encodeFrameDatas(); - install(target, frameInfoEncodings, encodedJavaConstants, sourceClassesArray, sourceMethodNamesArray, namesArray, adjuster); + install(target, frameInfoEncodings); } @Uninterruptible(reason = "Nonmovable object arrays are not visible to GC until installed in target.") - private static void install(CodeInfo target, NonmovableArray frameInfoEncodings, JavaConstant[] objectConstantsArray, Class[] sourceClassesArray, - String[] sourceMethodNamesArray, String[] namesArray, ReferenceAdjuster adjuster) { - - NonmovableObjectArray frameInfoObjectConstants = adjuster.copyOfObjectConstantArray(objectConstantsArray); - NonmovableObjectArray> frameInfoSourceClasses = (sourceClassesArray != null) ? adjuster.copyOfObjectArray(sourceClassesArray) : NonmovableArrays.nullArray(); - NonmovableObjectArray frameInfoSourceMethodNames = (sourceMethodNamesArray != null) ? adjuster.copyOfObjectArray(sourceMethodNamesArray) : NonmovableArrays.nullArray(); - NonmovableObjectArray frameInfoNames = (namesArray != null) ? adjuster.copyOfObjectArray(namesArray) : NonmovableArrays.nullArray(); - - CodeInfoAccess.setFrameInfo(target, frameInfoEncodings, frameInfoObjectConstants, frameInfoSourceClasses, frameInfoSourceMethodNames, frameInfoNames); - + private static void install(CodeInfo target, NonmovableArray frameInfoEncodings) { + CodeInfoAccess.setFrameInfo(target, frameInfoEncodings); afterInstallation(target); } @@ -642,7 +607,7 @@ private void encodeFrameData(FrameData data, UnsafeArrayTypeWriter encodingBuffe int deoptMethodIndex; if (cur.deoptMethod != null) { - deoptMethodIndex = -1 - objectConstants.getIndex(SubstrateObjectConstant.forObject(cur.deoptMethod)); + deoptMethodIndex = -1 - encoders.objectConstants.getIndex(SubstrateObjectConstant.forObject(cur.deoptMethod)); assert deoptMethodIndex < 0; assert cur.deoptMethodOffset == cur.deoptMethod.getDeoptOffsetInImage(); } else { @@ -664,8 +629,8 @@ private void encodeFrameData(FrameData data, UnsafeArrayTypeWriter encodingBuffe final boolean encodeDebugNames = needLocalValues && FrameInfoDecoder.encodeDebugNames(); if (encodeDebugNames || FrameInfoDecoder.encodeSourceReferences()) { - final int classIndex = sourceClasses.getIndex(cur.sourceClass); - final int methodIndex = sourceMethodNames.getIndex(cur.sourceMethodName); + final int classIndex = encoders.sourceClasses.getIndex(cur.sourceClass); + final int methodIndex = encoders.sourceMethodNames.getIndex(cur.sourceMethodName); cur.sourceClassIndex = classIndex; cur.sourceMethodNameIndex = methodIndex; @@ -677,7 +642,7 @@ private void encodeFrameData(FrameData data, UnsafeArrayTypeWriter encodingBuffe if (encodeDebugNames) { for (ValueInfo valueInfo : cur.valueInfos) { - valueInfo.nameIndex = names.getIndex(valueInfo.name); + valueInfo.nameIndex = encoders.names.getIndex(valueInfo.name); encodingBuffer.putUV(valueInfo.nameIndex); } } @@ -690,7 +655,7 @@ private void encodeValues(ValueInfo[] valueInfos, UnsafeArrayTypeWriter encoding for (ValueInfo valueInfo : valueInfos) { if (valueInfo.type == ValueType.Constant) { if (valueInfo.kind == JavaKind.Object) { - valueInfo.data = objectConstants.getIndex(valueInfo.value); + valueInfo.data = encoders.objectConstants.getIndex(valueInfo.value); } else { valueInfo.data = encodePrimitiveConstant(valueInfo.value); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ImageCodeInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ImageCodeInfo.java index 2d1f91c9bb658..fd54383d558b5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ImageCodeInfo.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/ImageCodeInfo.java @@ -61,6 +61,8 @@ public class ImageCodeInfo { @UnknownObjectField(types = {byte[].class}) byte[] codeInfoIndex; @UnknownObjectField(types = {byte[].class}) byte[] codeInfoEncodings; @UnknownObjectField(types = {byte[].class}) byte[] referenceMapEncoding; + @UnknownObjectField(types = {byte[].class}) byte[] methodDataEncoding; + @UnknownObjectField(types = {byte[].class}) byte[] methodDataIndexEncoding; @UnknownObjectField(types = {byte[].class}) byte[] frameInfoEncodings; @UnknownObjectField(types = {Object[].class}) Object[] frameInfoObjectConstants; @UnknownObjectField(types = {Class[].class}) Class[] frameInfoSourceClasses; @@ -94,6 +96,8 @@ CodeInfo prepareCodeInfo() { info.setCodeInfoIndex(NonmovableArrays.fromImageHeap(codeInfoIndex)); info.setCodeInfoEncodings(NonmovableArrays.fromImageHeap(codeInfoEncodings)); info.setStackReferenceMapEncoding(NonmovableArrays.fromImageHeap(referenceMapEncoding)); + info.setMethodReflectionMetadataEncoding(NonmovableArrays.fromImageHeap(methodDataEncoding)); + info.setMethodReflectionMetadataIndexEncoding(NonmovableArrays.fromImageHeap(methodDataIndexEncoding)); info.setFrameInfoEncodings(NonmovableArrays.fromImageHeap(frameInfoEncodings)); info.setFrameInfoObjectConstants(NonmovableArrays.fromImageHeap(frameInfoObjectConstants)); info.setFrameInfoSourceClasses(NonmovableArrays.fromImageHeap(frameInfoSourceClasses)); @@ -147,6 +151,16 @@ public NonmovableArray getStackReferenceMapEncoding() { return NonmovableArrays.fromImageHeap(referenceMapEncoding); } + @Override + public NonmovableArray getMethodReflectionMetadataEncoding() { + return NonmovableArrays.fromImageHeap(methodDataEncoding); + } + + @Override + public NonmovableArray getMethodReflectionMetadataIndexEncoding() { + return NonmovableArrays.fromImageHeap(methodDataIndexEncoding); + } + @Override public void setCodeStart(CodePointer value) { codeStart = value; @@ -197,6 +211,16 @@ public void setStackReferenceMapEncoding(NonmovableArray array) { referenceMapEncoding = NonmovableArrays.getHostedArray(array); } + @Override + public void setMethodReflectionMetadataEncoding(NonmovableArray array) { + methodDataEncoding = NonmovableArrays.getHostedArray(array); + } + + @Override + public void setMethodReflectionMetadataIndexEncoding(NonmovableArray array) { + methodDataIndexEncoding = NonmovableArrays.getHostedArray(array); + } + @Override public NonmovableArray getFrameInfoEncodings() { return NonmovableArrays.fromImageHeap(frameInfoEncodings); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoAccess.java index 22f2fd0cbe664..b4bb6de7cde57 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoAccess.java @@ -303,6 +303,8 @@ public static void forEachArray(CodeInfo info, NonmovableArrayAction action) { action.apply(impl.getCodeInfoIndex()); action.apply(impl.getCodeInfoEncodings()); action.apply(impl.getStackReferenceMapEncoding()); + action.apply(impl.getMethodReflectionMetadataEncoding()); + action.apply(impl.getMethodReflectionMetadataIndexEncoding()); action.apply(impl.getFrameInfoEncodings()); action.apply(impl.getDeoptimizationStartOffsets()); action.apply(impl.getDeoptimizationEncodings()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java index 5f91b1e6c622c..083b7a7d5b0cc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java @@ -93,19 +93,19 @@ private void parseClass(Map data) { /* Already handled. */ } else if (name.equals("allDeclaredConstructors")) { if (asBoolean(value, "allDeclaredConstructors")) { - delegate.registerDeclaredConstructors(clazz); + delegate.registerDeclaredConstructors(clazz, false); } } else if (name.equals("allPublicConstructors")) { if (asBoolean(value, "allPublicConstructors")) { - delegate.registerPublicConstructors(clazz); + delegate.registerPublicConstructors(clazz, false); } } else if (name.equals("allDeclaredMethods")) { if (asBoolean(value, "allDeclaredMethods")) { - delegate.registerDeclaredMethods(clazz); + delegate.registerDeclaredMethods(clazz, false); } } else if (name.equals("allPublicMethods")) { if (asBoolean(value, "allPublicMethods")) { - delegate.registerPublicMethods(clazz); + delegate.registerPublicMethods(clazz, false); } } else if (name.equals("allDeclaredFields")) { if (asBoolean(value, "allDeclaredFields")) { @@ -123,8 +123,26 @@ private void parseClass(Map data) { if (asBoolean(value, "allPublicClasses")) { delegate.registerPublicClasses(clazz); } + } else if (name.equals("queryAllDeclaredConstructors")) { + if (asBoolean(value, "queryAllDeclaredConstructors")) { + delegate.registerDeclaredConstructors(clazz, true); + } + } else if (name.equals("queryAllPublicConstructors")) { + if (asBoolean(value, "queryAllPublicConstructors")) { + delegate.registerPublicConstructors(clazz, true); + } + } else if (name.equals("queryAllDeclaredMethods")) { + if (asBoolean(value, "queryAllDeclaredMethods")) { + delegate.registerDeclaredMethods(clazz, true); + } + } else if (name.equals("queryAllPublicMethods")) { + if (asBoolean(value, "queryAllPublicMethods")) { + delegate.registerPublicMethods(clazz, true); + } } else if (name.equals("methods")) { - parseMethods(asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); + parseMethods(asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz, false); + } else if (name.equals("queriedMethods")) { + parseMethods(asList(value, "Attribute 'queriedMethods' must be an array of method descriptors"), clazz, true); } else if (name.equals("fields")) { parseFields(asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz); } else { @@ -173,13 +191,13 @@ private void parseField(Map data, T clazz) { } } - private void parseMethods(List methods, T clazz) { + private void parseMethods(List methods, T clazz, boolean queriedOnly) { for (Object method : methods) { - parseMethod(asMap(method, "Elements of 'methods' array must be method descriptor objects"), clazz); + parseMethod(asMap(method, "Elements of 'methods' array must be method descriptor objects"), clazz, queriedOnly); } } - private void parseMethod(Map data, T clazz) { + private void parseMethod(Map data, T clazz, boolean queriedOnly) { String methodName = null; List methodParameterTypes = null; for (Map.Entry entry : data.entrySet()) { @@ -206,9 +224,9 @@ private void parseMethod(Map data, T clazz) { if (methodParameterTypes != null) { try { if (isConstructor) { - delegate.registerConstructor(clazz, methodParameterTypes); + delegate.registerConstructor(clazz, methodParameterTypes, queriedOnly); } else { - delegate.registerMethod(clazz, methodName, methodParameterTypes); + delegate.registerMethod(clazz, methodName, methodParameterTypes, queriedOnly); } } catch (NoSuchMethodException e) { handleError("Method " + formatMethod(clazz, methodName, methodParameterTypes) + " not found."); @@ -219,9 +237,9 @@ private void parseMethod(Map data, T clazz) { try { boolean found; if (isConstructor) { - found = delegate.registerAllConstructors(clazz); + found = delegate.registerAllConstructors(clazz, queriedOnly); } else { - found = delegate.registerAllMethodsWithName(clazz, methodName); + found = delegate.registerAllMethodsWithName(clazz, methodName, queriedOnly); } if (!found) { throw new JSONParserException("Method " + formatMethod(clazz, methodName) + " not found"); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java index 731218fc330a3..783a4ca7c6fb5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java @@ -50,23 +50,23 @@ default T resolveType(String typeName) { void registerDeclaredFields(T type); - void registerPublicMethods(T type); + void registerPublicMethods(T type, boolean queriedOnly); - void registerDeclaredMethods(T type); + void registerDeclaredMethods(T type, boolean queriedOnly); - void registerPublicConstructors(T type); + void registerPublicConstructors(T type, boolean queriedOnly); - void registerDeclaredConstructors(T type); + void registerDeclaredConstructors(T type, boolean queriedOnly); void registerField(T type, String fieldName, boolean allowWrite) throws NoSuchFieldException; - boolean registerAllMethodsWithName(T type, String methodName); + boolean registerAllMethodsWithName(T type, String methodName, boolean queriedOnly); - void registerMethod(T type, String methodName, List methodParameterTypes) throws NoSuchMethodException; + void registerMethod(T type, String methodName, List methodParameterTypes, boolean queriedOnly) throws NoSuchMethodException; - void registerConstructor(T type, List methodParameterTypes) throws NoSuchMethodException; + void registerConstructor(T type, List methodParameterTypes, boolean queriedOnly) throws NoSuchMethodException; - boolean registerAllConstructors(T type); + boolean registerAllConstructors(T type, boolean queriedOnly); String getTypeName(T type); 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 bc25ccd5acce2..aa1e2f5837b0c 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 @@ -26,6 +26,8 @@ //Checkstyle: allow reflection +import static com.oracle.svm.core.annotate.TargetElement.CONSTRUCTOR_NAME; + import java.io.File; import java.io.InputStream; import java.io.Serializable; @@ -48,6 +50,8 @@ import java.security.CodeSource; import java.security.ProtectionDomain; import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.Set; @@ -75,6 +79,9 @@ import com.oracle.svm.core.annotate.UnknownObjectField; import com.oracle.svm.core.classinitialization.ClassInitializationInfo; import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; +import com.oracle.svm.core.code.CodeInfo; +import com.oracle.svm.core.code.CodeInfoEncoder; +import com.oracle.svm.core.code.CodeInfoTable; import com.oracle.svm.core.jdk.JDK11OrLater; import com.oracle.svm.core.jdk.JDK15OrLater; import com.oracle.svm.core.jdk.JDK16OrLater; @@ -1136,7 +1143,8 @@ private Method getMethod(@SuppressWarnings("hiding") String name, Class... pa * The original code of getMethods() does a recursive search to avoid creating objects for * all public methods. We prepare them during the image build and can just iterate here. */ - Method method = searchMethods(rd.publicMethods, name, parameterTypes); + maybeParseReflectionMetadata(); + Method method = searchMethods(companion.get().getExtendedRD().publicMethods, name, parameterTypes); if (method == null) { throw new NoSuchMethodException(describeMethod(getName() + "." + name + "(", parameterTypes, ")")); } @@ -1182,7 +1190,8 @@ private Class[] getClasses() { @Substitute private Constructor[] privateGetDeclaredConstructors(boolean publicOnly) { - return publicOnly ? rd.publicConstructors : rd.declaredConstructors; + maybeParseReflectionMetadata(); + return publicOnly ? companion.get().getExtendedRD().publicConstructors : companion.get().getExtendedRD().declaredConstructors; } @Substitute @@ -1192,7 +1201,77 @@ private Field[] privateGetDeclaredFields(boolean publicOnly) { @Substitute private Method[] privateGetDeclaredMethods(boolean publicOnly) { - return publicOnly ? rd.declaredPublicMethods : rd.declaredMethods; + maybeParseReflectionMetadata(); + return publicOnly ? companion.get().getExtendedRD().declaredPublicMethods : companion.get().getExtendedRD().declaredMethods; + } + + private void maybeParseReflectionMetadata() { + if (!companion.get().hasExtendedRD()) { + CodeInfo info = CodeInfoTable.getImageCodeInfo(); + CodeInfoEncoder.MethodData[] data = CodeInfoTable.getMethodMetadata(info, typeID); + + List newDeclaredMethodsList = new ArrayList<>(); + List newPublicMethodsList = new ArrayList<>(); + List> newDeclaredConstructorsList = new ArrayList<>(); + List> newPublicConstructorsList = new ArrayList<>(); + List newDeclaredPublicMethodsList = new ArrayList<>(); + + outer: for (CodeInfoEncoder.MethodData md : data) { + if (md.name.equals("")) { + Target_java_lang_reflect_Constructor cons = new Target_java_lang_reflect_Constructor(); + cons.constructor(md.declaringClass, md.paramTypes, md.exceptionTypes, md.modifiers, -1, null, null, null); + Constructor c = SubstrateUtil.cast(cons, Constructor.class); + for (Constructor c2 : rd.declaredConstructors) { + if (Arrays.equals(c.getParameterTypes(), c2.getParameterTypes())) { + continue outer; + } + } + newDeclaredConstructorsList.add(c); + if (Modifier.isPublic(c.getModifiers())) { + newPublicConstructorsList.add(c); + } + } else { + Target_java_lang_reflect_Method meth = new Target_java_lang_reflect_Method(); + meth.constructor(md.declaringClass, md.name, md.paramTypes, md.returnType, md.exceptionTypes, md.modifiers, -1, null, null, null, null); + Method m = SubstrateUtil.cast(meth, Method.class); + for (Method m2 : rd.declaredMethods) { + if (m.getName().equals(m2.getName()) && Arrays.equals(m.getParameterTypes(), m2.getParameterTypes())) { + continue outer; + } + } + newDeclaredMethodsList.add(m); + if (Modifier.isPublic(m.getModifiers())) { + newPublicMethodsList.add(m); // TODO add inherited methods + newDeclaredPublicMethodsList.add(m); + } + } + } + + Method[] newDeclaredMethods = new Method[rd.declaredMethods.length + newDeclaredMethodsList.size()]; + System.arraycopy(rd.declaredMethods, 0, newDeclaredMethods, 0, rd.declaredMethods.length); + System.arraycopy(newDeclaredMethodsList.toArray(new Method[0]), 0, newDeclaredMethods, rd.declaredMethods.length, newDeclaredMethodsList.size()); + + Method[] newPublicMethods = new Method[rd.publicMethods.length + newPublicMethodsList.size()]; + System.arraycopy(rd.publicMethods, 0, newPublicMethods, 0, rd.publicMethods.length); + System.arraycopy(newPublicMethodsList.toArray(new Method[0]), 0, newPublicMethods, rd.publicMethods.length, newPublicMethodsList.size()); + + Constructor[] newDeclaredConstructors = new Constructor[rd.declaredConstructors.length + newDeclaredConstructorsList.size()]; + System.arraycopy(rd.declaredConstructors, 0, newDeclaredConstructors, 0, rd.declaredConstructors.length); + System.arraycopy(newDeclaredConstructorsList.toArray(new Constructor[0]), 0, newDeclaredConstructors, rd.declaredConstructors.length, newDeclaredConstructorsList.size()); + + Constructor[] newPublicConstructors = new Constructor[rd.publicConstructors.length + newPublicConstructorsList.size()]; + System.arraycopy(rd.publicConstructors, 0, newPublicConstructors, 0, rd.publicConstructors.length); + System.arraycopy(newPublicConstructorsList.toArray(new Constructor[0]), 0, newPublicConstructors, rd.publicConstructors.length, newPublicConstructorsList.size()); + + Method[] newDeclaredPublicMethods = new Method[rd.declaredPublicMethods.length + newDeclaredPublicMethodsList.size()]; + System.arraycopy(rd.declaredPublicMethods, 0, newDeclaredPublicMethods, 0, rd.declaredPublicMethods.length); + System.arraycopy(newDeclaredPublicMethodsList.toArray(new Method[0]), 0, newDeclaredPublicMethods, rd.declaredPublicMethods.length, newDeclaredPublicMethodsList.size()); + + companion.get().setExtendedRD( + new ReflectionData(rd.declaredFields, rd.publicFields, rd.publicUnhiddenFields, newDeclaredMethods, newPublicMethods, newDeclaredConstructors, newPublicConstructors, + rd.nullaryConstructor, rd.declaredPublicFields, newDeclaredPublicMethods, rd.declaredClasses, rd.publicClasses, rd.enclosingMethodOrConstructor, + rd.recordComponents)); + } } @Substitute @@ -1674,3 +1753,36 @@ final class Target_jdk_internal_reflect_ConstantPool { @TargetClass(className = "java.lang.reflect.RecordComponent", onlyWith = JDK16OrLater.class) final class Target_java_lang_reflect_RecordComponent { } + +@TargetClass(value = Method.class) +final class Target_java_lang_reflect_Method { + + @Alias + @TargetElement(name = CONSTRUCTOR_NAME) + public native void constructor(Class declaringClass, + String name, + Class[] parameterTypes, + Class returnType, + Class[] checkedExceptions, + int modifiers, + int slot, + String signature, + byte[] annotations, + byte[] parameterAnnotations, + byte[] annotationDefault); +} + +@TargetClass(value = Constructor.class) +final class Target_java_lang_reflect_Constructor { + + @Alias + @TargetElement(name = CONSTRUCTOR_NAME) + public native void constructor(Class declaringClass, + Class[] parameterTypes, + Class[] checkedExceptions, + int modifiers, + int slot, + String signature, + byte[] annotations, + byte[] parameterAnnotations); +} \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java index 7ef741d66a49b..61e43935dd816 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java @@ -28,6 +28,7 @@ import java.security.ProtectionDomain; +import com.oracle.svm.core.hub.DynamicHub.ReflectionData; import com.oracle.svm.core.util.VMError; /** An optional, non-immutable companion to a {@link DynamicHub} instance. */ @@ -37,6 +38,7 @@ public final class DynamicHubCompanion { private String packageName; private Object classLoader = NO_CLASS_LOADER; private ProtectionDomain protectionDomain; + private ReflectionData extendedRD; public DynamicHubCompanion(DynamicHub hub) { this.hub = hub; @@ -75,4 +77,18 @@ public void setProtectionDomain(ProtectionDomain domain) { VMError.guarantee(protectionDomain == null && domain != null); protectionDomain = domain; } + + public boolean hasExtendedRD() { + return extendedRD != null; + } + + public ReflectionData getExtendedRD() { + VMError.guarantee(extendedRD != null); + return extendedRD; + } + + public void setExtendedRD(ReflectionData rd) { + VMError.guarantee(rd != null && rd != ReflectionData.EMPTY); + extendedRD = rd; + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java index 8a32363ee2585..146ff264fcff4 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java @@ -88,23 +88,23 @@ public void registerDeclaredFields(Class type) { } @Override - public void registerPublicMethods(Class type) { - registry.register(type.getMethods()); + public void registerPublicMethods(Class type, boolean queriedOnly) { + registerExecutable(queriedOnly, type.getMethods()); } @Override - public void registerDeclaredMethods(Class type) { - registry.register(type.getDeclaredMethods()); + public void registerDeclaredMethods(Class type, boolean queriedOnly) { + registerExecutable(queriedOnly, type.getDeclaredMethods()); } @Override - public void registerPublicConstructors(Class type) { - registry.register(type.getConstructors()); + public void registerPublicConstructors(Class type, boolean queriedOnly) { + registerExecutable(queriedOnly, type.getConstructors()); } @Override - public void registerDeclaredConstructors(Class type) { - registry.register(type.getDeclaredConstructors()); + public void registerDeclaredConstructors(Class type, boolean queriedOnly) { + registerExecutable(queriedOnly, type.getDeclaredConstructors()); } @Override @@ -113,12 +113,12 @@ public void registerField(Class type, String fieldName, boolean allowWrite) t } @Override - public boolean registerAllMethodsWithName(Class type, String methodName) { + public boolean registerAllMethodsWithName(Class type, String methodName, boolean queriedOnly) { boolean found = false; Executable[] methods = type.getDeclaredMethods(); for (Executable method : methods) { if (method.getName().equals(methodName)) { - registry.register(method); + registerExecutable(queriedOnly, method); found = true; } } @@ -126,16 +126,14 @@ public boolean registerAllMethodsWithName(Class type, String methodName) { } @Override - public boolean registerAllConstructors(Class clazz) { + public boolean registerAllConstructors(Class clazz, boolean queriedOnly) { Executable[] methods = clazz.getDeclaredConstructors(); - for (Executable method : methods) { - registry.register(method); - } + registerExecutable(queriedOnly, methods); return methods.length > 0; } @Override - public void registerMethod(Class type, String methodName, List> methodParameterTypes) throws NoSuchMethodException { + public void registerMethod(Class type, String methodName, List> methodParameterTypes, boolean queriedOnly) throws NoSuchMethodException { Class[] parameterTypesArray = methodParameterTypes.toArray(new Class[0]); Method method; try { @@ -155,13 +153,21 @@ public void registerMethod(Class type, String methodName, List> meth throw e; } } - registry.register(method); + registerExecutable(queriedOnly, method); } @Override - public void registerConstructor(Class clazz, List> methodParameterTypes) throws NoSuchMethodException { + public void registerConstructor(Class clazz, List> methodParameterTypes, boolean queriedOnly) throws NoSuchMethodException { Class[] parameterTypesArray = methodParameterTypes.toArray(new Class[0]); - registry.register(clazz.getDeclaredConstructor(parameterTypesArray)); + registerExecutable(queriedOnly, clazz.getDeclaredConstructor(parameterTypesArray)); + } + + private void registerExecutable(boolean queriedOnly, Executable... executable) { + if (queriedOnly) { + registry.registerAsQueried(executable); + } else { + registry.register(executable); + } } @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 39cf6c77553cb..374e789de859e 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 @@ -40,7 +40,6 @@ import java.util.concurrent.ForkJoinPool; import java.util.stream.Collectors; -import com.oracle.svm.core.option.HostedOptionValues; import org.graalvm.compiler.code.CompilationResult; import org.graalvm.compiler.code.DataSection; import org.graalvm.compiler.debug.DebugContext; @@ -69,6 +68,7 @@ import com.oracle.svm.core.graal.code.SubstrateDataBuilder; import com.oracle.svm.core.meta.SubstrateObjectConstant; import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.option.HostedOptionValues; import com.oracle.svm.core.util.Counter; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.NativeImageOptions; @@ -219,6 +219,23 @@ public void buildRuntimeMetadata(CFunctionPointer firstMethod, UnsignedWord code codeInfoEncoder.addMethod(method, compilation, method.getCodeAddressOffset()); } + for (HostedType type : imageHeap.getUniverse().getTypes()) { + if (type.getWrapped().isReachable()) { + codeInfoEncoder.addClass(type.getJavaClass()); + } + } + for (HostedType type : imageHeap.getUniverse().getTypes()) { + Class javaClass = null; + try { + javaClass = type.getJavaClass(); + } catch (Throwable t) { + System.out.println("Unknown type " + type + ": " + t); + } + if (javaClass != null && type.getWrapped().isReachable()) { + codeInfoEncoder.addMethodMetadata(javaClass, type.getTypeID()); + } + } + if (NativeImageOptions.PrintMethodHistogram.getValue()) { System.out.println("encoded deopt entry points ; " + frameInfoCustomization.numDeoptEntryPoints); System.out.println("encoded during call entry points ; " + frameInfoCustomization.numDuringCallEntryPoints); diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ExecutableAccessorComputer.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ExecutableAccessorComputer.java index 688788956b75f..0704c9d55e929 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ExecutableAccessorComputer.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ExecutableAccessorComputer.java @@ -56,8 +56,8 @@ public Object compute(MetaAccessProvider metaAccess, ResolvedJavaField original, ReflectionSubstitution subst = ImageSingletons.lookup(ReflectionSubstitution.class); Class proxyClass = subst.getProxyClass(member); if (proxyClass == null) { - // should never happen, but better check for it here than segfault later - throw VMError.shouldNotReachHere(); + // means the proxy didn't get included in the image + return null; } try { Proxy proxyInstance = (Proxy) UNSAFE.allocateInstance(proxyClass); diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java index 1ebe76596bed3..5d107b7db3ab1 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java @@ -45,6 +45,7 @@ import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.hub.ClassForNameSupport; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.jdk.RecordSupport; @@ -69,6 +70,7 @@ public class ReflectionDataBuilder implements RuntimeReflectionSupport { private final Set> reflectionClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set reflectionMethods = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set reflectionFields = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Set queriedMethods; // TODO where is better? /* Keep track of classes already processed for reflection. */ private final Set> processedClasses = new HashSet<>(); @@ -78,6 +80,7 @@ public class ReflectionDataBuilder implements RuntimeReflectionSupport { public ReflectionDataBuilder(FeatureAccessImpl access) { arrayReflectionData = getArrayReflectionData(); accessors = new ReflectionDataAccessors(access); + queriedMethods = SubstrateOptions.ConfigureReflectionMetadata.getValue() ? ConcurrentHashMap.newKeySet() : null; } private static DynamicHub.ReflectionData getArrayReflectionData() { @@ -127,6 +130,19 @@ public void register(Executable... methods) { } } + @Override + public void registerAsQueried(Executable... methods) { + checkNotSealed(); + if (!SubstrateOptions.ConfigureReflectionMetadata.getValue()) { + throw UserError.abort("Found manual reflection metadata configuration. Please use --configure-reflection-metadata to enable this behavior."); + } + for (Executable method : methods) { + if (queriedMethods.add(method)) { + modifiedClasses.add(method.getDeclaringClass()); + } + } + } + @Override public void register(boolean finalIsWritable, Field... fields) { checkNotSealed(); diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Method.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Method.java index d999d9a99c2f4..6cd8b9e33eddd 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Method.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Method.java @@ -60,7 +60,7 @@ public final class Target_java_lang_reflect_Method { @Substitute public Target_jdk_internal_reflect_MethodAccessor acquireMethodAccessor() { if (methodAccessor == null) { - throw VMError.unsupportedFeature("Runtime reflection is not supported."); + throw VMError.unsupportedFeature("Runtime reflection is not supported for " + this); } return methodAccessor; }