Skip to content

Commit 47afe25

Browse files
committed
Add reflection detection phase
1 parent 0c2731c commit 47afe25

File tree

6 files changed

+466
-3
lines changed

6 files changed

+466
-3
lines changed

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,8 @@ public final void applyResults(AnalysisMethod method) {
247247
return;
248248
}
249249

250+
preStrengthenGraphs(graph, method);
251+
250252
graph.resetDebug(debug);
251253
if (beforeCounters != null) {
252254
beforeCounters.collect(graph);
@@ -274,6 +276,8 @@ public final void applyResults(AnalysisMethod method) {
274276
}
275277
}
276278

279+
protected abstract void preStrengthenGraphs(StructuredGraph graph, AnalysisMethod method);
280+
277281
protected abstract void postStrengthenGraphs(StructuredGraph graph, AnalysisMethod method);
278282

279283
protected abstract void persistStrengthenGraph(AnalysisMethod method);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
import java.util.Optional;
8181
import java.util.StringJoiner;
8282

83+
import com.oracle.svm.core.NeverInlineTrivial;
8384
import org.graalvm.nativeimage.AnnotationAccess;
8485
import org.graalvm.nativeimage.ImageSingletons;
8586
import org.graalvm.nativeimage.Platform;
@@ -1602,27 +1603,31 @@ private static Constructor<?>[] copyConstructors(Constructor<?>[] original) {
16021603
private native Constructor<?> getEnclosingConstructor();
16031604

16041605
@Substitute
1606+
@NeverInlineTrivial("Used in metadata requiring call usage analysis: AnalyzeMethodsRequiringMetadataUsagePhase")
16051607
@Platforms(InternalPlatform.NATIVE_ONLY.class)
16061608
@CallerSensitive
16071609
private static Class<?> forName(String className) throws Throwable {
16081610
return forName(className, Reflection.getCallerClass());
16091611
}
16101612

16111613
@Substitute
1614+
@NeverInlineTrivial("Used in metadata requiring call usage analysis: AnalyzeMethodsRequiringMetadataUsagePhase")
16121615
@Platforms(InternalPlatform.NATIVE_ONLY.class)
16131616
@CallerSensitiveAdapter
16141617
private static Class<?> forName(String className, Class<?> caller) throws Throwable {
16151618
return forName(className, true, caller.getClassLoader(), caller);
16161619
}
16171620

16181621
@Substitute
1622+
@NeverInlineTrivial("Used in metadata requiring call usage analysis: AnalyzeMethodsRequiringMetadataUsagePhase")
16191623
@Platforms(InternalPlatform.NATIVE_ONLY.class)
16201624
@CallerSensitive
16211625
private static Class<?> forName(Module module, String className) throws Throwable {
16221626
return forName(module, className, Reflection.getCallerClass());
16231627
}
16241628

16251629
@Substitute
1630+
@NeverInlineTrivial("Used in metadata requiring call usage analysis: AnalyzeMethodsRequiringMetadataUsagePhase")
16261631
@Platforms(InternalPlatform.NATIVE_ONLY.class)
16271632
@CallerSensitiveAdapter
16281633
@TargetElement(onlyWith = JDK21OrEarlier.class)
@@ -1639,12 +1644,14 @@ private static Class<?> forName(@SuppressWarnings("unused") Module module, Strin
16391644
}
16401645

16411646
@Substitute
1647+
@NeverInlineTrivial("Used in metadata requiring call usage analysis: AnalyzeMethodsRequiringMetadataUsagePhase")
16421648
@CallerSensitive
16431649
private static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws Throwable {
16441650
return forName(name, initialize, loader, Reflection.getCallerClass());
16451651
}
16461652

16471653
@Substitute
1654+
@NeverInlineTrivial("Used in metadata requiring call usage analysis: AnalyzeMethodsRequiringMetadataUsagePhase")
16481655
@CallerSensitiveAdapter
16491656
@TargetElement(onlyWith = JDK21OrEarlier.class)
16501657
private static Class<?> forName(String name, boolean initialize, ClassLoader loader, @SuppressWarnings("unused") Class<?> caller) throws Throwable {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.hosted;
26+
27+
import com.oracle.svm.core.BuildArtifacts;
28+
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
29+
import com.oracle.svm.core.feature.InternalFeature;
30+
import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue;
31+
import com.oracle.svm.core.option.HostedOptionKey;
32+
import com.oracle.svm.core.option.HostedOptionValues;
33+
import com.oracle.svm.hosted.phases.AnalyzeMethodsRequiringMetadataUsagePhase;
34+
import jdk.graal.compiler.options.Option;
35+
import jdk.graal.compiler.util.json.JsonBuilder;
36+
import jdk.graal.compiler.util.json.JsonWriter;
37+
import jdk.vm.ci.meta.ResolvedJavaMethod;
38+
import org.graalvm.nativeimage.ImageSingletons;
39+
40+
import java.io.IOException;
41+
import java.nio.file.Path;
42+
import java.util.ArrayList;
43+
import java.util.Collections;
44+
import java.util.HashMap;
45+
import java.util.HashSet;
46+
import java.util.Objects;
47+
import java.util.Set;
48+
import java.util.TreeMap;
49+
import java.util.List;
50+
import java.util.Map;
51+
import java.util.concurrent.ConcurrentHashMap;
52+
53+
/**
54+
* This is a support class that keeps track of calls requiring metadata usage detected during
55+
* {@link AnalyzeMethodsRequiringMetadataUsagePhase} and outputs them to the image-build output.
56+
*/
57+
@AutomaticallyRegisteredFeature
58+
public final class AnalyzeMethodsRequiringMetadataUsageFeature implements InternalFeature {
59+
public static final String METHODTYPE_REFLECTION = "reflection";
60+
public static final String METHODTYPE_RESOURCE = "resource";
61+
public static final String METHODTYPE_SERIALIZATION = "serialization";
62+
public static final String METHODTYPE_PROXY = "proxy";
63+
private final Set<String> jarPaths;
64+
private final Map<String, Map<String, Map<String, List<String>>>> callsByJar;
65+
private final Set<FoldEntry> foldEntries = ConcurrentHashMap.newKeySet();
66+
67+
public AnalyzeMethodsRequiringMetadataUsageFeature() {
68+
this.callsByJar = new ConcurrentHashMap<>();
69+
this.jarPaths = Collections.unmodifiableSet(new HashSet<>(AnalyzeMethodsRequiringMetadataUsageFeature.Options.TrackMethodsRequiringMetadata.getValue().values()));
70+
}
71+
72+
public static AnalyzeMethodsRequiringMetadataUsageFeature instance() {
73+
return ImageSingletons.lookup(AnalyzeMethodsRequiringMetadataUsageFeature.class);
74+
}
75+
76+
public void addCall(String jarPath, String methodType, String call, String callLocation) {
77+
this.callsByJar.computeIfAbsent(jarPath, k -> new HashMap<>());
78+
this.callsByJar.get(jarPath).computeIfAbsent(methodType, k -> new TreeMap<>());
79+
this.callsByJar.get(jarPath).get(methodType).computeIfAbsent(call, k -> new ArrayList<>());
80+
this.callsByJar.get(jarPath).get(methodType).get(call).add(callLocation);
81+
}
82+
83+
public void printReportForJar(String jarPath) {
84+
System.out.println("Dynamic method usage detected in " + jarPath + ":");
85+
for (String methodType : callsByJar.get(jarPath).keySet()) {
86+
System.out.println(" " + methodType.substring(0, 1).toUpperCase() + methodType.substring(1) + " calls detected:");
87+
for (String call : callsByJar.get(jarPath).get(methodType).keySet()) {
88+
System.out.println(" " + call + ":");
89+
for (String callLocation : callsByJar.get(jarPath).get(methodType).get(call)) {
90+
System.out.println(" at " + callLocation);
91+
}
92+
}
93+
}
94+
}
95+
96+
public void dumpReportForJar(String jarPath) {
97+
String fileName = extractLibraryName(jarPath) + "-method-calls.json";
98+
Map<String, Map<String, List<String>>> calls = callsByJar.get(jarPath);
99+
Path targetPath = NativeImageGenerator.generatedFiles(HostedOptionValues.singleton()).resolve(fileName);
100+
try (var writer = new JsonWriter(targetPath);
101+
var builder = writer.objectBuilder()) {
102+
for (Map.Entry<String, Map<String, List<String>>> callEntry : calls.entrySet()) {
103+
try (JsonBuilder.ObjectBuilder methodsByTypeBuilder = builder.append(callEntry.getKey()).object()) {
104+
Map<String, List<String>> nestedMap = callEntry.getValue();
105+
for (Map.Entry<String, List<String>> entry : nestedMap.entrySet()) {
106+
try (JsonBuilder.ArrayBuilder array = methodsByTypeBuilder.append(entry.getKey()).array()) {
107+
for (String call : entry.getValue()) {
108+
array.append(call);
109+
}
110+
}
111+
}
112+
}
113+
}
114+
BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.BUILD_INFO, targetPath);
115+
} catch (IOException e) {
116+
System.out.println("Failed to print JSON to " + targetPath + ":");
117+
e.printStackTrace(System.out);
118+
}
119+
}
120+
121+
public void reportMethodUsage() {
122+
for (String jarPath : jarPaths) {
123+
printReportForJar(jarPath);
124+
dumpReportForJar(jarPath);
125+
}
126+
}
127+
128+
public Set<String> getJarPaths() {
129+
return jarPaths;
130+
}
131+
132+
public String extractLibraryName(String path) {
133+
String fileName = path.substring(path.lastIndexOf("/") + 1);
134+
135+
if (fileName.endsWith(".jar")) {
136+
fileName = fileName.substring(0, fileName.length() - 4);
137+
}
138+
139+
return fileName;
140+
}
141+
142+
/*
143+
* Support data structure used to keep track of calls which don't require metadata, but can't be
144+
* folded.
145+
*/
146+
public static class FoldEntry {
147+
private final int bci;
148+
private final ResolvedJavaMethod method;
149+
150+
public FoldEntry(int bci, ResolvedJavaMethod method) {
151+
this.bci = bci;
152+
this.method = method;
153+
}
154+
155+
@Override
156+
public boolean equals(Object obj) {
157+
if (this == obj) {
158+
return true;
159+
}
160+
if (obj == null || getClass() != obj.getClass()) {
161+
return false;
162+
}
163+
FoldEntry other = (FoldEntry) obj;
164+
return bci == other.bci && Objects.equals(method, other.method);
165+
}
166+
167+
@Override
168+
public int hashCode() {
169+
return Objects.hash(bci, method);
170+
}
171+
}
172+
173+
public void addFoldEntry(int bci, ResolvedJavaMethod method) {
174+
this.foldEntries.add(new FoldEntry(bci, method));
175+
}
176+
177+
/*
178+
* If a fold entry exists for the given method, the method should be ignored by the analysis
179+
* phase.
180+
*/
181+
public boolean containsFoldEntry(int bci, ResolvedJavaMethod method) {
182+
return this.foldEntries.contains(new FoldEntry(bci, method));
183+
}
184+
185+
@Override
186+
public void beforeCompilation(BeforeCompilationAccess access) {
187+
AnalyzeMethodsRequiringMetadataUsageFeature.instance().reportMethodUsage();
188+
}
189+
190+
public static class Options {
191+
@Option(help = "Output all metadata requiring call usages in the reached parts of the project, limited to the provided comma-separated list of JAR files.")//
192+
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> TrackMethodsRequiringMetadata = new HostedOptionKey<>(
193+
AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter());
194+
}
195+
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SubstrateStrengthenGraphs.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import com.oracle.svm.hosted.code.SubstrateCompilationDirectives;
4444
import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport;
4545
import com.oracle.svm.hosted.meta.HostedType;
46+
47+
import com.oracle.svm.hosted.phases.AnalyzeMethodsRequiringMetadataUsagePhase;
4648
import com.oracle.svm.hosted.phases.AnalyzeJavaHomeAccessPhase;
4749

4850
import jdk.graal.compiler.graph.Node;
@@ -60,14 +62,22 @@
6062
import jdk.vm.ci.meta.JavaTypeProfile;
6163

6264
public class SubstrateStrengthenGraphs extends StrengthenGraphs {
63-
65+
private final Boolean trackMetadataRequiringMethodUsage;
6466
private final Boolean trackJavaHomeAccess;
6567
private final Boolean trackJavaHomeAccessDetailed;
6668

6769
public SubstrateStrengthenGraphs(Inflation bb, Universe converter) {
6870
super(bb, converter);
69-
trackJavaHomeAccess = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccess.getValue(bb.getOptions());
70-
trackJavaHomeAccessDetailed = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccessDetailed.getValue(bb.getOptions());
71+
trackMetadataRequiringMethodUsage = AnalyzeMethodsRequiringMetadataUsageFeature.Options.TrackMethodsRequiringMetadata.hasBeenSet();
72+
trackJavaHomeAccess = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccess.getValue();
73+
trackJavaHomeAccessDetailed = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccessDetailed.getValue();
74+
}
75+
76+
@Override
77+
protected void preStrengthenGraphs(StructuredGraph graph, AnalysisMethod method) {
78+
if (trackMetadataRequiringMethodUsage) {
79+
new AnalyzeMethodsRequiringMetadataUsagePhase().apply(graph, bb.getProviders(method));
80+
}
7181
}
7282

7383
@Override

0 commit comments

Comments
 (0)