package com.oracle.svm.configure.trace;
import static com.oracle.svm.configure.trace.LazyValueUtils.lazyValue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.graalvm.compiler.phases.common.LazyValue;
import com.oracle.svm.configure.config.ConfigurationMemberKind;
import com.oracle.svm.configure.config.ConfigurationMethod;
import com.oracle.svm.configure.config.ProxyConfiguration;
import com.oracle.svm.configure.config.ResourceConfiguration;
import com.oracle.svm.configure.config.SignatureUtil;
import com.oracle.svm.configure.config.TypeConfiguration;
import jdk.vm.ci.meta.MetaUtil;
class ReflectionProcessor extends AbstractProcessor {
private final AccessAdvisor advisor;
private final TypeConfiguration configuration;
private final ProxyConfiguration proxyConfiguration;
private final ResourceConfiguration resourceConfiguration;
ReflectionProcessor(AccessAdvisor advisor, TypeConfiguration typeConfiguration, ProxyConfiguration proxyConfiguration, ResourceConfiguration resourceConfiguration) {
this.advisor = advisor;
this.configuration = typeConfiguration;
this.proxyConfiguration = proxyConfiguration;
this.resourceConfiguration = resourceConfiguration;
}
public TypeConfiguration getConfiguration() {
return configuration;
}
public ProxyConfiguration getProxyConfiguration() {
return proxyConfiguration;
}
public ResourceConfiguration getResourceConfiguration() {
return resourceConfiguration;
}
@Override
@SuppressWarnings("fallthrough")
public void processEntry(Map<String, ?> entry) {
boolean invalidResult = Boolean.FALSE.equals(entry.get("result"));
if (invalidResult) {
return;
}
String function = (String) entry.get("function");
List<?> args = (List<?>) entry.get("args");
switch (function) {
case "getResource":
case "getResourceAsStream":
case "getSystemResource":
case "getSystemResourceAsStream":
case "getResources":
case "getSystemResources":
String literal = singleElement(args);
String regex = Pattern.quote(literal);
resourceConfiguration.addResourcePattern(regex);
return;
}
String callerClass = (String) entry.get("caller_class");
boolean isLoadClass = function.equals("loadClass");
if (isLoadClass || function.equals("forName") || function.equals("findClass")) {
String name = singleElement(args);
if (isLoadClass) {
name = MetaUtil.internalNameToJava(MetaUtil.toInternalName(name), true, true);
}
if (!advisor.shouldIgnore(lazyValue(name), lazyValue(callerClass)) &&
!(isLoadClass && advisor.shouldIgnoreLoadClass(lazyValue(name), lazyValue(callerClass)))) {
configuration.getOrCreateType(name);
}
return;
}
String clazz = (String) entry.get("class");
if (advisor.shouldIgnore(lazyValue(clazz), lazyValue(callerClass))) {
return;
}
ConfigurationMemberKind memberKind = ConfigurationMemberKind.PUBLIC;
boolean unsafeAccess = false;
String clazzOrDeclaringClass = entry.containsKey("declaring_class") ? (String) entry.get("declaring_class") : clazz;
switch (function) {
case "getDeclaredFields": {
configuration.getOrCreateType(clazz).setAllDeclaredFields();
break;
}
case "getFields": {
configuration.getOrCreateType(clazz).setAllPublicFields();
break;
}
case "asInterfaceInstance":
case "getDeclaredMethods": {
configuration.getOrCreateType(clazz).setAllDeclaredMethods();
break;
}
case "getMethods": {
configuration.getOrCreateType(clazz).setAllPublicMethods();
break;
}
case "getDeclaredConstructors": {
configuration.getOrCreateType(clazz).setAllDeclaredConstructors();
break;
}
case "getConstructors": {
configuration.getOrCreateType(clazz).setAllPublicConstructors();
break;
}
case "getDeclaredClasses": {
configuration.getOrCreateType(clazz).setAllDeclaredClasses();
break;
}
case "getClasses": {
configuration.getOrCreateType(clazz).setAllPublicClasses();
break;
}
case "objectFieldOffset":
case "findFieldHandle":
case "unreflectField":
unsafeAccess = true;
case "getDeclaredField":
memberKind = "findFieldHandle".equals(function) ? ConfigurationMemberKind.PRESENT : ConfigurationMemberKind.DECLARED;
case "getField": {
configuration.getOrCreateType(clazzOrDeclaringClass).addField(singleElement(args), memberKind, false, unsafeAccess);
if (!clazzOrDeclaringClass.equals(clazz)) {
configuration.getOrCreateType(clazz);
}
break;
}
case "getDeclaredMethod":
case "findMethodHandle":
memberKind = "findMethodHandle".equals(function) ? ConfigurationMemberKind.PRESENT : ConfigurationMemberKind.DECLARED;
case "getMethod": {
expectSize(args, 2);
String name = (String) args.get(0);
List<?> parameterTypes = (List<?>) args.get(1);
if (parameterTypes == null) {
parameterTypes = Collections.emptyList();
}
configuration.getOrCreateType(clazzOrDeclaringClass).addMethod(name, SignatureUtil.toInternalSignature(parameterTypes), memberKind);
if (!clazzOrDeclaringClass.equals(clazz)) {
configuration.getOrCreateType(clazz);
}
break;
}
case "getDeclaredConstructor":
case "findConstructorHandle":
memberKind = "findConstructorHandle".equals(function) ? ConfigurationMemberKind.PRESENT : ConfigurationMemberKind.DECLARED;
case "getConstructor": {
List<String> parameterTypes = singleElement(args);
if (parameterTypes == null) {
parameterTypes = Collections.emptyList();
}
String signature = SignatureUtil.toInternalSignature(parameterTypes);
assert clazz.equals(clazzOrDeclaringClass) : "Constructor can only be accessed via declaring class";
configuration.getOrCreateType(clazzOrDeclaringClass).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, signature, memberKind);
break;
}
case "getProxyClass": {
expectSize(args, 2);
addDynamicProxy((List<?>) args.get(1), lazyValue(callerClass));
break;
}
case "newProxyInstance": {
expectSize(args, 3);
addDynamicProxy((List<?>) args.get(1), lazyValue(callerClass));
break;
}
case "newMethodHandleProxyInstance": {
expectSize(args, 1);
addDynamicProxyUnchecked((List<?>) args.get(0), Collections.singletonList("sun.invoke.WrapperInstance"), lazyValue(callerClass));
break;
}
case "getEnclosingConstructor":
case "getEnclosingMethod": {
String result = (String) entry.get("result");
addFullyQualifiedDeclaredMethod(result);
break;
}
case "newInstance": {
if (clazz.equals("java.lang.reflect.Array")) {
configuration.getOrCreateType((String) args.get(0));
} else {
configuration.getOrCreateType(clazz).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, "()V", ConfigurationMemberKind.DECLARED);
}
break;
}
case "getBundleImplJDK8OrEarlier": {
expectSize(args, 4);
resourceConfiguration.addBundle((String) args.get(0));
break;
}
case "getBundleImplJDK11OrLater": {
expectSize(args, 5);
resourceConfiguration.addBundle((String) args.get(2));
break;
}
default:
System.err.println("Unsupported reflection method: " + function);
}
}
private void addFullyQualifiedDeclaredMethod(String descriptor) {
int sigbegin = descriptor.indexOf('(');
int classend = descriptor.lastIndexOf('.', sigbegin - 1);
String qualifiedClass = descriptor.substring(0, classend);
String methodName = descriptor.substring(classend + 1, sigbegin);
String signature = descriptor.substring(sigbegin);
configuration.getOrCreateType(qualifiedClass).addMethod(methodName, signature, ConfigurationMemberKind.DECLARED);
}
private void addDynamicProxy(List<?> interfaceList, LazyValue<String> callerClass) {
@SuppressWarnings("unchecked")
List<String> interfaces = (List<String>) interfaceList;
for (String iface : interfaces) {
if (advisor.shouldIgnore(lazyValue(iface), callerClass)) {
return;
}
}
proxyConfiguration.add(interfaces);
}
private void addDynamicProxyUnchecked(List<?> checkedInterfaceList, List<?> uncheckedInterfaceList, LazyValue<String> callerClass) {
@SuppressWarnings("unchecked")
List<String> checkedInterfaces = (List<String>) checkedInterfaceList;
for (String iface : checkedInterfaces) {
if (advisor.shouldIgnore(lazyValue(iface), callerClass)) {
return;
}
}
@SuppressWarnings("unchecked")
List<String> uncheckedInterfaces = (List<String>) uncheckedInterfaceList;
List<String> interfaces = new ArrayList<>();
interfaces.addAll(checkedInterfaces);
interfaces.addAll(uncheckedInterfaces);
proxyConfiguration.add(interfaces);
}
}