diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java index a402d4edd5dd..50edd2e319e0 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java @@ -244,6 +244,8 @@ public final void applyResults(AnalysisMethod method) { return; } + preStrengthenGraphs(graph, method); + graph.resetDebug(debug); if (beforeCounters != null) { beforeCounters.collect(graph); @@ -271,6 +273,8 @@ public final void applyResults(AnalysisMethod method) { } } + protected abstract void preStrengthenGraphs(StructuredGraph graph, AnalysisMethod method); + protected abstract void postStrengthenGraphs(StructuredGraph graph, AnalysisMethod method); protected abstract void useSharedLayerGraph(AnalysisMethod method); 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 a0e6fb6249ba..b2cbee952212 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 @@ -75,6 +75,7 @@ import java.util.Optional; import java.util.StringJoiner; +import com.oracle.svm.core.NeverInlineTrivial; import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; @@ -1450,6 +1451,7 @@ private static Constructor[] copyConstructors(Constructor[] original) { private native Constructor getEnclosingConstructor(); @Substitute + @NeverInlineTrivial("Used in reflection usage analysis: AnalyzeReflectionUsagePhase") @Platforms(InternalPlatform.NATIVE_ONLY.class) @CallerSensitive private static Class forName(String className) throws Throwable { @@ -1457,6 +1459,7 @@ private static Class forName(String className) throws Throwable { } @Substitute + @NeverInlineTrivial("Used in reflection usage analysis: AnalyzeReflectionUsagePhase") @Platforms(InternalPlatform.NATIVE_ONLY.class) @CallerSensitiveAdapter private static Class forName(String className, Class caller) throws Throwable { @@ -1464,6 +1467,7 @@ private static Class forName(String className, Class caller) throws Throwa } @Substitute + @NeverInlineTrivial("Used in reflection usage analysis: AnalyzeReflectionUsagePhase") @Platforms(InternalPlatform.NATIVE_ONLY.class) @CallerSensitive private static Class forName(Module module, String className) throws Throwable { @@ -1471,6 +1475,7 @@ private static Class forName(Module module, String className) throws Throwabl } @Substitute + @NeverInlineTrivial("Used in reflection usage analysis: AnalyzeReflectionUsagePhase") @Platforms(InternalPlatform.NATIVE_ONLY.class) @CallerSensitiveAdapter private static Class forName(@SuppressWarnings("unused") Module module, String className, Class caller) throws Throwable { @@ -1486,12 +1491,14 @@ private static Class forName(@SuppressWarnings("unused") Module module, Strin } @Substitute + @NeverInlineTrivial("Used in reflection usage analysis: AnalyzeReflectionUsagePhase") @CallerSensitive private static Class forName(String name, boolean initialize, ClassLoader loader) throws Throwable { return forName(name, initialize, loader, Reflection.getCallerClass()); } @Substitute + @NeverInlineTrivial("Used in reflection usage analysis: AnalyzeReflectionUsagePhase") @CallerSensitiveAdapter private static Class forName(String name, boolean initialize, ClassLoader loader, @SuppressWarnings("unused") Class caller) throws Throwable { if (name == null) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeMethodsRequiringMetadataUsageSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeMethodsRequiringMetadataUsageSupport.java new file mode 100644 index 000000000000..33124ab43c41 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeMethodsRequiringMetadataUsageSupport.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2024, 2024, 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.hosted; + +import com.oracle.svm.core.BuildArtifacts; +import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; +import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.option.HostedOptionValues; +import com.oracle.svm.hosted.phases.AnalyzeMethodsRequiringMetadataUsagePhase; +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.util.json.JsonBuilder; +import jdk.graal.compiler.util.json.JsonWriter; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import org.graalvm.nativeimage.ImageSingletons; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This is a support class that keeps track of reflection usages detected during + * {@link AnalyzeMethodsRequiringMetadataUsagePhase} and outputs them to the + * image-build output. + */ +public final class AnalyzeMethodsRequiringMetadataUsageSupport { + public static final String METHODTYPE_REFLECTION = "reflection"; + public static final String METHODTYPE_RESOURCE = "resource"; + public static final String METHODTYPE_SERIALIZATION = "serialization"; + public static final String METHODTYPE_PROXY = "proxy"; + public static final String METHODTYPE_JNI = "jni"; + private final Map> reflectiveCalls; + private final Map> resourceCalls; + private final Map> serializationCalls; + private final Map> proxyCalls; + private final Map> JNICalls; + private final Set jarPaths; + private final Set foldEntries = ConcurrentHashMap.newKeySet(); + + public AnalyzeMethodsRequiringMetadataUsageSupport() { + this.reflectiveCalls = new TreeMap<>(); + this.resourceCalls = new TreeMap<>(); + this.serializationCalls = new TreeMap<>(); + this.proxyCalls = new TreeMap<>(); + this.JNICalls = new TreeMap<>(); + this.jarPaths = Collections.unmodifiableSet(new HashSet<>(AnalyzeMethodsRequiringMetadataUsageSupport.Options.TrackMethodsRequiringMetadata.getValue().values())); + } + + public static AnalyzeMethodsRequiringMetadataUsageSupport instance() { + AnalyzeMethodsRequiringMetadataUsageSupport trus = ImageSingletons.lookup(AnalyzeMethodsRequiringMetadataUsageSupport.class); + GraalError.guarantee(trus != null, "Should never be null."); + return trus; + } + + public void addCall(String methodType, String call, String callLocation) { + switch (methodType) { + case METHODTYPE_REFLECTION -> this.reflectiveCalls.computeIfAbsent(call, k -> new ArrayList<>()).add(callLocation); + case METHODTYPE_RESOURCE -> this.resourceCalls.computeIfAbsent(call, k -> new ArrayList<>()).add(callLocation); + case METHODTYPE_SERIALIZATION -> this.serializationCalls.computeIfAbsent(call, k -> new ArrayList<>()).add(callLocation); + case METHODTYPE_PROXY -> this.proxyCalls.computeIfAbsent(call, k -> new ArrayList<>()).add(callLocation); + case METHODTYPE_JNI -> this.JNICalls.computeIfAbsent(call, k -> new ArrayList<>()).add(callLocation); + default -> throw new IllegalArgumentException("Unknown method type: " + methodType); + } + } + + public void printReport(String methodType) { + Map> callMap = switch (methodType) { + case METHODTYPE_REFLECTION -> this.reflectiveCalls; + case METHODTYPE_RESOURCE -> this.resourceCalls; + case METHODTYPE_SERIALIZATION -> this.serializationCalls; + case METHODTYPE_PROXY -> this.proxyCalls; + case METHODTYPE_JNI -> this.JNICalls; + default -> throw new IllegalArgumentException("Unknown method type: " + methodType); + }; + + System.out.println(methodType.substring(0, 1).toUpperCase() + methodType.substring(1) + " calls detected:"); + for (String key : callMap.keySet()) { + System.out.println(" " + key + ":"); + callMap.get(key).sort(Comparator.comparing(String::toString)); + for (String callLocation : callMap.get(key)) { + System.out.println(" at " + callLocation); + } + } + } + + public void dumpReport(String methodType) { + Map> calls = switch (methodType) { + case METHODTYPE_REFLECTION -> this.reflectiveCalls; + case METHODTYPE_RESOURCE -> this.resourceCalls; + case METHODTYPE_SERIALIZATION -> this.serializationCalls; + case METHODTYPE_PROXY -> this.proxyCalls; + case METHODTYPE_JNI -> this.JNICalls; + default -> throw new IllegalArgumentException("Unknown method type: " + methodType); + }; + String fileName = methodType + "-usage.json"; + + Path targetPath = NativeImageGenerator.generatedFiles(HostedOptionValues.singleton()).resolve(fileName); + try (var writer = new JsonWriter(targetPath); + var builder = writer.objectBuilder()) { + for (Map.Entry> entry : calls.entrySet()) { + try (JsonBuilder.ArrayBuilder array = builder.append(entry.getKey()).array()) { + for (String call : entry.getValue()) { + array.append(call); + } + } + } + BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.BUILD_INFO, targetPath); + } catch (IOException e) { + System.out.println("Failed to print JSON to " + targetPath + ":"); + e.printStackTrace(System.out); + } + } + + public void reportReflection() { + Map>> callMaps = Map.of( + METHODTYPE_REFLECTION, this.reflectiveCalls, + METHODTYPE_RESOURCE, this.resourceCalls, + METHODTYPE_SERIALIZATION, this.serializationCalls, + METHODTYPE_PROXY, this.proxyCalls, + METHODTYPE_JNI, this.JNICalls + ); + for (Map.Entry>> entry : callMaps.entrySet()) { + String methodType = entry.getKey(); + Map> calls = entry.getValue(); + if (!calls.isEmpty()) { + printReport(methodType); + dumpReport(methodType); + } + } + } + + public Set getJarPaths() { + return jarPaths; + } + + /* + * Support data structure used to keep track of reflective calls which don't require metadata, + * but can't be folded. + */ + public static class FoldEntry { + private final int bci; + private final ResolvedJavaMethod method; + + public FoldEntry(int bci, ResolvedJavaMethod method) { + this.bci = bci; + this.method = method; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + FoldEntry other = (FoldEntry) obj; + return bci == other.bci && Objects.equals(method, other.method); + } + + @Override + public int hashCode() { + return Objects.hash(bci, method); + } + } + + public void addFoldEntry(int bci, ResolvedJavaMethod method) { + this.foldEntries.add(new FoldEntry(bci, method)); + } + + /* + * If a fold entry exists for the given method, the method should be ignored by the analysis + * phase. + */ + public boolean containsFoldEntry(int bci, ResolvedJavaMethod method) { + return this.foldEntries.contains(new FoldEntry(bci, method)); + } + + public static class Options { + @Option(help = "Output all reflection usages in the reached parts of the project, limited to the provided comma-separated list of JAR files.")// + public static final HostedOptionKey TrackMethodsRequiringMetadata = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SubstrateStrengthenGraphs.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SubstrateStrengthenGraphs.java index 711810d23e09..12e400d019cc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SubstrateStrengthenGraphs.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SubstrateStrengthenGraphs.java @@ -45,6 +45,7 @@ import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; import com.oracle.svm.hosted.meta.HostedType; +import com.oracle.svm.hosted.phases.AnalyzeMethodsRequiringMetadataUsagePhase; import com.oracle.svm.hosted.phases.AnalyzeJavaHomeAccessPhase; import jdk.graal.compiler.graph.Node; import jdk.graal.compiler.nodes.ConstantNode; @@ -61,14 +62,22 @@ import jdk.vm.ci.meta.JavaTypeProfile; public class SubstrateStrengthenGraphs extends StrengthenGraphs { - + private final Boolean trackReflectionUsage; private final Boolean trackJavaHomeAccess; private final Boolean trackJavaHomeAccessDetailed; public SubstrateStrengthenGraphs(Inflation bb, Universe converter) { super(bb, converter); - trackJavaHomeAccess = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccess.getValue(bb.getOptions()); - trackJavaHomeAccessDetailed = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccessDetailed.getValue(bb.getOptions()); + trackReflectionUsage = AnalyzeMethodsRequiringMetadataUsageSupport.Options.TrackMethodsRequiringMetadata.hasBeenSet(); + trackJavaHomeAccess = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccess.getValue(); + trackJavaHomeAccessDetailed = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccessDetailed.getValue(); + } + + @Override + protected void preStrengthenGraphs(StructuredGraph graph, AnalysisMethod method) { + if (trackReflectionUsage) { + new AnalyzeMethodsRequiringMetadataUsagePhase().apply(graph, bb.getProviders(method)); + } } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeMethodsRequiringMetadataUsagePhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeMethodsRequiringMetadataUsagePhase.java new file mode 100644 index 000000000000..1d9d11fd3de8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeMethodsRequiringMetadataUsagePhase.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2024, 2024, 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.hosted.phases; + +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.jni.functions.JNIFunctions; +import com.oracle.svm.hosted.AnalyzeMethodsRequiringMetadataUsageSupport; +import jdk.graal.compiler.graph.NodeSourcePosition; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.nodes.java.MethodCallTargetNode; +import jdk.graal.compiler.nodes.spi.CoreProviders; +import jdk.graal.compiler.phases.BasePhase; +import jdk.internal.loader.BuiltinClassLoader; +import jdk.internal.loader.Loader; +import org.graalvm.collections.Pair; +import sun.invoke.util.ValueConversions; +import sun.invoke.util.VerifyAccess; +import sun.reflect.annotation.AnnotationParser; +import sun.security.x509.X500Name; + +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.lang.invoke.ConstantBootstraps; +import java.lang.invoke.MethodHandleProxies; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.spi.FileSystemProvider; +import java.security.CodeSource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.random.RandomGeneratorFactory; + +import static com.oracle.svm.hosted.AnalyzeMethodsRequiringMetadataUsageSupport.METHODTYPE_JNI; +import static com.oracle.svm.hosted.AnalyzeMethodsRequiringMetadataUsageSupport.METHODTYPE_PROXY; +import static com.oracle.svm.hosted.AnalyzeMethodsRequiringMetadataUsageSupport.METHODTYPE_REFLECTION; +import static com.oracle.svm.hosted.AnalyzeMethodsRequiringMetadataUsageSupport.METHODTYPE_RESOURCE; +import static com.oracle.svm.hosted.AnalyzeMethodsRequiringMetadataUsageSupport.METHODTYPE_SERIALIZATION; + +/** + * This phase detects usages of reflective calls in reached parts of the project, given the JAR + * files in which to search, and outputs and serializes them to the image-build output. It is an + * optional phase that that happens before + * {@link com.oracle.graal.pointsto.results.StrengthenGraphs} by using the + * {@link AnalyzeMethodsRequiringMetadataUsageSupport.Options#TrackMethodsRequiringMetadata} option and providing the + * desired JAR path/s. + */ + +public class AnalyzeMethodsRequiringMetadataUsagePhase extends BasePhase { + private static final Map> reflectMethodNames = new HashMap<>(); + private static final Map> resourceMethodNames = new HashMap<>(); + private static final Map> serializationMethodNames = new HashMap<>(); + private static final Map> proxyMethodNames = new HashMap<>(); + private static final Map> JNIMethodNames = new HashMap<>(); + + static { + reflectMethodNames.put(Class.class.getTypeName(), Set.of( + "forName", + "getClasses", + "getDeclaredClasses", + "getConstructor", + "getConstructors", + "getDeclaredConstructor", + "getDeclaredConstructors", + "getField", + "getFields", + "getDeclaredField", + "getDeclaredFields", + "getMethod", + "getMethods", + "getDeclaredMethod", + "getDeclaredMethods", + "getNestMembers", + "getPermittedSubclasses", + "getRecordComponents", + "getSigners", + "arrayType", + "newInstance" + )); + reflectMethodNames.put(Method.class.getTypeName(), Set.of("invoke")); + reflectMethodNames.put(MethodHandles.Lookup.class.getTypeName(), Set.of( + "findClass", + "findVirtual", + "findStatic", + "findConstructor", + "findSpecial", + "findGetter", + "findSetter", + "findStaticGetter", + "findStaticSetter", + "findVarHandle", + "findStaticVarHandle", + "unreflect", + "unreflectSpecial", + "unreflectConstructor", + "unreflectGetter", + "unreflectSetter", + "unreflectVarHandle" + )); + reflectMethodNames.put(ClassLoader.class.getTypeName(), Set.of( + "loadClass", + "findBootstrapClassOrNull", + "findLoadedClass", + "findSystemClass" + )); + reflectMethodNames.put(URLClassLoader.class.getTypeName(), Set.of("loadClass")); + reflectMethodNames.put(Array.class.getTypeName(), Set.of("newInstance")); + reflectMethodNames.put(Constructor.class.getTypeName(), Set.of("newInstance")); + reflectMethodNames.put("java.lang.reflect.ReflectAccess", Set.of("newInstance")); + reflectMethodNames.put("sun.misc.Unsafe", Set.of("allocateInstance")); + reflectMethodNames.put("java.lang.constant.ReferenceClassDescImpl", Set.of("resolveConstantDesc")); + reflectMethodNames.put(ObjectInputStream.class.getTypeName(), Set.of("resolveClass", "resolveProxyClass")); + reflectMethodNames.put("javax.crypto.extObjectInputStream", Set.of("resolveClass")); + reflectMethodNames.put(VerifyAccess.class.getTypeName(), Set.of("isTypeVisible")); + reflectMethodNames.put("sun.reflect.generics.factory.CoreReflectionFactory", Set.of("makeNamedType")); + reflectMethodNames.put("sun.reflect.misc.ReflectUtil", Set.of("forName")); + reflectMethodNames.put("sun.security.tools.KeyStoreUtil", Set.of("loadProvidedByClass")); + reflectMethodNames.put("sun.util.locale.provider.LocaleProviderAdapter", Set.of("forType")); + reflectMethodNames.put("sun.reflect.misc.ConstructorUtil", Set.of("getConstructor", "getConstructors")); + reflectMethodNames.put("java.lang.invoke.ClassSpecializer", Set.of("reflectConstructor")); + reflectMethodNames.put("sun.reflect.misc.FieldUtil", Set.of("getField", "getFields")); + reflectMethodNames.put("sun.reflect.misc.MethodUtil", Set.of("getMethod", "getMethods", "loadClass")); + reflectMethodNames.put("sun.security.util.KeyStoreDelegator", Set.of("engineLoad", "engineProbe")); + reflectMethodNames.put(ValueConversions.class.getTypeName(), Set.of("boxExact")); + reflectMethodNames.put(ConstantBootstraps.class.getTypeName(), Set.of("getStaticFinal", "staticFieldVarHandle", "fieldVarHandle")); + reflectMethodNames.put(VarHandle.VarHandleDesc.class.getTypeName(), Set.of("resolveConstantDesc")); + reflectMethodNames.put(RandomGeneratorFactory.class.getTypeName(), Set.of("create")); + reflectMethodNames.put(X500Name.class.getTypeName(), Set.of("asX500Principal")); + reflectMethodNames.put(MethodHandleProxies.class.getTypeName(), Set.of("asInterfaceInstance")); + reflectMethodNames.put(AnnotationParser.class.getTypeName(), Set.of("annotationForMap")); + + resourceMethodNames.put(ClassLoader.class.getTypeName(), Set.of( + "getResource", + "getResources", + "getSystemResource", + "getSystemResources" + )); + resourceMethodNames.put(BuiltinClassLoader.class.getTypeName(), Set.of("findResource", "findResourceAsStream")); + resourceMethodNames.put(Loader.class.getTypeName(), Set.of("findResource")); + resourceMethodNames.put(ResourceBundle.class.getTypeName(), Set.of("getBundleImpl")); + resourceMethodNames.put(Module.class.getTypeName(), Set.of("getResourceAsStream")); + resourceMethodNames.put(Class.class.getTypeName(), Set.of("getResource", "getResourceAsStream")); + // Those methods can only throw missing registration errors when using a + // NativeImageResourceFileSystem and a NativeImageResourcePath. + resourceMethodNames.put(Files.class.getTypeName(), Set.of( + "walk", + "getFileStore", + "readAttributes", + "setAttribute", + "newByteChannel", + "newOutputStream", + "newInputStream", + "createDirectory", + "move", + "copy", + "newDirectoryStream", + "delete" + )); + resourceMethodNames.put(FileSystemProvider.class.getTypeName(), Set.of("newFileChannel")); + + serializationMethodNames.put(ObjectOutputStream.class.getTypeName(), Set.of("writeObject", "writeUnshared")); + serializationMethodNames.put(ObjectInputStream.class.getTypeName(), Set.of("readObject", "readUnshared")); + serializationMethodNames.put(ObjectStreamClass.class.getTypeName(), Set.of("lookup")); + serializationMethodNames.put("sun.reflect.ReflectionFactory", Set.of("newConstructorForSerialization")); + serializationMethodNames.put("jdk.internal.reflect.ReflectionFactory", Set.of("newConstructorForSerialization")); + + proxyMethodNames.put(Proxy.class.getTypeName(), Set.of("getProxyClass", "newProxyInstance")); + + JNIMethodNames.put(JNIFunctions.class.getTypeName(), Set.of( + "DefineClass", + "FindClass", + "AllocObject", + "GetMethodID", + "GetStaticMethodID", + "GetFieldID", + "GetStaticFieldID", + "ThrowNew", + "FromReflectedMethod", + "FromReflectedField", + "ToReflectedMethod", + "ToReflectedField", + "NewObjectArray" + )); + } + + @Override + protected void run(StructuredGraph graph, CoreProviders context) { + List callTargetNodes = graph.getNodes(MethodCallTargetNode.TYPE).snapshot(); + for (MethodCallTargetNode callTarget : callTargetNodes) { + Pair methodDetails = getMethod(graph, callTarget); + if (methodDetails != null) { + String methodType = methodDetails.getLeft(); + String methodName = methodDetails.getRight(); + + NodeSourcePosition nspToShow = callTarget.getNodeSourcePosition(); + if (nspToShow != null) { + int bci = nspToShow.getBCI(); + if (!AnalyzeMethodsRequiringMetadataUsageSupport.instance().containsFoldEntry(bci, nspToShow.getMethod())) { + String callLocation = nspToShow.getMethod().asStackTraceElement(bci).toString(); + AnalyzeMethodsRequiringMetadataUsageSupport.instance().addCall(methodType, methodName, callLocation); + } + } + } + } + } + + public Pair getMethod(StructuredGraph graph, MethodCallTargetNode callTarget) { + AnalysisType callerClass = (AnalysisType) graph.method().getDeclaringClass(); + if (!containedInJars(callerClass)) { + return null; + } + String methodName = callTarget.targetMethod().getName(); + String declaringClass = callTarget.targetMethod().getDeclaringClass().toJavaName(); + + if (reflectMethodNames.containsKey(declaringClass) && reflectMethodNames.get(declaringClass).contains(methodName)) { + return Pair.create(METHODTYPE_REFLECTION, declaringClass + "#" + methodName); + } else if (resourceMethodNames.containsKey(declaringClass) && resourceMethodNames.get(declaringClass).contains(methodName)) { + return Pair.create(METHODTYPE_RESOURCE, declaringClass + "#" + methodName); + } else if (serializationMethodNames.containsKey(declaringClass) && serializationMethodNames.get(declaringClass).contains(methodName)) { + return Pair.create(METHODTYPE_SERIALIZATION, declaringClass + "#" + methodName); + } else if (proxyMethodNames.containsKey(declaringClass) && proxyMethodNames.get(declaringClass).contains(methodName)) { + return Pair.create(METHODTYPE_PROXY, declaringClass + "#" + methodName); + } else if (JNIMethodNames.containsKey(declaringClass) && JNIMethodNames.get(declaringClass).contains(methodName)) { + return Pair.create(METHODTYPE_JNI, declaringClass + "#" + methodName); + } + return null; + } + + private boolean containedInJars(AnalysisType callerClass) { + try { + CodeSource jarPathSource = callerClass.getJavaClass().getProtectionDomain().getCodeSource(); + if (jarPathSource == null) { + return false; + } + + URL jarPathURL = jarPathSource.getLocation(); + if (jarPathURL == null) { + return false; + } + + return AnalyzeMethodsRequiringMetadataUsageSupport.instance().getJarPaths().contains(jarPathURL.toURI().getPath()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index c4bd43670b13..a4dd2dd3f826 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -38,6 +38,7 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import com.oracle.svm.hosted.AnalyzeMethodsRequiringMetadataUsageSupport; import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.function.CFunctionPointer; @@ -353,6 +354,10 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { /* Make sure array classes don't need to be registered for reflection. */ RuntimeReflection.register(Object.class.getDeclaredMethods()); + + if (AnalyzeMethodsRequiringMetadataUsageSupport.Options.TrackMethodsRequiringMetadata.getValue() != null) { + ImageSingletons.add(AnalyzeMethodsRequiringMetadataUsageSupport.class, new AnalyzeMethodsRequiringMetadataUsageSupport()); + } } @Override @@ -363,6 +368,10 @@ public void afterAnalysis(AfterAnalysisAccess access) { @Override public void beforeCompilation(BeforeCompilationAccess access) { + if (AnalyzeMethodsRequiringMetadataUsageSupport.Options.TrackMethodsRequiringMetadata.getValue() != null) { + AnalyzeMethodsRequiringMetadataUsageSupport.instance().reportReflection(); + } + metaAccess = ((BeforeCompilationAccessImpl) access).getMetaAccess(); if (ImageSingletons.contains(FallbackFeature.class)) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java index b8c81501d43c..9e13507b2dd0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java @@ -48,6 +48,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import com.oracle.svm.hosted.AnalyzeMethodsRequiringMetadataUsageSupport; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; @@ -121,6 +122,8 @@ static class Options { private final FallbackFeature fallbackFeature; private final ClassInitializationSupport classInitializationSupport; + private final boolean analyzeReflectionUsage; + private ReflectionPlugins(ImageClassLoader imageClassLoader, AnnotationSubstitutionProcessor annotationSubstitutions, ClassInitializationPlugin classInitializationPlugin, AnalysisUniverse aUniverse, ParsingReason reason, FallbackFeature fallbackFeature) { this.imageClassLoader = imageClassLoader; @@ -129,6 +132,7 @@ private ReflectionPlugins(ImageClassLoader imageClassLoader, AnnotationSubstitut this.aUniverse = aUniverse; this.reason = reason; this.fallbackFeature = fallbackFeature; + this.analyzeReflectionUsage = AnalyzeMethodsRequiringMetadataUsageSupport.Options.TrackMethodsRequiringMetadata.hasBeenSet(); this.classInitializationSupport = (ClassInitializationSupport) ImageSingletons.lookup(RuntimeClassInitializationSupport.class); } @@ -573,6 +577,9 @@ private boolean registerConstantBulkReflectionQuery(GraphBuilderContext b, R } b.add(ReachabilityRegistrationNode.create(() -> registerForRuntimeReflection((T) receiverValue, registrationCallback), reason)); + if (analyzeReflectionUsage) { + AnalyzeMethodsRequiringMetadataUsageSupport.instance().addFoldEntry(b.bci(), b.getMethod()); + } return true; }