package com.oracle.svm.hosted.analysis;
import static com.oracle.graal.pointsto.reports.AnalysisReportsOptions.PrintAnalysisCallTree;
import static com.oracle.svm.hosted.NativeImageOptions.MaxReachableTypes;
import static jdk.vm.ci.common.JVMCIError.shouldNotReachHere;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.MalformedParameterizedTypeException;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ForkJoinPool;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.graalvm.compiler.core.common.SuppressSVMWarnings;
import org.graalvm.compiler.graph.NodeSourcePosition;
import org.graalvm.compiler.options.OptionValues;
import org.graalvm.word.WordBase;
import com.oracle.graal.pointsto.BigBang;
import com.oracle.graal.pointsto.ObjectScanner;
import com.oracle.graal.pointsto.ObjectScanner.ScanReason;
import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException;
import com.oracle.graal.pointsto.flow.MethodTypeFlow;
import com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder;
import com.oracle.graal.pointsto.flow.TypeFlow;
import com.oracle.graal.pointsto.meta.AnalysisField;
import com.oracle.graal.pointsto.meta.AnalysisMetaAccess;
import com.oracle.graal.pointsto.meta.AnalysisMethod;
import com.oracle.graal.pointsto.meta.AnalysisType;
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
import com.oracle.graal.pointsto.meta.HostedProviders;
import com.oracle.graal.pointsto.reports.CallTreePrinter;
import com.oracle.graal.pointsto.util.AnalysisError.TypeNotFoundError;
import com.oracle.svm.core.annotate.UnknownObjectField;
import com.oracle.svm.core.annotate.UnknownPrimitiveField;
import com.oracle.svm.core.graal.meta.SubstrateReplacements;
import com.oracle.svm.core.hub.AnnotatedSuperInfo;
import com.oracle.svm.core.hub.AnnotationsEncoding;
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.hub.GenericInfo;
import com.oracle.svm.core.meta.SubstrateObjectConstant;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.hosted.SVMHost;
import com.oracle.svm.hosted.analysis.flow.SVMMethodTypeFlowBuilder;
import com.oracle.svm.hosted.meta.HostedType;
import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor;
import jdk.vm.ci.common.JVMCIError;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.ResolvedJavaType;
public class Inflation extends BigBang {
private Set<AnalysisField> handledUnknownValueFields;
private Map<GenericInterfacesEncodingKey, Type[]> genericInterfacesMap;
private Map<AnnotatedInterfacesEncodingKey, AnnotatedType[]> annotatedInterfacesMap;
private Map<InterfacesEncodingKey, DynamicHub[]> interfacesEncodings;
private final Pattern illegalCalleesPattern;
private final Pattern targetCallersPattern;
private final AnnotationSubstitutionProcessor annotationSubstitutionProcessor;
public Inflation(OptionValues options, AnalysisUniverse universe, HostedProviders providers, AnnotationSubstitutionProcessor annotationSubstitutionProcessor, ForkJoinPool executor,
Runnable heartbeatCallback) {
super(options, universe, providers, universe.hostVM(), executor, heartbeatCallback, new SubstrateUnsupportedFeatures());
this.annotationSubstitutionProcessor = annotationSubstitutionProcessor;
String[] targetCallers = new String[]{"com\\.oracle\\.graal\\.", "org\\.graalvm[^\\.polyglot\\.nativeapi]"};
targetCallersPattern = buildPrefixMatchPattern(targetCallers);
String[] illegalCallees = new String[]{"java\\.util\\.stream", "java\\.util\\.Collection\\.stream", "java\\.util\\.Arrays\\.stream"};
illegalCalleesPattern = buildPrefixMatchPattern(illegalCallees);
handledUnknownValueFields = new HashSet<>();
genericInterfacesMap = new HashMap<>();
annotatedInterfacesMap = new HashMap<>();
interfacesEncodings = new HashMap<>();
}
@Override
public MethodTypeFlowBuilder createMethodTypeFlowBuilder(BigBang bb, MethodTypeFlow methodFlow) {
return new SVMMethodTypeFlowBuilder(bb, methodFlow);
}
@Override
protected void checkObjectGraph(ObjectScanner objectScanner) {
universe.getFields().forEach(this::handleUnknownValueField);
universe.getTypes().stream().filter(AnalysisType::isReachable).forEach(this::checkType);
universe.getTypes().stream().filter(AnalysisType::isReachable).forEach(type -> scanHub(objectScanner, type));
}
@Override
public SVMHost getHostVM() {
return (SVMHost) hostVM;
}
private void checkType(AnalysisType type) {
assert type.isReachable();
DynamicHub hub = getHostVM().dynamicHub(type);
if (hub.getGenericInfo() == null) {
fillGenericInfo(type, hub);
}
if (hub.getAnnotatedSuperInfo() == null) {
fillAnnotatedSuperInfo(type, hub);
}
if (type.getJavaKind() == JavaKind.Object) {
if (type.isArray()) {
hub.getComponentHub().setArrayHub(hub);
}
try {
AnalysisType enclosingType = type.getEnclosingType();
if (enclosingType != null) {
hub.setEnclosingClass(getHostVM().dynamicHub(enclosingType));
}
} catch (UnsupportedFeatureException ex) {
getUnsupportedFeatures().addMessage(type.toJavaName(true), null, ex.getMessage(), null, ex);
}
if (hub.getInterfacesEncoding() == null) {
fillInterfaces(type, hub);
}
try {
Annotation[] annotations = type.getWrappedWithoutResolve().getAnnotations();
Annotation[] declared = type.getWrappedWithoutResolve().getDeclaredAnnotations();
hub.setAnnotationsEncoding(encodeAnnotations(metaAccess, annotations, declared, hub.getAnnotationsEncoding()));
} catch (ArrayStoreException e) {
hub.setAnnotationsEncoding(e);
}
if (type.isEnum() && hub.shouldInitEnumConstants()) {
if (getHostVM().getClassInitializationSupport().shouldInitializeAtRuntime(type)) {
hub.initEnumConstantsAtRuntime(type.getJavaClass());
} else {
AnalysisField found = null;
for (AnalysisField f : type.getStaticFields()) {
if (f.getName().endsWith("$VALUES")) {
if (found != null) {
found = null;
break;
}
found = f;
}
}
Enum<?>[] enumConstants;
if (found == null) {
enumConstants = (Enum<?>[]) type.getJavaClass().getEnumConstants();
} else {
enumConstants = (Enum<?>[]) SubstrateObjectConstant.asObject(getConstantReflectionProvider().readFieldValue(found, null));
assert enumConstants != null;
}
hub.initEnumConstants(enumConstants);
}
}
}
}
@Override
public void cleanupAfterAnalysis() {
super.cleanupAfterAnalysis();
handledUnknownValueFields = null;
genericInterfacesMap = null;
annotatedInterfacesMap = null;
interfacesEncodings = null;
}
@Override
public void checkUserLimitations() {
int maxReachableTypes = MaxReachableTypes.getValue();
if (maxReachableTypes >= 0) {
CallTreePrinter callTreePrinter = new CallTreePrinter(this);
callTreePrinter.buildCallTree();
int numberOfTypes = callTreePrinter.classesSet(false).size();
if (numberOfTypes > maxReachableTypes) {
throw UserError.abort("Reachable %d types but only %d allowed (because the %s option is set). To see all reachable types use %s; to change the maximum number of allowed types use %s.",
numberOfTypes, maxReachableTypes, MaxReachableTypes.getName(), PrintAnalysisCallTree.getName(), MaxReachableTypes.getName());
}
}
}
public AnnotationSubstitutionProcessor getAnnotationSubstitutionProcessor() {
return annotationSubstitutionProcessor;
}
static class GenericInterfacesEncodingKey {
final Type[] interfaces;
GenericInterfacesEncodingKey(Type[] aInterfaces) {
this.interfaces = aInterfaces;
}
@Override
public boolean equals(Object obj) {
return obj instanceof GenericInterfacesEncodingKey && Arrays.equals(interfaces, ((GenericInterfacesEncodingKey) obj).interfaces);
}
@Override
public int hashCode() {
return Arrays.hashCode(interfaces);
}
}
private static boolean shallowEquals(Object[] a, Object[] a2) {
if (a == a2) {
return true;
} else if (a == null || a2 == null) {
return false;
}
int length = a.length;
if (a2.length != length) {
return false;
}
for (int i = 0; i < length; i++) {
if (a[i] != a2[i]) {
return false;
}
}
return true;
}
private static int shallowHashCode(Object[] a) {
if (a == null) {
return 0;
}
int result = 1;
for (Object element : a) {
result = 31 * result + System.identityHashCode(element);
}
return result;
}
static class AnnotatedInterfacesEncodingKey {
final AnnotatedType[] interfaces;
AnnotatedInterfacesEncodingKey(AnnotatedType[] aInterfaces) {
this.interfaces = aInterfaces;
}
@Override
public boolean equals(Object obj) {
return obj instanceof AnnotatedInterfacesEncodingKey && shallowEquals(interfaces, ((AnnotatedInterfacesEncodingKey) obj).interfaces);
}
@Override
public int hashCode() {
return shallowHashCode(interfaces);
}
}
private void fillGenericInfo(AnalysisType type, DynamicHub hub) {
Class<?> javaClass = type.getJavaClass();
TypeVariable<?>[] typeParameters = javaClass.getTypeParameters();
Type[] allGenericInterfaces;
try {
allGenericInterfaces = javaClass.getGenericInterfaces();
} catch (MalformedParameterizedTypeException | TypeNotPresentException | LinkageError t) {
allGenericInterfaces = new Type[0];
}
Type[] genericInterfaces = Arrays.stream(allGenericInterfaces).filter(this::isTypeAllowed).toArray(Type[]::new);
Type[] cachedGenericInterfaces;
try {
cachedGenericInterfaces = genericInterfacesMap.computeIfAbsent(new GenericInterfacesEncodingKey(genericInterfaces), k -> genericInterfaces);
} catch (MalformedParameterizedTypeException | TypeNotPresentException | LinkageError t) {
cachedGenericInterfaces = genericInterfaces;
}
Type genericSuperClass;
try {
genericSuperClass = javaClass.getGenericSuperclass();
} catch (MalformedParameterizedTypeException | TypeNotPresentException | LinkageError t) {
genericSuperClass = null;
}
if (!isTypeAllowed(genericSuperClass)) {
genericSuperClass = null;
}
hub.setGenericInfo(GenericInfo.factory(typeParameters, cachedGenericInterfaces, genericSuperClass));
}
private void fillAnnotatedSuperInfo(AnalysisType type, DynamicHub hub) {
Class<?> javaClass = type.getJavaClass();
AnnotatedType annotatedSuperclass;
try {
annotatedSuperclass = javaClass.getAnnotatedSuperclass();
} catch (MalformedParameterizedTypeException | TypeNotPresentException | LinkageError t) {
annotatedSuperclass = null;
}
if (annotatedSuperclass != null && !isTypeAllowed(annotatedSuperclass.getType())) {
annotatedSuperclass = null;
}
AnnotatedType[] allAnnotatedInterfaces;
try {
allAnnotatedInterfaces = javaClass.getAnnotatedInterfaces();
} catch (MalformedParameterizedTypeException | TypeNotPresentException | LinkageError t) {
allAnnotatedInterfaces = new AnnotatedType[0];
}
AnnotatedType[] annotatedInterfaces = Arrays.stream(allAnnotatedInterfaces)
.filter(ai -> isTypeAllowed(ai.getType())).toArray(AnnotatedType[]::new);
AnnotatedType[] cachedAnnotatedInterfaces = annotatedInterfacesMap.computeIfAbsent(
new AnnotatedInterfacesEncodingKey(annotatedInterfaces), k -> annotatedInterfaces);
hub.setAnnotatedSuperInfo(AnnotatedSuperInfo.factory(annotatedSuperclass, cachedAnnotatedInterfaces));
}
private boolean isTypeAllowed(Type t) {
if (t instanceof Class) {
Optional<? extends ResolvedJavaType> resolved = metaAccess.optionalLookupJavaType((Class<?>) t);
return resolved.isPresent() && universe.platformSupported(resolved.get());
}
return true;
}
class InterfacesEncodingKey {
final AnalysisType[] aInterfaces;
InterfacesEncodingKey(AnalysisType[] aInterfaces) {
this.aInterfaces = aInterfaces;
}
DynamicHub[] createHubs() {
SVMHost svmHost = (SVMHost) hostVM;
DynamicHub[] hubs = new DynamicHub[aInterfaces.length];
for (int i = 0; i < hubs.length; i++) {
hubs[i] = svmHost.dynamicHub(aInterfaces[i]);
}
return hubs;
}
@Override
public boolean equals(Object obj) {
return obj instanceof InterfacesEncodingKey && Arrays.equals(aInterfaces, ((InterfacesEncodingKey) obj).aInterfaces);
}
@Override
public int hashCode() {
return Arrays.hashCode(aInterfaces);
}
}
private void fillInterfaces(AnalysisType type, DynamicHub hub) {
SVMHost svmHost = (SVMHost) hostVM;
AnalysisType[] aInterfaces = type.getInterfaces();
if (aInterfaces.length == 0) {
hub.setInterfacesEncoding(null);
} else if (aInterfaces.length == 1) {
hub.setInterfacesEncoding(svmHost.dynamicHub(aInterfaces[0]));
} else {
hub.setInterfacesEncoding(interfacesEncodings.computeIfAbsent(new InterfacesEncodingKey(aInterfaces), InterfacesEncodingKey::createHubs));
}
}
private void scanHub(ObjectScanner objectScanner, AnalysisType type) {
SVMHost svmHost = (SVMHost) hostVM;
JavaConstant hubConstant = SubstrateObjectConstant.forObject(svmHost.dynamicHub(type));
objectScanner.scanConstant(hubConstant, ScanReason.HUB);
}
private void handleUnknownValueField(AnalysisField field) {
if (handledUnknownValueFields.contains(field)) {
return;
}
UnknownObjectField unknownObjectField = field.getAnnotation(UnknownObjectField.class);
UnknownPrimitiveField unknownPrimitiveField = field.getAnnotation(UnknownPrimitiveField.class);
if (unknownObjectField != null) {
assert !Modifier.isFinal(field.getModifiers()) : "@UnknownObjectField annotated field " + field.format("%H.%n") + " cannot be final";
assert field.getJavaKind() == JavaKind.Object;
field.setCanBeNull(unknownObjectField.canBeNull());
List<AnalysisType> aAnnotationTypes = extractAnnotationTypes(field, unknownObjectField);
if (field.isAccessed()) {
for (AnalysisType type : aAnnotationTypes) {
type.registerAsAllocated(null);
}
}
handleUnknownObjectField(field, aAnnotationTypes.toArray(new AnalysisType[0]));
} else if (unknownPrimitiveField != null) {
assert !Modifier.isFinal(field.getModifiers()) : "@UnknownPrimitiveField annotated field " + field.format("%H.%n") + " cannot be final";
field.registerAsWritten(null);
}
handledUnknownValueFields.add(field);
}
private List<AnalysisType> (AnalysisField field, UnknownObjectField unknownObjectField) {
List<Class<?>> annotationTypes = new ArrayList<>(Arrays.asList(unknownObjectField.types()));
for (String annotationTypeName : unknownObjectField.fullyQualifiedTypes()) {
try {
Class<?> annotationType = Class.forName(annotationTypeName);
annotationTypes.add(annotationType);
} catch (ClassNotFoundException e) {
throw shouldNotReachHere("Annotation type not found " + annotationTypeName);
}
}
List<AnalysisType> aAnnotationTypes = new ArrayList<>();
AnalysisType declaredType = field.getType();
for (Class<?> annotationType : annotationTypes) {
AnalysisType aAnnotationType = metaAccess.lookupJavaType(annotationType);
assert !WordBase.class.isAssignableFrom(annotationType) : "Annotation type must not be a subtype of WordBase: field: " + field + " | declared type: " + declaredType +
" | annotation type: " + annotationType;
assert declaredType.isAssignableFrom(aAnnotationType) : "Annotation type must be a subtype of the declared type: field: " + field + " | declared type: " + declaredType +
" | annotation type: " + annotationType;
assert aAnnotationType.isArray() || (aAnnotationType.isInstanceClass() && !Modifier.isAbstract(aAnnotationType.getModifiers())) : "Annotation type failure: field: " + field +
" | annotation type " + aAnnotationType;
aAnnotationTypes.add(aAnnotationType);
}
return aAnnotationTypes;
}
private void handleUnknownObjectField(AnalysisField aField, AnalysisType... declaredTypes) {
assert aField.getJavaKind() == JavaKind.Object;
aField.registerAsWritten(null);
for (AnalysisType fieldDeclaredType : declaredTypes) {
TypeFlow<?> fieldDeclaredTypeFlow = fieldDeclaredType.getTypeFlow(this, true);
if (aField.isStatic()) {
fieldDeclaredTypeFlow.addUse(this, aField.getStaticFieldFlow());
} else {
fieldDeclaredTypeFlow.addUse(this, aField.getInitialInstanceFieldFlow());
if (fieldDeclaredType.isArray()) {
AnalysisType fieldComponentType = fieldDeclaredType.getComponentType();
aField.getInitialInstanceFieldFlow().addUse(this, aField.getInstanceFieldFlow());
if (!fieldComponentType.isPrimitive()) {
TypeFlow<?> elementsFlow = fieldDeclaredType.getContextInsensitiveAnalysisObject().getArrayElementsFlow(this, true);
fieldComponentType.getTypeFlow(this, false).addUse(this, elementsFlow);
}
}
}
}
}
private static Set<Annotation> filterUsedAnnotation(Set<Annotation> used, Annotation[] rest) {
if (rest == null) {
return null;
}
Set<Annotation> restUsed = new HashSet<>();
for (Annotation a : rest) {
if (used.contains(a)) {
restUsed.add(a);
}
}
return restUsed;
}
public static Object encodeAnnotations(AnalysisMetaAccess metaAccess, Annotation[] allAnnotations, Annotation[] declaredAnnotations, Object oldEncoding) {
Object newEncoding;
if (allAnnotations.length == 0 && declaredAnnotations.length == 0) {
newEncoding = null;
} else {
Set<Annotation> all = new HashSet<>();
Collections.addAll(all, allAnnotations);
Collections.addAll(all, declaredAnnotations);
final Set<Annotation> usedAnnotations = all.stream()
.filter(a -> {
try {
AnalysisType annotationClass = metaAccess.lookupJavaType(a.getClass());
return isAnnotationUsed(annotationClass);
} catch (TypeNotFoundError e) {
return false;
}
}).collect(Collectors.toSet());
Set<Annotation> usedDeclared = filterUsedAnnotation(usedAnnotations, declaredAnnotations);
newEncoding = usedAnnotations.size() == 0
? null
: AnnotationsEncoding.encodeAnnotations(usedAnnotations, usedDeclared);
}
if (oldEncoding != null && oldEncoding.equals(newEncoding)) {
return oldEncoding;
} else {
return newEncoding;
}
}
private static boolean isAnnotationUsed(AnalysisType annotationType) {
if (annotationType.isReachable()) {
return true;
}
assert annotationType.getInterfaces().length == 1 : annotationType;
AnalysisType annotationInterfaceType = annotationType.getInterfaces()[0];
return annotationInterfaceType.isReachable();
}
public static ResolvedJavaType toWrappedType(ResolvedJavaType type) {
if (type instanceof AnalysisType) {
return ((AnalysisType) type).getWrappedWithoutResolve();
} else if (type instanceof HostedType) {
return ((HostedType) type).getWrapped().getWrappedWithoutResolve();
} else {
return type;
}
}
@Override
public boolean trackConcreteAnalysisObjects(AnalysisType type) {
if (SVMHost.isUnknownClass(type)) {
return false;
}
if (type.isArray() && SVMHost.isUnknownClass(type.getComponentType())) {
throw JVMCIError.unimplemented();
}
return true;
}
private static Pattern buildPrefixMatchPattern(String[] targetPrefixes) {
StringBuilder patternStr = new StringBuilder("^(");
for (int i = 0; i < targetPrefixes.length; i++) {
String prefix = targetPrefixes[i];
patternStr.append(prefix);
patternStr.append("(.*)");
if (i < targetPrefixes.length - 1) {
patternStr.append("|");
}
}
patternStr.append(')');
return Pattern.compile(patternStr.toString());
}
@Override
public boolean isCallAllowed(BigBang bb, AnalysisMethod caller, AnalysisMethod callee, NodeSourcePosition srcPosition) {
String calleeName = callee.getQualifiedName();
if (illegalCalleesPattern.matcher(calleeName).find()) {
String callerName = caller.getQualifiedName();
if (targetCallersPattern.matcher(callerName).find()) {
SuppressSVMWarnings suppress = caller.getAnnotation(SuppressSVMWarnings.class);
AnalysisType callerType = caller.getDeclaringClass();
while (suppress == null && callerType != null) {
suppress = callerType.getAnnotation(SuppressSVMWarnings.class);
callerType = callerType.getEnclosingType();
}
if (suppress != null) {
String[] reasons = suppress.value();
for (String r : reasons) {
if (r.equals("AllowUseOfStreamAPI")) {
return true;
}
}
}
String message = "Illegal: Graal/Truffle use of Stream API: " + calleeName;
int bci = srcPosition.getBCI();
String trace = caller.asStackTraceElement(bci).toString();
bb.getUnsupportedFeatures().addMessage(callerName, caller, message, trace);
return false;
}
}
return true;
}
@Override
public SubstrateReplacements getReplacements() {
return (SubstrateReplacements) super.getReplacements();
}
}