From a400faa55a367948ffd1fe779d37a44167392dfd Mon Sep 17 00:00:00 2001 From: Aca-S Date: Sun, 15 Dec 2024 21:46:01 +0100 Subject: [PATCH] Implement a bytecode-level analysis for constant reflection calls --- compiler/mx.compiler/suite.py | 14 +- .../graphbuilderconf/GraphBuilderContext.java | 15 + .../replacements/IntrinsicGraphBuilder.java | 8 + .../compiler/replacements/PEGraphDecoder.java | 13 + sdk/mx.sdk/suite.py | 1 + .../impl/reflectiontags/ConstantTags.java | 226 ++++++++++ substratevm/mx.substratevm/mx_substratevm.py | 14 + substratevm/mx.substratevm/suite.py | 55 ++- .../com/oracle/svm/core/SubstrateOptions.java | 3 + .../com/oracle/svm/driver/NativeImage.java | 6 + .../SimulateClassInitializerSupport.java | 3 + .../hosted/snippets/ReflectionPlugins.java | 417 ++++++++++++++++-- .../svm/reflectionagent/MethodCallUtils.java | 71 +++ .../NativeImageReflectionAgent.java | 328 ++++++++++++++ ...ativeImageReflectionAgentJNIHandleSet.java | 61 +++ .../analyzers/AnalyzerSuite.java | 67 +++ .../analyzers/ConstantArrayAnalyzer.java | 320 ++++++++++++++ .../analyzers/ConstantBooleanAnalyzer.java | 52 +++ .../analyzers/ConstantClassAnalyzer.java | 76 ++++ .../ConstantMethodHandlesLookupAnalyzer.java | 49 ++ .../analyzers/ConstantMethodTypeAnalyzer.java | 49 ++ .../analyzers/ConstantStringAnalyzer.java | 50 +++ .../analyzers/ConstantValueAnalyzer.java | 76 ++++ .../cfg/ControlFlowGraphAnalyzer.java | 54 +++ .../cfg/ControlFlowGraphNode.java | 44 ++ 25 files changed, 2030 insertions(+), 42 deletions(-) create mode 100644 sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/reflectiontags/ConstantTags.java create mode 100644 substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/MethodCallUtils.java create mode 100644 substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/NativeImageReflectionAgent.java create mode 100644 substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/NativeImageReflectionAgentJNIHandleSet.java create mode 100644 substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/AnalyzerSuite.java create mode 100644 substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantArrayAnalyzer.java create mode 100644 substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantBooleanAnalyzer.java create mode 100644 substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantClassAnalyzer.java create mode 100644 substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantMethodHandlesLookupAnalyzer.java create mode 100644 substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantMethodTypeAnalyzer.java create mode 100644 substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantStringAnalyzer.java create mode 100644 substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantValueAnalyzer.java create mode 100644 substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/cfg/ControlFlowGraphAnalyzer.java create mode 100644 substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/cfg/ControlFlowGraphNode.java diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py index 5c78c8c8675d..9c37f1ba50f7 100644 --- a/compiler/mx.compiler/suite.py +++ b/compiler/mx.compiler/suite.py @@ -120,6 +120,18 @@ "license" : "BSD-new", }, + "ASM_ANALYSIS_9.7.1" : { + "digest" : "sha512:a8bd265c81d9bb4371cafd3f5d18f96ad79aec65031457d518c54599144d199d9feddf13b8dc822b2598b8b504a88edbd81d1f2c52991a70a6b343d8f5bb6fe5", + "sourceDigest" : "sha512:ddfa874109ce46473f0a2aca46880f484bc5f598fccd4ed6dd48df95257114833654d6aed07e3f28994465b8c7b02e01517fedb1fe54cb11b922b1bed97b21b8", + "maven" : { + "groupId" : "org.ow2.asm", + "artifactId" : "asm-analysis", + "version" : "9.7.1", + }, + "dependencies" : ["ASM_TREE_9.7.1"], + "license" : "BSD-new", + }, + "HSDIS" : { "urlbase" : "https://lafo.ssw.uni-linz.ac.at/pub/graal-external-deps/hsdis", "packedResource" : True, @@ -615,7 +627,7 @@ "jdk.graal.compiler.nodes.graphbuilderconf to org.graalvm.nativeimage.driver,org.graalvm.nativeimage.librarysupport", "jdk.graal.compiler.options to org.graalvm.nativeimage.driver,org.graalvm.nativeimage.junitsupport", "jdk.graal.compiler.phases.common to org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.configure", - "jdk.graal.compiler.serviceprovider to jdk.graal.compiler.management,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.diagnostics", + "jdk.graal.compiler.serviceprovider to jdk.graal.compiler.management,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.diagnostics,org.graalvm.nativeimage.agent.reflection", "jdk.graal.compiler.util.json to org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.configure,org.graalvm.nativeimage.driver", ], "uses" : [ diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderContext.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderContext.java index 684315bfb0fe..d0471892f5c5 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderContext.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderContext.java @@ -79,6 +79,9 @@ import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaMethod; +import java.util.ArrayList; +import java.util.List; + /** * Used by a {@link GraphBuilderPlugin} to interface with an object that parses the bytecode of a * single {@linkplain #getMethod() method} as part of building a {@linkplain #getGraph() graph} . @@ -278,6 +281,18 @@ default int getDepth() { return result; } + /** + * Gets the inlined call stack for this context. A list with only one element implies that no + * inlining has taken place. + */ + default List getCallStack() { + List callStack = new ArrayList<>(); + for (GraphBuilderContext cur = this; cur != null; cur = cur.getParent()) { + callStack.add(cur.getMethod().asStackTraceElement(cur.bci())); + } + return callStack; + } + /** * Computes the recursive inlining depth of the provided method, i.e., counts how often the * provided method is already in the {@link #getParent()} chain starting at this context. diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/IntrinsicGraphBuilder.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/IntrinsicGraphBuilder.java index 27a18d59d57d..da753bae3f46 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/IntrinsicGraphBuilder.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/IntrinsicGraphBuilder.java @@ -76,6 +76,9 @@ import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.Signature; +import java.util.ArrayList; +import java.util.List; + /** * Implementation of {@link GraphBuilderContext} used to produce a graph for a method based on an * {@link InvocationPlugin} for the method. @@ -325,6 +328,11 @@ public int getDepth() { return 0; } + @Override + public List getCallStack() { + return new ArrayList<>(); + } + @Override public boolean parsingIntrinsic() { return false; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/PEGraphDecoder.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/PEGraphDecoder.java index ba0af5322903..bae8b64de96e 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/PEGraphDecoder.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/PEGraphDecoder.java @@ -412,6 +412,19 @@ public int getDepth() { return methodScope.inliningDepth; } + @Override + public List getCallStack() { + List callStack = new ArrayList<>(Arrays.asList(methodScope.getCallStack())); + /* + * If we're processing an invocation plugin, we want the top stack element to be the + * callee of the method targeted by the plugin, and not the target itself. + */ + if (isParsingInvocationPlugin()) { + callStack.removeFirst(); + } + return callStack; + } + @Override public int recursiveInliningDepth(ResolvedJavaMethod method) { int result = 0; diff --git a/sdk/mx.sdk/suite.py b/sdk/mx.sdk/suite.py index ac5744c25674..0f27a367e342 100644 --- a/sdk/mx.sdk/suite.py +++ b/sdk/mx.sdk/suite.py @@ -821,6 +821,7 @@ class UniversalDetector { "org.graalvm.nativeimage.c.constant", "org.graalvm.nativeimage.c", "org.graalvm.nativeimage", + "org.graalvm.nativeimage.impl.reflectiontags", """org.graalvm.nativeimage.impl to org.graalvm.nativeimage.pointsto, org.graalvm.nativeimage.base, org.graalvm.nativeimage.builder, diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/reflectiontags/ConstantTags.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/reflectiontags/ConstantTags.java new file mode 100644 index 000000000000..5b7ea530c3bc --- /dev/null +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/reflectiontags/ConstantTags.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.nativeimage.impl.reflectiontags; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.RecordComponent; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Used for representing constant reflection calls caught by {@link com.oracle.svm.reflectionagent}. + *

+ * Due to build-time initialization, the calls must implement the equivalent logic of their corresponding + * reflection method. Since these calls will be folded into constants, they will never be executed during + * image run-time. + */ +public final class ConstantTags { + + public static final Map TAG_TO_ORIGINAL_MAPPING; + + static { + Map mapping = new HashMap<>(); + try { + mapping.put(ConstantTags.class.getDeclaredMethod("forName", String.class), Class.class.getDeclaredMethod("forName", String.class)); + mapping.put(ConstantTags.class.getDeclaredMethod("forName", String.class, boolean.class, ClassLoader.class), Class.class.getDeclaredMethod("forName", String.class, boolean.class, ClassLoader.class)); + mapping.put(ConstantTags.class.getDeclaredMethod("getField", Class.class, String.class), Class.class.getDeclaredMethod("getField", String.class)); + mapping.put(ConstantTags.class.getDeclaredMethod("getDeclaredField", Class.class, String.class), Class.class.getDeclaredMethod("getDeclaredField", String.class)); + mapping.put(ConstantTags.class.getDeclaredMethod("getConstructor", Class.class, Class[].class), Class.class.getDeclaredMethod("getConstructor", Class[].class)); + mapping.put(ConstantTags.class.getDeclaredMethod("getDeclaredConstructor", Class.class, Class[].class), Class.class.getDeclaredMethod("getDeclaredConstructor", Class[].class)); + mapping.put(ConstantTags.class.getDeclaredMethod("getMethod", Class.class, String.class, Class[].class), Class.class.getDeclaredMethod("getMethod", String.class, Class[].class)); + mapping.put(ConstantTags.class.getDeclaredMethod("getDeclaredMethod", Class.class, String.class, Class[].class), Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class)); + mapping.put(ConstantTags.class.getDeclaredMethod("getFields", Class.class), Class.class.getDeclaredMethod("getFields")); + mapping.put(ConstantTags.class.getDeclaredMethod("getDeclaredFields", Class.class), Class.class.getDeclaredMethod("getDeclaredFields")); + mapping.put(ConstantTags.class.getDeclaredMethod("getConstructors", Class.class), Class.class.getDeclaredMethod("getConstructors")); + mapping.put(ConstantTags.class.getDeclaredMethod("getDeclaredConstructors", Class.class), Class.class.getDeclaredMethod("getDeclaredConstructors")); + mapping.put(ConstantTags.class.getDeclaredMethod("getMethods", Class.class), Class.class.getDeclaredMethod("getMethods")); + mapping.put(ConstantTags.class.getDeclaredMethod("getDeclaredMethods", Class.class), Class.class.getDeclaredMethod("getDeclaredMethods")); + mapping.put(ConstantTags.class.getDeclaredMethod("getClasses", Class.class), Class.class.getDeclaredMethod("getClasses")); + mapping.put(ConstantTags.class.getDeclaredMethod("getDeclaredClasses", Class.class), Class.class.getDeclaredMethod("getDeclaredClasses")); + mapping.put(ConstantTags.class.getDeclaredMethod("getNestMembers", Class.class), Class.class.getDeclaredMethod("getNestMembers")); + mapping.put(ConstantTags.class.getDeclaredMethod("getPermittedSubclasses", Class.class), Class.class.getDeclaredMethod("getPermittedSubclasses")); + mapping.put(ConstantTags.class.getDeclaredMethod("getRecordComponents", Class.class), Class.class.getDeclaredMethod("getRecordComponents")); + mapping.put(ConstantTags.class.getDeclaredMethod("getSigners", Class.class), Class.class.getDeclaredMethod("getSigners")); + mapping.put(ConstantTags.class.getDeclaredMethod("findClass", MethodHandles.Lookup.class, String.class), MethodHandles.Lookup.class.getDeclaredMethod("findClass", String.class)); + mapping.put(ConstantTags.class.getDeclaredMethod("findVirtual", MethodHandles.Lookup.class, Class.class, String.class, MethodType.class), MethodHandles.Lookup.class.getDeclaredMethod("findVirtual", Class.class, String.class, MethodType.class)); + mapping.put(ConstantTags.class.getDeclaredMethod("findStatic", MethodHandles.Lookup.class, Class.class, String.class, MethodType.class), MethodHandles.Lookup.class.getDeclaredMethod("findStatic", Class.class, String.class, MethodType.class)); + mapping.put(ConstantTags.class.getDeclaredMethod("findConstructor", MethodHandles.Lookup.class, Class.class, MethodType.class), MethodHandles.Lookup.class.getDeclaredMethod("findConstructor", Class.class, MethodType.class)); + mapping.put(ConstantTags.class.getDeclaredMethod("findGetter", MethodHandles.Lookup.class, Class.class, String.class, Class.class), MethodHandles.Lookup.class.getDeclaredMethod("findGetter", Class.class, String.class, Class.class)); + mapping.put(ConstantTags.class.getDeclaredMethod("findStaticGetter", MethodHandles.Lookup.class, Class.class, String.class, Class.class), MethodHandles.Lookup.class.getDeclaredMethod("findStaticGetter", Class.class, String.class, Class.class)); + mapping.put(ConstantTags.class.getDeclaredMethod("findSetter", MethodHandles.Lookup.class, Class.class, String.class, Class.class), MethodHandles.Lookup.class.getDeclaredMethod("findSetter", Class.class, String.class, Class.class)); + mapping.put(ConstantTags.class.getDeclaredMethod("findStaticSetter", MethodHandles.Lookup.class, Class.class, String.class, Class.class), MethodHandles.Lookup.class.getDeclaredMethod("findStaticSetter", Class.class, String.class, Class.class)); + mapping.put(ConstantTags.class.getDeclaredMethod("findVarHandle", MethodHandles.Lookup.class, Class.class, String.class, Class.class), MethodHandles.Lookup.class.getDeclaredMethod("findVarHandle", Class.class, String.class, Class.class)); + mapping.put(ConstantTags.class.getDeclaredMethod("findStaticVarHandle", MethodHandles.Lookup.class, Class.class, String.class, Class.class), MethodHandles.Lookup.class.getDeclaredMethod("findStaticVarHandle", Class.class, String.class, Class.class)); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + TAG_TO_ORIGINAL_MAPPING = Collections.unmodifiableMap(mapping); + } + + private static final StackWalker stackWalker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + + public static Class forName(String className) throws ClassNotFoundException { + return Class.forName(className, true, stackWalker.getCallerClass().getClassLoader()); + } + + public static Class forName(String className, boolean initialize, ClassLoader classLoader) throws ClassNotFoundException { + return Class.forName(className, initialize, classLoader); + } + + public static Field getField(Class clazz, String fieldName) throws NoSuchFieldException { + return clazz.getField(fieldName); + } + + public static Field getDeclaredField(Class clazz, String fieldName) throws NoSuchFieldException { + return clazz.getDeclaredField(fieldName); + } + + public static Constructor getConstructor(Class clazz, Class... parameterTypes) throws NoSuchMethodException { + return clazz.getConstructor(parameterTypes); + } + + public static Constructor getDeclaredConstructor(Class clazz, Class... parameterTypes) throws NoSuchMethodException { + return clazz.getDeclaredConstructor(parameterTypes); + } + + public static Method getMethod(Class clazz, String methodName, Class... parameterTypes) throws NoSuchMethodException { + return clazz.getMethod(methodName, parameterTypes); + } + + public static Method getDeclaredMethod(Class clazz, String methodName, Class... parameterTypes) throws NoSuchMethodException { + return clazz.getDeclaredMethod(methodName, parameterTypes); + } + + public static Field[] getFields(Class clazz) { + return clazz.getFields(); + } + + public static Field[] getDeclaredFields(Class clazz) { + return clazz.getDeclaredFields(); + } + + public static Constructor[] getConstructors(Class clazz) { + return clazz.getConstructors(); + } + + public static Constructor[] getDeclaredConstructors(Class clazz) { + return clazz.getDeclaredConstructors(); + } + + public static Method[] getMethods(Class clazz) { + return clazz.getMethods(); + } + + public static Method[] getDeclaredMethods(Class clazz) { + return clazz.getDeclaredMethods(); + } + + public static Class[] getClasses(Class clazz) { + return clazz.getClasses(); + } + + public static Class[] getDeclaredClasses(Class clazz) { + return clazz.getDeclaredClasses(); + } + + public static Class[] getNestMembers(Class clazz) { + return clazz.getNestMembers(); + } + + public static Class[] getPermittedSubclasses(Class clazz) { + return clazz.getPermittedSubclasses(); + } + + public static RecordComponent[] getRecordComponents(Class clazz) { + return clazz.getRecordComponents(); + } + + public static Object[] getSigners(Class clazz) { + return clazz.getSigners(); + } + + public static Class findClass(MethodHandles.Lookup lookup, String className) throws ClassNotFoundException, IllegalAccessException { + return lookup.findClass(className); + } + + public static MethodHandle findVirtual(MethodHandles.Lookup lookup, Class clazz, String methodName, MethodType methodType) throws NoSuchMethodException, IllegalAccessException { + return lookup.findVirtual(clazz, methodName, methodType); + } + + public static MethodHandle findStatic(MethodHandles.Lookup lookup, Class clazz, String methodName, MethodType methodType) throws NoSuchMethodException, IllegalAccessException { + return lookup.findStatic(clazz, methodName, methodType); + } + + public static MethodHandle findConstructor(MethodHandles.Lookup lookup, Class clazz, MethodType methodType) throws NoSuchMethodException, IllegalAccessException { + return lookup.findConstructor(clazz, methodType); + } + + public static MethodHandle findGetter(MethodHandles.Lookup lookup, Class clazz, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + return lookup.findGetter(clazz, name, type); + } + + public static MethodHandle findStaticGetter(MethodHandles.Lookup lookup, Class clazz, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + return lookup.findStaticGetter(clazz, name, type); + } + + public static MethodHandle findSetter(MethodHandles.Lookup lookup, Class clazz, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + return lookup.findSetter(clazz, name, type); + } + + public static MethodHandle findStaticSetter(MethodHandles.Lookup lookup, Class clazz, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + return lookup.findStaticSetter(clazz, name, type); + } + + public static VarHandle findVarHandle(MethodHandles.Lookup lookup, Class clazz, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + return lookup.findVarHandle(clazz, name, type); + } + + public static VarHandle findStaticVarHandle(MethodHandles.Lookup lookup, Class clazz, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + return lookup.findStaticVarHandle(clazz, name, type); + } +} diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 551ceff88ae3..018cfdd396ef 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1362,6 +1362,20 @@ def _native_image_launcher_extra_jvm_args(): headers=False, home_finder=False, ), + mx_sdk_vm.LibraryConfig( + use_modules='image', + destination="", + jvm_library=True, + jar_distributions=[ + 'substratevm:JVMTI_AGENT_BASE', + 'substratevm:SVM_REFLECTION_AGENT', + ], + build_args=driver_build_args + [ + '--features=com.oracle.svm.reflectionagent.NativeImageReflectionAgent$RegistrationFeature', + ], + headers=False, + home_finder=False, + ), ], installable=True, stability="earlyadopter", diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index a9ce2286a9c9..c66e1189f15d 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -265,6 +265,7 @@ "shadedDependencies" : [ "compiler:ASM_9.7.1", "compiler:ASM_TREE_9.7.1", + "compiler:ASM_ANALYSIS_9.7.1", ], "class" : "ShadedLibraryProject", "shade" : { @@ -1481,6 +1482,32 @@ "jacoco" : "exclude", }, + "com.oracle.svm.reflectionagent": { + "subDir": "src", + "sourceDirs": [ + "src", + "resources" + ], + "dependencies": [ + "JVMTI_AGENT_BASE", + "com.oracle.svm.configure", + ], + "requiresConcealed" : { + "java.base" : [ + "jdk.internal.loader", + ], + }, + "checkstyle": "com.oracle.svm.hosted", + "workingSets": "SVM", + "annotationProcessors": [ + "compiler:GRAAL_PROCESSOR", + "SVM_PROCESSOR", + ], + "javaCompliance" : "21+", + "spotbugs": "false", + "jacoco" : "exclude", + }, + "com.oracle.svm.truffle.tck" : { "subDir": "src", "sourceDirs": ["src"], @@ -1679,6 +1706,7 @@ org.graalvm.nativeimage.agent.jvmtibase, org.graalvm.nativeimage.agent.tracing, org.graalvm.nativeimage.agent.diagnostics, + org.graalvm.nativeimage.agent.reflection, com.oracle.svm.svm_enterprise, com.oracle.svm.svm_enterprise.llvm, com.oracle.svm_enterprise.ml_dataset, @@ -2108,6 +2136,30 @@ "maven": False, }, + "SVM_REFLECTION_AGENT": { + "subDir": "src", + "description" : "Native-image agent for constant reflection detection", + "dependencies": [ + "com.oracle.svm.reflectionagent", + "com.oracle.svm.configure", + ], + "distDependencies": [ + "JVMTI_AGENT_BASE", + "LIBRARY_SUPPORT", + "SVM_CONFIGURE" + ], + "moduleInfo" : { + "name" : "org.graalvm.nativeimage.agent.reflection", + "exports" : [ + "com.oracle.svm.reflectionagent", + ], + "requires": [ + "org.graalvm.nativeimage.builder", + ], + }, + "maven": False, + }, + "SVM_CONFIGURE": { "subDir": "src", "description" : "SubstrateVM native-image configuration tool", @@ -2122,6 +2174,7 @@ "name" : "org.graalvm.nativeimage.configure", "exports" : [ "* to org.graalvm.nativeimage.agent.tracing", + "* to org.graalvm.nativeimage.agent.reflection", "com.oracle.svm.configure", "com.oracle.svm.configure.command", ], @@ -2157,7 +2210,7 @@ "org.graalvm.collections", ], "exports" : [ - "com.oracle.svm.util to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.llvm,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.agent.diagnostics,org.graalvm.nativeimage.junitsupport,com.oracle.svm.svm_enterprise,com.oracle.svm_enterprise.ml_dataset,org.graalvm.extraimage.builder,com.oracle.svm.extraimage_enterprise,org.graalvm.extraimage.librarysupport,org.graalvm.nativeimage.foreign,org.graalvm.truffle.runtime.svm,com.oracle.truffle.enterprise.svm", + "com.oracle.svm.util to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.llvm,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.agent.diagnostics,nativeimage.agent.reflection,org,org.graalvm.nativeimage.junitsupport,com.oracle.svm.svm_enterprise,com.oracle.svm_enterprise.ml_dataset,org.graalvm.extraimage.builder,com.oracle.svm.extraimage_enterprise,org.graalvm.extraimage.librarysupport,org.graalvm.nativeimage.foreign,org.graalvm.truffle.runtime.svm,com.oracle.truffle.enterprise.svm", "com.oracle.svm.common.meta to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.llvm,org.graalvm.extraimage.builder,org.graalvm.nativeimage.foreign,org.graalvm.truffle.runtime.svm,com.oracle.truffle.enterprise.svm", "com.oracle.svm.common.option to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.foreign,org.graalvm.truffle.runtime.svm,com.oracle.truffle.enterprise.svm", ], 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 b8a9f5df9415..359bb7f0fb3f 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 @@ -1392,4 +1392,7 @@ public static class TruffleStableOptions { @Option(help = "file:doc-files/LibGraalClassLoader.txt")// public static final HostedOptionKey LibGraalClassLoader = new HostedOptionKey<>(""); + + @Option(help = "Enable the deterministic, bytecode level analysis for constant reflection usage.")// + public static final HostedOptionKey EnableStrictReflection = new HostedOptionKey<>(true); } diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java index 0b7eb0fea53c..0553a79b5731 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java @@ -285,6 +285,7 @@ private static String oR(OptionKey option) { final String oHInspectServerContentPath = oH(PointstoOptions.InspectServerContentPath); final String oHDeadlockWatchdogInterval = oH(SubstrateOptions.DeadlockWatchdogInterval); + final String oHDisableStrictReflection = oHDisabled(SubstrateOptions.EnableStrictReflection); final Map imageBuilderEnvironment = new HashMap<>(); private final ArrayList imageBuilderArgs = new ArrayList<>(); @@ -1263,6 +1264,11 @@ private int completeImageBuild() { imageBuilderJavaArgs.addAll(getAgentArguments()); + Path reflectionAgentPath = config.rootDir.resolve(Paths.get("lib", "libnative-image-reflection-agent.so")); + if (imageBuilderArgs.stream().noneMatch(arg -> arg.contains(oHDisableStrictReflection)) && Files.exists(reflectionAgentPath)) { + imageBuilderJavaArgs.add("-agentlib:native-image-reflection-agent"); + } + Optional lastMainClass = getHostedOptionArgument(imageBuilderArgs, oHClass); mainClass = lastMainClass.map(ArgumentEntry::value).orElse(null); buildExecutable = imageBuilderArgs.stream().noneMatch(arg -> arg.startsWith(oHEnableSharedLibraryFlagPrefix) || arg.startsWith(oHEnableImageLayerFlagPrefix)); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerSupport.java index 06fb72a952f8..eefc9e0a050b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerSupport.java @@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; +import com.oracle.svm.hosted.ReachabilityRegistrationNode; import org.graalvm.collections.EconomicSet; import org.graalvm.nativeimage.ImageSingletons; @@ -527,6 +528,8 @@ private static void processEffectsOfNode(SimulateClassInitializerClusterMember c return; } } + } else if (node instanceof ReachabilityRegistrationNode) { + return; } clusterMember.notInitializedReasons.add(node); 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..b39e0737db9c 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 @@ -24,6 +24,11 @@ */ package com.oracle.svm.hosted.snippets; +import java.io.BufferedOutputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -36,18 +41,31 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.ByteOrder; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Queue; import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.Consumer; import java.util.function.Predicate; -import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.reflect.proxy.DynamicProxySupport; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.util.LogUtils; +import jdk.graal.compiler.util.json.JsonBuilder; +import jdk.graal.compiler.util.json.JsonPrettyWriter; +import jdk.graal.compiler.util.json.JsonWriter; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; @@ -89,6 +107,7 @@ import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; +import org.graalvm.nativeimage.impl.reflectiontags.ConstantTags; /** * Performs constant folding of methods that perform reflection lookups when all arguments are @@ -103,10 +122,6 @@ * JDK so that any code that would rely on object identity is error-prone on any JVM. */ public final class ReflectionPlugins { - static class Options { - @Option(help = "Enable trace logging for reflection plugins.")// - static final HostedOptionKey ReflectionPluginTracing = new HostedOptionKey<>(false); - } /** * Marker value for parameters that are null, to distinguish from "not able to {@link #unbox}". @@ -120,6 +135,7 @@ static class Options { private final ParsingReason reason; private final FallbackFeature fallbackFeature; private final ClassInitializationSupport classInitializationSupport; + private final StrictReflectionSupport strictReflectionSupport; private ReflectionPlugins(ImageClassLoader imageClassLoader, AnnotationSubstitutionProcessor annotationSubstitutions, ClassInitializationPlugin classInitializationPlugin, AnalysisUniverse aUniverse, ParsingReason reason, FallbackFeature fallbackFeature) { @@ -131,6 +147,7 @@ private ReflectionPlugins(ImageClassLoader imageClassLoader, AnnotationSubstitut this.fallbackFeature = fallbackFeature; this.classInitializationSupport = (ClassInitializationSupport) ImageSingletons.lookup(RuntimeClassInitializationSupport.class); + this.strictReflectionSupport = new StrictReflectionSupport(); } public static void registerInvocationPlugins(ImageClassLoader imageClassLoader, AnnotationSubstitutionProcessor annotationSubstitutions, @@ -138,6 +155,10 @@ public static void registerInvocationPlugins(ImageClassLoader imageClassLoader, ReflectionPlugins rp = new ReflectionPlugins(imageClassLoader, annotationSubstitutions, classInitializationPlugin, aUniverse, reason, fallbackFeature); rp.registerMethodHandlesPlugins(plugins); rp.registerClassPlugins(plugins); + + if (SubstrateOptions.EnableStrictReflection.getValue()) { + rp.strictReflectionSupport.registerStrictModePlugins(plugins); + } } private static final Class VAR_FORM_CLASS = ReflectionUtil.lookupClass(false, "java.lang.invoke.VarForm"); @@ -348,8 +369,6 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec * the constructor parameter. */ private boolean processMethodHandlesLookup(GraphBuilderContext b, ResolvedJavaMethod targetMethod) { - Supplier targetParameters = () -> ""; - if (StackTraceUtils.ignoredBySecurityStackWalk(b.getMetaAccess(), b.getMethod())) { /* * If our immediate caller (which is the only method available at the time the @@ -364,9 +383,9 @@ private boolean processMethodHandlesLookup(GraphBuilderContext b, ResolvedJavaMe /* The constructor of Lookup is not public, so we need to invoke it via reflection. */ lookup = LOOKUP_CONSTRUCTOR.newInstance(callerClass); } catch (Throwable ex) { - return throwException(b, targetMethod, targetParameters, ex.getClass(), ex.getMessage()); + return throwException(b, targetMethod, null, new Object[]{}, ex.getClass(), ex.getMessage()); } - return pushConstant(b, targetMethod, targetParameters, JavaKind.Object, lookup, false) != null; + return pushConstant(b, targetMethod, null, new Object[]{}, JavaKind.Object, lookup, false) != null; } /** @@ -383,19 +402,23 @@ private boolean processClassForName(GraphBuilderContext b, ResolvedJavaMethod ta } String className = (String) classNameValue; boolean initialize = (Boolean) initializeValue; - Supplier targetParameters = () -> className + ", " + initialize; + /* + * Check which variant of Class.forName was called in order to avoid logging the initialize + * argument value for the single parameter version of the call. + */ + Object[] argValues = targetMethod.getParameters().length == 1 ? new Object[]{className} : new Object[]{className, initialize}; TypeResult> typeResult = imageClassLoader.findClass(className, false); if (!typeResult.isPresent()) { Throwable e = typeResult.getException(); - return throwException(b, targetMethod, targetParameters, e.getClass(), e.getMessage()); + return throwException(b, targetMethod, null, argValues, e.getClass(), e.getMessage()); } Class clazz = typeResult.get(); if (PredefinedClassesSupport.isPredefined(clazz)) { return false; } - JavaConstant classConstant = pushConstant(b, targetMethod, targetParameters, JavaKind.Object, clazz, false); + JavaConstant classConstant = pushConstant(b, targetMethod, null, argValues, JavaKind.Object, clazz, false); if (classConstant == null) { return false; } @@ -433,7 +456,7 @@ private boolean processClassGetClassLoader(GraphBuilderContext b, ResolvedJavaMe if (result != null) { b.addPush(JavaKind.Object, ConstantNode.forConstant(result, b.getMetaAccess())); - traceConstant(b, targetMethod, clazz::getName, result); + traceConstant(b, targetMethod, clazz, new Object[]{}, result); return true; } @@ -476,6 +499,7 @@ private void registerFoldInvocationPlugin(InvocationPlugins plugins, Method refl plugins.register(reflectionMethod.getDeclaringClass(), new RequiredInvocationPlugin(reflectionMethod.getName(), parameterTypes.toArray(new Class[0])) { @Override public boolean defaultHandler(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode... args) { + assert !reflectionMethod.getDeclaringClass().equals(ConstantTags.class) || Arrays.stream(args).allMatch(ValueNode::isConstant); return foldInvocationUsingReflection(b, targetMethod, reflectionMethod, receiver, args, allowConstantFolding); } }); @@ -520,17 +544,13 @@ private boolean foldInvocationUsingReflection(GraphBuilderContext b, ResolvedJav return false; } - /* String representation of the parameters for debug printing. */ - Supplier targetParameters = () -> (receiverValue == null ? "" : receiverValue + "; ") + - Stream.of(argValues).map(arg -> arg instanceof Object[] ? Arrays.toString((Object[]) arg) : Objects.toString(arg)).collect(Collectors.joining(", ")); - Object returnValue; try { returnValue = reflectionMethod.invoke(receiverValue, argValues); } catch (InvocationTargetException ex) { - return throwException(b, targetMethod, targetParameters, ex.getTargetException().getClass(), ex.getTargetException().getMessage()); + return throwException(b, targetMethod, receiverValue, argValues, ex.getTargetException().getClass(), ex.getTargetException().getMessage()); } catch (Throwable ex) { - return throwException(b, targetMethod, targetParameters, ex.getClass(), ex.getMessage()); + return throwException(b, targetMethod, receiverValue, argValues, ex.getClass(), ex.getMessage()); } JavaKind returnKind = targetMethod.getSignature().getReturnKind(); @@ -538,11 +558,11 @@ private boolean foldInvocationUsingReflection(GraphBuilderContext b, ResolvedJav /* * The target method is a side-effect free void method that did not throw an exception. */ - traceConstant(b, targetMethod, targetParameters, JavaKind.Void); + traceConstant(b, targetMethod, receiverValue, argValues, JavaKind.Void); return true; } - return pushConstant(b, targetMethod, targetParameters, returnKind, returnValue, false) != null; + return pushConstant(b, targetMethod, receiverValue, argValues, returnKind, returnValue, false) != null; } private void registerBulkInvocationPlugin(InvocationPlugins plugins, Class declaringClass, String methodName, Consumer registrationCallback) { @@ -555,13 +575,13 @@ public boolean isDecorator() { @Override public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver) { VMError.guarantee(!targetMethod.isStatic(), "Bulk reflection queries are not static"); - return registerConstantBulkReflectionQuery(b, receiver, registrationCallback); + return registerConstantBulkReflectionQuery(b, targetMethod, receiver, registrationCallback); } }); } @SuppressWarnings("unchecked") - private boolean registerConstantBulkReflectionQuery(GraphBuilderContext b, Receiver receiver, Consumer registrationCallback) { + private boolean registerConstantBulkReflectionQuery(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, Consumer registrationCallback) { /* * Calling receiver.get(true) can add a null check guard, i.e., modifying the graph in the * process. It is an error for invocation plugins that do not replace the call to modify the @@ -573,6 +593,7 @@ private boolean registerConstantBulkReflectionQuery(GraphBuilderContext b, R } b.add(ReachabilityRegistrationNode.create(() -> registerForRuntimeReflection((T) receiverValue, registrationCallback), reason)); + traceConstant(b, targetMethod, receiverValue, new Object[]{}, new Object[]{}); return true; } @@ -725,7 +746,7 @@ private static boolean isDeleted(T element, MetaAccessProvider metaAccess) { return annotated != null && annotated.isAnnotationPresent(Delete.class); } - private JavaConstant pushConstant(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Supplier targetParameters, JavaKind returnKind, Object returnValue, + private JavaConstant pushConstant(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, JavaKind returnKind, Object returnValue, boolean allowNullReturnValue) { Object intrinsicValue = getIntrinsic(b, returnValue == null && allowNullReturnValue ? NULL_MARKER : returnValue); if (intrinsicValue == null) { @@ -742,11 +763,12 @@ private JavaConstant pushConstant(GraphBuilderContext b, ResolvedJavaMethod targ } b.addPush(returnKind, ConstantNode.forConstant(intrinsicConstant, b.getMetaAccess())); - traceConstant(b, targetMethod, targetParameters, intrinsicValue); + traceConstant(b, targetMethod, targetCaller, targetArguments, intrinsicValue); return intrinsicConstant; } - private boolean throwException(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Supplier targetParameters, Class exceptionClass, String originalMessage) { + private boolean throwException(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Class exceptionClass, + String originalMessage) { /* Get the exception throwing method that has a message parameter. */ Method exceptionMethod = ExceptionSynthesizer.throwExceptionMethodOrNull(exceptionClass, String.class); if (exceptionMethod == null) { @@ -757,28 +779,343 @@ private boolean throwException(GraphBuilderContext b, ResolvedJavaMethod targetM return false; } - String message = originalMessage + ". This exception was synthesized during native image building from a call to " + targetMethod.format("%H.%n(%p)") + + /* + * Because tracing adds a ReachabilityRegistrationNode to the graph, it has to happen before + * exception synthesis. + */ + traceException(b, targetMethod, targetCaller, targetArguments, exceptionClass); + + /* + * We don't want the user to know about the strict mode constant tags, so we're replacing + * them method name in the synthesized exception message with the original. + */ + ResolvedJavaMethod throwingMethod = targetMethod; + if (SubstrateOptions.EnableStrictReflection.getValue()) { + ResolvedJavaMethod originalMethod = strictReflectionSupport.getOriginalMethod(targetMethod, b.getMetaAccess()); + if (originalMethod != null) { + throwingMethod = originalMethod; + } + } + String message = originalMessage + ". This exception was synthesized during native image building from a call to " + throwingMethod.format("%H.%n(%p)") + " with constant arguments."; ExceptionSynthesizer.throwException(b, exceptionMethod, message); - traceException(b, targetMethod, targetParameters, exceptionClass); return true; } - private static void traceConstant(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Supplier targetParameters, Object value) { - if (Options.ReflectionPluginTracing.getValue()) { - System.out.println("Call to " + targetMethod.format("%H.%n(%p)") + - " reached in " + b.getMethod().format("%H.%n(%p)") + - " with parameters (" + targetParameters.get() + ")" + - " was reduced to the constant " + value); + private void traceConstant(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Object value) { + if (reason.duringAnalysis() && reason != ParsingReason.JITCompilation && ReflectionPluginsTracingFeature.isEnabled()) { + /* + * We're capturing the call stack here in order to avoid late binding in the + * reachability node callback. + */ + List callStack = b.getCallStack(); + b.add(ReachabilityRegistrationNode.create(() -> ReflectionPluginsTracingFeature.traceConstant(callStack, targetMethod, targetCaller, targetArguments, value), reason)); + } + } + + private void traceException(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Class exceptionClass) { + if (reason.duringAnalysis() && reason != ParsingReason.JITCompilation && ReflectionPluginsTracingFeature.isEnabled()) { + /* + * We're capturing the call stack here in order to avoid late binding in the + * reachability node callback. + */ + List callStack = b.getCallStack(); + b.add(ReachabilityRegistrationNode.create(() -> ReflectionPluginsTracingFeature.traceException(callStack, targetMethod, targetCaller, targetArguments, exceptionClass), reason)); + } + } + + private final class StrictReflectionSupport { + + public ResolvedJavaMethod getOriginalMethod(ResolvedJavaMethod targetMethod, MetaAccessProvider access) { + for (Map.Entry entry : ConstantTags.TAG_TO_ORIGINAL_MAPPING.entrySet()) { + if (access.lookupJavaMethod(entry.getKey()).equals(targetMethod)) { + return access.lookupJavaMethod(entry.getValue()); + } + } + return null; + } + + public void registerStrictModePlugins(InvocationPlugins plugins) { + plugins.register(ConstantTags.class, new RequiredInvocationPlugin("forName", String.class) { + @Override + public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode nameNode) { + assert nameNode.isConstant(); + return processClassForName(b, targetMethod, nameNode, ConstantNode.forBoolean(true)); + } + }); + + plugins.register(ConstantTags.class, new RequiredInvocationPlugin("forName", String.class, boolean.class, ClassLoader.class) { + @Override + public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode nameNode, ValueNode initializeNode, ValueNode classLoaderNode) { + assert nameNode.isConstant() && initializeNode.isConstant(); + return processClassForName(b, targetMethod, nameNode, initializeNode); + } + }); + + registerFoldInvocationPlugins(plugins, ConstantTags.class, "getField", "getDeclaredField", "getConstructor", "getDeclaredConstructor", "getMethod", "getDeclaredMethod"); + + registerFoldInvocationPlugins(plugins, ConstantTags.class, "findClass", "findVirtual", "findStatic", "findConstructor", "findGetter", "findStaticGetter", "findSetter", "findStaticSetter", + "findVarHandle", "findStaticVarHandle"); + + if (reason.duringAnalysis() && reason != ParsingReason.JITCompilation) { + registerStrictBulkQuery(plugins, ConstantTags.class, "getFields", RuntimeReflection::registerAllFields); + registerStrictBulkQuery(plugins, ConstantTags.class, "getDeclaredFields", RuntimeReflection::registerAllDeclaredFields); + registerStrictBulkQuery(plugins, ConstantTags.class, "getConstructors", RuntimeReflection::registerAllConstructors); + registerStrictBulkQuery(plugins, ConstantTags.class, "getDeclaredConstructors", RuntimeReflection::registerAllDeclaredConstructors); + registerStrictBulkQuery(plugins, ConstantTags.class, "getMethods", RuntimeReflection::registerAllMethods); + registerStrictBulkQuery(plugins, ConstantTags.class, "getDeclaredMethods", RuntimeReflection::registerAllDeclaredMethods); + registerStrictBulkQuery(plugins, ConstantTags.class, "getClasses", RuntimeReflection::registerAllClasses); + registerStrictBulkQuery(plugins, ConstantTags.class, "getDeclaredClasses", RuntimeReflection::registerAllDeclaredClasses); + registerStrictBulkQuery(plugins, ConstantTags.class, "getNestMembers", RuntimeReflection::registerAllNestMembers); + registerStrictBulkQuery(plugins, ConstantTags.class, "getPermittedSubclasses", RuntimeReflection::registerAllPermittedSubclasses); + registerStrictBulkQuery(plugins, ConstantTags.class, "getRecordComponents", RuntimeReflection::registerAllRecordComponents); + registerStrictBulkQuery(plugins, ConstantTags.class, "getSigners", RuntimeReflection::registerAllSigners); + } + } + + private void registerStrictBulkQuery(InvocationPlugins plugins, Class clazz, String methodName, Consumer> registrationCallback) { + plugins.register(clazz, new RequiredInvocationPlugin(methodName, Class.class) { + @Override + public boolean isDecorator() { + return true; + } + + @Override + public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode clazzNode) { + assert clazzNode.isConstant(); + Object clazzValue = unbox(b, clazzNode, JavaKind.Object); + if (!(clazzValue instanceof Class)) { + return false; + } + b.add(ReachabilityRegistrationNode.create(() -> registerForRuntimeReflection((Class) clazzValue, registrationCallback), reason)); + traceConstant(b, targetMethod, null, new Object[]{clazzValue}, new Object[]{}); + return true; + } + }); + } + } +} + +@AutomaticallyRegisteredFeature +final class ReflectionPluginsTracingFeature implements InternalFeature { + + static class Options { + @Option(help = "Specify the trace logging location for reflection plugins.")// + static final HostedOptionKey ReflectionPluginTraceLocation = new HostedOptionKey<>(null); + + @Option(help = "Specify the trace logging format for reflection plugins.")// + static final HostedOptionKey ReflectionPluginTraceFormat = new HostedOptionKey<>("json", key -> { + if (!key.getValue().equals("json") && !key.getValue().equals("plain")) { + throw UserError.invalidOptionValue(key, key.getValue(), "Value must be either \"json\" or \"plain\"."); + } + }); + + @Option(help = "Log only the constant folding occurring in user provided application classes.")// + static final HostedOptionKey ReflectionPluginTraceUserOnly = new HostedOptionKey<>(true); + } + + private static final Queue log = new ConcurrentLinkedQueue<>(); + private static final Set strictModeTargets = ConstantTags.TAG_TO_ORIGINAL_MAPPING.keySet().stream().map(Method::getName).collect(Collectors.toUnmodifiableSet()); + + private ImageClassLoader imageClassLoader; + private ReflectionPluginLogSupport logger; + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + imageClassLoader = ((FeatureImpl.AfterRegistrationAccessImpl) access).getImageClassLoader(); + + String dumpLocation = Options.ReflectionPluginTraceLocation.getValue(); + if (dumpLocation == null) { + return; + } + + String logFormat = Options.ReflectionPluginTraceFormat.getValue(); + logger = logFormat.equals("json") ? new ReflectionPluginJsonLogSupport(dumpLocation) : new ReflectionPluginPlainLogSupport(dumpLocation); + } + + @Override + public void afterAnalysis(AfterAnalysisAccess access) { + if (isEnabled()) { + if (logger != null) { + logger.dump(Options.ReflectionPluginTraceUserOnly.getValue() ? log.stream().filter(this::isUserProvided).toList() : log); + } + if (SubstrateOptions.EnableStrictReflection.getValue()) { + log.stream().filter(this::isUserProvided) + .filter(ReflectionPluginsTracingFeature::missedByStrictMode) + .forEach(entry -> LogUtils.warning(entry + + " outside of the strict constant reflection mode." + + " Consider adding the appropriate entry to your reachability metadata" + + " (https://www.graalvm.org/latest/reference-manual/native-image/metadata/#reflection).")); + } + } + } + + /** + * Checks if the entry was created in a user provided class (class loaded by + * NativeImageClassLoader, ignoring proxy classes). + */ + private boolean isUserProvided(TraceEntry entry) { + String className = entry.callStack.getFirst().getClassName(); + if (DynamicProxySupport.PROXY_CLASS_NAME_PATTERN.matcher(className).matches()) { + return false; + } + TypeResult> clazz = imageClassLoader.findClass(className); + if (!clazz.isPresent()) { + return false; + } else { + return clazz.get().getClassLoader() == imageClassLoader.getClassLoader(); + } + } + + private static boolean missedByStrictMode(TraceEntry entry) { + String targetClassName = entry.targetMethod.getDeclaringClass().toJavaName(true); + return (targetClassName.equals("java.lang.Class") || targetClassName.equals("java.lang.MethodHandles.Lookup")) && + strictModeTargets.contains(entry.targetMethod.getName()); + } + + public static boolean isEnabled() { + return SubstrateOptions.EnableStrictReflection.getValue() || Options.ReflectionPluginTraceLocation.getValue() != null; + } + + public static void traceConstant(List callStack, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Object value) { + log.add(new ConstantTraceEntry(callStack, targetMethod, targetCaller, targetArguments, value)); + } + + public static void traceException(List callStack, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Class exceptionClass) { + log.add(new ExceptionTraceEntry(callStack, targetMethod, targetCaller, targetArguments, exceptionClass)); + } + + private abstract static class TraceEntry { + + private final List callStack; + private final ResolvedJavaMethod targetMethod; + private final Object targetCaller; + private final Object[] targetArguments; + + TraceEntry(List callStack, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments) { + this.callStack = callStack; + this.targetMethod = targetMethod; + this.targetCaller = targetCaller; + this.targetArguments = targetArguments; + } + + @Override + public String toString() { + String targetArgumentsString = Stream.of(targetArguments) + .map(arg -> arg instanceof Object[] ? Arrays.toString((Object[]) arg) : Objects.toString(arg)).collect(Collectors.joining(", ")); + + return "Call to " + targetMethod.format("%H.%n(%p)") + + " reached in " + callStack.getFirst() + + (targetCaller != null ? " with caller " + targetCaller + " and" : "") + + " with arguments (" + targetArgumentsString + ") was reduced"; + } + + public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException { + try (JsonBuilder.ArrayBuilder foldContextBuilder = builder.append("foldContext").array()) { + for (StackTraceElement element : callStack) { + foldContextBuilder.append(element); + } + } + builder.append("targetMethod", targetMethod.format("%H.%n(%p)")); + if (targetCaller != null) { + builder.append("targetCaller", targetCaller); + } + try (JsonBuilder.ArrayBuilder argsBuilder = builder.append("targetArguments").array()) { + for (Object arg : targetArguments) { + argsBuilder.append(arg instanceof Object[] ? Arrays.toString((Object[]) arg) : Objects.toString(arg)); + } + } + } + } + + private static class ConstantTraceEntry extends TraceEntry { + + private final Object value; + + ConstantTraceEntry(List callStack, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Object value) { + super(callStack, targetMethod, targetCaller, targetArguments); + this.value = value; + } + + @Override + public String toString() { + return super.toString() + " to the constant " + value; + } + + @Override + public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException { + super.toJson(builder); + builder.append("constantValue", value); + } + } + + private static class ExceptionTraceEntry extends TraceEntry { + + private final Class exceptionClass; + + ExceptionTraceEntry(List callStack, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Class exceptionClass) { + super(callStack, targetMethod, targetCaller, targetArguments); + this.exceptionClass = exceptionClass; + } + + @Override + public String toString() { + return super.toString() + " to a \"throw new " + exceptionClass.getName() + "(...)\""; + } + + @Override + public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException { + super.toJson(builder); + builder.append("exception", exceptionClass.getName()); } } - private static void traceException(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Supplier targetParameters, Class exceptionClass) { - if (Options.ReflectionPluginTracing.getValue()) { - System.out.println("Call to " + targetMethod.format("%H.%n(%p)") + - " reached in " + b.getMethod().format("%H.%n(%p)") + - " with parameters (" + targetParameters.get() + ")" + - " was reduced to a \"throw new " + exceptionClass.getName() + "(...)\""); + private abstract static class ReflectionPluginLogSupport { + + protected final String location; + + ReflectionPluginLogSupport(String location) { + this.location = location; + } + + public abstract void dump(Iterable constantReflectionLog); + } + + private static final class ReflectionPluginPlainLogSupport extends ReflectionPluginLogSupport { + + ReflectionPluginPlainLogSupport(String location) { + super(location); + } + + @Override + public void dump(Iterable constantReflectionLog) { + try (PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream(location)))) { + constantReflectionLog.forEach(out::println); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + } + + private static final class ReflectionPluginJsonLogSupport extends ReflectionPluginLogSupport { + + ReflectionPluginJsonLogSupport(String location) { + super(location); + } + + @Override + public void dump(Iterable constantReflectionLog) { + try (JsonWriter out = new JsonPrettyWriter(Path.of(location))) { + try (JsonBuilder.ArrayBuilder arrayBuilder = out.arrayBuilder()) { + for (TraceEntry entry : constantReflectionLog) { + try (JsonBuilder.ObjectBuilder objectBuilder = arrayBuilder.nextEntry().object()) { + entry.toJson(objectBuilder); + } + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } } } } diff --git a/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/MethodCallUtils.java b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/MethodCallUtils.java new file mode 100644 index 000000000000..9ca692ad0bcc --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/MethodCallUtils.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024, 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.reflectionagent; + +import com.oracle.svm.shaded.org.objectweb.asm.Type; +import com.oracle.svm.shaded.org.objectweb.asm.tree.MethodInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Frame; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.SourceValue; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.INVOKESTATIC; + +public final class MethodCallUtils { + + private MethodCallUtils() { + + } + + public record Signature(String owner, String name, String desc) { + + public Signature(MethodInsnNode methodCall) { + this(methodCall.owner, methodCall.name, methodCall.desc); + } + + public Signature(Class owner, String name, Class returnType, Class... parameterTypes) { + this(Type.getInternalName(owner), name, buildDescriptor(returnType, parameterTypes)); + } + + private static String buildDescriptor(Class returnType, Class... parameterTypes) { + return "(" + Arrays.stream(parameterTypes).map(Type::getDescriptor).collect(Collectors.joining()) + ")" + Type.getDescriptor(returnType); + } + } + + public static SourceValue getCallArg(MethodInsnNode call, int argIdx, Frame frame) { + int numOfArgs = Type.getArgumentTypes(call.desc).length + (call.getOpcode() == INVOKESTATIC ? 0 : 1); + int stackPos = frame.getStackSize() - numOfArgs + argIdx; + return frame.getStack(stackPos); + } + + public static String getTypeDescOfArg(MethodInsnNode methodCall, int argIdx) { + if (methodCall.getOpcode() != INVOKESTATIC) { + return argIdx == 0 ? "L" + methodCall.owner + ";" : Type.getArgumentTypes(methodCall.desc)[argIdx - 1].getDescriptor(); + } else { + return Type.getArgumentTypes(methodCall.desc)[argIdx].getDescriptor(); + } + } +} diff --git a/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/NativeImageReflectionAgent.java b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/NativeImageReflectionAgent.java new file mode 100644 index 000000000000..6de743558d50 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/NativeImageReflectionAgent.java @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2024, 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.reflectionagent; + +import com.oracle.svm.configure.trace.AccessAdvisor; +import com.oracle.svm.core.c.function.CEntryPointOptions; +import com.oracle.svm.core.jni.headers.JNIEnvironment; +import com.oracle.svm.core.jni.headers.JNIJavaVM; +import com.oracle.svm.core.jni.headers.JNIObjectHandle; +import com.oracle.svm.jvmtiagentbase.AgentIsolate; +import com.oracle.svm.jvmtiagentbase.JNIHandleSet; +import com.oracle.svm.jvmtiagentbase.JvmtiAgentBase; +import com.oracle.svm.jvmtiagentbase.Support; +import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEnv; +import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEventCallbacks; +import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEventMode; +import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiInterface; +import com.oracle.svm.reflectionagent.analyzers.AnalyzerSuite; +import com.oracle.svm.reflectionagent.analyzers.ConstantArrayAnalyzer; +import com.oracle.svm.reflectionagent.analyzers.ConstantBooleanAnalyzer; +import com.oracle.svm.reflectionagent.analyzers.ConstantClassAnalyzer; +import com.oracle.svm.reflectionagent.analyzers.ConstantMethodHandlesLookupAnalyzer; +import com.oracle.svm.reflectionagent.analyzers.ConstantMethodTypeAnalyzer; +import com.oracle.svm.reflectionagent.analyzers.ConstantStringAnalyzer; +import com.oracle.svm.reflectionagent.cfg.ControlFlowGraphAnalyzer; +import com.oracle.svm.reflectionagent.cfg.ControlFlowGraphNode; +import com.oracle.svm.shaded.org.objectweb.asm.ClassReader; +import com.oracle.svm.shaded.org.objectweb.asm.ClassWriter; +import com.oracle.svm.shaded.org.objectweb.asm.tree.AbstractInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.ClassNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.MethodInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.MethodNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Analyzer; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.AnalyzerException; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.SourceInterpreter; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.SourceValue; +import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.nativeimage.c.function.CEntryPointLiteral; +import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.CCharPointerPointer; +import org.graalvm.nativeimage.c.type.CIntPointer; +import org.graalvm.nativeimage.c.type.CTypeConversion; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.impl.reflectiontags.ConstantTags; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.RecordComponent; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.oracle.svm.core.jni.JNIObjectHandles.nullHandle; +import static com.oracle.svm.jvmtiagentbase.Support.check; +import static com.oracle.svm.jvmtiagentbase.Support.jniFunctions; +import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_CLASS_FILE_LOAD_HOOK; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.INVOKESTATIC; + +/** + * A JVMTI agent which analyzes user provided classes and tags reflective method calls which can be + * proven constant. + *

+ * This way of marking reflective calls as constant decouples the analysis and image runtime + * behaviour w.r.t. reflection from various optimizations executed on IR graphs. + */ +@SuppressWarnings("unused") +public class NativeImageReflectionAgent extends JvmtiAgentBase { + + private static final Class CONSTANT_TAGS_CLASS = ConstantTags.class; + + /* + * Mapping from method signature to indices of arguments which must be constant in order for the + * call to that method to be considered constant. In case a method is not static, a 0 index + * corresponds to the method caller. + */ + private static final Map REFLECTIVE_CALL_CONSTANT_DEFINITIONS = createReflectiveCallConstantDefinitions(); + private static final Map NON_REFLECTIVE_CALL_CONSTANT_DEFINITIONS = createNonReflectiveCallConstantDefinitions(); + + /** + * Defines the reflective methods (which can potentially throw a + * {@link org.graalvm.nativeimage.MissingReflectionRegistrationError}) which we want to tag for + * folding in {@link com.oracle.svm.hosted.snippets.ReflectionPlugins}. + *

+ * If proven as constant by our analysis, calls to these methods will be tagged by redirecting + * their owner to {@link org.graalvm.nativeimage.impl.reflectiontags.ConstantTags} (making them + * static invocations in the process if necessary). + */ + private static Map createReflectiveCallConstantDefinitions() { + Map definitions = new HashMap<>(); + + definitions.put(new MethodCallUtils.Signature(Class.class, "forName", Class.class, String.class), new int[]{0}); + definitions.put(new MethodCallUtils.Signature(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), new int[]{0, 1}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getField", Field.class, String.class), new int[]{0, 1}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getDeclaredField", Field.class, String.class), new int[]{0, 1}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getConstructor", Constructor.class, Class[].class), new int[]{0, 1}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getDeclaredConstructor", Constructor.class, Class[].class), new int[]{0, 1}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getMethod", Method.class, String.class, Class[].class), new int[]{0, 1, 2}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getDeclaredMethod", Method.class, String.class, Class[].class), new int[]{0, 1, 2}); + + definitions.put(new MethodCallUtils.Signature(Class.class, "getFields", Field[].class), new int[]{0}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getDeclaredFields", Field[].class), new int[]{0}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getConstructors", Constructor[].class), new int[]{0}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getDeclaredConstructors", Constructor[].class), new int[]{0}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getMethods", Method[].class), new int[]{0}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getDeclaredMethods", Method[].class), new int[]{0}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getClasses", Class[].class), new int[]{0}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getDeclaredClasses", Class[].class), new int[]{0}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getNestMembers", Class[].class), new int[]{0}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getPermittedSubclasses", Class[].class), new int[]{0}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getRecordComponents", RecordComponent[].class), new int[]{0}); + definitions.put(new MethodCallUtils.Signature(Class.class, "getSigners", Object[].class), new int[]{0}); + + definitions.put(new MethodCallUtils.Signature(MethodHandles.Lookup.class, "findClass", Class.class, String.class), new int[]{0, 1}); + definitions.put(new MethodCallUtils.Signature(MethodHandles.Lookup.class, "findVirtual", MethodHandle.class, Class.class, String.class, MethodType.class), new int[]{0, 1, 2, 3}); + definitions.put(new MethodCallUtils.Signature(MethodHandles.Lookup.class, "findStatic", MethodHandle.class, Class.class, String.class, MethodType.class), new int[]{0, 1, 2, 3}); + definitions.put(new MethodCallUtils.Signature(MethodHandles.Lookup.class, "findConstructor", MethodHandle.class, Class.class, MethodType.class), new int[]{0, 1, 2}); + definitions.put(new MethodCallUtils.Signature(MethodHandles.Lookup.class, "findGetter", MethodHandle.class, Class.class, String.class, Class.class), new int[]{0, 1, 2, 3}); + definitions.put(new MethodCallUtils.Signature(MethodHandles.Lookup.class, "findStaticGetter", MethodHandle.class, Class.class, String.class, Class.class), new int[]{0, 1, 2, 3}); + definitions.put(new MethodCallUtils.Signature(MethodHandles.Lookup.class, "findSetter", MethodHandle.class, Class.class, String.class, Class.class), new int[]{0, 1, 2, 3}); + definitions.put(new MethodCallUtils.Signature(MethodHandles.Lookup.class, "findStaticSetter", MethodHandle.class, Class.class, String.class, Class.class), new int[]{0, 1, 2, 3}); + definitions.put(new MethodCallUtils.Signature(MethodHandles.Lookup.class, "findVarHandle", VarHandle.class, Class.class, String.class, Class.class), new int[]{0, 1, 2, 3}); + definitions.put(new MethodCallUtils.Signature(MethodHandles.Lookup.class, "findStaticVarHandle", VarHandle.class, Class.class, String.class, Class.class), new int[]{0, 1, 2, 3}); + + return definitions; + } + + /** + * Defines methods which we still need to track, but not tag with + * {@link org.graalvm.nativeimage.impl.reflectiontags.ConstantTags}. An example of this are + * various methods for {@link java.lang.invoke.MethodType} construction. + */ + private static Map createNonReflectiveCallConstantDefinitions() { + Map definitions = new HashMap<>(); + + definitions.put(new MethodCallUtils.Signature(MethodType.class, "methodType", MethodType.class, Class.class), new int[]{0}); + definitions.put(new MethodCallUtils.Signature(MethodType.class, "methodType", MethodType.class, Class.class, Class.class), new int[]{0, 1}); + definitions.put(new MethodCallUtils.Signature(MethodType.class, "methodType", MethodType.class, Class.class, Class[].class), new int[]{0, 1}); + definitions.put(new MethodCallUtils.Signature(MethodType.class, "methodType", MethodType.class, Class.class, Class.class, Class[].class), new int[]{0, 1, 2}); + definitions.put(new MethodCallUtils.Signature(MethodType.class, "methodType", MethodType.class, Class.class, MethodType.class), new int[]{0, 1}); + + definitions.put(new MethodCallUtils.Signature(MethodHandles.class, "lookup", MethodHandles.Lookup.class), new int[]{}); + definitions.put(new MethodCallUtils.Signature(MethodHandles.class, "privateLookupIn", MethodHandles.Lookup.class, Class.class, MethodHandles.Lookup.class), new int[]{0, 1}); + + return definitions; + } + + private static final CEntryPointLiteral ON_CLASS_FILE_LOAD_HOOK = CEntryPointLiteral.create(NativeImageReflectionAgent.class, "onClassFileLoadHook", + JvmtiEnv.class, JNIEnvironment.class, JNIObjectHandle.class, JNIObjectHandle.class, CCharPointer.class, JNIObjectHandle.class, int.class, CCharPointer.class, CIntPointer.class, + CCharPointerPointer.class); + + @Override + protected JNIHandleSet constructJavaHandles(JNIEnvironment env) { + return new NativeImageReflectionAgentJNIHandleSet(env); + } + + @Override + protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, String options) { + callbacks.setClassFileLoadHook(ON_CLASS_FILE_LOAD_HOOK.getFunctionPointer()); + return 0; + } + + @Override + protected void onVMInitCallback(JvmtiEnv jvmti, JNIEnvironment jni, JNIObjectHandle thread) { + check(jvmti.getFunctions().SetEventNotificationMode().invoke(jvmti, JvmtiEventMode.JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, nullHandle())); + } + + @CEntryPoint + @CEntryPointOptions(prologue = AgentIsolate.Prologue.class) + @SuppressWarnings("unused") + private static void onClassFileLoadHook(JvmtiEnv jvmti, JNIEnvironment jni, JNIObjectHandle classBeingRedefined, + JNIObjectHandle loader, CCharPointer name, JNIObjectHandle protectionDomain, int classDataLen, CCharPointer classData, + CIntPointer newClassDataLen, CCharPointerPointer newClassData) { + if (shouldIgnoreClassLoader(jni, loader)) { + return; + } + + String className = CTypeConversion.toJavaString(name); + if (AccessAdvisor.PROXY_CLASS_NAME_PATTERN.matcher(className).matches()) { + return; + } + + byte[] clazzData = new byte[classDataLen]; + CTypeConversion.asByteBuffer(classData, classDataLen).get(clazzData); + try { + byte[] newClazzData = instrumentClass(clazzData); + int newClazzDataLen = newClazzData.length; + Support.check(jvmti.getFunctions().Allocate().invoke(jvmti, newClazzDataLen, newClassData)); + CTypeConversion.asByteBuffer(newClassData.read(), newClazzDataLen).put(newClazzData); + newClassDataLen.write(newClazzDataLen); + } catch (AnalyzerException e) { + throw new RuntimeException(e); + } + } + + /** + * We're only interested in analyzing and instrumenting user provided classes, so we're handling + * that by checking which class loader the class was loaded by. In case a class was loaded by a + * builtin class loader, we ignore it. + */ + private static boolean shouldIgnoreClassLoader(JNIEnvironment jni, JNIObjectHandle loader) { + NativeImageReflectionAgent agent = singleton(); + JNIObjectHandle platformClassLoader = agent.handles().platformClassLoader; + JNIObjectHandle builtinAppClassLoader = agent.handles().builtinAppClassLoader; + JNIObjectHandle jdkInternalReflectDelegatingClassLoader = agent.handles().jdkInternalReflectDelegatingClassLoader; + + return loader.equal(nullHandle()) // Bootstrap class loader + || jniFunctions().getIsSameObject().invoke(jni, loader, agent.handles().systemClassLoader) || + !platformClassLoader.equal(nullHandle()) && jniFunctions().getIsSameObject().invoke(jni, loader, platformClassLoader) || + !builtinAppClassLoader.equal(nullHandle()) && jniFunctions().getIsSameObject().invoke(jni, loader, builtinAppClassLoader) || + !jdkInternalReflectDelegatingClassLoader.equal(nullHandle()) && jniFunctions().getIsInstanceOf().invoke(jni, loader, jdkInternalReflectDelegatingClassLoader); + } + + private static byte[] instrumentClass(byte[] classData) throws AnalyzerException { + ClassReader reader = new ClassReader(classData); + ClassNode classNode = new ClassNode(); + reader.accept(classNode, 0); + for (MethodNode methodNode : classNode.methods) { + instrumentMethod(methodNode, classNode); + } + ClassWriter writer = new ClassWriter(0); + classNode.accept(writer); + return writer.toByteArray(); + } + + private static void instrumentMethod(MethodNode methodNode, ClassNode classNode) throws AnalyzerException { + Analyzer analyzer = new ControlFlowGraphAnalyzer<>(new SourceInterpreter()); + + AbstractInsnNode[] instructions = methodNode.instructions.toArray(); + @SuppressWarnings("unchecked") + ControlFlowGraphNode[] frames = Arrays.stream(analyzer.analyze(classNode.name, methodNode)) + .map(frame -> (ControlFlowGraphNode) frame).toArray(ControlFlowGraphNode[]::new); + Set constantCalls = new HashSet<>(); + + Set allCalls = new HashSet<>(REFLECTIVE_CALL_CONSTANT_DEFINITIONS.keySet()); + allCalls.addAll(NON_REFLECTIVE_CALL_CONSTANT_DEFINITIONS.keySet()); + + AnalyzerSuite analyzerSuite = new AnalyzerSuite(); + analyzerSuite.registerAnalyzer(new ConstantStringAnalyzer(instructions, frames, constantCalls)); + analyzerSuite.registerAnalyzer(new ConstantBooleanAnalyzer(instructions, frames, constantCalls)); + analyzerSuite.registerAnalyzer(new ConstantClassAnalyzer(instructions, frames, constantCalls)); + analyzerSuite.registerAnalyzer(new ConstantArrayAnalyzer(instructions, frames, allCalls, new ConstantClassAnalyzer(instructions, frames, constantCalls))); + analyzerSuite.registerAnalyzer(new ConstantMethodTypeAnalyzer(instructions, frames, constantCalls)); + analyzerSuite.registerAnalyzer(new ConstantMethodHandlesLookupAnalyzer(instructions, frames, constantCalls)); + + for (int i = 0; i < instructions.length; i++) { + if (instructions[i] instanceof MethodInsnNode methodCall) { + int[] mustBeConstantArgs = REFLECTIVE_CALL_CONSTANT_DEFINITIONS.get(new MethodCallUtils.Signature(methodCall)); + if (mustBeConstantArgs == null) { + mustBeConstantArgs = NON_REFLECTIVE_CALL_CONSTANT_DEFINITIONS.get(new MethodCallUtils.Signature(methodCall)); + } + if (mustBeConstantArgs != null && analyzerSuite.isConstant(methodCall, frames[i], mustBeConstantArgs)) { + constantCalls.add(methodCall); + } + } + } + + constantCalls.stream() + .filter(cc -> REFLECTIVE_CALL_CONSTANT_DEFINITIONS.containsKey(new MethodCallUtils.Signature(cc))) + .forEach(NativeImageReflectionAgent::tagAsConstant); + } + + private static void tagAsConstant(MethodInsnNode methodCall) { + if (methodCall.getOpcode() != INVOKESTATIC) { + methodCall.setOpcode(INVOKESTATIC); + methodCall.desc = "(L" + methodCall.owner + ";" + methodCall.desc.substring(1); + } + methodCall.owner = CONSTANT_TAGS_CLASS.getName().replace('.', '/'); + } + + @Override + protected int onUnloadCallback(JNIJavaVM vm) { + return 0; + } + + @Override + protected void onVMStartCallback(JvmtiEnv jvmti, JNIEnvironment jni) { + + } + + @Override + protected void onVMDeathCallback(JvmtiEnv jvmti, JNIEnvironment jni) { + + } + + @Override + protected int getRequiredJvmtiVersion() { + return JvmtiInterface.JVMTI_VERSION_9; + } + + @SuppressWarnings("unused") + public static class RegistrationFeature implements Feature { + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + JvmtiAgentBase.registerAgent(new NativeImageReflectionAgent()); + } + } +} diff --git a/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/NativeImageReflectionAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/NativeImageReflectionAgentJNIHandleSet.java new file mode 100644 index 000000000000..a99cdc77177a --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/NativeImageReflectionAgentJNIHandleSet.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024, 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.reflectionagent; + +import com.oracle.svm.core.jni.headers.JNIEnvironment; +import com.oracle.svm.core.jni.headers.JNIMethodId; +import com.oracle.svm.core.jni.headers.JNIObjectHandle; +import com.oracle.svm.jvmtiagentbase.JNIHandleSet; +import com.oracle.svm.jvmtiagentbase.Support; + +import static com.oracle.svm.core.jni.JNIObjectHandles.nullHandle; + +public class NativeImageReflectionAgentJNIHandleSet extends JNIHandleSet { + + final JNIObjectHandle classLoader; + final JNIObjectHandle jdkInternalReflectDelegatingClassLoader; + + final JNIObjectHandle systemClassLoader; + final JNIObjectHandle platformClassLoader; + final JNIObjectHandle builtinAppClassLoader; + + @SuppressWarnings("this-escape") + public NativeImageReflectionAgentJNIHandleSet(JNIEnvironment env) { + super(env); + classLoader = newClassGlobalRef(env, "java/lang/ClassLoader"); + + JNIObjectHandle reflectLoader = findClassOptional(env, "jdk/internal/reflect/DelegatingClassLoader"); + jdkInternalReflectDelegatingClassLoader = reflectLoader.equal(nullHandle()) ? nullHandle() : newTrackedGlobalRef(env, reflectLoader); + + JNIMethodId getSystemClassLoader = getMethodId(env, classLoader, "getSystemClassLoader", "()Ljava/lang/ClassLoader;", true); + systemClassLoader = newTrackedGlobalRef(env, Support.callObjectMethod(env, classLoader, getSystemClassLoader)); + + JNIMethodId getPlatformClassLoader = getMethodIdOptional(env, classLoader, "getPlatformClassLoader", "()Ljava/lang/ClassLoader;", true); + platformClassLoader = getPlatformClassLoader.equal(nullHandle()) ? nullHandle() : newTrackedGlobalRef(env, Support.callObjectMethod(env, classLoader, getPlatformClassLoader)); + + JNIMethodId getBuiltinAppClassLoader = getMethodIdOptional(env, classLoader, "getBuiltinAppClassLoader", "()Ljava/lang/ClassLoader;", true); + builtinAppClassLoader = getBuiltinAppClassLoader.equal(nullHandle()) ? nullHandle() : newTrackedGlobalRef(env, Support.callObjectMethod(env, classLoader, getBuiltinAppClassLoader)); + } +} diff --git a/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/AnalyzerSuite.java b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/AnalyzerSuite.java new file mode 100644 index 000000000000..daab5d419e1c --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/AnalyzerSuite.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024, 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.reflectionagent.analyzers; + +import com.oracle.svm.reflectionagent.MethodCallUtils; +import com.oracle.svm.reflectionagent.cfg.ControlFlowGraphNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.MethodInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.SourceValue; + +import java.util.HashMap; +import java.util.Map; + +public class AnalyzerSuite { + + private final Map valueAnalyzers = new HashMap<>(); + private final Map arrayAnalyzers = new HashMap<>(); + + public void registerAnalyzer(ConstantValueAnalyzer analyzer) { + valueAnalyzers.put(analyzer.typeDescriptor(), analyzer); + } + + public void registerAnalyzer(ConstantArrayAnalyzer analyzer) { + arrayAnalyzers.put(analyzer.typeDescriptor(), analyzer); + } + + public boolean isConstant(MethodInsnNode methodCall, ControlFlowGraphNode frame, int[] mustBeConstantArgs) { + for (int argIdx : mustBeConstantArgs) { + String argTypeName = MethodCallUtils.getTypeDescOfArg(methodCall, argIdx); + if (argTypeName.startsWith("[")) { + ConstantArrayAnalyzer analyzer = arrayAnalyzers.get(argTypeName); + assert analyzer != null : "Analyzer for " + argTypeName + " not found"; + if (!analyzer.isConstant(MethodCallUtils.getCallArg(methodCall, argIdx, frame), methodCall)) { + return false; + } + } else { + ConstantValueAnalyzer analyzer = valueAnalyzers.get(argTypeName); + assert analyzer != null : "Analyzer for " + argTypeName + " not found"; + if (!analyzer.isConstant(MethodCallUtils.getCallArg(methodCall, argIdx, frame))) { + return false; + } + } + } + return true; + } +} diff --git a/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantArrayAnalyzer.java b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantArrayAnalyzer.java new file mode 100644 index 000000000000..64e8209c193d --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantArrayAnalyzer.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2024, 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.reflectionagent.analyzers; + +import com.oracle.svm.reflectionagent.MethodCallUtils; +import com.oracle.svm.reflectionagent.cfg.ControlFlowGraphNode; +import com.oracle.svm.shaded.org.objectweb.asm.Opcodes; +import com.oracle.svm.shaded.org.objectweb.asm.Type; +import com.oracle.svm.shaded.org.objectweb.asm.tree.AbstractInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.IntInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.LdcInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.MethodInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.TypeInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.VarInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Frame; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.SourceValue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.AASTORE; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ALOAD; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ANEWARRAY; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ASTORE; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.BIPUSH; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.DUP; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ICONST_0; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ICONST_1; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ICONST_2; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ICONST_3; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ICONST_4; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ICONST_5; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ICONST_M1; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.INVOKEINTERFACE; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.INVOKESTATIC; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.INVOKEVIRTUAL; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.LDC; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.PUTFIELD; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.PUTSTATIC; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.SIPUSH; + +public class ConstantArrayAnalyzer { + + private final AbstractInsnNode[] instructions; + private final ControlFlowGraphNode[] frames; + private final Set safeMethods; + private final ConstantValueAnalyzer valueAnalyzer; + + public ConstantArrayAnalyzer(AbstractInsnNode[] instructions, ControlFlowGraphNode[] frames, Set safeMethods, ConstantValueAnalyzer valueAnalyzer) { + this.instructions = instructions; + this.frames = frames; + this.safeMethods = safeMethods; + this.valueAnalyzer = valueAnalyzer; + } + + public boolean isConstant(SourceValue value, AbstractInsnNode callSite) { + return referenceIsConstant(value, callSite) && elementsAreConstant(value, callSite); + } + + private boolean referenceIsConstant(SourceValue value, AbstractInsnNode callSite) { + if (value.insns.size() != 1) { + return false; + } + + AbstractInsnNode sourceInstruction = value.insns.iterator().next(); + int sourceInstructionIndex = Arrays.asList(instructions).indexOf(sourceInstruction); + Frame sourceInstructionFrame = frames[sourceInstructionIndex]; + + return switch (sourceInstruction.getOpcode()) { + case ANEWARRAY -> { + Optional arraySize = extractConstInt(sourceInstructionFrame.getStack(sourceInstructionFrame.getStackSize() - 1)); + yield arraySize.isPresent(); + } + case ALOAD -> { + SourceValue sourceValue = sourceInstructionFrame.getLocal(((VarInsnNode) sourceInstruction).var); + yield referenceIsConstant(sourceValue, callSite) && noForbiddenUsages(sourceValue.insns.iterator().next(), callSite); + } + case ASTORE -> { + SourceValue sourceValue = sourceInstructionFrame.getStack(sourceInstructionFrame.getStackSize() - 1); + yield referenceIsConstant(sourceValue, callSite) && sourceValue.insns.iterator().next().getOpcode() == ANEWARRAY; + } + default -> false; + }; + } + + private static Optional extractConstInt(SourceValue value) { + if (value.insns.size() != 1) { + return Optional.empty(); + } + + AbstractInsnNode sourceInstruction = value.insns.iterator().next(); + + return switch (sourceInstruction.getOpcode()) { + case ICONST_M1 -> Optional.of(-1); + case ICONST_0 -> Optional.of(0); + case ICONST_1 -> Optional.of(1); + case ICONST_2 -> Optional.of(2); + case ICONST_3 -> Optional.of(3); + case ICONST_4 -> Optional.of(4); + case ICONST_5 -> Optional.of(5); + case BIPUSH, SIPUSH -> Optional.of(((IntInsnNode) sourceInstruction).operand); + case LDC -> { + LdcInsnNode ldc = (LdcInsnNode) sourceInstruction; + if (ldc.cst instanceof Integer intValue) { + yield Optional.of(intValue); + } + yield Optional.empty(); + } + default -> Optional.empty(); + }; + } + + private boolean noForbiddenUsages(AbstractInsnNode originalStoreInstruction, AbstractInsnNode callSite) { + int callSiteInstructionIndex = Arrays.asList(instructions).indexOf(callSite); + + List nodeIndices = new ArrayList<>(); + nodeIndices.add(callSiteInstructionIndex); + + /* + * Run a BFS in the reversed CFG from the call site, looking for any potential forbidden + * operations for constant arrays. + */ + boolean[] visited = new boolean[frames.length]; + while (!nodeIndices.isEmpty()) { + Integer currentNodeIndex = nodeIndices.removeLast(); + visited[currentNodeIndex] = true; + if (isForbiddenStore(currentNodeIndex, originalStoreInstruction) || isForbiddenMethodCall(currentNodeIndex, originalStoreInstruction)) { + return false; + } + + ControlFlowGraphNode currentNode = frames[currentNodeIndex]; + for (int adjacent : currentNode.predecessors) { + if (!visited[adjacent]) { + nodeIndices.add(adjacent); + } + } + } + + return true; + } + + /** + * Checks if the instruction at {@code instructionIndex} is an assignment instruction (to a + * variable or a field) and its argument's value can be traced to + * {@code originalStoreInstruction} which represents the initial assignment of an array + * reference to a variable. + *

+ * We want to avoid these cases when marking an array as constant because it could potentially + * be modified through a field or variable which we aren't tracking. + *

+ * In the following example, we want to avoid marking the params array as constant when + * attempting to fold the second {@code getMethod} call: + * + *

+     * {@code
+     * Class[] params = new Class[2];
+     * params[0] = String.class;
+     * params[1] = int.class;
+     * Method m1 = Integer.class.getMethod("parseInt", params); // This call is foldable
+     * someField = params; // params could now be modified through someField
+     * // ...
+     * Method m2 = Integer.class.getMethod("parseInt", params); // We must not fold this
+     * }
+     * 
+ */ + private boolean isForbiddenStore(int instructionIndex, AbstractInsnNode originalStoreInstruction) { + AbstractInsnNode instruction = instructions[instructionIndex]; + Frame frame = frames[instructionIndex]; + + if (Stream.of(Opcodes.ASTORE, PUTFIELD, PUTSTATIC).noneMatch(opc -> opc == instruction.getOpcode())) { + return false; + } + + SourceValue storeValue = frame.getStack(frame.getStackSize() - 1); + return loadedValueTracesToStore(storeValue, originalStoreInstruction); + } + + /** + * Similar as with {@code isForbiddenStore}, arrays could be modified in methods they are passed + * to, so we want to avoid marking them as constant after that. An exception to this are the + * reflective methods we're already tracking with our analysis, as we know they won't modify the + * passed array in any way. + */ + private boolean isForbiddenMethodCall(int instructionIndex, AbstractInsnNode originalStoreInstruction) { + AbstractInsnNode instruction = instructions[instructionIndex]; + Frame frame = frames[instructionIndex]; + + if (Stream.of(INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE).noneMatch(opc -> opc == instruction.getOpcode())) { + return false; + } + + MethodInsnNode methodCall = (MethodInsnNode) instruction; + if (safeMethods.contains(new MethodCallUtils.Signature(methodCall))) { + return false; + } + + int numOfArgs = Type.getArgumentTypes(methodCall.desc).length; + return IntStream.range(0, numOfArgs) + .anyMatch(i -> loadedValueTracesToStore(MethodCallUtils.getCallArg(methodCall, i, frame), originalStoreInstruction)); + } + + private boolean loadedValueTracesToStore(SourceValue value, AbstractInsnNode originalStoreInstruction) { + return value.insns.stream().anyMatch(insn -> { + if (insn.getOpcode() != ALOAD) { + return false; + } + + int loadInstructionIndex = Arrays.asList(instructions).indexOf(insn); + Frame loadInstructionFrame = frames[loadInstructionIndex]; + SourceValue loadSourceValue = loadInstructionFrame.getLocal(((VarInsnNode) insn).var); + + return loadSourceValue.insns.stream().anyMatch(storeInsn -> storeInsn == originalStoreInstruction); + }); + } + + private Optional traceArrayRefToOrigin(SourceValue value) { + if (value.insns.size() != 1) { + return Optional.empty(); + } + + AbstractInsnNode sourceInstruction = value.insns.iterator().next(); + int sourceInstructionIndex = Arrays.asList(instructions).indexOf(sourceInstruction); + Frame sourceInstructionFrame = frames[sourceInstructionIndex]; + + return switch (sourceInstruction.getOpcode()) { + case ANEWARRAY -> Optional.of((TypeInsnNode) sourceInstruction); + case ALOAD -> { + SourceValue sourceValue = sourceInstructionFrame.getLocal(((VarInsnNode) sourceInstruction).var); + yield traceArrayRefToOrigin(sourceValue); + } + case ASTORE, DUP -> { + SourceValue sourceValue = sourceInstructionFrame.getStack(sourceInstructionFrame.getStackSize() - 1); + yield traceArrayRefToOrigin(sourceValue); + } + default -> Optional.empty(); + }; + } + + private boolean elementsAreConstant(SourceValue value, AbstractInsnNode callSite) { + int callSiteInstructionIndex = Arrays.asList(instructions).indexOf(callSite); + + Optional arrayInitInsn = traceArrayRefToOrigin(value); + if (arrayInitInsn.isEmpty()) { + return false; + } + + int arrayInitInstructionIndex = Arrays.asList(instructions).indexOf(arrayInitInsn.get()); + Frame arrayInitInstructionFrame = frames[arrayInitInstructionIndex]; + + SourceValue arraySizeValue = arrayInitInstructionFrame.getStack(arrayInitInstructionFrame.getStackSize() - 1); + Optional arraySize = extractConstInt(arraySizeValue); + if (arraySize.isEmpty()) { + return false; + } + + Set constantElements = new HashSet<>(); + + for (int i = arrayInitInstructionIndex; i < callSiteInstructionIndex; i++) { + AbstractInsnNode currentInstruction = instructions[i]; + ControlFlowGraphNode currentInstructionFrame = frames[i]; + + if (currentInstructionFrame.successors.size() != 1) { + return false; + } + + if (currentInstruction.getOpcode() == AASTORE) { + SourceValue storedValue = currentInstructionFrame.getStack(currentInstructionFrame.getStackSize() - 1); + SourceValue indexValue = currentInstructionFrame.getStack(currentInstructionFrame.getStackSize() - 2); + SourceValue arrayRefValue = currentInstructionFrame.getStack(currentInstructionFrame.getStackSize() - 3); + + Optional arrayReference = traceArrayRefToOrigin(arrayRefValue); + if (arrayReference.isEmpty() || arrayReference.get() != arrayInitInsn.get()) { + continue; + } + + Optional elementIndex = extractConstInt(indexValue); + if (elementIndex.isEmpty() || !valueAnalyzer.isConstant(storedValue) || constantElements.contains(elementIndex.get())) { + return false; + } + + constantElements.add(elementIndex.get()); + } + } + + return constantElements.size() == arraySize.get(); + } + + public String typeDescriptor() { + return "[" + valueAnalyzer.typeDescriptor(); + } +} diff --git a/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantBooleanAnalyzer.java b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantBooleanAnalyzer.java new file mode 100644 index 000000000000..342603de7f9f --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantBooleanAnalyzer.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, 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.reflectionagent.analyzers; + +import com.oracle.svm.shaded.org.objectweb.asm.tree.AbstractInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.MethodInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Frame; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.SourceValue; + +import java.util.Set; + +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ICONST_0; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ICONST_1; + +public class ConstantBooleanAnalyzer extends ConstantValueAnalyzer { + + public ConstantBooleanAnalyzer(AbstractInsnNode[] instructions, Frame[] frames, Set constantCalls) { + super(instructions, frames, constantCalls); + } + + @Override + protected boolean isConstant(SourceValue value, AbstractInsnNode sourceInstruction, Frame sourceInstructionFrame) { + return sourceInstruction.getOpcode() == ICONST_0 || sourceInstruction.getOpcode() == ICONST_1; + } + + @Override + protected String typeDescriptor() { + return "Z"; + } +} diff --git a/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantClassAnalyzer.java b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantClassAnalyzer.java new file mode 100644 index 000000000000..c476ebc77ba9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantClassAnalyzer.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024, 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.reflectionagent.analyzers; + +import com.oracle.svm.shaded.org.objectweb.asm.Type; +import com.oracle.svm.shaded.org.objectweb.asm.tree.AbstractInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.FieldInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.LdcInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.MethodInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Frame; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.SourceValue; + +import java.util.Set; + +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.GETSTATIC; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.LDC; + +public class ConstantClassAnalyzer extends ConstantValueAnalyzer { + + public ConstantClassAnalyzer(AbstractInsnNode[] instructions, Frame[] frames, Set constantCalls) { + super(instructions, frames, constantCalls); + } + + @Override + protected boolean isConstant(SourceValue value, AbstractInsnNode sourceInstruction, Frame sourceInstructionFrame) { + return switch (sourceInstruction.getOpcode()) { + case LDC -> { + LdcInsnNode ldc = (LdcInsnNode) sourceInstruction; + yield ldc.cst instanceof Type type && (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY); + } + case GETSTATIC -> { + FieldInsnNode field = (FieldInsnNode) sourceInstruction; + yield isPrimitiveType(field); + } + default -> false; + }; + } + + private static boolean isPrimitiveType(FieldInsnNode field) { + if (!field.name.equals("TYPE")) { + return false; + } + + return switch (field.owner) { + case "java/lang/Byte", "java/lang/Char", "java/lang/Short", "java/lang/Integer", "java/lang/Long", "java/lang/Float", "java/lang/Double", "java/lang/Void" -> true; + default -> false; + }; + } + + @Override + protected String typeDescriptor() { + return "Ljava/lang/Class;"; + } +} diff --git a/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantMethodHandlesLookupAnalyzer.java b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantMethodHandlesLookupAnalyzer.java new file mode 100644 index 000000000000..84d5349e9ad1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantMethodHandlesLookupAnalyzer.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, 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.reflectionagent.analyzers; + +import com.oracle.svm.shaded.org.objectweb.asm.tree.AbstractInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.MethodInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Frame; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.SourceValue; + +import java.util.Set; + +public class ConstantMethodHandlesLookupAnalyzer extends ConstantValueAnalyzer { + + public ConstantMethodHandlesLookupAnalyzer(AbstractInsnNode[] instructions, Frame[] frames, Set constantCalls) { + super(instructions, frames, constantCalls); + } + + @Override + protected boolean isConstant(SourceValue value, AbstractInsnNode sourceInstruction, Frame sourceInstructionFrame) { + return false; + } + + @Override + protected String typeDescriptor() { + return "Ljava/lang/invoke/MethodHandles$Lookup;"; + } +} diff --git a/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantMethodTypeAnalyzer.java b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantMethodTypeAnalyzer.java new file mode 100644 index 000000000000..f9847719644d --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantMethodTypeAnalyzer.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, 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.reflectionagent.analyzers; + +import com.oracle.svm.shaded.org.objectweb.asm.tree.AbstractInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.MethodInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Frame; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.SourceValue; + +import java.util.Set; + +public class ConstantMethodTypeAnalyzer extends ConstantValueAnalyzer { + + public ConstantMethodTypeAnalyzer(AbstractInsnNode[] instructions, Frame[] frames, Set constantCalls) { + super(instructions, frames, constantCalls); + } + + @Override + protected boolean isConstant(SourceValue value, AbstractInsnNode sourceInstruction, Frame sourceInstructionFrame) { + return false; + } + + @Override + protected String typeDescriptor() { + return "Ljava/lang/invoke/MethodType;"; + } +} diff --git a/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantStringAnalyzer.java b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantStringAnalyzer.java new file mode 100644 index 000000000000..2a7a0b0cf474 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantStringAnalyzer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024, 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.reflectionagent.analyzers; + +import com.oracle.svm.shaded.org.objectweb.asm.tree.AbstractInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.LdcInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.MethodInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Frame; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.SourceValue; + +import java.util.Set; + +public class ConstantStringAnalyzer extends ConstantValueAnalyzer { + + public ConstantStringAnalyzer(AbstractInsnNode[] instructions, Frame[] frames, Set constantCalls) { + super(instructions, frames, constantCalls); + } + + @Override + protected boolean isConstant(SourceValue value, AbstractInsnNode sourceInstruction, Frame sourceInstructionFrame) { + return sourceInstruction instanceof LdcInsnNode ldc && ldc.cst instanceof String; + } + + @Override + protected String typeDescriptor() { + return "Ljava/lang/String;"; + } +} diff --git a/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantValueAnalyzer.java b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantValueAnalyzer.java new file mode 100644 index 000000000000..9f90aab5f10f --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/analyzers/ConstantValueAnalyzer.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024, 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.reflectionagent.analyzers; + +import com.oracle.svm.shaded.org.objectweb.asm.tree.AbstractInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.MethodInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.VarInsnNode; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Frame; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.SourceValue; + +import java.util.Arrays; +import java.util.Set; + +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ALOAD; +import static com.oracle.svm.shaded.org.objectweb.asm.Opcodes.ASTORE; + +public abstract class ConstantValueAnalyzer { + + private final AbstractInsnNode[] instructions; + private final Frame[] frames; + private final Set constantCalls; + + public ConstantValueAnalyzer(AbstractInsnNode[] instructions, Frame[] frames, Set constantCalls) { + this.instructions = instructions; + this.frames = frames; + this.constantCalls = constantCalls; + } + + public boolean isConstant(SourceValue value) { + if (value.insns.size() != 1) { + return false; + } + + AbstractInsnNode sourceInstruction = value.insns.iterator().next(); + int sourceInstructionIndex = Arrays.asList(instructions).indexOf(sourceInstruction); + Frame sourceInstructionFrame = frames[sourceInstructionIndex]; + + if (sourceInstruction.getOpcode() == ALOAD) { + SourceValue sourceValue = sourceInstructionFrame.getLocal(((VarInsnNode) sourceInstruction).var); + return isConstant(sourceValue); + } else if (sourceInstruction.getOpcode() == ASTORE) { + SourceValue sourceValue = sourceInstructionFrame.getStack(sourceInstructionFrame.getStackSize() - 1); + return isConstant(sourceValue); + } else if (sourceInstruction instanceof MethodInsnNode methodCall) { + return constantCalls.contains(methodCall); + } + + return isConstant(value, sourceInstruction, sourceInstructionFrame); + } + + protected abstract boolean isConstant(SourceValue value, AbstractInsnNode sourceInstruction, Frame sourceInstructionFrame); + + protected abstract String typeDescriptor(); +} diff --git a/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/cfg/ControlFlowGraphAnalyzer.java b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/cfg/ControlFlowGraphAnalyzer.java new file mode 100644 index 000000000000..96c19f99a844 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/cfg/ControlFlowGraphAnalyzer.java @@ -0,0 +1,54 @@ +/* + * 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.reflectionagent.cfg; + +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Analyzer; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Frame; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Interpreter; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Value; + +public class ControlFlowGraphAnalyzer extends Analyzer { + public ControlFlowGraphAnalyzer(Interpreter interpreter) { + super(interpreter); + } + + @Override + protected Frame newFrame(int numLocals, int numStack) { + return new ControlFlowGraphNode<>(numLocals, numStack); + } + + @Override + protected Frame newFrame(Frame frame) { + return new ControlFlowGraphNode<>(frame); + } + + @Override + protected void newControlFlowEdge(int instructionIndex, int successorIndex) { + ControlFlowGraphNode source = (ControlFlowGraphNode) getFrames()[instructionIndex]; + ControlFlowGraphNode destination = (ControlFlowGraphNode) getFrames()[successorIndex]; + source.successors.add(successorIndex); + destination.predecessors.add(instructionIndex); + } +} diff --git a/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/cfg/ControlFlowGraphNode.java b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/cfg/ControlFlowGraphNode.java new file mode 100644 index 000000000000..576023363505 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflectionagent/src/com/oracle/svm/reflectionagent/cfg/ControlFlowGraphNode.java @@ -0,0 +1,44 @@ +/* + * 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.reflectionagent.cfg; + +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Frame; +import com.oracle.svm.shaded.org.objectweb.asm.tree.analysis.Value; + +import java.util.HashSet; +import java.util.Set; + +public class ControlFlowGraphNode extends Frame { + public Set successors = new HashSet<>(); + public Set predecessors = new HashSet<>(); + + public ControlFlowGraphNode(int numLocals, int maxStack) { + super(numLocals, maxStack); + } + + public ControlFlowGraphNode(Frame frame) { + super(frame); + } +}