Skip to content

Commit 5453059

Browse files
committed
Add reflection detection phase
1 parent ad42202 commit 5453059

File tree

7 files changed

+484
-3
lines changed

7 files changed

+484
-3
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ public final void applyResults(AnalysisMethod method) {
244244
return;
245245
}
246246

247+
preStrengthenGraphs(graph, method);
248+
247249
graph.resetDebug(debug);
248250
if (beforeCounters != null) {
249251
beforeCounters.collect(graph);
@@ -271,6 +273,8 @@ public final void applyResults(AnalysisMethod method) {
271273
}
272274
}
273275

276+
protected abstract void preStrengthenGraphs(StructuredGraph graph, AnalysisMethod method);
277+
274278
protected abstract void postStrengthenGraphs(StructuredGraph graph, AnalysisMethod method);
275279

276280
protected abstract void useSharedLayerGraph(AnalysisMethod method);

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

+7
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import java.util.Optional;
7676
import java.util.StringJoiner;
7777

78+
import com.oracle.svm.core.NeverInlineTrivial;
7879
import org.graalvm.nativeimage.AnnotationAccess;
7980
import org.graalvm.nativeimage.ImageSingletons;
8081
import org.graalvm.nativeimage.Platform;
@@ -1450,27 +1451,31 @@ private static Constructor<?>[] copyConstructors(Constructor<?>[] original) {
14501451
private native Constructor<?> getEnclosingConstructor();
14511452

14521453
@Substitute
1454+
@NeverInlineTrivial("Used in metadata requiring call usage analysis: AnalyzeMethodsRequiringMetadataUsagePhase")
14531455
@Platforms(InternalPlatform.NATIVE_ONLY.class)
14541456
@CallerSensitive
14551457
private static Class<?> forName(String className) throws Throwable {
14561458
return forName(className, Reflection.getCallerClass());
14571459
}
14581460

14591461
@Substitute
1462+
@NeverInlineTrivial("Used in metadata requiring call usage analysis: AnalyzeMethodsRequiringMetadataUsagePhase")
14601463
@Platforms(InternalPlatform.NATIVE_ONLY.class)
14611464
@CallerSensitiveAdapter
14621465
private static Class<?> forName(String className, Class<?> caller) throws Throwable {
14631466
return forName(className, true, caller.getClassLoader(), caller);
14641467
}
14651468

14661469
@Substitute
1470+
@NeverInlineTrivial("Used in metadata requiring call usage analysis: AnalyzeMethodsRequiringMetadataUsagePhase")
14671471
@Platforms(InternalPlatform.NATIVE_ONLY.class)
14681472
@CallerSensitive
14691473
private static Class<?> forName(Module module, String className) throws Throwable {
14701474
return forName(module, className, Reflection.getCallerClass());
14711475
}
14721476

14731477
@Substitute
1478+
@NeverInlineTrivial("Used in metadata requiring call usage analysis: AnalyzeMethodsRequiringMetadataUsagePhase")
14741479
@Platforms(InternalPlatform.NATIVE_ONLY.class)
14751480
@CallerSensitiveAdapter
14761481
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
14861491
}
14871492

14881493
@Substitute
1494+
@NeverInlineTrivial("Used in metadata requiring call usage analysis: AnalyzeMethodsRequiringMetadataUsagePhase")
14891495
@CallerSensitive
14901496
private static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws Throwable {
14911497
return forName(name, initialize, loader, Reflection.getCallerClass());
14921498
}
14931499

