package com.oracle.graal.pointsto.meta;
import static jdk.vm.ci.common.JVMCIError.shouldNotReachHere;
import static jdk.vm.ci.common.JVMCIError.unimplemented;
import java.lang.annotation.Annotation;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.debug.GraalError;
import org.graalvm.compiler.java.BytecodeParser.BytecodeParserError;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin;
import org.graalvm.util.GuardedAnnotationAccess;
import com.oracle.graal.pointsto.BigBang;
import com.oracle.graal.pointsto.api.PointstoOptions;
import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException;
import com.oracle.graal.pointsto.flow.AbstractVirtualInvokeTypeFlow;
import com.oracle.graal.pointsto.flow.InvokeTypeFlow;
import com.oracle.graal.pointsto.flow.MethodTypeFlow;
import com.oracle.graal.pointsto.infrastructure.GraphProvider;
import com.oracle.graal.pointsto.infrastructure.OriginalMethodProvider;
import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod;
import com.oracle.graal.pointsto.infrastructure.WrappedSignature;
import com.oracle.graal.pointsto.results.StaticAnalysisResults;
import com.oracle.graal.pointsto.util.AnalysisError;
import com.oracle.svm.util.ReflectionUtil;
import jdk.vm.ci.code.BytecodePosition;
import jdk.vm.ci.meta.Constant;
import jdk.vm.ci.meta.ConstantPool;
import jdk.vm.ci.meta.ExceptionHandler;
import jdk.vm.ci.meta.JavaType;
import jdk.vm.ci.meta.LineNumberTable;
import jdk.vm.ci.meta.Local;
import jdk.vm.ci.meta.LocalVariableTable;
import jdk.vm.ci.meta.ProfilingInfo;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
import jdk.vm.ci.meta.SpeculationLog;
public class AnalysisMethod implements WrappedJavaMethod, GraphProvider, OriginalMethodProvider {
private final AnalysisUniverse universe;
public final ResolvedJavaMethod wrapped;
private final int id;
private final ExceptionHandler[] exceptionHandlers;
private final LocalVariableTable localVariableTable;
private final String qualifiedName;
private MethodTypeFlow typeFlow;
private final AnalysisType declaringClass;
private boolean isRootMethod;
private boolean isIntrinsicMethod;
private Object entryPointData;
private boolean isInvoked;
private boolean isImplementationInvoked;
protected AnalysisMethod[] implementations;
private ConcurrentMap<InvokeTypeFlow, Object> invokedBy;
private ConcurrentMap<InvokeTypeFlow, Object> implementationInvokedBy;
public AnalysisMethod(AnalysisUniverse universe, ResolvedJavaMethod wrapped) {
this.universe = universe;
this.wrapped = wrapped;
this.id = universe.nextMethodId.getAndIncrement();
declaringClass = universe.lookup(wrapped.getDeclaringClass());
if (PointstoOptions.TrackAccessChain.getValue(universe.hostVM().options())) {
startTrackInvocations();
}
ExceptionHandler[] original = wrapped.getExceptionHandlers();
exceptionHandlers = new ExceptionHandler[original.length];
for (int i = 0; i < original.length; i++) {
ExceptionHandler h = original[i];
JavaType catchType = getCatchType(h);
exceptionHandlers[i] = new ExceptionHandler(h.getStartBCI(), h.getEndBCI(), h.getHandlerBCI(), h.catchTypeCPI(), catchType);
}
LocalVariableTable newLocalVariableTable = null;
if (wrapped.getLocalVariableTable() != null) {
try {
Local[] origLocals = wrapped.getLocalVariableTable().getLocals();
Local[] newLocals = new Local[origLocals.length];
ResolvedJavaType accessingClass = getDeclaringClass().getWrapped();
for (int i = 0; i < newLocals.length; ++i) {
Local origLocal = origLocals[i];
ResolvedJavaType origLocalType = origLocal.getType() instanceof ResolvedJavaType ? (ResolvedJavaType) origLocal.getType() : origLocal.getType().resolve(accessingClass);
AnalysisType type = universe.lookup(origLocalType);
newLocals[i] = new Local(origLocal.getName(), type, origLocal.getStartBCI(), origLocal.getEndBCI(), origLocal.getSlot());
}
newLocalVariableTable = new LocalVariableTable(newLocals);
} catch (LinkageError | UnsupportedFeatureException | BytecodeParserError e) {
newLocalVariableTable = null;
}
}
localVariableTable = newLocalVariableTable;
typeFlow = new MethodTypeFlow(universe.hostVM().options(), this);
if (getName().startsWith("$SWITCH_TABLE$")) {
assert Modifier.isStatic(getModifiers());
assert getSignature().getParameterCount(false) == 0;
try {
Method switchTableMethod = ReflectionUtil.lookupMethod(getDeclaringClass().getJavaClass(), getName());
switchTableMethod.invoke(null);
} catch (ReflectiveOperationException ex) {
throw GraalError.shouldNotReachHere(ex);
}
}
this.qualifiedName = format("%H.%n(%P)");
}
public String getQualifiedName() {
return qualifiedName;
}
private JavaType getCatchType(ExceptionHandler handler) {
JavaType catchType = handler.getCatchType();
if (catchType == null) {
return null;
}
ResolvedJavaType resolvedCatchType;
try {
resolvedCatchType = catchType.resolve(wrapped.getDeclaringClass());
} catch (LinkageError e) {
return catchType;
}
return universe.lookup(resolvedCatchType);
}
public void cleanupAfterAnalysis() {
typeFlow = null;
invokedBy = null;
implementationInvokedBy = null;
}
public void startTrackInvocations() {
if (invokedBy == null) {
invokedBy = new ConcurrentHashMap<>();
}
if (implementationInvokedBy == null) {
implementationInvokedBy = new ConcurrentHashMap<>();
}
}
public int getId() {
return id;
}
public MethodTypeFlow getTypeFlow() {
return typeFlow;
}
public void registerAsIntrinsicMethod() {
isIntrinsicMethod = true;
}
public void registerAsEntryPoint(Object newEntryPointData) {
assert newEntryPointData != null;
if (entryPointData != null && !entryPointData.equals(newEntryPointData)) {
throw new UnsupportedFeatureException("Method is registered as entry point with conflicting entry point data: " + entryPointData + ", " + newEntryPointData);
}
entryPointData = newEntryPointData;
startTrackInvocations();
}
public void registerAsInvoked(InvokeTypeFlow invoke) {
isInvoked = true;
if (invokedBy != null && invoke != null) {
invokedBy.put(invoke, Boolean.TRUE);
}
}
public void registerAsImplementationInvoked(InvokeTypeFlow invoke) {
assert !Modifier.isAbstract(getModifiers());
isImplementationInvoked = true;
if (implementationInvokedBy != null && invoke != null) {
implementationInvokedBy.put(invoke, Boolean.TRUE);
}
getDeclaringClass().registerAsReachable();
}
public Set<AnalysisMethod> getCallers() {
return getInvokeLocations().stream().map(location -> (AnalysisMethod) location.getMethod()).collect(Collectors.toSet());
}
public List<BytecodePosition> getInvokeLocations() {
List<BytecodePosition> locations = new ArrayList<>();
for (InvokeTypeFlow invoke : implementationInvokedBy.keySet()) {
if (InvokeTypeFlow.isContextInsensitiveVirtualInvoke(invoke)) {
locations.addAll(((AbstractVirtualInvokeTypeFlow) invoke).getInvokeLocations());
} else {
locations.add(invoke.getSource());
}
}
return locations;
}
public boolean isEntryPoint() {
return entryPointData != null;
}
public Object getEntryPointData() {
return entryPointData;
}
public boolean isIntrinsicMethod() {
return isIntrinsicMethod;
}
public void registerAsRootMethod() {
isRootMethod = true;
getDeclaringClass().registerAsReachable();
}
public boolean isRootMethod() {
return isRootMethod;
}
public boolean isSimplyInvoked() {
return isInvoked;
}
public boolean isSimplyImplementationInvoked() {
return isImplementationInvoked;
}
public boolean isInvoked() {
return isIntrinsicMethod || isEntryPoint() || isInvoked;
}
public boolean isImplementationInvoked() {
return !Modifier.isAbstract(getModifiers()) && (isIntrinsicMethod || isEntryPoint() || isImplementationInvoked);
}
@Override
public ResolvedJavaMethod getWrapped() {
return wrapped;
}
@Override
public String getName() {
return wrapped.getName();
}
@Override
public WrappedSignature getSignature() {
return universe.lookup(wrapped.getSignature(), getDeclaringClass());
}
@Override
public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, HostedProviders providers, Purpose purpose) {
if (wrapped instanceof GraphProvider) {
return ((GraphProvider) wrapped).buildGraph(debug, method, providers, purpose);
}
return null;
}
@Override
public boolean allowRuntimeCompilation() {
if (wrapped instanceof GraphProvider) {
return ((GraphProvider) wrapped).allowRuntimeCompilation();
}
return true;
}
@Override
public byte[] getCode() {
return wrapped.getCode();
}
@Override
public int getCodeSize() {
return wrapped.getCodeSize();
}
@Override
public AnalysisType getDeclaringClass() {
return declaringClass;
}
@Override
public int getMaxLocals() {
return wrapped.getMaxLocals();
}
@Override
public int getMaxStackSize() {
return wrapped.getMaxStackSize();
}
@Override
public Parameter[] getParameters() {
return wrapped.getParameters();
}
@Override
public int getModifiers() {
return wrapped.getModifiers();
}
@Override
public boolean isSynthetic() {
return wrapped.isSynthetic();
}
@Override
public boolean isVarArgs() {
throw unimplemented();
}
@Override
public boolean isBridge() {
return wrapped.isBridge();
}
@Override
public boolean isClassInitializer() {
return wrapped.isClassInitializer();
}
@Override
public boolean isConstructor() {
return wrapped.isConstructor();
}
@Override
public boolean canBeStaticallyBound() {
boolean result = wrapped.canBeStaticallyBound();
assert !isStatic() || result : "static methods must always be statically bindable: " + format("%H.%n");
return result;
}
public AnalysisMethod[] getImplementations() {
assert universe.analysisDataValid;
if (implementations == null) {
return new AnalysisMethod[0];
}
return implementations;
}
@Override
public ExceptionHandler[] getExceptionHandlers() {
return exceptionHandlers;
}
@Override
public StackTraceElement asStackTraceElement(int bci) {
return wrapped.asStackTraceElement(bci);
}
@Override
public ProfilingInfo getProfilingInfo(boolean includeNormal, boolean includeOSR) {
return StaticAnalysisResults.NO_RESULTS;
}
@Override
public ConstantPool getConstantPool() {
return universe.lookup(wrapped.getConstantPool(), getDeclaringClass());
}
@Override
public Annotation[] getAnnotations() {
return GuardedAnnotationAccess.getAnnotations(wrapped);
}
@Override
public Annotation[] getDeclaredAnnotations() {
return GuardedAnnotationAccess.getDeclaredAnnotations(wrapped);
}
@Override
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
return GuardedAnnotationAccess.getAnnotation(wrapped, annotationClass);
}
@Override
public Annotation[][] getParameterAnnotations() {
return wrapped.getParameterAnnotations();
}
@Override
public Type[] getGenericParameterTypes() {
return wrapped.getGenericParameterTypes();
}
@Override
public boolean canBeInlined() {
return true;
}
@Override
public boolean hasNeverInlineDirective() {
return wrapped.hasNeverInlineDirective();
}
@Override
public boolean shouldBeInlined() {
throw unimplemented();
}
@Override
public LineNumberTable getLineNumberTable() {
return wrapped.getLineNumberTable();
}
@Override
public String toString() {
return "AnalysisMethod<" + format("%H.%n") + " -> " + wrapped.toString() + ">";
}
@Override
public LocalVariableTable getLocalVariableTable() {
return localVariableTable;
}
@Override
public void reprofile() {
throw unimplemented();
}
@Override
public Constant getEncoding() {
throw unimplemented();
}
@Override
public boolean isInVirtualMethodTable(ResolvedJavaType resolved) {
return false;
}
@Override
public boolean isDefault() {
return wrapped.isDefault();
}
@Override
public SpeculationLog getSpeculationLog() {
throw shouldNotReachHere();
}
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object obj) {
return this == obj;
}
@Override
public Executable getJavaMethod() {
return OriginalMethodProvider.getJavaMethod(universe.getOriginalSnippetReflection(), wrapped);
}
private final AtomicReference<InvokeTypeFlow> contextInsensitiveInvoke = new AtomicReference<>();
public InvokeTypeFlow initAndGetContextInsensitiveInvoke(BigBang bb, BytecodePosition originalLocation) {
if (contextInsensitiveInvoke.get() == null) {
InvokeTypeFlow invoke = InvokeTypeFlow.createContextInsensitiveInvoke(bb, this, originalLocation);
boolean set = contextInsensitiveInvoke.compareAndSet(null, invoke);
if (set) {
InvokeTypeFlow.initContextInsensitiveInvoke(bb, this, invoke);
}
}
return contextInsensitiveInvoke.get();
}
public InvokeTypeFlow getContextInsensitiveInvoke() {
InvokeTypeFlow invoke = contextInsensitiveInvoke.get();
AnalysisError.guarantee(invoke != null);
return invoke;
}
}