package com.oracle.svm.hosted.snippets;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
import org.graalvm.compiler.nodes.ConstantNode;
import org.graalvm.compiler.nodes.ValueNode;
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext;
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin;
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin.Receiver;
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins.Registration;
import org.graalvm.compiler.options.Option;
import org.graalvm.nativeimage.ImageSingletons;
import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider;
import com.oracle.svm.core.annotate.Delete;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.hosted.ExceptionSynthesizer;
import com.oracle.svm.hosted.ImageClassLoader;
import com.oracle.svm.hosted.SVMHost;
import com.oracle.svm.hosted.c.GraalAccess;
import com.oracle.svm.hosted.phases.SubstrateClassInitializationPlugin;
import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor;
import com.oracle.svm.hosted.substitute.DeletedElementException;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
public class ReflectionPlugins {
public static class ReflectionPluginRegistry extends IntrinsificationPluginRegistry {
public static AutoCloseable startThreadLocalRegistry() {
return ImageSingletons.lookup(ReflectionPluginRegistry.class).startThreadLocalReflectionRegistry();
}
}
static class Options {
@Option(help = "Enable trace logging for reflection plugins.")
static final HostedOptionKey<Boolean> ReflectionPluginTracing = new HostedOptionKey<>(false);
}
public static void registerInvocationPlugins(ImageClassLoader imageClassLoader, SnippetReflectionProvider snippetReflection, AnnotationSubstitutionProcessor annotationSubstitutions,
InvocationPlugins plugins, SVMHost hostVM, boolean analysis, boolean hosted) {
if (hosted && analysis) {
if (!ImageSingletons.contains(ReflectionPluginRegistry.class)) {
ImageSingletons.add(ReflectionPluginRegistry.class, new ReflectionPluginRegistry());
}
}
registerClassPlugins(imageClassLoader, snippetReflection, annotationSubstitutions, plugins, hostVM, analysis, hosted);
}
private static void registerClassPlugins(ImageClassLoader imageClassLoader, SnippetReflectionProvider snippetReflection, AnnotationSubstitutionProcessor annotationSubstitutions,
InvocationPlugins plugins, SVMHost hostVM, boolean analysis, boolean hosted) {
Registration r = new Registration(plugins, Class.class);
r.register1("forName", String.class, new InvocationPlugin() {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode name) {
return processForName(b, hostVM, targetMethod, name, ConstantNode.forBoolean(true), imageClassLoader, snippetReflection, analysis, hosted);
}
});
r.register3("forName", String.class, boolean.class, ClassLoader.class, new InvocationPlugin() {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode name, ValueNode initialize, ValueNode classLoader) {
return processForName(b, hostVM, targetMethod, name, initialize, imageClassLoader, snippetReflection, analysis, hosted);
}
});
r.register2("getDeclaredField", Receiver.class, String.class, new InvocationPlugin() {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode name) {
return processGetField(b, targetMethod, receiver, name, snippetReflection, true, analysis, hosted);
}
});
r.register2("getField", Receiver.class, String.class, new InvocationPlugin() {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode name) {
return processGetField(b, targetMethod, receiver, name, snippetReflection, false, analysis, hosted);
}
});
r.register3("getDeclaredMethod", Receiver.class, String.class, Class[].class, new InvocationPlugin() {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode name, ValueNode parameterTypes) {
return processGetMethod(b, targetMethod, receiver, name, parameterTypes, annotationSubstitutions, snippetReflection, true, analysis, hosted);
}
});
r.register3("getMethod", Receiver.class, String.class, Class[].class, new InvocationPlugin() {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode name, ValueNode parameterTypes) {
return processGetMethod(b, targetMethod, receiver, name, parameterTypes, annotationSubstitutions, snippetReflection, false, analysis, hosted);
}
});
r.register2("getDeclaredConstructor", Receiver.class, Class[].class, new InvocationPlugin() {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode parameterTypes) {
return processGetConstructor(b, targetMethod, receiver, parameterTypes, snippetReflection, annotationSubstitutions, true, analysis, hosted);
}
});
r.register2("getConstructor", Receiver.class, Class[].class, new InvocationPlugin() {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode parameterTypes) {
return processGetConstructor(b, targetMethod, receiver, parameterTypes, snippetReflection, annotationSubstitutions, false, analysis, hosted);
}
});
}
private static boolean processForName(GraphBuilderContext b, SVMHost host, ResolvedJavaMethod targetMethod, ValueNode name, ValueNode initialize,
ImageClassLoader imageClassLoader, SnippetReflectionProvider snippetReflection, boolean analysis, boolean hosted) {
if (name.isConstant() && initialize.isConstant()) {
String className = snippetReflection.asObject(String.class, name.asJavaConstant());
Class<?> clazz = imageClassLoader.findClass(className).get();
if (clazz == null) {
return throwException(b, targetMethod, analysis, hosted, className, ClassNotFoundException.class, className);
} else {
Class<?> intrinsic = getIntrinsic(analysis, hosted, b, clazz);
if (intrinsic == null) {
return false;
}
ResolvedJavaType type = b.getMetaAccess().lookupJavaType(clazz);
JavaConstant hub = b.getConstantReflection().asJavaClass(type);
pushConstant(b, targetMethod, hub, className);
boolean doInitialize = initialize.asJavaConstant().asInt() != 0;
if (doInitialize && host.getClassInitializationSupport().shouldInitializeAtRuntime(clazz)) {
SubstrateClassInitializationPlugin.emitEnsureClassInitialized(b, hub);
}
return true;
}
}
return false;
}
private static boolean processGetField(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode name,
SnippetReflectionProvider snippetReflection, boolean declared, boolean analysis, boolean hosted) {
if (receiver.isConstant() && name.isConstant()) {
Class<?> clazz = getReceiverClass(b, receiver);
String fieldName = snippetReflection.asObject(String.class, name.asJavaConstant());
String target = clazz.getTypeName() + "." + fieldName;
try {
Field field = declared ? clazz.getDeclaredField(fieldName) : clazz.getField(fieldName);
return pushConstant(b, targetMethod, snippetReflection, analysis, hosted, field, target);
} catch (NoSuchFieldException | LinkageError e) {
return throwException(b, targetMethod, analysis, hosted, target, e.getClass(), e.getMessage());
}
}
return false;
}
private static boolean processGetMethod(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode name,
ValueNode parameterTypes, AnnotationSubstitutionProcessor annotationSubstitutions, SnippetReflectionProvider snippetReflection, boolean declared, boolean analysis,
boolean hosted) {
if (receiver.isConstant() && name.isConstant()) {
Class<?>[] paramTypes = SubstrateGraphBuilderPlugins.extractClassArray(annotationSubstitutions, snippetReflection, parameterTypes, true);
if (paramTypes != null) {
Class<?> clazz = getReceiverClass(b, receiver);
String methodName = snippetReflection.asObject(String.class, name.asJavaConstant());
String target = clazz.getTypeName() + "." + methodName + "(" + Stream.of(paramTypes).map(Class::getTypeName).collect(Collectors.joining(", ")) + ")";
try {
Method method = declared ? clazz.getDeclaredMethod(methodName, paramTypes) : clazz.getMethod(methodName, paramTypes);
return pushConstant(b, targetMethod, snippetReflection, analysis, hosted, method, target);
} catch (NoSuchMethodException | LinkageError e) {
return throwException(b, targetMethod, analysis, hosted, target, e.getClass(), e.getMessage());
}
}
}
return false;
}
private static boolean processGetConstructor(GraphBuilderContext b, ResolvedJavaMethod targetMethod,
Receiver receiver, ValueNode parameterTypes,
SnippetReflectionProvider snippetReflection, AnnotationSubstitutionProcessor annotationSubstitutions, boolean declared,
boolean analysis, boolean hosted) {
if (receiver.isConstant()) {
Class<?>[] paramTypes = SubstrateGraphBuilderPlugins.extractClassArray(annotationSubstitutions, snippetReflection, parameterTypes, true);
if (paramTypes != null) {
Class<?> clazz = getReceiverClass(b, receiver);
String target = clazz.getTypeName() + ".<init>(" + Stream.of(paramTypes).map(Class::getTypeName).collect(Collectors.joining(", ")) + ")";
try {
Constructor<?> constructor = declared ? clazz.getDeclaredConstructor(paramTypes) : clazz.getConstructor(paramTypes);
return pushConstant(b, targetMethod, snippetReflection, analysis, hosted, constructor, target);
} catch (NoSuchMethodException | LinkageError e) {
return throwException(b, targetMethod, analysis, hosted, target, e.getClass(), e.getMessage());
}
}
}
return false;
}
private static Class<?> getReceiverClass(GraphBuilderContext b, Receiver receiver) {
ResolvedJavaType javaType = b.getConstantReflection().asJavaType(receiver.get().asJavaConstant());
return OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), javaType);
}
private static <T> T getIntrinsic(boolean analysis, boolean hosted, GraphBuilderContext context, T element) {
if (!hosted) {
return element;
}
if (context.bciCanBeDuplicated()) {
return null;
}
if (analysis) {
if (isDeleted(element, context.getMetaAccess())) {
return null;
}
ImageSingletons.lookup(ReflectionPluginRegistry.class).add(context.getCallingContext(), element);
}
return ImageSingletons.lookup(ReflectionPluginRegistry.class).get(context.getCallingContext());
}
private static <T> boolean isDeleted(T element, MetaAccessProvider metaAccess) {
AnnotatedElement annotated = null;
try {
if (element instanceof Executable) {
annotated = metaAccess.lookupJavaMethod((Executable) element);
} else if (element instanceof Field) {
annotated = metaAccess.lookupJavaField((Field) element);
}
} catch (DeletedElementException ex) {
return true;
}
if (annotated != null && annotated.isAnnotationPresent(Delete.class)) {
return true;
}
return false;
}
private static <T> boolean pushConstant(GraphBuilderContext b, ResolvedJavaMethod targetMethod, SnippetReflectionProvider snippetReflection,
boolean analysis, boolean hosted, T element, String targetElement) {
T intrinsic = getIntrinsic(analysis, hosted, b, element);
if (intrinsic == null) {
return false;
}
pushConstant(b, targetMethod, snippetReflection.forObject(intrinsic), targetElement);
return true;
}
private static void pushConstant(GraphBuilderContext b, ResolvedJavaMethod reflectionMethod, JavaConstant constant, String targetElement) {
b.addPush(JavaKind.Object, ConstantNode.forConstant(constant, b.getMetaAccess(), b.getGraph()));
traceConstant(b.getMethod(), reflectionMethod, targetElement);
}
private static boolean throwException(GraphBuilderContext b, ResolvedJavaMethod reflectionMethod, boolean analysis, boolean hosted, String targetElement,
Class<? extends Throwable> exceptionClass, String originalMessage) {
Method exceptionMethod = ExceptionSynthesizer.throwExceptionMethod(exceptionClass, String.class);
Method intrinsic = getIntrinsic(analysis, hosted, b, exceptionMethod);
if (intrinsic == null) {
return false;
}
throwException(b, reflectionMethod, targetElement, exceptionMethod, originalMessage);
return true;
}
private static void throwException(GraphBuilderContext b, ResolvedJavaMethod reflectionMethod, String targetElement, Method exceptionMethod, String originalMessage) {
String message = originalMessage + ". This exception was synthesized during native image building from a call to " + reflectionMethod.format("%H.%n(%p)") +
" with constant arguments.";
ExceptionSynthesizer.throwException(b, exceptionMethod, message);
traceException(b.getMethod(), reflectionMethod, targetElement, exceptionMethod);
}
private static void traceConstant(ResolvedJavaMethod contextMethod, ResolvedJavaMethod reflectionMethod, String targetElement) {
if (Options.ReflectionPluginTracing.getValue()) {
System.out.println("Call to " + reflectionMethod.format("%H.%n(%p)") + " reached in " + contextMethod.format("%H.%n(%p)") +
" for target " + targetElement + " was reduced to a constant.");
}
}
private static void traceException(ResolvedJavaMethod contextMethod, ResolvedJavaMethod reflectionMethod, String targetElement, Method exceptionMethod) {
if (Options.ReflectionPluginTracing.getValue()) {
String exception = exceptionMethod.getExceptionTypes()[0].getName();
System.out.println("Call to " + reflectionMethod.format("%H.%n(%p)") + " reached in " + contextMethod.format("%H.%n(%p)") +
" for target " + targetElement + " was reduced to a \"throw new " + exception + "(...)\"");
}
}
}