package com.oracle.svm.hosted.ameta;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import org.graalvm.compiler.serviceprovider.GraalUnsafeAccess;
import org.graalvm.compiler.word.Word;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.word.WordBase;
import com.oracle.graal.pointsto.meta.AnalysisField;
import com.oracle.graal.pointsto.meta.AnalysisType;
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
import com.oracle.graal.pointsto.util.AnalysisError;
import com.oracle.svm.core.RuntimeAssertionsSupport;
import com.oracle.svm.core.annotate.InjectAccessors;
import com.oracle.svm.core.graal.meta.SharedConstantReflectionProvider;
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.meta.ReadableJavaField;
import com.oracle.svm.core.meta.SubstrateObjectConstant;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.SVMHost;
import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport;
import jdk.vm.ci.meta.Constant;
import jdk.vm.ci.meta.ConstantReflectionProvider;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.MemoryAccessProvider;
import jdk.vm.ci.meta.ResolvedJavaField;
import jdk.vm.ci.meta.ResolvedJavaType;
@Platforms(Platform.HOSTED_ONLY.class)
public class AnalysisConstantReflectionProvider extends SharedConstantReflectionProvider {
private final AnalysisUniverse universe;
private final ConstantReflectionProvider originalConstantReflection;
private final ClassInitializationSupport classInitializationSupport;
public AnalysisConstantReflectionProvider(AnalysisUniverse universe, ConstantReflectionProvider originalConstantReflection, ClassInitializationSupport classInitializationSupport) {
this.universe = universe;
this.originalConstantReflection = originalConstantReflection;
this.classInitializationSupport = classInitializationSupport;
}
@Override
public MemoryAccessProvider getMemoryAccessProvider() {
return EmptyMemoryAcessProvider.SINGLETON;
}
@Override
public final JavaConstant readFieldValue(ResolvedJavaField field, JavaConstant receiver) {
if (field instanceof AnalysisField) {
return readValue((AnalysisField) field, receiver);
} else {
return super.readFieldValue(field, receiver);
}
}
public JavaConstant readValue(AnalysisField field, JavaConstant receiver) {
JavaConstant value;
if (classInitializationSupport.shouldInitializeAtRuntime(field.getDeclaringClass())) {
if (field.isStatic()) {
value = readUninitializedStaticValue(field);
} else {
throw VMError.shouldNotReachHere("Cannot read instance field of a class that is initialized at run time: " + field.format("%H.%n"));
}
} else {
value = universe.lookup(ReadableJavaField.readFieldValue(originalConstantReflection, field.wrapped, universe.toHosted(receiver)));
}
return interceptValue(field, value);
}
private static JavaConstant readUninitializedStaticValue(AnalysisField field) {
JavaKind kind = field.getJavaKind();
boolean canHaveConstantValueAttribute = kind.isPrimitive() || field.getType().toJavaName(true).equals("java.lang.String");
if (!canHaveConstantValueAttribute || !field.isFinal()) {
return JavaConstant.defaultForKind(kind);
}
assert Modifier.isStatic(field.getModifiers());
Object base = field.getDeclaringClass().getJavaClass();
long offset = field.wrapped.getOffset();
Field reflectionField = field.getJavaField();
if (reflectionField != null) {
assert kind == JavaKind.fromJavaClass(reflectionField.getType());
Object reflectionFieldBase = GraalUnsafeAccess.getUnsafe().staticFieldBase(reflectionField);
long reflectionFieldOffset = GraalUnsafeAccess.getUnsafe().staticFieldOffset(reflectionField);
AnalysisError.guarantee(reflectionFieldBase == base && reflectionFieldOffset == offset);
}
switch (kind) {
case Boolean:
return JavaConstant.forBoolean(GraalUnsafeAccess.getUnsafe().getBoolean(base, offset));
case Byte:
return JavaConstant.forByte(GraalUnsafeAccess.getUnsafe().getByte(base, offset));
case Char:
return JavaConstant.forChar(GraalUnsafeAccess.getUnsafe().getChar(base, offset));
case Short:
return JavaConstant.forShort(GraalUnsafeAccess.getUnsafe().getShort(base, offset));
case Int:
return JavaConstant.forInt(GraalUnsafeAccess.getUnsafe().getInt(base, offset));
case Long:
return JavaConstant.forLong(GraalUnsafeAccess.getUnsafe().getLong(base, offset));
case Float:
return JavaConstant.forFloat(GraalUnsafeAccess.getUnsafe().getFloat(base, offset));
case Double:
return JavaConstant.forDouble(GraalUnsafeAccess.getUnsafe().getDouble(base, offset));
case Object:
Object value = GraalUnsafeAccess.getUnsafe().getObject(base, offset);
assert value == null || value instanceof String : "String is currently the only specified object type for the ConstantValue class file attribute";
return SubstrateObjectConstant.forObject(value);
default:
throw VMError.shouldNotReachHere();
}
}
public JavaConstant interceptValue(AnalysisField field, JavaConstant value) {
JavaConstant result = value;
if (result != null) {
result = filterInjectedAccessor(field, result);
result = replaceObject(result);
result = interceptAssertionStatus(field, result);
result = interceptWordType(field, result);
}
return result;
}
private static JavaConstant filterInjectedAccessor(AnalysisField field, JavaConstant value) {
if (field.getAnnotation(InjectAccessors.class) != null) {
assert !field.isAccessed();
return JavaConstant.defaultForKind(value.getJavaKind());
}
return value;
}
private JavaConstant replaceObject(JavaConstant value) {
if (value == JavaConstant.NULL_POINTER) {
return JavaConstant.NULL_POINTER;
}
if (value.getJavaKind() == JavaKind.Object) {
Object oldObject = universe.getSnippetReflection().asObject(Object.class, value);
Object newObject = universe.replaceObject(oldObject);
if (newObject != oldObject) {
return universe.getSnippetReflection().forObject(newObject);
}
}
return value;
}
private static JavaConstant interceptAssertionStatus(AnalysisField field, JavaConstant value) {
if (field.isStatic() && field.isSynthetic() && field.getName().startsWith("$assertionsDisabled")) {
Class<?> clazz = field.getDeclaringClass().getJavaClass();
boolean assertionsEnabled = RuntimeAssertionsSupport.singleton().desiredAssertionStatus(clazz);
return JavaConstant.forBoolean(!assertionsEnabled);
}
return value;
}
private JavaConstant interceptWordType(AnalysisField field, JavaConstant value) {
if (value.getJavaKind() == JavaKind.Object) {
Object originalObject = universe.getSnippetReflection().asObject(Object.class, value);
if (universe.hostVM().isRelocatedPointer(originalObject)) {
return value;
} else if (originalObject instanceof WordBase) {
return JavaConstant.forIntegerKind(universe.getWordKind(), ((WordBase) originalObject).rawValue());
} else if (originalObject == null && field.getType().isWordType()) {
return JavaConstant.forIntegerKind(universe.getWordKind(), 0);
}
}
return value;
}
@Override
public ResolvedJavaType asJavaType(Constant constant) {
if (constant instanceof SubstrateObjectConstant) {
Object obj = SubstrateObjectConstant.asObject(constant);
if (obj instanceof DynamicHub) {
return getHostVM().lookupType((DynamicHub) obj);
} else if (obj instanceof Class) {
throw VMError.shouldNotReachHere("Must not have java.lang.Class object: " + obj);
}
}
return null;
}
@Override
public JavaConstant asJavaClass(ResolvedJavaType type) {
DynamicHub dynamicHub = getHostVM().dynamicHub(type);
registerAsReachable(getHostVM(), dynamicHub);
assert dynamicHub != null : type.toClassName() + " has a null dynamicHub.";
return SubstrateObjectConstant.forObject(dynamicHub);
}
protected static void registerAsReachable(SVMHost hostVM, DynamicHub dynamicHub) {
assert dynamicHub != null;
AnalysisType valueType = hostVM.lookupType(dynamicHub);
valueType.registerAsReachable();
}
private SVMHost getHostVM() {
return (SVMHost) universe.hostVM();
}
}