Skip to content

Commit

Permalink
Add reflection detection phase
Browse files Browse the repository at this point in the history
  • Loading branch information
jormundur00 committed Dec 23, 2024
1 parent 0c2731c commit 47afe25
Show file tree
Hide file tree
Showing 6 changed files with 466 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ public final void applyResults(AnalysisMethod method) {
return;
}

preStrengthenGraphs(graph, method);

graph.resetDebug(debug);
if (beforeCounters != null) {
beforeCounters.collect(graph);
Expand Down Expand Up @@ -274,6 +276,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 persistStrengthenGraph(AnalysisMethod method);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,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;
Expand Down Expand Up @@ -1602,27 +1603,31 @@ private static Constructor<?>[] copyConstructors(Constructor<?>[] original) {
private native Constructor<?> getEnclosingConstructor();

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

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

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

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

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

@Substitute
@NeverInlineTrivial("Used in metadata requiring call usage analysis: AnalyzeMethodsRequiringMetadataUsagePhase")
@CallerSensitiveAdapter
@TargetElement(onlyWith = JDK21OrEarlier.class)
private static Class<?> forName(String name, boolean initialize, ClassLoader loader, @SuppressWarnings("unused") Class<?> caller) throws Throwable {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* 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.feature.AutomaticallyRegisteredFeature;
import com.oracle.svm.core.feature.InternalFeature;
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.options.Option;
import jdk.graal.compiler.util.json.JsonBuilder;
import jdk.graal.compiler.util.json.JsonWriter;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import org.graalvm.nativeimage.ImageSingletons;

import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* This is a support class that keeps track of calls requiring metadata usage detected during
* {@link AnalyzeMethodsRequiringMetadataUsagePhase} and outputs them to the image-build output.
*/
@AutomaticallyRegisteredFeature
public final class AnalyzeMethodsRequiringMetadataUsageFeature implements InternalFeature {
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";
private final Set<String> jarPaths;
private final Map<String, Map<String, Map<String, List<String>>>> callsByJar;
private final Set<FoldEntry> foldEntries = ConcurrentHashMap.newKeySet();

public AnalyzeMethodsRequiringMetadataUsageFeature() {
this.callsByJar = new ConcurrentHashMap<>();
this.jarPaths = Collections.unmodifiableSet(new HashSet<>(AnalyzeMethodsRequiringMetadataUsageFeature.Options.TrackMethodsRequiringMetadata.getValue().values()));
}

public static AnalyzeMethodsRequiringMetadataUsageFeature instance() {
return ImageSingletons.lookup(AnalyzeMethodsRequiringMetadataUsageFeature.class);
}

public void addCall(String jarPath, String methodType, String call, String callLocation) {
this.callsByJar.computeIfAbsent(jarPath, k -> new HashMap<>());
this.callsByJar.get(jarPath).computeIfAbsent(methodType, k -> new TreeMap<>());
this.callsByJar.get(jarPath).get(methodType).computeIfAbsent(call, k -> new ArrayList<>());
this.callsByJar.get(jarPath).get(methodType).get(call).add(callLocation);
}

public void printReportForJar(String jarPath) {
System.out.println("Dynamic method usage detected in " + jarPath + ":");
for (String methodType : callsByJar.get(jarPath).keySet()) {
System.out.println(" " + methodType.substring(0, 1).toUpperCase() + methodType.substring(1) + " calls detected:");
for (String call : callsByJar.get(jarPath).get(methodType).keySet()) {
System.out.println(" " + call + ":");
for (String callLocation : callsByJar.get(jarPath).get(methodType).get(call)) {
System.out.println(" at " + callLocation);
}
}
}
}

public void dumpReportForJar(String jarPath) {
String fileName = extractLibraryName(jarPath) + "-method-calls.json";
Map<String, Map<String, List<String>>> calls = callsByJar.get(jarPath);
Path targetPath = NativeImageGenerator.generatedFiles(HostedOptionValues.singleton()).resolve(fileName);
try (var writer = new JsonWriter(targetPath);
var builder = writer.objectBuilder()) {
for (Map.Entry<String, Map<String, List<String>>> callEntry : calls.entrySet()) {
try (JsonBuilder.ObjectBuilder methodsByTypeBuilder = builder.append(callEntry.getKey()).object()) {
Map<String, List<String>> nestedMap = callEntry.getValue();
for (Map.Entry<String, List<String>> entry : nestedMap.entrySet()) {
try (JsonBuilder.ArrayBuilder array = methodsByTypeBuilder.append(entry.getKey()).array()) {
for (String call : entry.getValue()) {
array.append(call);
}
}
}
}
}
BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.BUILD_INFO, targetPath);
} catch (IOException e) {
System.out.println("Failed to print JSON to " + targetPath + ":");
e.printStackTrace(System.out);
}
}

public void reportMethodUsage() {
for (String jarPath : jarPaths) {
printReportForJar(jarPath);
dumpReportForJar(jarPath);
}
}

public Set<String> getJarPaths() {
return jarPaths;
}

public String extractLibraryName(String path) {
String fileName = path.substring(path.lastIndexOf("/") + 1);

if (fileName.endsWith(".jar")) {
fileName = fileName.substring(0, fileName.length() - 4);
}

return fileName;
}

/*
* Support data structure used to keep track of calls which don't require metadata, but can't be
* folded.
*/
public static class FoldEntry {
private final int bci;
private final ResolvedJavaMethod method;

public FoldEntry(int bci, ResolvedJavaMethod method) {
this.bci = bci;
this.method = method;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
FoldEntry other = (FoldEntry) obj;
return bci == other.bci && Objects.equals(method, other.method);
}

@Override
public int hashCode() {
return Objects.hash(bci, method);
}
}

public void addFoldEntry(int bci, ResolvedJavaMethod method) {
this.foldEntries.add(new FoldEntry(bci, method));
}

/*
* If a fold entry exists for the given method, the method should be ignored by the analysis
* phase.
*/
public boolean containsFoldEntry(int bci, ResolvedJavaMethod method) {
return this.foldEntries.contains(new FoldEntry(bci, method));
}

@Override
public void beforeCompilation(BeforeCompilationAccess access) {
AnalyzeMethodsRequiringMetadataUsageFeature.instance().reportMethodUsage();
}

public static class Options {
@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.")//
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> TrackMethodsRequiringMetadata = new HostedOptionKey<>(
AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
import com.oracle.svm.hosted.code.SubstrateCompilationDirectives;
import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport;
import com.oracle.svm.hosted.meta.HostedType;

import com.oracle.svm.hosted.phases.AnalyzeMethodsRequiringMetadataUsagePhase;
import com.oracle.svm.hosted.phases.AnalyzeJavaHomeAccessPhase;

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

public class SubstrateStrengthenGraphs extends StrengthenGraphs {

private final Boolean trackMetadataRequiringMethodUsage;
private final Boolean trackJavaHomeAccess;
private final Boolean trackJavaHomeAccessDetailed;

public SubstrateStrengthenGraphs(Inflation bb, Universe converter) {
super(bb, converter);
trackJavaHomeAccess = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccess.getValue(bb.getOptions());
trackJavaHomeAccessDetailed = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccessDetailed.getValue(bb.getOptions());
trackMetadataRequiringMethodUsage = AnalyzeMethodsRequiringMetadataUsageFeature.Options.TrackMethodsRequiringMetadata.hasBeenSet();
trackJavaHomeAccess = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccess.getValue();
trackJavaHomeAccessDetailed = AnalyzeJavaHomeAccessFeature.Options.TrackJavaHomeAccessDetailed.getValue();
}

@Override
protected void preStrengthenGraphs(StructuredGraph graph, AnalysisMethod method) {
if (trackMetadataRequiringMethodUsage) {
new AnalyzeMethodsRequiringMetadataUsagePhase().apply(graph, bb.getProviders(method));
}
}

@Override
Expand Down
Loading

0 comments on commit 47afe25

Please sign in to comment.