14941500
@Substitute
1501+
@NeverInlineTrivial("Used in metadata requiring call usage analysis: AnalyzeMethodsRequiringMetadataUsagePhase")
14951502
@CallerSensitiveAdapter
14961503
private static Class<?> forName(String name, boolean initialize, ClassLoader loader, @SuppressWarnings("unused") Class<?> caller) throws Throwable {
14971504
if (name == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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.option.AccumulatingLocatableMultiOptionValue;
29+
import com.oracle.svm.core.option.HostedOptionKey;
30+
import com.oracle.svm.core.option.HostedOptionValues;
31+
import com.oracle.svm.hosted.phases.AnalyzeMethodsRequiringMetadataUsagePhase;
32+
import jdk.graal.compiler.debug.GraalError;
33+
import jdk.graal.compiler.options.Option;
34+
import jdk.graal.compiler.util.json.JsonBuilder;
35+
import jdk.graal.compiler.util.json.JsonWriter;
36+
import jdk.vm.ci.meta.ResolvedJavaMethod;
37+
import org.graalvm.nativeimage.ImageSingletons;
38+
39+
import java.io.IOException;
40+
import java.nio.file.Path;
41+
import java.util.ArrayList;
42+
import java.util.Collections;
43+
import java.util.Comparator;
44+
import java.util.HashSet;
45+
import java.util.Objects;
46+
import java.util.Set;
47+
import java.util.TreeMap;
48+
import java.util.List;
49+
import java.util.Map;
50+
import java.util.concurrent.ConcurrentHashMap;
51+
52+
/**
53+
* This is a support class that keeps track of calls requiring metadata usage detected during
54+
* {@link AnalyzeMethodsRequiringMetadataUsagePhase} and outputs them to the image-build output.
55+
*/
56+
public final class AnalyzeMethodsRequiringMetadataUsageSupport {
57+
public static final String METHODTYPE_REFLECTION = "reflection";
58+
public static final String METHODTYPE_RESOURCE = "resource";
59+
public static final String METHODTYPE_SERIALIZATION = "serialization";
60+
public static final String METHODTYPE_PROXY = "proxy";
61+
private final Map<String, List<String>> reflectiveCalls;
62+
private final Map<String, List<String>> resourceCalls;
63+
private final Map<String, List<String>> serializationCalls;
64+
private final Map<String, List<String>> proxyCalls;
65+
private final Set<String> jarPaths;
66+
private final Set<FoldEntry> foldEntries = ConcurrentHashMap.newKeySet();
67+
68+
public AnalyzeMethodsRequiringMetadataUsageSupport() {
69+
this.reflectiveCalls = new TreeMap<>();
70+
this.resourceCalls = new TreeMap<>();
71+
this.serializationCalls = new TreeMap<>();
72+
this.proxyCalls = new TreeMap<>();
73+
this.jarPaths = Collections.unmodifiableSet(new HashSet<>(AnalyzeMethodsRequiringMetadataUsageSupport.Options.TrackMethodsRequiringMetadata.getValue().values()));
74+
}
75+
76+
public static AnalyzeMethodsRequiringMetadataUsageSupport instance() {
77+
AnalyzeMethodsRequiringMetadataUsageSupport trus = ImageSingletons.lookup(AnalyzeMethodsRequiringMetadataUsageSupport.class);
78+
GraalError.guarantee(trus != null, "Should never be null.");
79+
return trus;
80+
}
81+
82+
public void addCall(String methodType, String call, String callLocation) {
83+
switch (methodType) {
84+
case METHODTYPE_REFLECTION -> this.reflectiveCalls.computeIfAbsent(call, k -> new ArrayList<>()).add(callLocation);
85+
case METHODTYPE_RESOURCE -> this.resourceCalls.computeIfAbsent(call, k -> new ArrayList<>()).add(callLocation);
86+
case METHODTYPE_SERIALIZATION -> this.serializationCalls.computeIfAbsent(call, k -> new ArrayList<>()).add(callLocation);
87+
case METHODTYPE_PROXY -> this.proxyCalls.computeIfAbsent(call, k -> new ArrayList<>()).add(callLocation);
88+
default -> throw new IllegalArgumentException("Unknown method type: " + methodType);
89+
}
90+
}
91+
92+
public void printReport(String methodType) {
93+
Map<String, List<String>> callMap = switch (methodType) {
94+
case METHODTYPE_REFLECTION -> this.reflectiveCalls;
95+
case METHODTYPE_RESOURCE -> this.resourceCalls;
96+
case METHODTYPE_SERIALIZATION -> this.serializationCalls;
97+
case METHODTYPE_PROXY -> this.proxyCalls;
98+
default -> throw new IllegalArgumentException("Unknown method type: " + methodType);
99+
};
100+
101+
System.out.println(methodType.substring(0, 1).toUpperCase() + methodType.substring(1) + " calls detected:");
102+
for (String key : callMap.keySet()) {
103+
System.out.println(" " + key + ":");
104+
callMap.get(key).sort(Comparator.comparing(String::toString));
105+
for (String callLocation : callMap.get(key)) {
106+
System.out.println(" at " + callLocation);
107+
}
108+
}
109+
}
110+
111+
public void dumpReport(String methodType) {
112+
Map<String, List<String>> calls = switch (methodType) {
113+
case METHODTYPE_REFLECTION -> this.reflectiveCalls;
114+
case METHODTYPE_RESOURCE -> this.resourceCalls;
115+
case METHODTYPE_SERIALIZATION -> this.serializationCalls;
116+
case METHODTYPE_PROXY -> this.proxyCalls;
117+
default -> throw new IllegalArgumentException("Unknown method type: " + methodType);
118+
};
119+
String fileName = methodType + "-usage.json";
120+
121+
Path targetPath = NativeImageGenerator.generatedFiles(HostedOptionValues.singleton()).resolve(fileName);
122+
try (var writer = new JsonWriter(targetPath);
123+
var builder = writer.objectBuilder()) {
124+
for (Map.Entry<String, List<String>> entry : calls.entrySet()) {
125+
try (JsonBuilder.ArrayBuilder array = builder.append(entry.getKey()).array()) {
126+
for (String call : entry.getValue()) {
127+
array.append(call);
128+
}
129+
}
130+
}
131+
BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.BUILD_INFO, targetPath);
132+
} catch (IOException e) {
133+
System.out.println("Failed to print JSON to " + targetPath + ":");
134+
e.printStackTrace(System.out);
135+
}
136+
}
137+
138+
public void reportReflection() {
139+
Map<String, Map<String, List<String>>> callMaps = Map.of(
140+
METHODTYPE_REFLECTION, this.reflectiveCalls,
141+
METHODTYPE_RESOURCE, this.resourceCalls,
142+
METHODTYPE_SERIALIZATION, this.serializationCalls,
143+
METHODTYPE_PROXY, this.proxyCalls);
144+
for (Map.Entry<String, Map<String, List<String>>> entry : callMaps.entrySet()) {
145+
String methodType = entry.getKey();
146+
Map<String, List<String>> calls = entry.getValue();
147+
if (!calls.isEmpty()) {
148+
printReport(methodType);
149+
dumpReport(methodType);
150+
}
151+
}
152+
}
153+
154+
public Set<String> getJarPaths() {
155+
return jarPaths;
156+
}
157+
158+
/*
159+
* Support data structure used to keep track of calls which don't require metadata, but can't be
160+
* folded.
161+
*/
162+
public static class FoldEntry {
163+
private final int bci;
164+
private final ResolvedJavaMethod method;
165+
166+
public FoldEntry(int bci, ResolvedJavaMethod method) {
167+
this.bci = bci;
168+
this.method = method;
169+
}
170+
171+
@Override
172+
public boolean equals(Object obj) {
173+
if (this == obj) {
174+
return true;
175+
}
176+
if (obj == null || getClass() != obj.getClass()) {
177+
return false;
178+
}
179+
FoldEntry other = (FoldEntry) obj;
180+
return bci == other.bci && Objects.equals(method, other.method);
181+
}
182+
183+
@Override
184+
public int hashCode() {
185+
return Objects.hash(bci, method);
186+
}
187+
}
188+
189+
public void addFoldEntry(int bci, ResolvedJavaMethod method) {
190+
this.foldEntries.add(new FoldEntry(bci, method));
191+
}
192+
193+
/*
194+
* If a fold entry exists for the given method, the method should be ignored by the analysis
195+
* phase.
196+
*/
197+
public boolean containsFoldEntry(int bci, ResolvedJavaMethod method) {
198+
return this.foldEntries.contains(new FoldEntry(bci, method));
199+
}
200+
201+
public static class Options {
202+
@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.")//
203+
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> TrackMethodsRequiringMetadata = new HostedOptionKey<>(
204+
AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter());
205+
}
206+
}

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

+12-3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport;
4646
import com.oracle.svm.hosted.meta.HostedType;
4747

48+
import com.oracle.svm.hosted.phases.AnalyzeMethodsRequiringMetadataUsagePhase;
4849
import com.oracle.svm.hosted.phases.AnalyzeJavaHomeAccessPhase;
4950
import jdk.graal.compiler.graph.Node;
5051
import jdk.graal.compiler.nodes.ConstantNode;
@@ -61,14 +62,22 @@
6162
import jdk.vm.ci.meta.JavaTypeProfile;
6263

6364
public class SubstrateStrengthenGraphs extends StrengthenGraphs {
64-
65+
private final Boolean trackReflectionUsage;
6566
private final Boolean trackJavaHomeAccess;
6667
private final Boolean trackJavaHomeAccessDetailed;
6768

6869
public SubstrateStrengthenGraphs(Inflation bb, Universe converter) {
6970
super(bb, converter);
70-
trackJavaHomeAccess = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccess.getValue(bb.getOptions());
71-
trackJavaHomeAccessDetailed = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccessDetailed.getValue(bb.getOptions());
71+
trackReflectionUsage = AnalyzeMethodsRequiringMetadataUsageSupport.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 (trackReflectionUsage) {
79+
new AnalyzeMethodsRequiringMetadataUsagePhase().apply(graph, bb.getProviders(method));
80+
}
7281
}
7382

7483
@Override

0 commit comments

Comments
 (0)