Skip to content

[GR-57253] Add dynamic call usage detection #10176

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ public final void applyResults(AnalysisMethod method) {
return;
}

preStrengthenGraphs(graph, method);

graph.resetDebug(debug);
if (beforeCounters != null) {
beforeCounters.collect(graph);
Expand Down Expand Up @@ -279,6 +281,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
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2025, 2025, 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.core;

import java.util.function.BooleanSupplier;

public class AnalyzeJavaHomeAccessEnabled implements BooleanSupplier {

@Override
public boolean getAsBoolean() {
return SubstrateOptions.TrackJavaHomeAccess.getValue();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2025, 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
Expand Down Expand Up @@ -31,6 +31,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.BooleanSupplier;

/**
* Every thus annotated method is never trivially inlined by the compiler. Specific inlining to
Expand All @@ -44,5 +45,28 @@
/**
* Documents the reason why the annotated code must not be inlined.
*/
String value();
String reason();

/**
* Prevent inlining only if all of the provided predicates are true (default: a predicate that
* always prevents inlining).
*
* The classes must implement {@link BooleanSupplier}.
*/
Class<?>[] onlyWith() default NeverInlined.class;

/**
* A BooleanSupplier that always returns true, used to indicate methods should never be inlined.
* This serves as the default value for the {@link NeverInlineTrivial#onlyWith()} attribute,
* preventing inlining by default.
*/
final class NeverInlined implements BooleanSupplier {
NeverInlined() {
}

@Override
public boolean getAsBoolean() {
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1484,4 +1484,20 @@ public static boolean hasDumpRuntimeCompiledMethodsSupport() {
return !Platform.includedIn(Platform.WINDOWS.class) && ConcealedOptions.DumpRuntimeCompiledMethods.getValue();
}

@Option(help = "file:doc-files/TrackDynamicAccessHelp.txt")//
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> TrackDynamicAccess = new HostedOptionKey<>(
AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter());

@Option(help = "Track System.getProperty(\\\"java.home\\\") usage in reachable parts of the project.")//
public static final HostedOptionKey<Boolean> TrackJavaHomeAccess = new HostedOptionKey<>(false);

@Option(help = "Output all System.getProperty(\\\"java.home\\\") calls in reachable parts of the project.")//
public static final HostedOptionKey<Boolean> TrackJavaHomeAccessDetailed = new HostedOptionKey<>(false) {
@Override
protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Boolean oldValue, Boolean newValue) {
if (newValue) {
TrackJavaHomeAccess.update(values, true);
}
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2025, 2025, 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.core;

import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;

import java.util.function.BooleanSupplier;

public class TrackDynamicAccessEnabled implements BooleanSupplier {

@Override
public boolean getAsBoolean() {
return isTrackDynamicAccessEnabled();
}

public static boolean isTrackDynamicAccessEnabled() {
return ImageSingletons.contains(TrackDynamicAccessEnabledSingleton.class);
}

@Platforms(Platform.HOSTED_ONLY.class)
public interface TrackDynamicAccessEnabledSingleton {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Reports all reflection (e.g., Class#forName(String)) and resource (e.g., Class#getResource(String)) calls in reachable code that require metadata for dynamic access.
The analysis is limited to the provided comma-separated list of class-path entries, and module or package names.
If any dynamic access calls are found, a "dynamic-access" directory is created in the native image output,
and the calls are serialized in "dynamic-access/<entry-name>/[reflection-calls.json][resource-calls.json]".

Usage: -H:TrackDynamicAccess=[all|none|to-console|path=<cp-entry>|module=<module>|package=<package>][,...]

The flag can be used in following ways:
1. -H:TrackDynamicAccess=all reports all dynamic access calls made across the entire project
2. -H:TrackDynamicAccess=path=<cp-entry> reports all dynamic access calls made from the specified class-path entry
3. -H:TrackDynamicAccess=module=<module> reports all dynamic access calls made from the specified module
4. -H:TrackDynamicAccess=package=<package> reports all dynamic access calls made from the specified package
5. -H:TrackDynamicAccess=none disables all previous selections for dynamic access detection
6. -H:TrackDynamicAccess=to-console outputs all detected dynamic access calls to the console
7. A comma-separated list of the previous cases. For example, -H:TrackDynamicAccess=path=<cp-entry>,module=<module>,package=<package>

Example of the option usage:

native-image -cp lib/app.jar:lib/util.jar:lib/tck.jar -H:TrackDynamicAccess=path=lib/app.jar,path=lib/util.jar HelloWorld

In this example, the phase will look for dynamic access calls in lib/app.jar and lib/util.jar, and will not detect any calls in lib/tck.jar.
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
import java.util.function.BiFunction;
import java.util.function.IntFunction;

import com.oracle.svm.core.TrackDynamicAccessEnabled;
import org.graalvm.nativeimage.AnnotationAccess;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
Expand All @@ -93,6 +94,7 @@
import com.oracle.svm.core.BuildPhaseProvider.AfterHostedUniverse;
import com.oracle.svm.core.BuildPhaseProvider.CompileQueueFinished;
import com.oracle.svm.core.NeverInline;
import com.oracle.svm.core.NeverInlineTrivial;
import com.oracle.svm.core.RuntimeAssertionsSupport;
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.Uninterruptible;
Expand Down Expand Up @@ -1586,18 +1588,21 @@ private static Constructor<?>[] copyConstructors(Constructor<?>[] original) {
private native Constructor<?> getEnclosingConstructor();

@Substitute
@NeverInlineTrivial(reason = "Used in dynamic access call usage analysis: DynamicAccessDetectionPhase", onlyWith = TrackDynamicAccessEnabled.class)
@CallerSensitive
private static Class<?> forName(String className) throws Throwable {
return forName(className, Reflection.getCallerClass());
}

@Substitute
@NeverInlineTrivial(reason = "Used in dynamic access call usage analysis: DynamicAccessDetectionPhase", onlyWith = TrackDynamicAccessEnabled.class)
@CallerSensitiveAdapter
private static Class<?> forName(String className, Class<?> caller) throws Throwable {
return forName(className, true, caller == null ? ClassLoader.getSystemClassLoader() : caller.getClassLoader(), caller);
}

@Substitute
@NeverInlineTrivial(reason = "Used in dynamic access call usage analysis: DynamicAccessDetectionPhase", onlyWith = TrackDynamicAccessEnabled.class)
@CallerSensitive
private static Class<?> forName(Module module, String className) throws Throwable {
return forName(module, className, Reflection.getCallerClass());
Expand All @@ -1617,6 +1622,7 @@ private static Class<?> forName(@SuppressWarnings("unused") Module module, Strin
}

@Substitute
@NeverInlineTrivial(reason = "Used in dynamic access call usage analysis: DynamicAccessDetectionPhase", onlyWith = TrackDynamicAccessEnabled.class)
@CallerSensitive
private static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws Throwable {
return forName(name, initialize, loader, Reflection.getCallerClass());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.util.function.BooleanSupplier;
import java.util.stream.Stream;

import com.oracle.svm.core.AnalyzeJavaHomeAccessEnabled;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.hosted.FieldValueTransformer;
Expand Down Expand Up @@ -403,7 +404,7 @@ public static String setProperty(String key, String value) {
}

@Substitute
@NeverInlineTrivial("Used in 'java.home' access analysis: AnalyzeJavaHomeAccessPhase")
@NeverInlineTrivial(reason = "Used in 'java.home' access analysis: AnalyzeJavaHomeAccessPhase", onlyWith = AnalyzeJavaHomeAccessEnabled.class)
private static String getProperty(String key) {
checkKey(key);
return SystemPropertiesSupport.singleton().getCurrentProperty(key);
Expand All @@ -416,7 +417,7 @@ public static String clearProperty(String key) {
}

@Substitute
@NeverInlineTrivial("Used in 'java.home' access analysis: AnalyzeJavaHomeAccessPhase")
@NeverInlineTrivial(reason = "Used in 'java.home' access analysis: AnalyzeJavaHomeAccessPhase", onlyWith = AnalyzeJavaHomeAccessEnabled.class)
private static String getProperty(String key, String def) {
checkKey(key);
return SystemPropertiesSupport.singleton().getCurrentProperty(key, def);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@

import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
import com.oracle.svm.core.feature.InternalFeature;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.util.LogUtils;
import jdk.graal.compiler.options.Option;
import jdk.graal.compiler.options.OptionKey;
import org.graalvm.collections.EconomicMap;
import org.graalvm.nativeimage.ImageSingletons;

import java.util.Collections;
Expand Down Expand Up @@ -74,19 +70,4 @@ public void printJavaHomeUsageLocations() {
public void beforeCompilation(BeforeCompilationAccess access) {
AnalyzeJavaHomeAccessFeature.instance().printJavaHomeUsageLocations();
}

public static class Options {
@Option(help = "Track System.getProperty(\\\"java.home\\\") usage in reachable parts of the project.")//
public static final HostedOptionKey<Boolean> TrackJavaHomeAccess = new HostedOptionKey<>(false);

@Option(help = "Output all System.getProperty(\\\"java.home\\\") calls in reachable parts of the project.")//
public static final HostedOptionKey<Boolean> TrackJavaHomeAccessDetailed = new HostedOptionKey<>(false) {
@Override
protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Boolean oldValue, Boolean newValue) {
if (newValue) {
TrackJavaHomeAccess.update(values, true);
}
}
};
}
}
Loading
Loading