package com.oracle.svm.hosted.code;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.graph.Node;
import org.graalvm.compiler.nodes.Invoke;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.java.MonitorEnterNode;
import org.graalvm.compiler.options.Option;
import com.oracle.svm.core.annotate.MustNotSynchronize;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.hosted.meta.HostedMethod;
public final class MustNotSynchronizeAnnotationChecker {
public static class Options {
@Option(help = "Print warnings for @MustNotSynchronize annotations.")
public static final HostedOptionKey<Boolean> PrintMustNotSynchronizeWarnings = new HostedOptionKey<>(true);
@Option(help = "Print path for @MustNotSynchronize warnings.")
public static final HostedOptionKey<Boolean> PrintMustNotSynchronizePath = new HostedOptionKey<>(true);
@Option(help = "Warnings for @MustNotSynchronize annotations are fatal.")
public static final HostedOptionKey<Boolean> MustNotSynchronizeWarningsAreFatal = new HostedOptionKey<>(true);
}
private final Collection<HostedMethod> methods;
private final Deque<HostedMethod> methodPath;
private final Deque<HostedMethod> methodImplPath;
private MustNotSynchronizeAnnotationChecker(Collection<HostedMethod> methods) {
this.methods = methods;
this.methodPath = new ArrayDeque<>();
this.methodImplPath = new ArrayDeque<>();
}
public static void check(DebugContext debug, Collection<HostedMethod> methods) {
final MustNotSynchronizeAnnotationChecker checker = new MustNotSynchronizeAnnotationChecker(methods);
checker.checkMethods(debug);
}
@SuppressWarnings("try")
public void checkMethods(DebugContext debug) {
for (HostedMethod method : methods) {
try (DebugContext.Scope s = debug.scope("MustNotSynchronizeAnnotationChecker", method.compilationInfo.graph, method, this)) {
MustNotSynchronize annotation = method.getAnnotation(MustNotSynchronize.class);
if ((annotation != null) && (annotation.list() == MustNotSynchronize.BLACKLIST)) {
methodPath.clear();
methodImplPath.clear();
try {
checkMethod(method, method);
} catch (WarningException we) {
throw new WarningException(we.getMessage());
}
}
} catch (Throwable t) {
throw debug.handle(t);
}
}
}
protected boolean checkMethod(HostedMethod method, HostedMethod methodImpl) throws WarningException {
if (methodImplPath.contains(methodImpl)) {
return false;
}
MustNotSynchronize annotation = methodImpl.getAnnotation(MustNotSynchronize.class);
if ((annotation != null) && (annotation.list() == MustNotSynchronize.WHITELIST)) {
return false;
}
methodPath.push(method);
methodImplPath.push(methodImpl);
try {
if (synchronizesDirectly(methodImpl)) {
return true;
}
if (synchronizesIndirectly(methodImpl)) {
return true;
}
return false;
} finally {
methodPath.pop();
methodImplPath.pop();
}
}
protected boolean synchronizesDirectly(HostedMethod methodImpl) throws WarningException {
final StructuredGraph graph = methodImpl.compilationInfo.getGraph();
if (graph != null) {
for (Node node : graph.getNodes()) {
if (node instanceof MonitorEnterNode) {
postMustNotSynchronizeWarning();
return true;
}
}
}
return false;
}
protected boolean synchronizesIndirectly(HostedMethod methodImpl) throws WarningException {
boolean result = false;
final StructuredGraph graph = methodImpl.compilationInfo.getGraph();
if (graph != null) {
for (Invoke invoke : graph.getInvokes()) {
final HostedMethod callee = (HostedMethod) invoke.callTarget().targetMethod();
if (invoke.callTarget().invokeKind().isDirect()) {
result |= checkMethod(callee, callee);
if (result) {
return result;
}
} else {
for (HostedMethod calleeImpl : callee.getImplementations()) {
result |= checkMethod(callee, calleeImpl);
if (result) {
return result;
}
}
}
}
}
return result;
}
private void postMustNotSynchronizeWarning() throws WarningException {
final HostedMethod blacklistMethod = methodPath.getLast();
String message = "@MustNotSynchronize warning: ";
if (methodPath.size() == 1) {
message += "Blacklisted method: " + blacklistMethod.format("%h.%n(%p)") + " synchronizes.";
} else {
final HostedMethod witness = methodPath.getFirst();
message += "Blacklisted method: " + blacklistMethod.format("%h.%n(%p)") + " calls " + witness.format("%h.%n(%p)") + " that synchronizes.";
}
if (Options.PrintMustNotSynchronizeWarnings.getValue()) {
System.err.println(message);
if (Options.PrintMustNotSynchronizePath.getValue() && (1 < methodPath.size())) {
printPath();
}
}
if (Options.MustNotSynchronizeWarningsAreFatal.getValue()) {
throw new WarningException(message);
}
}
private void printPath() {
System.out.print(" [Path: ");
final Iterator<HostedMethod> methodIterator = methodPath.iterator();
final Iterator<HostedMethod> methodImplIterator = methodImplPath.iterator();
while (methodIterator.hasNext()) {
final HostedMethod method = methodIterator.next();
final HostedMethod methodImpl = methodImplIterator.next();
System.err.println();
if (method.equals(methodImpl)) {
System.err.print(" " + method.format("%h.%n(%p)"));
} else {
System.err.print(" " + method.format("%f %h.%n(%p)") + " implemented by " + methodImpl.format("%h.%n(%p)"));
}
}
System.err.println("]");
}
public static class WarningException extends Exception {
public WarningException(String message) {
super(message);
}
private static final long serialVersionUID = 5793144021924912791L;
}
}