From d47616ef67e11486ac17281189e27550cd34abe1 Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Wed, 28 May 2025 12:37:23 +0200 Subject: [PATCH] Enable lambda reflection queries --- .../native-image/ReachabilityMetadata.md | 34 ++++++ .../reachability-metadata-schema-v1.1.0.json | 41 ++++++- substratevm/CHANGELOG.md | 3 +- .../svm/agent/BreakpointInterceptor.java | 38 +++++-- .../oracle/svm/agent/JniCallInterceptor.java | 5 +- .../svm/configure/ConfigurationParser.java | 66 +++++++++++- .../ConfigurationTypeDescriptor.java | 3 +- .../LambdaConfigurationTypeDescriptor.java | 99 +++++++++++++++++ .../ReflectionConfigurationParser.java | 1 - ...ReflectionConfigurationParserDelegate.java | 2 + .../configure/ReflectionMetadataParser.java | 86 +++++++-------- .../SerializationConfigurationParser.java | 2 + .../config/ParserConfigurationAdapter.java | 6 ++ .../configure/trace/AbstractProcessor.java | 35 ++++++ .../svm/configure/trace/AccessAdvisor.java | 3 +- .../svm/configure/trace/JniProcessor.java | 22 ++-- .../svm/configure/trace/JsonFileWriter.java | 5 +- .../configure/trace/ReflectionProcessor.java | 14 +-- .../config/ReflectionRegistryAdapter.java | 13 ++- .../svm/hosted/config/RegistryAdapter.java | 102 ++++++++++++++++-- .../svm/hosted/lambda/LambdaParser.java | 26 +++++ .../src/com/oracle/svm/util/TypeResult.java | 24 +++++ 22 files changed, 533 insertions(+), 97 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/LambdaConfigurationTypeDescriptor.java diff --git a/docs/reference-manual/native-image/ReachabilityMetadata.md b/docs/reference-manual/native-image/ReachabilityMetadata.md index 2710ba0316b5..83a5bfd81d78 100644 --- a/docs/reference-manual/native-image/ReachabilityMetadata.md +++ b/docs/reference-manual/native-image/ReachabilityMetadata.md @@ -241,6 +241,40 @@ Metadata, for proxy classes, is in the form an ordered collection of interfaces } ``` +To provide metadata for a lambda class, the following metadata must be added to the `reflection` array in +_reachability-metadata.json_ + +```json +{ + "type": { + "lambda": { + "declaringClass": "FullyQualifiedLambdaDeclaringType", + "declaringMethod": { + "name": "declaringMethodName", + "parameterType": [ + "FullyQualifiedParameterType1", + "...", + "FullyQualifiedParameterType2" + ] + }, + "interfaces": [ + "FullyQualifiedLambdaInterface1", + "...", + "FullyQualifiedLamdbaInterface2" + ] + } + } +} +``` + +The `"declaringClass"` field specifies in which class, and the optional `"declaringMethod"` field specifies in which +method the lambda is defined. +If `"declaringMethod"` is not specified, the lambda class is searched through all methods of the specified declaring +class. +The `"interfaces"` field specifies which interfaces are implemented by the lambda class. +Such a definition can match multiple lambda classes. If that is the case, the registration entry applies to all those +classes. + Invocation of methods above without the provided metadata will result in throwing `MissingReflectionRegistrationError` which extends `java.lang.Error` and should not be handled. Note that even if a type does not exist on the classpath, the methods above will throw a `MissingReflectionRegistrationError`. diff --git a/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json b/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json index 5677283e3307..9e3933791841 100644 --- a/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json +++ b/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json @@ -355,10 +355,47 @@ "title": "Fully qualified name of the interface defining the proxy class", "type": "string" } + }, + "lambda": { + "title": "Lambda class descriptor", + "type": "object", + "properties": { + "declaringClass": { + "title": "The class in which the lambda class is defined", + "type": "#/$defs/type" + }, + "declaringMethod": { + "title": "The method in which the lambda class is defined", + "type": "#/$defs/method", + "default": {} + }, + "interfaces": { + "title": "Non-empty list of interfaces implemented by the lambda class", + "type": "array", + "items": { + "title": "Fully qualified name of the interface implemented by the lambda class", + "type": "string" + } + }, + "required": [ + "declaringClass", + "interfaces" + ], + "additionalProperties": false + } } }, - "required": [ - "proxy" + "oneOf": [ + { + "required": [ + "proxy" + ] + }, + { + "required": [ + "lambda" + ] + } ], "additionalProperties": false } diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 2028618b0f67..a8f87a133250 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -26,11 +26,12 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-60208) Adds the Tracing Agent support for applications using the Foreign Function & Memory (FFM) API. The agent generates FFM configuration in _foreign-config.json_. Additionally, support for FFM configurations has been added to the `native-image-configure` tool. * (GR-64787) Enable `--install-exit-handlers` by default for executables and deprecate the option. If shared libraries were using this flag, the same functionality can be restored by using `-H:+InstallExitHandlers`. * (GR-47881) Remove the total number of loaded types, fields, and methods from the build output, deprecated these metrics in the build output schema, and removed already deprecated build output metrics. -* (GR-64619) Missing registration errors are now subclasses of `LinkageError` +* (GR-64619) Missing registration errors are now subclasses of `LinkageError`. * (GR-63591) Resource bundle registration is now included as part of the `"resources"` section of _reachability-metadata.json_. When this is the case, the bundle name is specified using the `"bundle"` field. * (GR-57827) Move the initialization of security providers from build time to runtime. * (GR-57827) Security providers can now be initialized at run time (instead of build time) when using the option `--future-defaults=all` or `--future-defaults=run-time-initialized-jdk`. Run-time initialization of security providers helps reduce image heap size by avoiding unnecessary objects inclusion. +* (GR-48191) Enable lambda classes to be registered for reflection and serialization in _reachability-metadata.json_. The format is detailed [here](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/ReachabilityMetadata.md). ## GraalVM for JDK 24 (Internal Version 24.2.0) * (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning. 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 3c0e11c89e0a..9b032e46f43d 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 @@ -80,6 +80,9 @@ import com.oracle.svm.agent.stackaccess.EagerlyLoadedJavaStackAccess; import com.oracle.svm.agent.stackaccess.InterceptedState; import com.oracle.svm.agent.tracing.core.Tracer; +import com.oracle.svm.configure.LambdaConfigurationTypeDescriptor; +import com.oracle.svm.configure.NamedConfigurationTypeDescriptor; +import com.oracle.svm.configure.ProxyConfigurationTypeDescriptor; import com.oracle.svm.configure.trace.AccessAdvisor; import com.oracle.svm.core.c.function.CEntryPointOptions; import com.oracle.svm.core.jni.headers.JNIEnvironment; @@ -194,8 +197,8 @@ private static void traceBreakpoint(JNIEnvironment env, String context, JNIObjec if (tracer != null) { tracer.traceCall(context, function, - getClassOrProxyInterfaceNames(env, clazz), - getClassOrProxyInterfaceNames(env, declaringClass), + getTypeDescriptor(env, clazz), + getTypeDescriptor(env, declaringClass), getClassNameOr(env, callerClass, null, Tracer.UNKNOWN_VALUE), result, stackTrace, @@ -224,7 +227,7 @@ private static void traceBreakpoint(JNIEnvironment env, String context, JNIObjec * @return The interface, or the original class if it is not a proxy or implements multiple * interfaces. */ - private static Object getClassOrProxyInterfaceNames(JNIEnvironment env, JNIObjectHandle clazz) { + static Object getTypeDescriptor(JNIEnvironment env, JNIObjectHandle clazz) { if (clazz.equal(nullHandle())) { return null; } @@ -233,17 +236,30 @@ private static Object getClassOrProxyInterfaceNames(JNIEnvironment env, JNIObjec if (Support.clearException(env)) { return Tracer.UNKNOWN_VALUE; } - - if (!isProxy) { - return getClassNameOr(env, clazz, null, Tracer.UNKNOWN_VALUE); + String className = getClassNameOr(env, clazz, null, Tracer.UNKNOWN_VALUE); + if (className == null || className.equals(Tracer.UNKNOWN_VALUE)) { + return className; } - - JNIObjectHandle interfaces = Support.callObjectMethod(env, clazz, agent.handles().javaLangClassGetInterfaces); - if (Support.clearException(env)) { - return Tracer.UNKNOWN_VALUE; + boolean isLambda = className.contains(LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING); + if (isProxy || isLambda) { + JNIObjectHandle interfaces = Support.callObjectMethod(env, clazz, agent.handles().javaLangClassGetInterfaces); + if (Support.clearException(env)) { + return Tracer.UNKNOWN_VALUE; + } + Object interfaceNames = getClassArrayNames(env, interfaces); + if (interfaceNames.equals(Tracer.EXPLICIT_NULL) || interfaceNames.equals(Tracer.UNKNOWN_VALUE)) { + return interfaceNames; + } + List interfaceNameString = Arrays.asList((String[]) interfaceNames); + if (isProxy) { + return ProxyConfigurationTypeDescriptor.fromInterfaceReflectionNames(interfaceNameString); + } else if (isLambda) { + String declaringClass = className.substring(0, className.indexOf(LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING)); + return LambdaConfigurationTypeDescriptor.fromReflectionNames(declaringClass, interfaceNameString); + } } - return getClassArrayNames(env, interfaces); + return NamedConfigurationTypeDescriptor.fromReflectionName(className); } private static boolean forName(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/JniCallInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/JniCallInterceptor.java index 43ea95289287..8c71be9fd90a 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/JniCallInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/JniCallInterceptor.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.agent; +import static com.oracle.svm.agent.BreakpointInterceptor.getTypeDescriptor; import static com.oracle.svm.core.jni.JNIObjectHandles.nullHandle; import static com.oracle.svm.jvmtiagentbase.Support.check; import static com.oracle.svm.jvmtiagentbase.Support.checkJni; @@ -94,8 +95,8 @@ private static void traceCall(JNIEnvironment env, String function, JNIObjectHand tracer.traceCall("jni", function, - getClassNameOr(env, clazz, null, Tracer.UNKNOWN_VALUE), - getClassNameOr(env, declaringClass, null, Tracer.UNKNOWN_VALUE), + getTypeDescriptor(env, clazz), + getTypeDescriptor(env, declaringClass), getClassNameOr(env, callerClass, null, Tracer.UNKNOWN_VALUE), result, state.getFullStackTraceOrNull(), diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationParser.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationParser.java index 105196125e1c..d01fb36bafd3 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationParser.java @@ -32,8 +32,10 @@ import java.io.Reader; import java.net.URI; import java.net.URL; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -49,6 +51,9 @@ import jdk.graal.compiler.util.json.JsonParser; import jdk.graal.compiler.util.json.JsonParserException; +import jdk.graal.compiler.util.json.JsonPrintable; +import jdk.graal.compiler.util.json.JsonPrinter; +import jdk.graal.compiler.util.json.JsonWriter; public abstract class ConfigurationParser { public static InputStream openStream(URI uri) throws IOException { @@ -63,6 +68,11 @@ public static InputStream openStream(URI uri) throws IOException { public static final String NAME_KEY = "name"; public static final String TYPE_KEY = "type"; public static final String PROXY_KEY = "proxy"; + public static final String LAMBDA_KEY = "lambda"; + public static final String DECLARING_CLASS_KEY = "declaringClass"; + public static final String DECLARING_METHOD_KEY = "declaringMethod"; + public static final String INTERFACES_KEY = "interfaces"; + public static final String PARAMETER_TYPES_KEY = "parameterTypes"; public static final String REFLECTION_KEY = "reflection"; public static final String JNI_KEY = "jni"; public static final String FOREIGN_KEY = "foreign"; @@ -252,7 +262,7 @@ protected static Optional parseName(EconomicMap parseTypeContents(Object typeObject) { + protected Optional parseTypeContents(Object typeObject) { if (typeObject instanceof String stringValue) { return Optional.of(NamedConfigurationTypeDescriptor.fromJSONName(stringValue)); } else { @@ -260,12 +270,14 @@ protected static Optional parseTypeContents(Object if (type.containsKey(PROXY_KEY)) { checkHasExactlyOneAttribute(type, "type descriptor object", Set.of(PROXY_KEY)); return Optional.of(getProxyDescriptor(type.get(PROXY_KEY))); + } else if (type.containsKey(LAMBDA_KEY)) { + return Optional.of(getLambdaDescriptor(type.get(LAMBDA_KEY))); } /* * We return if we find a future version of a type descriptor (as a JSON object) instead * of failing parsing. */ - // TODO warn + // TODO warn (GR-65606) return Optional.empty(); } } @@ -275,4 +287,54 @@ private static ProxyConfigurationTypeDescriptor getProxyDescriptor(Object proxyO List proxyInterfaceNames = proxyInterfaces.stream().map(obj -> asString(obj, "proxy")).toList(); return ProxyConfigurationTypeDescriptor.fromInterfaceTypeNames(proxyInterfaceNames); } + + private LambdaConfigurationTypeDescriptor getLambdaDescriptor(Object lambdaObject) { + EconomicMap lambda = asMap(lambdaObject, "lambda type descriptor should be an object"); + checkAttributes(lambda, "lambda descriptor object", List.of(DECLARING_CLASS_KEY, INTERFACES_KEY), List.of(DECLARING_METHOD_KEY)); + Optional declaringType = parseTypeContents(lambda.get(DECLARING_CLASS_KEY)); + if (declaringType.isEmpty()) { + throw new JsonParserException("Could not parse lambda declaring type"); + } + ConfigurationMethodDescriptor method = null; + if (lambda.containsKey(DECLARING_METHOD_KEY)) { + EconomicMap methodObject = asMap(lambda.get(DECLARING_METHOD_KEY), "lambda declaring method descriptor should be an object"); + method = parseMethod(methodObject); + } + List interfaceNames = asList(lambda.get(INTERFACES_KEY), "lambda implemented interfaces must be specified"); + if (interfaceNames.isEmpty()) { + throw new JsonParserException("Lambda interfaces must not be empty"); + } + List interfaces = interfaceNames.stream().map(s -> NamedConfigurationTypeDescriptor.fromJSONName(asString(s))).toList(); + return new LambdaConfigurationTypeDescriptor(declaringType.get(), method, interfaces); + } + + public record ConfigurationMethodDescriptor(String name, List parameterTypes) implements JsonPrintable, Comparable { + @Override + public int compareTo(ConfigurationMethodDescriptor other) { + return Comparator.comparing(ConfigurationMethodDescriptor::name) + .thenComparing((a, b) -> Arrays.compare(a.parameterTypes.toArray(ConfigurationTypeDescriptor[]::new), b.parameterTypes.toArray(ConfigurationTypeDescriptor[]::new))) + .compare(this, other); + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.appendObjectStart(); + writer.quote(NAME_KEY).appendFieldSeparator().quote(name); + if (parameterTypes != null) { + writer.appendSeparator().quote(PARAMETER_TYPES_KEY).appendFieldSeparator(); + JsonPrinter.printCollection(writer, parameterTypes, ConfigurationTypeDescriptor::compareTo, ConfigurationTypeDescriptor::printJson); + } + } + } + + protected ConfigurationMethodDescriptor parseMethod(EconomicMap methodJson) { + checkAttributes(methodJson, "method descriptor", List.of(NAME_KEY), List.of(PARAMETER_TYPES_KEY)); + String name = asString(methodJson.get(NAME_KEY)); + List parameterTypes = null; + if (methodJson.containsKey(PARAMETER_TYPES_KEY)) { + List parameterTypesStrings = asList(methodJson.get(PARAMETER_TYPES_KEY), "parameter types list"); + parameterTypes = parameterTypesStrings.stream().map(s -> NamedConfigurationTypeDescriptor.fromJSONName(asString(s))).toList(); + } + return new ConfigurationMethodDescriptor(name, parameterTypes); + } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTypeDescriptor.java index 322f91a97460..64a71e5dfa56 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTypeDescriptor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTypeDescriptor.java @@ -40,7 +40,8 @@ public interface ConfigurationTypeDescriptor extends Comparable, JsonPrintable { enum Kind { NAMED, - PROXY + PROXY, + LAMBDA } Kind getDescriptorType(); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/LambdaConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/LambdaConfigurationTypeDescriptor.java new file mode 100644 index 000000000000..0af02d4e5bb7 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/LambdaConfigurationTypeDescriptor.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.configure; + +import static com.oracle.svm.configure.ConfigurationParser.DECLARING_CLASS_KEY; +import static com.oracle.svm.configure.ConfigurationParser.DECLARING_METHOD_KEY; +import static com.oracle.svm.configure.ConfigurationParser.INTERFACES_KEY; +import static com.oracle.svm.configure.ConfigurationParser.LAMBDA_KEY; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; + +import jdk.graal.compiler.util.json.JsonPrinter; +import jdk.graal.compiler.util.json.JsonWriter; + +public record LambdaConfigurationTypeDescriptor(ConfigurationTypeDescriptor declaringClass, ConfigurationParser.ConfigurationMethodDescriptor declaringMethod, + List interfaces) implements ConfigurationTypeDescriptor { + public static LambdaConfigurationTypeDescriptor fromReflectionNames(String declaringClass, List interfaces) { + return new LambdaConfigurationTypeDescriptor(NamedConfigurationTypeDescriptor.fromReflectionName(declaringClass), null, + interfaces.stream().map(NamedConfigurationTypeDescriptor::fromReflectionName).toList()); + } + + public static LambdaConfigurationTypeDescriptor fromTypeNames(String declaringClass, List interfaces) { + return new LambdaConfigurationTypeDescriptor(NamedConfigurationTypeDescriptor.fromTypeName(declaringClass), null, + interfaces.stream().map(NamedConfigurationTypeDescriptor::fromTypeName).toList()); + } + + @Override + public Kind getDescriptorType() { + return Kind.LAMBDA; + } + + @Override + public Collection getAllQualifiedJavaNames() { + List allNames = new ArrayList<>(declaringClass.getAllQualifiedJavaNames()); + for (ConfigurationTypeDescriptor intf : interfaces) { + allNames.addAll(intf.getAllQualifiedJavaNames()); + } + return allNames; + } + + @Override + public int compareTo(ConfigurationTypeDescriptor other) { + if (other instanceof LambdaConfigurationTypeDescriptor lambdaOther) { + return Comparator.comparing(LambdaConfigurationTypeDescriptor::declaringClass) + .thenComparing(LambdaConfigurationTypeDescriptor::declaringMethod, Comparator.nullsFirst(ConfigurationParser.ConfigurationMethodDescriptor::compareTo)) + .thenComparing((a, b) -> Arrays.compare(a.interfaces.toArray(ConfigurationTypeDescriptor[]::new), b.interfaces.toArray(ConfigurationTypeDescriptor[]::new))) + .compare(this, lambdaOther); + } else { + return getDescriptorType().compareTo(other.getDescriptorType()); + } + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.appendObjectStart().quote(LAMBDA_KEY).appendFieldSeparator().appendObjectStart(); + + writer.quote(DECLARING_CLASS_KEY).appendFieldSeparator(); + declaringClass.printJson(writer); + writer.appendSeparator(); + + if (declaringMethod != null) { + writer.quote(DECLARING_METHOD_KEY).appendFieldSeparator(); + declaringMethod.printJson(writer); + writer.appendSeparator(); + } + + writer.quote(INTERFACES_KEY).appendFieldSeparator(); + JsonPrinter.printCollection(writer, interfaces, ConfigurationTypeDescriptor::compareTo, ConfigurationTypeDescriptor::printJson); + + writer.appendObjectEnd().appendObjectEnd(); + } +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ReflectionConfigurationParser.java index d71b50dcbe63..d75220033b8b 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ReflectionConfigurationParser.java @@ -45,7 +45,6 @@ */ public abstract class ReflectionConfigurationParser extends ConditionalConfigurationParser { private static final String CONSTRUCTOR_NAME = ""; - private static final String PARAMETER_TYPES_KEY = "parameterTypes"; protected final ConfigurationConditionResolver conditionResolver; protected final ReflectionConfigurationParserDelegate delegate; diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ReflectionConfigurationParserDelegate.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ReflectionConfigurationParserDelegate.java index accf8176c8ec..b0572563d322 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ReflectionConfigurationParserDelegate.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ReflectionConfigurationParserDelegate.java @@ -32,6 +32,8 @@ public interface ReflectionConfigurationParserDelegate { TypeResult resolveType(C condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives, boolean jniAccessible); + TypeResult> resolveTypes(C condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives, boolean jniAccessible); + void registerType(C condition, T type); void registerPublicClasses(C condition, T type); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ReflectionMetadataParser.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ReflectionMetadataParser.java index 8c8ca28d06a7..d5a619dee799 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ReflectionMetadataParser.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ReflectionMetadataParser.java @@ -77,59 +77,61 @@ protected void parseClass(EconomicMap data) { * Even if primitives cannot be queried through Class.forName, they can be registered to * allow getDeclaredMethods() and similar bulk queries at run time. */ - TypeResult result = delegate.resolveType(condition, type.get(), true, typeJniAccessible); + TypeResult> result = delegate.resolveTypes(condition, type.get(), true, typeJniAccessible); if (!result.isPresent()) { handleMissingElement("Could not resolve " + type.get() + " for reflection configuration.", result.getException()); return; } C queryCondition = conditionResolver.alwaysTrue(); - T clazz = result.get(); - delegate.registerType(conditionResult.get(), clazz); + List classes = result.get(); + for (T clazz : classes) { + delegate.registerType(conditionResult.get(), clazz); - delegate.registerDeclaredClasses(queryCondition, clazz); - delegate.registerPublicClasses(queryCondition, clazz); - if (!jniParser) { - delegate.registerRecordComponents(queryCondition, clazz); - delegate.registerPermittedSubclasses(queryCondition, clazz); - delegate.registerNestMembers(queryCondition, clazz); - delegate.registerSigners(queryCondition, clazz); - } - delegate.registerDeclaredConstructors(queryCondition, true, jniParser, clazz); - delegate.registerPublicConstructors(queryCondition, true, jniParser, clazz); - delegate.registerDeclaredMethods(queryCondition, true, jniParser, clazz); - delegate.registerPublicMethods(queryCondition, true, jniParser, clazz); - delegate.registerDeclaredFields(queryCondition, true, jniParser, clazz); - delegate.registerPublicFields(queryCondition, true, jniParser, clazz); + delegate.registerDeclaredClasses(queryCondition, clazz); + delegate.registerPublicClasses(queryCondition, clazz); + if (!jniParser) { + delegate.registerRecordComponents(queryCondition, clazz); + delegate.registerPermittedSubclasses(queryCondition, clazz); + delegate.registerNestMembers(queryCondition, clazz); + delegate.registerSigners(queryCondition, clazz); + } + delegate.registerDeclaredConstructors(queryCondition, true, jniParser, clazz); + delegate.registerPublicConstructors(queryCondition, true, jniParser, clazz); + delegate.registerDeclaredMethods(queryCondition, true, jniParser, clazz); + delegate.registerPublicMethods(queryCondition, true, jniParser, clazz); + delegate.registerDeclaredFields(queryCondition, true, jniParser, clazz); + delegate.registerPublicFields(queryCondition, true, jniParser, clazz); - if (!jniParser) { - registerIfNotDefault(data, false, clazz, "serializable", () -> delegate.registerAsSerializable(condition, clazz)); - registerIfNotDefault(data, false, clazz, "jniAccessible", () -> delegate.registerAsJniAccessed(condition, clazz)); - } + if (!jniParser) { + registerIfNotDefault(data, false, clazz, "serializable", () -> delegate.registerAsSerializable(condition, clazz)); + registerIfNotDefault(data, false, clazz, "jniAccessible", () -> delegate.registerAsJniAccessed(condition, clazz)); + } - registerIfNotDefault(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, typeJniAccessible, clazz)); - registerIfNotDefault(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, typeJniAccessible, clazz)); - registerIfNotDefault(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, typeJniAccessible, clazz)); - registerIfNotDefault(data, false, clazz, "allPublicMethods", () -> delegate.registerPublicMethods(condition, false, typeJniAccessible, clazz)); - registerIfNotDefault(data, false, clazz, "allDeclaredFields", () -> delegate.registerDeclaredFields(condition, false, typeJniAccessible, clazz)); - registerIfNotDefault(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, typeJniAccessible, clazz)); - registerIfNotDefault(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, typeJniAccessible, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, typeJniAccessible, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, typeJniAccessible, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicMethods", () -> delegate.registerPublicMethods(condition, false, typeJniAccessible, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredFields", () -> delegate.registerDeclaredFields(condition, false, typeJniAccessible, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, typeJniAccessible, clazz)); + registerIfNotDefault(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz)); - MapCursor cursor = data.getEntries(); - while (cursor.advance()) { - String name = cursor.getKey(); - Object value = cursor.getValue(); - try { - switch (name) { - case "methods": - parseMethods(condition, false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz, typeJniAccessible); - break; - case "fields": - parseFields(condition, asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz, typeJniAccessible); - break; + MapCursor cursor = data.getEntries(); + while (cursor.advance()) { + String name = cursor.getKey(); + Object value = cursor.getValue(); + try { + switch (name) { + case "methods": + parseMethods(condition, false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz, typeJniAccessible); + break; + case "fields": + parseFields(condition, asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz, typeJniAccessible); + break; + } + } catch (LinkageError e) { + handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + name + " for reflection.", e); } - } catch (LinkageError e) { - handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + name + " for reflection.", e); } } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/SerializationConfigurationParser.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/SerializationConfigurationParser.java index cf9ba53e4301..1f35e357bfc2 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/SerializationConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/SerializationConfigurationParser.java @@ -68,6 +68,8 @@ protected void registerType(ConfigurationTypeDescriptor targetSerializationClass switch (targetSerializationClass.getDescriptorType()) { case NAMED -> serializationSupport.register(condition, ((NamedConfigurationTypeDescriptor) targetSerializationClass).name()); case PROXY -> serializationSupport.registerProxyClass(condition, ((ProxyConfigurationTypeDescriptor) targetSerializationClass).interfaceNames()); + case LAMBDA -> serializationSupport.registerLambdaCapturingClass(condition, + ((NamedConfigurationTypeDescriptor) ((LambdaConfigurationTypeDescriptor) targetSerializationClass).declaringClass()).name()); } } } 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 cd6707bc10ed..cc9c8ca78a18 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 @@ -52,6 +52,12 @@ public TypeResult resolveType(UnresolvedConfigurationConditio return TypeResult.forType(typeDescriptor.toString(), result); } + @Override + public TypeResult> resolveTypes(UnresolvedConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives, boolean jniAccessible) { + TypeResult result = resolveType(condition, typeDescriptor, allowPrimitives, jniAccessible); + return TypeResult.toList(result); + } + @Override public void registerType(UnresolvedConfigurationCondition condition, ConfigurationType type) { checkArguments(condition.equals(type.getCondition()), "condition is already a part of the type"); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AbstractProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AbstractProcessor.java index 1aa10532c4b6..ab37f9e1b7ec 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AbstractProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AbstractProcessor.java @@ -24,12 +24,21 @@ */ package com.oracle.svm.configure.trace; +import static com.oracle.svm.configure.ConfigurationParser.DECLARING_CLASS_KEY; +import static com.oracle.svm.configure.ConfigurationParser.INTERFACES_KEY; +import static com.oracle.svm.configure.ConfigurationParser.LAMBDA_KEY; +import static com.oracle.svm.configure.ConfigurationParser.PROXY_KEY; + import java.util.Base64; import java.util.Collection; import java.util.List; import org.graalvm.collections.EconomicMap; +import com.oracle.svm.configure.ConfigurationTypeDescriptor; +import com.oracle.svm.configure.LambdaConfigurationTypeDescriptor; +import com.oracle.svm.configure.NamedConfigurationTypeDescriptor; +import com.oracle.svm.configure.ProxyConfigurationTypeDescriptor; import com.oracle.svm.configure.config.ConfigurationSet; public abstract class AbstractProcessor { @@ -72,4 +81,30 @@ protected EconomicMap copyWithUniqueEntry(EconomicMap) { + EconomicMap map = (EconomicMap) clazz; + if (map.containsKey(LAMBDA_KEY)) { + String declaringClass = (String) map.get(DECLARING_CLASS_KEY); + List interfaces = (List) map.get(INTERFACES_KEY); + return LambdaConfigurationTypeDescriptor.fromTypeNames(declaringClass, interfaces); + } else if (map.containsKey(PROXY_KEY)) { + List interfaces = (List) map.get(PROXY_KEY); + return ProxyConfigurationTypeDescriptor.fromInterfaceTypeNames(interfaces); + } else { + throw new IllegalArgumentException("Unknown descriptor type: " + clazz); + } + } else if (clazz instanceof List) { + return ProxyConfigurationTypeDescriptor.fromInterfaceReflectionNames(((List) clazz)); + } else { + return NamedConfigurationTypeDescriptor.fromReflectionName((String) clazz); + } + } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java index 0e3b2bc0cc26..d5e263c1457f 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java @@ -302,7 +302,8 @@ public boolean shouldIgnoreJniLookup(String jniFunction, LazyValue queri public boolean shouldIgnoreResourceLookup(LazyValue resource, EconomicMap entry) { boolean result = Set.of("META-INF/services/jdk.vm.ci.services.JVMCIServiceLocator", "META-INF/services/java.lang.System$LoggerFinder", - "META-INF/services/jdk.vm.ci.hotspot.HotSpotJVMCIBackendFactory").contains(resource.get()); + "META-INF/services/jdk.vm.ci.hotspot.HotSpotJVMCIBackendFactory", "META-INF/services/jdk.graal.compiler.options.OptionDescriptors", + "META-INF/services/com.oracle.graal.phases.preciseinline.priorityinline.PolicyFactory").contains(resource.get()); if (result) { logIgnoredEntry("blocklisted resource", entry); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java index 2b32481f7b99..2f54f3427c5a 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java @@ -81,13 +81,12 @@ void processEntry(EconomicMap entry, ConfigurationSet configurat } return; } - String className = (String) entry.get("class"); - ConfigurationTypeDescriptor clazz = NamedConfigurationTypeDescriptor.fromReflectionName(className); - if (advisor.shouldIgnore(lazyValue(className), callerClassLazyValue, entry)) { + ConfigurationTypeDescriptor clazz = descriptorForClass(entry.get("class")); + if (clazz.getAllQualifiedJavaNames().stream().anyMatch(c -> advisor.shouldIgnore(lazyValue(c), callerClassLazyValue, entry))) { return; } boolean hasDeclaringClass = entry.containsKey("declaring_class"); - ConfigurationTypeDescriptor declaringClass = hasDeclaringClass ? NamedConfigurationTypeDescriptor.fromReflectionName((String) entry.get("declaring_class")) : null; + ConfigurationTypeDescriptor declaringClass = hasDeclaringClass ? descriptorForClass(entry.get("declaring_class")) : null; ConfigurationTypeDescriptor declaringClassOrClazz = hasDeclaringClass ? declaringClass : clazz; ConfigurationMemberDeclaration declaration = hasDeclaringClass ? ConfigurationMemberDeclaration.DECLARED : ConfigurationMemberDeclaration.PRESENT; TypeConfiguration config = configurationSet.getReflectionConfiguration(); @@ -106,7 +105,8 @@ void processEntry(EconomicMap entry, ConfigurationSet configurat expectSize(args, 2); String name = (String) args.get(0); String signature = (String) args.get(1); - if (!advisor.shouldIgnoreJniLookup(function, lazyValue(className), lazyValue(name), lazyValue(signature), callerClassLazyValue, entry)) { + if (clazz.getAllQualifiedJavaNames().stream() + .noneMatch(c -> advisor.shouldIgnoreJniLookup(function, lazyValue(c), lazyValue(name), lazyValue(signature), callerClassLazyValue, entry))) { ConfigurationType type = getOrCreateJniAccessibleType(config, condition, declaringClassOrClazz); type.addMethod(name, signature, declaration); if (!declaringClassOrClazz.equals(clazz)) { @@ -120,7 +120,8 @@ void processEntry(EconomicMap entry, ConfigurationSet configurat expectSize(args, 2); String name = (String) args.get(0); String signature = (String) args.get(1); - if (!advisor.shouldIgnoreJniLookup(function, lazyValue(className), lazyValue(name), lazyValue(signature), callerClassLazyValue, entry)) { + if (clazz.getAllQualifiedJavaNames().stream() + .noneMatch(c -> advisor.shouldIgnoreJniLookup(function, lazyValue(c), lazyValue(name), lazyValue(signature), callerClassLazyValue, entry))) { ConfigurationType type = getOrCreateJniAccessibleType(config, condition, declaringClassOrClazz); type.addField(name, declaration, false); if (!declaringClassOrClazz.equals(clazz)) { @@ -133,7 +134,8 @@ void processEntry(EconomicMap entry, ConfigurationSet configurat expectSize(args, 1); // exception message, ignore String name = ConfigurationMethod.CONSTRUCTOR_NAME; String signature = "(Ljava/lang/String;)V"; - if (!advisor.shouldIgnoreJniLookup(function, lazyValue(className), lazyValue(name), lazyValue(signature), callerClassLazyValue, entry)) { + if (clazz.getAllQualifiedJavaNames().stream() + .noneMatch(c -> advisor.shouldIgnoreJniLookup(function, lazyValue(c), lazyValue(name), lazyValue(signature), callerClassLazyValue, entry))) { ConfigurationType type = getOrCreateJniAccessibleType(config, condition, declaringClassOrClazz); type.addMethod(name, signature, declaration); assert declaringClassOrClazz.equals(clazz) : "Constructor can only be accessed via declaring class"; @@ -169,8 +171,10 @@ void processEntry(EconomicMap entry, ConfigurationSet configurat } case "NewObjectArray": { expectSize(args, 0); - /* Array class name is already in Class.forName format */ - config.getOrCreateType(condition, clazz); + if (clazz.getAllQualifiedJavaNames().stream().noneMatch(c -> advisor.shouldIgnoreJniLookup(function, lazyValue(c), null, null, callerClassLazyValue, entry))) { + /* Array class name is already in Class.forName format */ + config.getOrCreateType(condition, clazz); + } break; } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JsonFileWriter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JsonFileWriter.java index ad967471d6fa..b06c6994df68 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JsonFileWriter.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JsonFileWriter.java @@ -35,6 +35,7 @@ import org.graalvm.collections.EconomicMap; import org.graalvm.collections.MapCursor; +import jdk.graal.compiler.util.json.JsonPrintable; import jdk.graal.compiler.util.json.JsonWriter; public class JsonFileWriter implements Closeable { @@ -62,7 +63,9 @@ public void printObject(EconomicMap entry) { json.append(", "); } json.quote(cursor.getKey()).append(':'); - if (cursor.getValue() instanceof Object[]) { + if (cursor.getValue() instanceof JsonPrintable value) { + value.printJson(json); + } else if (cursor.getValue() instanceof Object[]) { printArray(json, (Object[]) cursor.getValue()); } else { printValue(json, cursor.getValue()); 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 5d1daa89bbad..9de762cdef69 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 @@ -35,7 +35,6 @@ import com.oracle.svm.configure.ClassNameSupport; import com.oracle.svm.configure.ConfigurationTypeDescriptor; import com.oracle.svm.configure.NamedConfigurationTypeDescriptor; -import com.oracle.svm.configure.ProxyConfigurationTypeDescriptor; import com.oracle.svm.configure.UnresolvedConfigurationCondition; import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility; import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; @@ -119,7 +118,7 @@ public void processEntry(EconomicMap entry, ConfigurationSet con ConfigurationTypeDescriptor clazz = descriptorForClass(entry.get("class")); if (clazz != null) { for (String className : clazz.getAllQualifiedJavaNames()) { - if (advisor.shouldIgnore(lazyValue(className), lazyValue(callerClass), copyWithUniqueEntry(entry, "ignoredClassName", className))) { + if (advisor.shouldIgnore(lazyValue(className), lazyValue(callerClass), false, copyWithUniqueEntry(entry, "ignoredClassName", className))) { return; } } @@ -294,17 +293,6 @@ public void processEntry(EconomicMap entry, ConfigurationSet con } } - @SuppressWarnings("unchecked") - private static ConfigurationTypeDescriptor descriptorForClass(Object clazz) { - if (clazz == null) { - return null; - } else if (clazz instanceof List) { - return ProxyConfigurationTypeDescriptor.fromInterfaceReflectionNames(((List) clazz)); - } else { - return NamedConfigurationTypeDescriptor.fromReflectionName((String) clazz); - } - } - private static void addFullyQualifiedDeclaredMethod(String descriptor, TypeConfiguration configuration) { int sigbegin = descriptor.indexOf('('); int classend = descriptor.lastIndexOf('.', sigbegin - 1); 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 e1f7cd84b98d..80c59629bc6b 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 @@ -70,6 +70,18 @@ public void registerType(ConfigurationCondition condition, Class type) { @Override public TypeResult> resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives, boolean jniAccessible) { TypeResult> result = super.resolveType(condition, typeDescriptor, allowPrimitives, jniAccessible); + registerTypeResolutionErrors(result, condition, typeDescriptor, jniAccessible); + return result; + } + + @Override + public TypeResult>> resolveTypes(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives, boolean jniAccessible) { + TypeResult>> result = super.resolveTypes(condition, typeDescriptor, allowPrimitives, jniAccessible); + registerTypeResolutionErrors(result, condition, typeDescriptor, jniAccessible); + return result; + } + + private void registerTypeResolutionErrors(TypeResult result, ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean jniAccessible) { if (!result.isPresent() && typeDescriptor instanceof NamedConfigurationTypeDescriptor namedDescriptor) { Throwable classLookupException = result.getException(); if (classLookupException instanceof LinkageError) { @@ -80,7 +92,6 @@ public TypeResult> resolveType(ConfigurationCondition condition, Config jniSupport.registerClassLookup(condition, jniName); } } - return result; } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java index 687150cb9cb1..01857745abe7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java @@ -32,6 +32,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.ConfigurationCondition; @@ -42,13 +43,16 @@ import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import com.oracle.svm.configure.ClassNameSupport; +import com.oracle.svm.configure.ConfigurationParser; import com.oracle.svm.configure.ConfigurationTypeDescriptor; +import com.oracle.svm.configure.LambdaConfigurationTypeDescriptor; import com.oracle.svm.configure.NamedConfigurationTypeDescriptor; import com.oracle.svm.configure.ProxyConfigurationTypeDescriptor; import com.oracle.svm.configure.ReflectionConfigurationParserDelegate; import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.lambda.LambdaParser; import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.TypeResult; @@ -79,16 +83,24 @@ public void registerType(ConfigurationCondition condition, Class type) { @Override public TypeResult> resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives, boolean jniAccessible) { + return TypeResult.toSingleElement(resolveTypes(condition, typeDescriptor, allowPrimitives, jniAccessible)); + } + + @Override + public TypeResult>> resolveTypes(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives, boolean jniAccessible) { + TypeResult>> result = resolveTypesInternal(typeDescriptor, allowPrimitives); + if (typeDescriptor.getDescriptorType() == ConfigurationTypeDescriptor.Kind.NAMED && !result.isPresent()) { + if (throwMissingRegistrationErrors() && result.getException() instanceof ClassNotFoundException) { + registry.registerClassLookup(condition, result.getName()); + } + } + return result; + } + + private TypeResult> resolveTypeInternal(ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives) { switch (typeDescriptor.getDescriptorType()) { case NAMED -> { - String reflectionName = ClassNameSupport.typeNameToReflectionName(((NamedConfigurationTypeDescriptor) typeDescriptor).name()); - TypeResult> result = resolveNamedType(reflectionName, allowPrimitives); - if (!result.isPresent()) { - if (throwMissingRegistrationErrors() && result.getException() instanceof ClassNotFoundException) { - registry.registerClassLookup(condition, reflectionName); - } - } - return result; + return resolveNamedType((NamedConfigurationTypeDescriptor) typeDescriptor, allowPrimitives); } case PROXY -> { return resolveProxyType((ProxyConfigurationTypeDescriptor) typeDescriptor); @@ -99,7 +111,15 @@ public TypeResult> resolveType(ConfigurationCondition condition, Config } } - private TypeResult> resolveNamedType(String reflectionName, boolean allowPrimitives) { + private TypeResult>> resolveTypesInternal(ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives) { + if (Objects.requireNonNull(typeDescriptor.getDescriptorType()) == ConfigurationTypeDescriptor.Kind.LAMBDA) { + return resolveLambdaType((LambdaConfigurationTypeDescriptor) typeDescriptor); + } + return TypeResult.toList(resolveTypeInternal(typeDescriptor, allowPrimitives)); + } + + private TypeResult> resolveNamedType(NamedConfigurationTypeDescriptor namedDescriptor, boolean allowPrimitives) { + String reflectionName = ClassNameSupport.typeNameToReflectionName(namedDescriptor.name()); TypeResult> result = classLoader.findClass(reflectionName, allowPrimitives); if (!result.isPresent() && result.getException() instanceof NoClassDefFoundError) { /* @@ -121,7 +141,7 @@ private TypeResult> resolveNamedType(String reflectionName, boolean all private TypeResult> resolveProxyType(ProxyConfigurationTypeDescriptor typeDescriptor) { String typeName = typeDescriptor.toString(); List>> interfaceResults = typeDescriptor.interfaceNames().stream() - .map(interfaceTypeName -> resolveNamedType(ClassNameSupport.typeNameToReflectionName(interfaceTypeName), false)).toList(); + .map(interfaceTypeName -> resolveNamedType(NamedConfigurationTypeDescriptor.fromTypeName(interfaceTypeName), false)).toList(); List> interfaces = new ArrayList<>(); for (TypeResult> intf : interfaceResults) { if (!intf.isPresent()) { @@ -138,6 +158,68 @@ private TypeResult> resolveProxyType(ProxyConfigurationTypeDescriptor t } } + private TypeResult>> resolveLambdaType(LambdaConfigurationTypeDescriptor typeDescriptor) { + TypeResult> declaringClass = resolveTypeInternal(typeDescriptor.declaringClass(), false); + if (!declaringClass.isPresent()) { + return TypeResult.forException(typeDescriptor.toString(), declaringClass.getException()); + } + TypeResult declaringMethod = null; + if (typeDescriptor.declaringMethod() != null) { + declaringMethod = resolveMethod(declaringClass.get(), typeDescriptor.declaringMethod()); + if (!declaringMethod.isPresent()) { + return TypeResult.forException(typeDescriptor.toString(), declaringMethod.getException()); + } + } + List> implementedInterfaces = new ArrayList<>(); + for (NamedConfigurationTypeDescriptor interfaceDescriptor : typeDescriptor.interfaces()) { + TypeResult> intf = resolveNamedType(interfaceDescriptor, false); + if (!intf.isPresent()) { + return TypeResult.forException(typeDescriptor.toString(), intf.getException()); + } + implementedInterfaces.add(intf.get()); + } + + List> lambdaClasses; + try { + if (declaringMethod == null) { + lambdaClasses = LambdaParser.getLambdaClassesInClass(declaringClass.get(), implementedInterfaces); + } else { + lambdaClasses = LambdaParser.getLambdaClassesInMethod(declaringMethod.get(), implementedInterfaces); + } + return !lambdaClasses.isEmpty() ? TypeResult.forType(typeDescriptor.toString(), lambdaClasses) + : exceptionResult(typeDescriptor, declaringClass, declaringMethod, implementedInterfaces, null); + } catch (Throwable t) { + return exceptionResult(typeDescriptor, declaringClass, declaringMethod, implementedInterfaces, t); + } + } + + private static TypeResult>> exceptionResult(ConfigurationTypeDescriptor typeDescriptor, TypeResult> declaringClass, TypeResult declaringMethod, + List> implementedInterfaces, Throwable cause) { + NoClassDefFoundError error = new NoClassDefFoundError( + "No lambda class found in " + (declaringMethod != null ? declaringMethod.get() : declaringClass.get()) + " implementing " + implementedInterfaces); + if (cause != null) { + error.initCause(cause); + } + return TypeResult.forException(typeDescriptor.toString(), error); + } + + private TypeResult resolveMethod(Class declaringClass, ConfigurationParser.ConfigurationMethodDescriptor methodDescriptor) { + String name = methodDescriptor.name(); + List> parameterTypes = new ArrayList<>(); + for (NamedConfigurationTypeDescriptor parameterType : methodDescriptor.parameterTypes()) { + TypeResult> resolvedParameterType = resolveNamedType(parameterType, true); + if (!resolvedParameterType.isPresent()) { + return TypeResult.forException(resolvedParameterType.getName(), resolvedParameterType.getException()); + } + parameterTypes.add(resolvedParameterType.get()); + } + try { + return TypeResult.forType(methodDescriptor.toString(), declaringClass.getDeclaredMethod(name, parameterTypes.toArray(Class[]::new))); + } catch (NoSuchMethodException e) { + return TypeResult.forException(methodDescriptor.toString(), e); + } + } + @Override public void registerPublicClasses(ConfigurationCondition condition, Class type) { } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaParser.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaParser.java index e7dfb464f045..c2bce157e5ad 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaParser.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaParser.java @@ -25,7 +25,10 @@ package com.oracle.svm.hosted.lambda; import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.stream.Stream; import com.oracle.graal.pointsto.phases.NoClassInitializationPlugin; @@ -33,6 +36,7 @@ import com.oracle.svm.util.ClassUtil; import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.graph.iterators.NodeIterable; import jdk.graal.compiler.java.BytecodeParser; import jdk.graal.compiler.java.GraphBuilderPhase; import jdk.graal.compiler.java.LambdaUtils; @@ -54,6 +58,28 @@ import jdk.vm.ci.meta.ResolvedJavaType; public class LambdaParser { + public static List> getLambdaClassesInClass(Class declaringClass, List> implementedInterfaces) { + List> result = new ArrayList<>(); + for (Method method : declaringClass.getDeclaredMethods()) { + result.addAll(getLambdaClassesInMethod(method, implementedInterfaces)); + } + return result; + } + + public static List> getLambdaClassesInMethod(Method capturingMethod, List> implementedInterfaces) { + ResolvedJavaMethod method = GraalAccess.getOriginalProviders().getMetaAccess().lookupJavaMethod(capturingMethod); + StructuredGraph graph = createMethodGraph(method, new OptionValues(OptionValues.newOptionMap())); + NodeIterable constantNodes = ConstantNode.getConstantNodes(graph); + List> lambdaClasses = new ArrayList<>(); + for (ConstantNode cNode : constantNodes) { + Class lambdaClass = getLambdaClassFromConstantNode(cNode); + if (lambdaClass != null && implementedInterfaces.stream().allMatch(i -> i.isAssignableFrom(lambdaClass))) { + lambdaClasses.add(lambdaClass); + } + } + return lambdaClasses; + } + /** * Create a {@link StructuredGraph} using {@link LambdaGraphBuilderPhase.LambdaBytecodeParser}, * a simple {@link BytecodeParser}. diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/TypeResult.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/TypeResult.java index 2a6be0899621..651494ffc168 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/TypeResult.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/TypeResult.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.util; +import java.util.List; import java.util.function.Consumer; import java.util.function.Function; @@ -45,6 +46,25 @@ public static TypeResult forException(String name, Throwable exception) { return new TypeResult<>(name, null, exception); } + public static TypeResult toSingleElement(TypeResult> result) { + if (result.isPresent()) { + if (result.get().size() != 1) { + throw new IllegalArgumentException("Ambiguous type descriptor not allowed here"); + } + return forType(result.getName(), result.get().getFirst()); + } else { + return forException(result.getName(), result.getException()); + } + } + + public static TypeResult> toList(TypeResult result) { + if (result.isPresent()) { + return forType(result.getName(), List.of(result.get())); + } else { + return forException(result.getName(), result.getException()); + } + } + private final String name; private final T type; private final Throwable exception; @@ -82,6 +102,10 @@ public TypeResult map(Function f) { } + public String getName() { + return name; + } + public Throwable getException() { return exception; }