From 1a4783d727d8507361c2f977f25839a19e89b22d Mon Sep 17 00:00:00 2001 From: jvukicev Date: Tue, 22 Oct 2024 08:48:28 +0200 Subject: [PATCH 01/13] Add reflection detection phase --- .../pointsto/results/StrengthenGraphs.java | 4 + .../com/oracle/svm/core/SubstrateOptions.java | 3 + .../com/oracle/svm/core/hub/DynamicHub.java | 7 + .../hosted/AnalyzeReflectionUsageSupport.java | 104 +++++++++++++ .../svm/hosted/SubstrateStrengthenGraphs.java | 13 ++ .../phases/AnalyzeReflectionUsagePhase.java | 137 ++++++++++++++++++ .../svm/hosted/reflect/ReflectionFeature.java | 8 + 7 files changed, 276 insertions(+) create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java 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/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 33e3e3e347ab..70bfc653866c 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 @@ -1351,4 +1351,7 @@ public static class TruffleStableOptions { throw UserError.invalidOptionValue(key, key.getValue(), "Mapping the image heap with mremap() is only supported on Linux."); } }); + + @Option(help = "Output all reflection usages in the reached parts of the project in a report file.")// + public static final HostedOptionKey TrackReflectionUsage = new HostedOptionKey<>(null); } 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/AnalyzeReflectionUsageSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java new file mode 100644 index 000000000000..080d87f5b81a --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java @@ -0,0 +1,104 @@ +/* + * 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.HostedOptionValues; +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.util.json.JsonBuilder; +import jdk.graal.compiler.util.json.JsonWriter; +import org.graalvm.nativeimage.ImageSingletons; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.TreeMap; +import java.util.List; +import java.util.Map; + +public final class AnalyzeReflectionUsageSupport { + private final Map> reflectiveCalls; + private static final String ARTIFACT_FILE_NAME = "reflection-usage.json"; + + public AnalyzeReflectionUsageSupport() { + this.reflectiveCalls = new TreeMap<>(); + } + + public static AnalyzeReflectionUsageSupport instance() { + AnalyzeReflectionUsageSupport trus = ImageSingletons.lookup(AnalyzeReflectionUsageSupport.class); + GraalError.guarantee(trus != null, "Should never be null."); + return trus; + } + + public void addReflectiveCall(String reflectiveCall, String callLocation) { + if (!this.reflectiveCalls.containsKey(reflectiveCall)) { + List callLocationList = new ArrayList<>(); + this.reflectiveCalls.put(reflectiveCall, callLocationList); + } + this.reflectiveCalls.get(reflectiveCall).add(callLocation); + } + + public void printReflectionReport() { + System.out.println("Reflective calls detected:"); + for (String key : this.reflectiveCalls.keySet()) { + System.out.println(" " + key + ":"); + this.reflectiveCalls.get(key).sort(Comparator.comparing(String::toString)); + for (String callLocation : this.reflectiveCalls.get(key)) { + System.out.println(" at " + callLocation); + } + } + } + + public void dumpReflectionReport() { + try (var writer = new JsonWriter(getTargetPath()); + var builder = writer.objectBuilder()) { + for (Map.Entry> entry : this.reflectiveCalls.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, getTargetPath()); // Maybe shouldn't be BUILD_INFO + } catch (IOException e) { + System.out.println("Failed to print JSON:"); + e.printStackTrace(System.out); + } + } + + public void reportReflection() { + if (!this.reflectiveCalls.isEmpty()) { + printReflectionReport(); + dumpReflectionReport(); + } + } + + private static Path getTargetPath() { + Path buildPath = NativeImageGenerator.generatedFiles(HostedOptionValues.singleton()); + return buildPath.resolve(ARTIFACT_FILE_NAME); + } +} + 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..71f2de175fe6 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 @@ -24,14 +24,17 @@ */ package com.oracle.svm.hosted; +import java.nio.file.Path; import java.util.function.Supplier; +import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.heap.ImageLayerLoader; import com.oracle.graal.pointsto.infrastructure.Universe; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.results.StrengthenGraphs; import com.oracle.svm.common.meta.MultiMethod; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.graal.nodes.InlinedInvokeArgumentsNode; @@ -45,6 +48,7 @@ import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; import com.oracle.svm.hosted.meta.HostedType; +import com.oracle.svm.hosted.phases.AnalyzeReflectionUsagePhase; import com.oracle.svm.hosted.phases.AnalyzeJavaHomeAccessPhase; import jdk.graal.compiler.graph.Node; import jdk.graal.compiler.nodes.ConstantNode; @@ -61,16 +65,25 @@ import jdk.vm.ci.meta.JavaTypeProfile; public class SubstrateStrengthenGraphs extends StrengthenGraphs { + private final String reflectionUsageJarPaths; private final Boolean trackJavaHomeAccess; private final Boolean trackJavaHomeAccessDetailed; public SubstrateStrengthenGraphs(Inflation bb, Universe converter) { super(bb, converter); + reflectionUsageJarPaths = SubstrateOptions.TrackReflectionUsage.getValue(bb.getOptions()); trackJavaHomeAccess = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccess.getValue(bb.getOptions()); trackJavaHomeAccessDetailed = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccessDetailed.getValue(bb.getOptions()); } + @Override + protected void preStrengthenGraphs(StructuredGraph graph, AnalysisMethod method) { + if (reflectionUsageJarPaths != null) { + new AnalyzeReflectionUsagePhase(reflectionUsageJarPaths).apply(graph, bb.getProviders(method)); + } + } + @Override protected void postStrengthenGraphs(StructuredGraph graph, AnalysisMethod method) { if (trackJavaHomeAccess) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java new file mode 100644 index 000000000000..9e916b76eb29 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java @@ -0,0 +1,137 @@ +/* + * 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.hosted.AnalyzeReflectionUsageSupport; +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 java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class AnalyzeReflectionUsagePhase extends BasePhase { + private static final Map> reflectMethodNames = Map.of( + 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"), + Method.class.getTypeName(), Set.of("invoke"), + Constructor.class.getTypeName(), Set.of("newInstance"), + Proxy.class.getTypeName(), Set.of("getProxyClass", "newProxyInstance"), + "java.lang.reflect.ReflectAccess", Set.of("newInstance"), + "jdk.internal.access.JavaLangAccess", Set.of("getDeclaredPublicMethods"), + "sun.misc.Unsafe", Set.of("allocateInstance")); + private static final AnalyzeReflectionUsageSupport singleton = AnalyzeReflectionUsageSupport.instance(); + private final Set includedPackages = new HashSet<>(); + + public AnalyzeReflectionUsagePhase(String jarPaths) { + for (String jarPath : jarPaths.split(":")) { + getPackagesFromJar(Paths.get(jarPath), includedPackages); + } + } + + @Override + protected void run(StructuredGraph graph, CoreProviders context) { + List callTargetNodes = graph.getNodes(MethodCallTargetNode.TYPE).snapshot(); + for (MethodCallTargetNode callTarget : callTargetNodes) { + String reflectiveMethodName = getReflectiveMethod(graph, callTarget); + if (reflectiveMethodName != null) { + NodeSourcePosition nspToShow = callTarget.getNodeSourcePosition(); + if (nspToShow != null) { + int bci = nspToShow.getBCI(); + singleton.addReflectiveCall(reflectiveMethodName, nspToShow.getMethod().asStackTraceElement(bci).toString()); + } + } + } + } + + private String getReflectiveMethod(StructuredGraph graph, MethodCallTargetNode callTarget) { + AnalysisType callerClass = (AnalysisType) graph.method().getDeclaringClass(); + if (!includedPackages.contains(callerClass.getJavaClass().getPackageName())) { + return null; + } + String methodName = callTarget.targetMethod().getName(); + String declaringClass = callTarget.targetMethod().getDeclaringClass().toJavaName(); + if (reflectMethodNames.containsKey(declaringClass)) { + if (reflectMethodNames.get(declaringClass).contains(methodName)) { + return methodName; + } + } + return null; + } + + private static void getPackagesFromJar(Path jarPath, Set packageSet) { + try (JarFile jarFile = new JarFile(jarPath.toFile())) { + jarFile.stream() + .filter(entry -> entry.getName().endsWith(".class")) + .map(AnalyzeReflectionUsagePhase::getPackageName) + .filter(pkg -> !pkg.isEmpty()) + .forEach(packageSet::add); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static String getPackageName(JarEntry entry) { + String entryName = entry.getName(); + int lastSlashIndex = entryName.lastIndexOf('/'); + if (lastSlashIndex == -1) { + return ""; // Default package + } + return entryName.substring(0, lastSlashIndex).replace('/', '.'); + } +} 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..cdcfa5be2764 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,8 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import com.oracle.svm.hosted.AnalyzeReflectionUsageSupport; +import com.oracle.svm.core.option.HostedOptionValues; import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.function.CFunctionPointer; @@ -353,6 +355,10 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { /* Make sure array classes don't need to be registered for reflection. */ RuntimeReflection.register(Object.class.getDeclaredMethods()); + + if (SubstrateOptions.TrackReflectionUsage.getValue(HostedOptionValues.singleton()) != null) { + ImageSingletons.add(AnalyzeReflectionUsageSupport.class, new AnalyzeReflectionUsageSupport()); + } } @Override @@ -363,6 +369,8 @@ public void afterAnalysis(AfterAnalysisAccess access) { @Override public void beforeCompilation(BeforeCompilationAccess access) { + AnalyzeReflectionUsageSupport.instance().reportReflection(); + metaAccess = ((BeforeCompilationAccessImpl) access).getMetaAccess(); if (ImageSingletons.contains(FallbackFeature.class)) { From cc583ed8febf33a63f63ba81c12ce0123730f2de Mon Sep 17 00:00:00 2001 From: jvukicev Date: Tue, 26 Nov 2024 11:42:36 +0100 Subject: [PATCH 02/13] Fix singleton retrieval when not initialized --- .../com/oracle/svm/hosted/SubstrateStrengthenGraphs.java | 6 +++--- .../svm/hosted/phases/AnalyzeReflectionUsagePhase.java | 3 +-- .../com/oracle/svm/hosted/reflect/ReflectionFeature.java | 6 ++++-- 3 files changed, 8 insertions(+), 7 deletions(-) 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 71f2de175fe6..9a409263e5d3 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 @@ -72,9 +72,9 @@ public class SubstrateStrengthenGraphs extends StrengthenGraphs { public SubstrateStrengthenGraphs(Inflation bb, Universe converter) { super(bb, converter); - reflectionUsageJarPaths = SubstrateOptions.TrackReflectionUsage.getValue(bb.getOptions()); - trackJavaHomeAccess = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccess.getValue(bb.getOptions()); - trackJavaHomeAccessDetailed = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccessDetailed.getValue(bb.getOptions()); + reflectionUsageJarPaths = SubstrateOptions.TrackReflectionUsage.getValue(); + trackJavaHomeAccess = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccess.getValue(); + trackJavaHomeAccessDetailed = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccessDetailed.getValue(); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java index 9e916b76eb29..7d4620e0503d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java @@ -75,7 +75,6 @@ public class AnalyzeReflectionUsagePhase extends BasePhase { "java.lang.reflect.ReflectAccess", Set.of("newInstance"), "jdk.internal.access.JavaLangAccess", Set.of("getDeclaredPublicMethods"), "sun.misc.Unsafe", Set.of("allocateInstance")); - private static final AnalyzeReflectionUsageSupport singleton = AnalyzeReflectionUsageSupport.instance(); private final Set includedPackages = new HashSet<>(); public AnalyzeReflectionUsagePhase(String jarPaths) { @@ -93,7 +92,7 @@ protected void run(StructuredGraph graph, CoreProviders context) { NodeSourcePosition nspToShow = callTarget.getNodeSourcePosition(); if (nspToShow != null) { int bci = nspToShow.getBCI(); - singleton.addReflectiveCall(reflectiveMethodName, nspToShow.getMethod().asStackTraceElement(bci).toString()); + AnalyzeReflectionUsageSupport.instance().addReflectiveCall(reflectiveMethodName, nspToShow.getMethod().asStackTraceElement(bci).toString()); } } } 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 cdcfa5be2764..c8d3f4304806 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 @@ -356,7 +356,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { /* Make sure array classes don't need to be registered for reflection. */ RuntimeReflection.register(Object.class.getDeclaredMethods()); - if (SubstrateOptions.TrackReflectionUsage.getValue(HostedOptionValues.singleton()) != null) { + if (SubstrateOptions.TrackReflectionUsage.getValue() != null) { ImageSingletons.add(AnalyzeReflectionUsageSupport.class, new AnalyzeReflectionUsageSupport()); } } @@ -369,7 +369,9 @@ public void afterAnalysis(AfterAnalysisAccess access) { @Override public void beforeCompilation(BeforeCompilationAccess access) { - AnalyzeReflectionUsageSupport.instance().reportReflection(); + if (SubstrateOptions.TrackReflectionUsage.getValue() != null) { + AnalyzeReflectionUsageSupport.instance().reportReflection(); + } metaAccess = ((BeforeCompilationAccessImpl) access).getMetaAccess(); From 6ed7f7f439047baba506c659806ae26ad9ef9739 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Wed, 27 Nov 2024 09:45:15 +0100 Subject: [PATCH 03/13] Remove jar parsing and cache jar package retrieval --- .../hosted/AnalyzeReflectionUsageSupport.java | 11 ++++- .../svm/hosted/SubstrateStrengthenGraphs.java | 2 +- .../phases/AnalyzeReflectionUsagePhase.java | 49 +++++++------------ .../svm/hosted/reflect/ReflectionFeature.java | 2 +- 4 files changed, 30 insertions(+), 34 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java index 080d87f5b81a..a72c1e0e3a9e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java @@ -34,17 +34,22 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; import java.util.TreeMap; import java.util.List; import java.util.Map; public final class AnalyzeReflectionUsageSupport { private final Map> reflectiveCalls; + private final Set jarPaths = new HashSet<>(); private static final String ARTIFACT_FILE_NAME = "reflection-usage.json"; - public AnalyzeReflectionUsageSupport() { + public AnalyzeReflectionUsageSupport(String paths) { this.reflectiveCalls = new TreeMap<>(); + this.jarPaths.addAll(Arrays.asList(paths.split(":"))); } public static AnalyzeReflectionUsageSupport instance() { @@ -100,5 +105,9 @@ private static Path getTargetPath() { Path buildPath = NativeImageGenerator.generatedFiles(HostedOptionValues.singleton()); return buildPath.resolve(ARTIFACT_FILE_NAME); } + + public Set getJarPaths() { + return jarPaths; + } } 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 9a409263e5d3..b8bdac7c81e3 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 @@ -80,7 +80,7 @@ public SubstrateStrengthenGraphs(Inflation bb, Universe converter) { @Override protected void preStrengthenGraphs(StructuredGraph graph, AnalysisMethod method) { if (reflectionUsageJarPaths != null) { - new AnalyzeReflectionUsagePhase(reflectionUsageJarPaths).apply(graph, bb.getProviders(method)); + new AnalyzeReflectionUsagePhase().apply(graph, bb.getProviders(method)); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java index 7d4620e0503d..19a4a9e365aa 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java @@ -32,18 +32,15 @@ import jdk.graal.compiler.nodes.spi.CoreProviders; import jdk.graal.compiler.phases.BasePhase; -import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashSet; +import java.net.URISyntaxException; +import java.net.URL; +import java.security.CodeSource; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; public class AnalyzeReflectionUsagePhase extends BasePhase { private static final Map> reflectMethodNames = Map.of( @@ -75,13 +72,6 @@ public class AnalyzeReflectionUsagePhase extends BasePhase { "java.lang.reflect.ReflectAccess", Set.of("newInstance"), "jdk.internal.access.JavaLangAccess", Set.of("getDeclaredPublicMethods"), "sun.misc.Unsafe", Set.of("allocateInstance")); - private final Set includedPackages = new HashSet<>(); - - public AnalyzeReflectionUsagePhase(String jarPaths) { - for (String jarPath : jarPaths.split(":")) { - getPackagesFromJar(Paths.get(jarPath), includedPackages); - } - } @Override protected void run(StructuredGraph graph, CoreProviders context) { @@ -100,7 +90,7 @@ protected void run(StructuredGraph graph, CoreProviders context) { private String getReflectiveMethod(StructuredGraph graph, MethodCallTargetNode callTarget) { AnalysisType callerClass = (AnalysisType) graph.method().getDeclaringClass(); - if (!includedPackages.contains(callerClass.getJavaClass().getPackageName())) { + if (!containedInJars(callerClass)) { return null; } String methodName = callTarget.targetMethod().getName(); @@ -113,24 +103,21 @@ private String getReflectiveMethod(StructuredGraph graph, MethodCallTargetNode c return null; } - private static void getPackagesFromJar(Path jarPath, Set packageSet) { - try (JarFile jarFile = new JarFile(jarPath.toFile())) { - jarFile.stream() - .filter(entry -> entry.getName().endsWith(".class")) - .map(AnalyzeReflectionUsagePhase::getPackageName) - .filter(pkg -> !pkg.isEmpty()) - .forEach(packageSet::add); - } catch (IOException e) { - throw new RuntimeException(e); - } - } + private boolean containedInJars(AnalysisType callerClass) { + try { + CodeSource jarPathSource = callerClass.getJavaClass().getProtectionDomain().getCodeSource(); + if (jarPathSource == null) { + return false; + } - private static String getPackageName(JarEntry entry) { - String entryName = entry.getName(); - int lastSlashIndex = entryName.lastIndexOf('/'); - if (lastSlashIndex == -1) { - return ""; // Default package + URL jarPathURL = jarPathSource.getLocation(); + if (jarPathURL == null) { + return false; + } + + return AnalyzeReflectionUsageSupport.instance().getJarPaths().contains(jarPathURL.toURI().getPath()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); } - return entryName.substring(0, lastSlashIndex).replace('/', '.'); } } 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 c8d3f4304806..b718dbac2d75 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 @@ -357,7 +357,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { RuntimeReflection.register(Object.class.getDeclaredMethods()); if (SubstrateOptions.TrackReflectionUsage.getValue() != null) { - ImageSingletons.add(AnalyzeReflectionUsageSupport.class, new AnalyzeReflectionUsageSupport()); + ImageSingletons.add(AnalyzeReflectionUsageSupport.class, new AnalyzeReflectionUsageSupport(SubstrateOptions.TrackReflectionUsage.getValue())); } } From ce74465c86eaa2f0d2b5206e723aef00f9a2e04c Mon Sep 17 00:00:00 2001 From: jvukicev Date: Wed, 27 Nov 2024 11:16:34 +0100 Subject: [PATCH 04/13] Refactor option to be compliant with the project and update documentation --- .../src/com/oracle/svm/core/SubstrateOptions.java | 4 ++-- .../svm/hosted/AnalyzeReflectionUsageSupport.java | 12 ++++++------ .../oracle/svm/hosted/SubstrateStrengthenGraphs.java | 7 +++---- .../oracle/svm/hosted/reflect/ReflectionFeature.java | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) 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 70bfc653866c..f1349c52ddf0 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 @@ -1352,6 +1352,6 @@ public static class TruffleStableOptions { } }); - @Option(help = "Output all reflection usages in the reached parts of the project in a report file.")// - public static final HostedOptionKey TrackReflectionUsage = new HostedOptionKey<>(null); + @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 TrackReflectionUsage = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java index a72c1e0e3a9e..002e62ddce0a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java @@ -25,6 +25,7 @@ package com.oracle.svm.hosted; import com.oracle.svm.core.BuildArtifacts; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.option.HostedOptionValues; import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.util.json.JsonBuilder; @@ -34,7 +35,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Set; @@ -44,12 +45,12 @@ public final class AnalyzeReflectionUsageSupport { private final Map> reflectiveCalls; - private final Set jarPaths = new HashSet<>(); + private final Set jarPaths; private static final String ARTIFACT_FILE_NAME = "reflection-usage.json"; - public AnalyzeReflectionUsageSupport(String paths) { + public AnalyzeReflectionUsageSupport() { this.reflectiveCalls = new TreeMap<>(); - this.jarPaths.addAll(Arrays.asList(paths.split(":"))); + this.jarPaths = Collections.unmodifiableSet(new HashSet<>(SubstrateOptions.TrackReflectionUsage.getValue().values())); } public static AnalyzeReflectionUsageSupport instance() { @@ -89,7 +90,7 @@ public void dumpReflectionReport() { } BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.BUILD_INFO, getTargetPath()); // Maybe shouldn't be BUILD_INFO } catch (IOException e) { - System.out.println("Failed to print JSON:"); + System.out.println("Failed to print JSON to " + getTargetPath() + ":"); e.printStackTrace(System.out); } } @@ -110,4 +111,3 @@ public Set getJarPaths() { return jarPaths; } } - 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 b8bdac7c81e3..91eaaaf6cfa2 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 @@ -65,21 +65,20 @@ import jdk.vm.ci.meta.JavaTypeProfile; public class SubstrateStrengthenGraphs extends StrengthenGraphs { - private final String reflectionUsageJarPaths; - + private final Boolean trackReflectionUsage; private final Boolean trackJavaHomeAccess; private final Boolean trackJavaHomeAccessDetailed; public SubstrateStrengthenGraphs(Inflation bb, Universe converter) { super(bb, converter); - reflectionUsageJarPaths = SubstrateOptions.TrackReflectionUsage.getValue(); + trackReflectionUsage = SubstrateOptions.TrackReflectionUsage.hasBeenSet(); trackJavaHomeAccess = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccess.getValue(); trackJavaHomeAccessDetailed = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccessDetailed.getValue(); } @Override protected void preStrengthenGraphs(StructuredGraph graph, AnalysisMethod method) { - if (reflectionUsageJarPaths != null) { + if (trackReflectionUsage) { new AnalyzeReflectionUsagePhase().apply(graph, bb.getProviders(method)); } } 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 b718dbac2d75..c8d3f4304806 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 @@ -357,7 +357,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { RuntimeReflection.register(Object.class.getDeclaredMethods()); if (SubstrateOptions.TrackReflectionUsage.getValue() != null) { - ImageSingletons.add(AnalyzeReflectionUsageSupport.class, new AnalyzeReflectionUsageSupport(SubstrateOptions.TrackReflectionUsage.getValue())); + ImageSingletons.add(AnalyzeReflectionUsageSupport.class, new AnalyzeReflectionUsageSupport()); } } From d379dae976c5a6fb13f232c20955c236f26b2791 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Wed, 27 Nov 2024 15:54:30 +0100 Subject: [PATCH 05/13] Add system to ignore bulk methods that have ReachabilityRegistrationNode-s but can't be folded --- .../hosted/AnalyzeReflectionUsageSupport.java | 49 +++++++++++++++++++ .../phases/AnalyzeReflectionUsagePhase.java | 4 +- .../hosted/snippets/ReflectionPlugins.java | 2 + 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java index 002e62ddce0a..06069458b670 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java @@ -30,6 +30,7 @@ import jdk.graal.compiler.debug.GraalError; 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; @@ -38,14 +39,17 @@ 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; public final class AnalyzeReflectionUsageSupport { private final Map> reflectiveCalls; private final Set jarPaths; + private final Set foldEntries = ConcurrentHashMap.newKeySet(); private static final String ARTIFACT_FILE_NAME = "reflection-usage.json"; public AnalyzeReflectionUsageSupport() { @@ -110,4 +114,49 @@ private static Path getTargetPath() { public Set getJarPaths() { return jarPaths; } + + public Set getFoldEntries() { + return this.foldEntries; + } + + /* + * Support data structure used to keep track of reflective calls which don't require metadata, but can't be folded. + */ + public static class FoldEntry { + public FoldEntry(int bci, ResolvedJavaMethod method) { + this.bci = bci; + this.method = method; + } + + int bci; + ResolvedJavaMethod 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)); + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java index 19a4a9e365aa..7b771fd7ca17 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java @@ -82,7 +82,9 @@ protected void run(StructuredGraph graph, CoreProviders context) { NodeSourcePosition nspToShow = callTarget.getNodeSourcePosition(); if (nspToShow != null) { int bci = nspToShow.getBCI(); - AnalyzeReflectionUsageSupport.instance().addReflectiveCall(reflectiveMethodName, nspToShow.getMethod().asStackTraceElement(bci).toString()); + if (!AnalyzeReflectionUsageSupport.instance().containsFoldEntry(bci, nspToShow.getMethod())) { + AnalyzeReflectionUsageSupport.instance().addReflectiveCall(reflectiveMethodName, nspToShow.getMethod().asStackTraceElement(bci).toString()); + } } } } 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..e96ce20e21dd 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.AnalyzeReflectionUsageSupport; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; @@ -573,6 +574,7 @@ private boolean registerConstantBulkReflectionQuery(GraphBuilderContext b, R } b.add(ReachabilityRegistrationNode.create(() -> registerForRuntimeReflection((T) receiverValue, registrationCallback), reason)); + AnalyzeReflectionUsageSupport.instance().addFoldEntry(b.bci(), b.getMethod()); return true; } From 6f8765bdb28dd475a4425503230ef77a23bf5cc9 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Thu, 28 Nov 2024 09:20:29 +0100 Subject: [PATCH 06/13] Move option to phase support class and add documentation --- .../com/oracle/svm/core/SubstrateOptions.java | 3 - .../hosted/AnalyzeReflectionUsageSupport.java | 27 ++++++-- .../svm/hosted/SubstrateStrengthenGraphs.java | 2 +- .../phases/AnalyzeReflectionUsagePhase.java | 64 +++++++++++-------- .../svm/hosted/reflect/ReflectionFeature.java | 4 +- .../hosted/snippets/ReflectionPlugins.java | 5 +- 6 files changed, 64 insertions(+), 41 deletions(-) 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 f1349c52ddf0..33e3e3e347ab 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 @@ -1351,7 +1351,4 @@ public static class TruffleStableOptions { throw UserError.invalidOptionValue(key, key.getValue(), "Mapping the image heap with mremap() is only supported on Linux."); } }); - - @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 TrackReflectionUsage = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java index 06069458b670..4d466f370f8b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java @@ -25,9 +25,11 @@ package com.oracle.svm.hosted; import com.oracle.svm.core.BuildArtifacts; -import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; +import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.HostedOptionValues; 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; @@ -46,6 +48,11 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +/** + * This is a support class that keeps track of reflection usages detected during + * {@link com.oracle.svm.hosted.phases.AnalyzeReflectionUsagePhase} and outputs them to the + * image-build output. + */ public final class AnalyzeReflectionUsageSupport { private final Map> reflectiveCalls; private final Set jarPaths; @@ -54,7 +61,7 @@ public final class AnalyzeReflectionUsageSupport { public AnalyzeReflectionUsageSupport() { this.reflectiveCalls = new TreeMap<>(); - this.jarPaths = Collections.unmodifiableSet(new HashSet<>(SubstrateOptions.TrackReflectionUsage.getValue().values())); + this.jarPaths = Collections.unmodifiableSet(new HashSet<>(AnalyzeReflectionUsageSupport.Options.TrackReflectionUsage.getValue().values())); } public static AnalyzeReflectionUsageSupport instance() { @@ -84,7 +91,7 @@ public void printReflectionReport() { public void dumpReflectionReport() { try (var writer = new JsonWriter(getTargetPath()); - var builder = writer.objectBuilder()) { + var builder = writer.objectBuilder()) { for (Map.Entry> entry : this.reflectiveCalls.entrySet()) { try (JsonBuilder.ArrayBuilder array = builder.append(entry.getKey()).array()) { for (String call : entry.getValue()) { @@ -92,7 +99,7 @@ public void dumpReflectionReport() { } } } - BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.BUILD_INFO, getTargetPath()); // Maybe shouldn't be BUILD_INFO + BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.BUILD_INFO, getTargetPath()); } catch (IOException e) { System.out.println("Failed to print JSON to " + getTargetPath() + ":"); e.printStackTrace(System.out); @@ -120,7 +127,8 @@ public Set getFoldEntries() { } /* - * Support data structure used to keep track of reflective calls which don't require metadata, but can't be folded. + * Support data structure used to keep track of reflective calls which don't require metadata, + * but can't be folded. */ public static class FoldEntry { public FoldEntry(int bci, ResolvedJavaMethod method) { @@ -154,9 +162,16 @@ public void addFoldEntry(int bci, ResolvedJavaMethod method) { } /* - * If a fold entry exists for the given method, the method should be ignored by the analysis phase. + * 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 TrackReflectionUsage = 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 91eaaaf6cfa2..8229012d89f1 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 @@ -71,7 +71,7 @@ public class SubstrateStrengthenGraphs extends StrengthenGraphs { public SubstrateStrengthenGraphs(Inflation bb, Universe converter) { super(bb, converter); - trackReflectionUsage = SubstrateOptions.TrackReflectionUsage.hasBeenSet(); + trackReflectionUsage = AnalyzeReflectionUsageSupport.Options.TrackReflectionUsage.hasBeenSet(); trackJavaHomeAccess = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccess.getValue(); trackJavaHomeAccessDetailed = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccessDetailed.getValue(); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java index 7b771fd7ca17..6f9fc2193fb5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java @@ -42,36 +42,44 @@ import java.util.Map; import java.util.Set; +/** + * 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 AnalyzeReflectionUsageSupport.Options#TrackReflectionUsage} option and providing the + * desired JAR path/s. + */ public class AnalyzeReflectionUsagePhase extends BasePhase { private static final Map> reflectMethodNames = Map.of( - 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"), - Method.class.getTypeName(), Set.of("invoke"), - Constructor.class.getTypeName(), Set.of("newInstance"), - Proxy.class.getTypeName(), Set.of("getProxyClass", "newProxyInstance"), - "java.lang.reflect.ReflectAccess", Set.of("newInstance"), - "jdk.internal.access.JavaLangAccess", Set.of("getDeclaredPublicMethods"), - "sun.misc.Unsafe", Set.of("allocateInstance")); + 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"), + Method.class.getTypeName(), Set.of("invoke"), + Constructor.class.getTypeName(), Set.of("newInstance"), + Proxy.class.getTypeName(), Set.of("getProxyClass", "newProxyInstance"), + "java.lang.reflect.ReflectAccess", Set.of("newInstance"), + "jdk.internal.access.JavaLangAccess", Set.of("getDeclaredPublicMethods"), + "sun.misc.Unsafe", Set.of("allocateInstance")); @Override protected void run(StructuredGraph graph, CoreProviders context) { 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 c8d3f4304806..3234668cec97 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 @@ -356,7 +356,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { /* Make sure array classes don't need to be registered for reflection. */ RuntimeReflection.register(Object.class.getDeclaredMethods()); - if (SubstrateOptions.TrackReflectionUsage.getValue() != null) { + if (AnalyzeReflectionUsageSupport.Options.TrackReflectionUsage.getValue() != null) { ImageSingletons.add(AnalyzeReflectionUsageSupport.class, new AnalyzeReflectionUsageSupport()); } } @@ -369,7 +369,7 @@ public void afterAnalysis(AfterAnalysisAccess access) { @Override public void beforeCompilation(BeforeCompilationAccess access) { - if (SubstrateOptions.TrackReflectionUsage.getValue() != null) { + if (AnalyzeReflectionUsageSupport.Options.TrackReflectionUsage.getValue() != null) { AnalyzeReflectionUsageSupport.instance().reportReflection(); } 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 e96ce20e21dd..6fe3439d2ae5 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.core.SubstrateOptions; import com.oracle.svm.hosted.AnalyzeReflectionUsageSupport; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeReflection; @@ -574,7 +575,9 @@ private boolean registerConstantBulkReflectionQuery(GraphBuilderContext b, R } b.add(ReachabilityRegistrationNode.create(() -> registerForRuntimeReflection((T) receiverValue, registrationCallback), reason)); - AnalyzeReflectionUsageSupport.instance().addFoldEntry(b.bci(), b.getMethod()); + if (AnalyzeReflectionUsageSupport.Options.TrackReflectionUsage.getValue() != null) { + AnalyzeReflectionUsageSupport.instance().addFoldEntry(b.bci(), b.getMethod()); + } return true; } From 27b35d1d85ebf32cd9960c86c2934590fb371462 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Thu, 28 Nov 2024 11:15:34 +0100 Subject: [PATCH 07/13] Cache option value --- .../svm/hosted/AnalyzeReflectionUsageSupport.java | 10 +++------- .../oracle/svm/hosted/snippets/ReflectionPlugins.java | 5 ++++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java index 4d466f370f8b..9e9d9b2e5d32 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java @@ -122,23 +122,19 @@ public Set getJarPaths() { return jarPaths; } - public Set getFoldEntries() { - return this.foldEntries; - } - /* * 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; } - int bci; - ResolvedJavaMethod method; - @Override public boolean equals(Object obj) { if (this == obj) { 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 6fe3439d2ae5..6d185b934f4b 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 @@ -123,6 +123,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; @@ -131,6 +133,7 @@ private ReflectionPlugins(ImageClassLoader imageClassLoader, AnnotationSubstitut this.aUniverse = aUniverse; this.reason = reason; this.fallbackFeature = fallbackFeature; + this.analyzeReflectionUsage = AnalyzeReflectionUsageSupport.Options.TrackReflectionUsage.hasBeenSet(); this.classInitializationSupport = (ClassInitializationSupport) ImageSingletons.lookup(RuntimeClassInitializationSupport.class); } @@ -575,7 +578,7 @@ private boolean registerConstantBulkReflectionQuery(GraphBuilderContext b, R } b.add(ReachabilityRegistrationNode.create(() -> registerForRuntimeReflection((T) receiverValue, registrationCallback), reason)); - if (AnalyzeReflectionUsageSupport.Options.TrackReflectionUsage.getValue() != null) { + if (analyzeReflectionUsage) { AnalyzeReflectionUsageSupport.instance().addFoldEntry(b.bci(), b.getMethod()); } return true; From a4cb2d94689d992520a30f988e68b752e50d3eb6 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Thu, 28 Nov 2024 15:38:06 +0100 Subject: [PATCH 08/13] Expand reflective method list --- .../phases/AnalyzeReflectionUsagePhase.java | 92 +++++++++++++------ 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java index 6f9fc2193fb5..fb4cf885dca4 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java @@ -32,12 +32,17 @@ import jdk.graal.compiler.nodes.spi.CoreProviders; import jdk.graal.compiler.phases.BasePhase; +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandles; +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.security.CodeSource; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -51,35 +56,64 @@ * desired JAR path/s. */ public class AnalyzeReflectionUsagePhase extends BasePhase { - private static final Map> reflectMethodNames = Map.of( - 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"), - Method.class.getTypeName(), Set.of("invoke"), - Constructor.class.getTypeName(), Set.of("newInstance"), - Proxy.class.getTypeName(), Set.of("getProxyClass", "newProxyInstance"), - "java.lang.reflect.ReflectAccess", Set.of("newInstance"), - "jdk.internal.access.JavaLangAccess", Set.of("getDeclaredPublicMethods"), - "sun.misc.Unsafe", Set.of("allocateInstance")); + private static final Map> reflectMethodNames = 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.class.getTypeName(), Set.of("arrayConstructor")); + reflectMethodNames.put(MethodHandles.Lookup.class.getTypeName(), Set.of( + "defineClass", + "defineHiddenClass", + "defineHiddenClassWithClassData", + "findVirtual", + "findStatic", + "findConstructor", + "findSpecial", + "findGetter", + "findSetter", + "findStaticGetter", + "findStaticSetter", + "findVarHandle", + "findStaticVarHandle", + "unreflect", + "unreflectSpecial", + "unreflectConstructor", + "unreflectGetter", + "unreflectSetter", + "unreflectVarHandle", + "arrayConstructor")); + reflectMethodNames.put(ClassLoader.class.getTypeName(), Set.of("loadClass", "findBootstrapClassOrNull", "findLoadedClass")); + reflectMethodNames.put(URLClassLoader.class.getTypeName(), Set.of("loadClass")); + reflectMethodNames.put(LambdaMetafactory.class.getTypeName(), Set.of("metafactory", "altMetafactory")); + reflectMethodNames.put(Array.class.getTypeName(), Set.of("newInstance")); + reflectMethodNames.put(Constructor.class.getTypeName(), Set.of("newInstance")); + reflectMethodNames.put(Proxy.class.getTypeName(), Set.of("getProxyClass", "newProxyInstance")); + reflectMethodNames.put("java.lang.reflect.ReflectAccess", Set.of("newInstance")); + reflectMethodNames.put("jdk.internal.access.JavaLangAccess", Set.of("getDeclaredPublicMethods")); + reflectMethodNames.put("sun.misc.Unsafe", Set.of("allocateInstance")); + } @Override protected void run(StructuredGraph graph, CoreProviders context) { From f01db63162db289ad7563de01943117fa2c3852e Mon Sep 17 00:00:00 2001 From: jvukicev Date: Fri, 29 Nov 2024 14:51:32 +0100 Subject: [PATCH 09/13] Expand reflective method name list and change output --- .../phases/AnalyzeReflectionUsagePhase.java | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java index fb4cf885dca4..2cbbd0f301dc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java @@ -34,13 +34,13 @@ import java.lang.invoke.LambdaMetafactory; import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; 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.security.CodeSource; import java.util.HashMap; import java.util.List; @@ -80,10 +80,24 @@ public class AnalyzeReflectionUsagePhase extends BasePhase { "getRecordComponents", "getSigners", "arrayType", - "newInstance")); + "newInstance" + )); reflectMethodNames.put(Method.class.getTypeName(), Set.of("invoke")); - reflectMethodNames.put(MethodHandles.class.getTypeName(), Set.of("arrayConstructor")); + reflectMethodNames.put(MethodHandles.class.getTypeName(), Set.of( + "arrayLength", + "arrayElementGetter", + "arrayElementSetter", + "arrayElementVarHandle", + "byteArrayViewVarHandle", + "byteBufferViewVarHandle", + "publicLookup", + "privateLookupIn", + "arrayConstructor" + )); reflectMethodNames.put(MethodHandles.Lookup.class.getTypeName(), Set.of( + "in", + "findClass", + "accessClass", "defineClass", "defineHiddenClass", "defineHiddenClassWithClassData", @@ -102,10 +116,34 @@ public class AnalyzeReflectionUsagePhase extends BasePhase { "unreflectConstructor", "unreflectGetter", "unreflectSetter", - "unreflectVarHandle", - "arrayConstructor")); - reflectMethodNames.put(ClassLoader.class.getTypeName(), Set.of("loadClass", "findBootstrapClassOrNull", "findLoadedClass")); - reflectMethodNames.put(URLClassLoader.class.getTypeName(), Set.of("loadClass")); + "unreflectVarHandle" + )); + reflectMethodNames.put(ClassLoader.class.getTypeName(), Set.of( + "loadClass", + "defineClass", + "findBootstrapClassOrNull", + "findClass", + "findSystemClass", + "findLoadedClass" + )); + reflectMethodNames.put(MethodType.class.getTypeName(), Set.of( + "methodType", + "genericMethodType", + "makeImpl", + "changeParameterType", + "insertParameterTypes", + "appendParameterTypes", + "replaceParameterTypes", + "dropParameterTypes", + "changeReturnType", + "erase", + "generic", + "wrap", + "unwrap", + "parameterType", + "returnType", + "lastParameterType" + )); reflectMethodNames.put(LambdaMetafactory.class.getTypeName(), Set.of("metafactory", "altMetafactory")); reflectMethodNames.put(Array.class.getTypeName(), Set.of("newInstance")); reflectMethodNames.put(Constructor.class.getTypeName(), Set.of("newInstance")); @@ -141,7 +179,7 @@ private String getReflectiveMethod(StructuredGraph graph, MethodCallTargetNode c String declaringClass = callTarget.targetMethod().getDeclaringClass().toJavaName(); if (reflectMethodNames.containsKey(declaringClass)) { if (reflectMethodNames.get(declaringClass).contains(methodName)) { - return methodName; + return declaringClass + "#" + methodName; } } return null; From b9e8410d08d6ca8ba52c8be57b68ccffd0d77868 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Mon, 2 Dec 2024 11:33:52 +0100 Subject: [PATCH 10/13] Change getMethod() calls to getRootMethod() calls in output, to get the actual lines in some fringe cases --- .../svm/hosted/phases/AnalyzeReflectionUsagePhase.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java index 2cbbd0f301dc..39e8be2432cd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java @@ -162,8 +162,8 @@ protected void run(StructuredGraph graph, CoreProviders context) { NodeSourcePosition nspToShow = callTarget.getNodeSourcePosition(); if (nspToShow != null) { int bci = nspToShow.getBCI(); - if (!AnalyzeReflectionUsageSupport.instance().containsFoldEntry(bci, nspToShow.getMethod())) { - AnalyzeReflectionUsageSupport.instance().addReflectiveCall(reflectiveMethodName, nspToShow.getMethod().asStackTraceElement(bci).toString()); + if (!AnalyzeReflectionUsageSupport.instance().containsFoldEntry(bci, nspToShow.getRootMethod())) { + AnalyzeReflectionUsageSupport.instance().addReflectiveCall(reflectiveMethodName, nspToShow.getRootMethod().asStackTraceElement(bci).toString()); } } } @@ -179,6 +179,8 @@ private String getReflectiveMethod(StructuredGraph graph, MethodCallTargetNode c String declaringClass = callTarget.targetMethod().getDeclaringClass().toJavaName(); if (reflectMethodNames.containsKey(declaringClass)) { if (reflectMethodNames.get(declaringClass).contains(methodName)) { + System.out.println(callerClass); + System.out.println(declaringClass); return declaringClass + "#" + methodName; } } From aa7e9a37aef6ab9f16de3583471ba14d28bdfc92 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Sat, 7 Dec 2024 11:32:12 +0100 Subject: [PATCH 11/13] Finalize base reflective call list --- .../phases/AnalyzeReflectionUsagePhase.java | 47 +++---------------- 1 file changed, 6 insertions(+), 41 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java index 39e8be2432cd..5427c9ec1a62 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java @@ -41,6 +41,7 @@ import java.lang.reflect.Proxy; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLClassLoader; import java.security.CodeSource; import java.util.HashMap; import java.util.List; @@ -83,24 +84,7 @@ public class AnalyzeReflectionUsagePhase extends BasePhase { "newInstance" )); reflectMethodNames.put(Method.class.getTypeName(), Set.of("invoke")); - reflectMethodNames.put(MethodHandles.class.getTypeName(), Set.of( - "arrayLength", - "arrayElementGetter", - "arrayElementSetter", - "arrayElementVarHandle", - "byteArrayViewVarHandle", - "byteBufferViewVarHandle", - "publicLookup", - "privateLookupIn", - "arrayConstructor" - )); reflectMethodNames.put(MethodHandles.Lookup.class.getTypeName(), Set.of( - "in", - "findClass", - "accessClass", - "defineClass", - "defineHiddenClass", - "defineHiddenClassWithClassData", "findVirtual", "findStatic", "findConstructor", @@ -120,31 +104,10 @@ public class AnalyzeReflectionUsagePhase extends BasePhase { )); reflectMethodNames.put(ClassLoader.class.getTypeName(), Set.of( "loadClass", - "defineClass", "findBootstrapClassOrNull", - "findClass", - "findSystemClass", "findLoadedClass" )); - reflectMethodNames.put(MethodType.class.getTypeName(), Set.of( - "methodType", - "genericMethodType", - "makeImpl", - "changeParameterType", - "insertParameterTypes", - "appendParameterTypes", - "replaceParameterTypes", - "dropParameterTypes", - "changeReturnType", - "erase", - "generic", - "wrap", - "unwrap", - "parameterType", - "returnType", - "lastParameterType" - )); - reflectMethodNames.put(LambdaMetafactory.class.getTypeName(), Set.of("metafactory", "altMetafactory")); + 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(Proxy.class.getTypeName(), Set.of("getProxyClass", "newProxyInstance")); @@ -162,8 +125,10 @@ protected void run(StructuredGraph graph, CoreProviders context) { NodeSourcePosition nspToShow = callTarget.getNodeSourcePosition(); if (nspToShow != null) { int bci = nspToShow.getBCI(); - if (!AnalyzeReflectionUsageSupport.instance().containsFoldEntry(bci, nspToShow.getRootMethod())) { - AnalyzeReflectionUsageSupport.instance().addReflectiveCall(reflectiveMethodName, nspToShow.getRootMethod().asStackTraceElement(bci).toString()); + if (!AnalyzeReflectionUsageSupport.instance().containsFoldEntry(bci, nspToShow.getMethod())) { + if (nspToShow.getMethod().isPublic() || nspToShow.getMethod().isProtected()) { + AnalyzeReflectionUsageSupport.instance().addReflectiveCall(reflectiveMethodName, nspToShow.getMethod().asStackTraceElement(bci).toString()); + } } } } From 90359c9a9fd924dfaca31536dc6a666c626e52ec Mon Sep 17 00:00:00 2001 From: jvukicev Date: Mon, 9 Dec 2024 15:31:37 +0100 Subject: [PATCH 12/13] Add transitive java.base reflective calls --- .../phases/AnalyzeReflectionUsagePhase.java | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java index 5427c9ec1a62..a92ee5ea13b6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java @@ -31,10 +31,18 @@ import jdk.graal.compiler.nodes.java.MethodCallTargetNode; import jdk.graal.compiler.nodes.spi.CoreProviders; import jdk.graal.compiler.phases.BasePhase; +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.lang.invoke.ConstantBootstraps; import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandleProxies; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Method; @@ -47,6 +55,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.random.RandomGeneratorFactory; /** * This phase detects usages of reflective calls in reached parts of the project, given the JAR @@ -85,6 +94,7 @@ public class AnalyzeReflectionUsagePhase extends BasePhase { )); reflectMethodNames.put(Method.class.getTypeName(), Set.of("invoke")); reflectMethodNames.put(MethodHandles.Lookup.class.getTypeName(), Set.of( + "findClass", "findVirtual", "findStatic", "findConstructor", @@ -105,15 +115,37 @@ public class AnalyzeReflectionUsagePhase extends BasePhase { reflectMethodNames.put(ClassLoader.class.getTypeName(), Set.of( "loadClass", "findBootstrapClassOrNull", - "findLoadedClass" + "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(Proxy.class.getTypeName(), Set.of("getProxyClass", "newProxyInstance")); reflectMethodNames.put("java.lang.reflect.ReflectAccess", Set.of("newInstance")); - reflectMethodNames.put("jdk.internal.access.JavaLangAccess", Set.of("getDeclaredPublicMethods")); reflectMethodNames.put("sun.misc.Unsafe", Set.of("allocateInstance")); + + // Transitive reflections + 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")); } @Override @@ -126,9 +158,7 @@ protected void run(StructuredGraph graph, CoreProviders context) { if (nspToShow != null) { int bci = nspToShow.getBCI(); if (!AnalyzeReflectionUsageSupport.instance().containsFoldEntry(bci, nspToShow.getMethod())) { - if (nspToShow.getMethod().isPublic() || nspToShow.getMethod().isProtected()) { - AnalyzeReflectionUsageSupport.instance().addReflectiveCall(reflectiveMethodName, nspToShow.getMethod().asStackTraceElement(bci).toString()); - } + AnalyzeReflectionUsageSupport.instance().addReflectiveCall(reflectiveMethodName, nspToShow.getMethod().asStackTraceElement(bci).toString()); } } } @@ -144,8 +174,6 @@ private String getReflectiveMethod(StructuredGraph graph, MethodCallTargetNode c String declaringClass = callTarget.targetMethod().getDeclaringClass().toJavaName(); if (reflectMethodNames.containsKey(declaringClass)) { if (reflectMethodNames.get(declaringClass).contains(methodName)) { - System.out.println(callerClass); - System.out.println(declaringClass); return declaringClass + "#" + methodName; } } From cb8446b46b4f52418e00f9b7dc36bff4a419270b Mon Sep 17 00:00:00 2001 From: jvukicev Date: Fri, 13 Dec 2024 14:09:48 +0100 Subject: [PATCH 13/13] Add tracking of non-reflective calls that require metadata Also changed the name of the phase to be concurrent with the new functionality. --- ...MethodsRequiringMetadataUsageSupport.java} | 106 +++++++++++----- .../svm/hosted/SubstrateStrengthenGraphs.java | 9 +- ...zeMethodsRequiringMetadataUsagePhase.java} | 119 +++++++++++++++--- .../svm/hosted/reflect/ReflectionFeature.java | 11 +- .../hosted/snippets/ReflectionPlugins.java | 7 +- 5 files changed, 184 insertions(+), 68 deletions(-) rename substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/{AnalyzeReflectionUsageSupport.java => AnalyzeMethodsRequiringMetadataUsageSupport.java} (51%) rename substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/{AnalyzeReflectionUsagePhase.java => AnalyzeMethodsRequiringMetadataUsagePhase.java} (58%) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeMethodsRequiringMetadataUsageSupport.java similarity index 51% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeMethodsRequiringMetadataUsageSupport.java index 9e9d9b2e5d32..33124ab43c41 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeReflectionUsageSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AnalyzeMethodsRequiringMetadataUsageSupport.java @@ -28,6 +28,7 @@ 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; @@ -50,74 +51,115 @@ /** * This is a support class that keeps track of reflection usages detected during - * {@link com.oracle.svm.hosted.phases.AnalyzeReflectionUsagePhase} and outputs them to the + * {@link AnalyzeMethodsRequiringMetadataUsagePhase} and outputs them to the * image-build output. */ -public final class AnalyzeReflectionUsageSupport { +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(); - private static final String ARTIFACT_FILE_NAME = "reflection-usage.json"; - public AnalyzeReflectionUsageSupport() { + public AnalyzeMethodsRequiringMetadataUsageSupport() { this.reflectiveCalls = new TreeMap<>(); - this.jarPaths = Collections.unmodifiableSet(new HashSet<>(AnalyzeReflectionUsageSupport.Options.TrackReflectionUsage.getValue().values())); + 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 AnalyzeReflectionUsageSupport instance() { - AnalyzeReflectionUsageSupport trus = ImageSingletons.lookup(AnalyzeReflectionUsageSupport.class); + public static AnalyzeMethodsRequiringMetadataUsageSupport instance() { + AnalyzeMethodsRequiringMetadataUsageSupport trus = ImageSingletons.lookup(AnalyzeMethodsRequiringMetadataUsageSupport.class); GraalError.guarantee(trus != null, "Should never be null."); return trus; } - public void addReflectiveCall(String reflectiveCall, String callLocation) { - if (!this.reflectiveCalls.containsKey(reflectiveCall)) { - List callLocationList = new ArrayList<>(); - this.reflectiveCalls.put(reflectiveCall, callLocationList); + 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); } - this.reflectiveCalls.get(reflectiveCall).add(callLocation); } - public void printReflectionReport() { - System.out.println("Reflective calls detected:"); - for (String key : this.reflectiveCalls.keySet()) { + 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 + ":"); - this.reflectiveCalls.get(key).sort(Comparator.comparing(String::toString)); - for (String callLocation : this.reflectiveCalls.get(key)) { + callMap.get(key).sort(Comparator.comparing(String::toString)); + for (String callLocation : callMap.get(key)) { System.out.println(" at " + callLocation); } } } - public void dumpReflectionReport() { - try (var writer = new JsonWriter(getTargetPath()); - var builder = writer.objectBuilder()) { - for (Map.Entry> entry : this.reflectiveCalls.entrySet()) { + 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, getTargetPath()); + BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.BUILD_INFO, targetPath); } catch (IOException e) { - System.out.println("Failed to print JSON to " + getTargetPath() + ":"); + System.out.println("Failed to print JSON to " + targetPath + ":"); e.printStackTrace(System.out); } } public void reportReflection() { - if (!this.reflectiveCalls.isEmpty()) { - printReflectionReport(); - dumpReflectionReport(); + 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); + } } } - private static Path getTargetPath() { - Path buildPath = NativeImageGenerator.generatedFiles(HostedOptionValues.singleton()); - return buildPath.resolve(ARTIFACT_FILE_NAME); - } - public Set getJarPaths() { return jarPaths; } @@ -167,7 +209,7 @@ public boolean containsFoldEntry(int bci, ResolvedJavaMethod 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 TrackReflectionUsage = new HostedOptionKey<>( + 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 8229012d89f1..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 @@ -24,17 +24,14 @@ */ package com.oracle.svm.hosted; -import java.nio.file.Path; import java.util.function.Supplier; -import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.heap.ImageLayerLoader; import com.oracle.graal.pointsto.infrastructure.Universe; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.results.StrengthenGraphs; import com.oracle.svm.common.meta.MultiMethod; -import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.graal.nodes.InlinedInvokeArgumentsNode; @@ -48,7 +45,7 @@ import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; import com.oracle.svm.hosted.meta.HostedType; -import com.oracle.svm.hosted.phases.AnalyzeReflectionUsagePhase; +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; @@ -71,7 +68,7 @@ public class SubstrateStrengthenGraphs extends StrengthenGraphs { public SubstrateStrengthenGraphs(Inflation bb, Universe converter) { super(bb, converter); - trackReflectionUsage = AnalyzeReflectionUsageSupport.Options.TrackReflectionUsage.hasBeenSet(); + trackReflectionUsage = AnalyzeMethodsRequiringMetadataUsageSupport.Options.TrackMethodsRequiringMetadata.hasBeenSet(); trackJavaHomeAccess = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccess.getValue(); trackJavaHomeAccessDetailed = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccessDetailed.getValue(); } @@ -79,7 +76,7 @@ public SubstrateStrengthenGraphs(Inflation bb, Universe converter) { @Override protected void preStrengthenGraphs(StructuredGraph graph, AnalysisMethod method) { if (trackReflectionUsage) { - new AnalyzeReflectionUsagePhase().apply(graph, bb.getProviders(method)); + new AnalyzeMethodsRequiringMetadataUsagePhase().apply(graph, bb.getProviders(method)); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeMethodsRequiringMetadataUsagePhase.java similarity index 58% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeMethodsRequiringMetadataUsagePhase.java index a92ee5ea13b6..1d9d11fd3de8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeReflectionUsagePhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalyzeMethodsRequiringMetadataUsagePhase.java @@ -25,23 +25,27 @@ package com.oracle.svm.hosted.phases; import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.svm.hosted.AnalyzeReflectionUsageSupport; +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.LambdaMetafactory; import java.lang.invoke.MethodHandleProxies; import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.lang.invoke.VarHandle; import java.lang.reflect.Array; import java.lang.reflect.Constructor; @@ -50,23 +54,37 @@ 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 AnalyzeReflectionUsageSupport.Options#TrackReflectionUsage} option and providing the + * {@link AnalyzeMethodsRequiringMetadataUsageSupport.Options#TrackMethodsRequiringMetadata} option and providing the * desired JAR path/s. */ -public class AnalyzeReflectionUsagePhase extends BasePhase { + +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( @@ -121,13 +139,10 @@ public class AnalyzeReflectionUsagePhase extends BasePhase { 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(Proxy.class.getTypeName(), Set.of("getProxyClass", "newProxyInstance")); reflectMethodNames.put("java.lang.reflect.ReflectAccess", Set.of("newInstance")); reflectMethodNames.put("sun.misc.Unsafe", Set.of("allocateInstance")); - - // Transitive reflections reflectMethodNames.put("java.lang.constant.ReferenceClassDescImpl", Set.of("resolveConstantDesc")); - reflectMethodNames.put(ObjectInputStream.class.getTypeName(), Set.of("resolveClass", "resolveProxyClass")); // + 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")); @@ -144,38 +159,102 @@ public class AnalyzeReflectionUsagePhase extends BasePhase { 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(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) { - String reflectiveMethodName = getReflectiveMethod(graph, callTarget); - if (reflectiveMethodName != null) { + 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 (!AnalyzeReflectionUsageSupport.instance().containsFoldEntry(bci, nspToShow.getMethod())) { - AnalyzeReflectionUsageSupport.instance().addReflectiveCall(reflectiveMethodName, nspToShow.getMethod().asStackTraceElement(bci).toString()); + if (!AnalyzeMethodsRequiringMetadataUsageSupport.instance().containsFoldEntry(bci, nspToShow.getMethod())) { + String callLocation = nspToShow.getMethod().asStackTraceElement(bci).toString(); + AnalyzeMethodsRequiringMetadataUsageSupport.instance().addCall(methodType, methodName, callLocation); } } } } } - private String getReflectiveMethod(StructuredGraph graph, MethodCallTargetNode callTarget) { + 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)) { - if (reflectMethodNames.get(declaringClass).contains(methodName)) { - return declaringClass + "#" + methodName; - } + + 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; } @@ -192,7 +271,7 @@ private boolean containedInJars(AnalysisType callerClass) { return false; } - return AnalyzeReflectionUsageSupport.instance().getJarPaths().contains(jarPathURL.toURI().getPath()); + 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 3234668cec97..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,8 +38,7 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; -import com.oracle.svm.hosted.AnalyzeReflectionUsageSupport; -import com.oracle.svm.core.option.HostedOptionValues; +import com.oracle.svm.hosted.AnalyzeMethodsRequiringMetadataUsageSupport; import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.function.CFunctionPointer; @@ -356,8 +355,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { /* Make sure array classes don't need to be registered for reflection. */ RuntimeReflection.register(Object.class.getDeclaredMethods()); - if (AnalyzeReflectionUsageSupport.Options.TrackReflectionUsage.getValue() != null) { - ImageSingletons.add(AnalyzeReflectionUsageSupport.class, new AnalyzeReflectionUsageSupport()); + if (AnalyzeMethodsRequiringMetadataUsageSupport.Options.TrackMethodsRequiringMetadata.getValue() != null) { + ImageSingletons.add(AnalyzeMethodsRequiringMetadataUsageSupport.class, new AnalyzeMethodsRequiringMetadataUsageSupport()); } } @@ -369,8 +368,8 @@ public void afterAnalysis(AfterAnalysisAccess access) { @Override public void beforeCompilation(BeforeCompilationAccess access) { - if (AnalyzeReflectionUsageSupport.Options.TrackReflectionUsage.getValue() != null) { - AnalyzeReflectionUsageSupport.instance().reportReflection(); + if (AnalyzeMethodsRequiringMetadataUsageSupport.Options.TrackMethodsRequiringMetadata.getValue() != null) { + AnalyzeMethodsRequiringMetadataUsageSupport.instance().reportReflection(); } metaAccess = ((BeforeCompilationAccessImpl) access).getMetaAccess(); 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 6d185b934f4b..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,8 +48,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.hosted.AnalyzeReflectionUsageSupport; +import com.oracle.svm.hosted.AnalyzeMethodsRequiringMetadataUsageSupport; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; @@ -133,7 +132,7 @@ private ReflectionPlugins(ImageClassLoader imageClassLoader, AnnotationSubstitut this.aUniverse = aUniverse; this.reason = reason; this.fallbackFeature = fallbackFeature; - this.analyzeReflectionUsage = AnalyzeReflectionUsageSupport.Options.TrackReflectionUsage.hasBeenSet(); + this.analyzeReflectionUsage = AnalyzeMethodsRequiringMetadataUsageSupport.Options.TrackMethodsRequiringMetadata.hasBeenSet(); this.classInitializationSupport = (ClassInitializationSupport) ImageSingletons.lookup(RuntimeClassInitializationSupport.class); } @@ -579,7 +578,7 @@ private boolean registerConstantBulkReflectionQuery(GraphBuilderContext b, R b.add(ReachabilityRegistrationNode.create(() -> registerForRuntimeReflection((T) receiverValue, registrationCallback), reason)); if (analyzeReflectionUsage) { - AnalyzeReflectionUsageSupport.instance().addFoldEntry(b.bci(), b.getMethod()); + AnalyzeMethodsRequiringMetadataUsageSupport.instance().addFoldEntry(b.bci(), b.getMethod()); } return true; }