com.oracle.svm.hosted.code.RestrictHeapAccessAnnotationChecker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of svm Show documentation
Show all versions of svm Show documentation
SubstrateVM image builder components
/*
* Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.hosted.code;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.Map;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.graph.Node;
import org.graalvm.compiler.graph.NodeSourcePosition;
import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind;
import org.graalvm.compiler.nodes.ConstantNode;
import org.graalvm.compiler.nodes.Invoke;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.extended.UnsafeAccessNode;
import org.graalvm.compiler.nodes.java.AbstractNewObjectNode;
import org.graalvm.compiler.nodes.java.AccessArrayNode;
import org.graalvm.compiler.nodes.java.AccessFieldNode;
import org.graalvm.compiler.nodes.java.NewMultiArrayNode;
import org.graalvm.compiler.options.Option;
import org.graalvm.nativeimage.ImageSingletons;
import com.oracle.graal.pointsto.meta.AnalysisMethod;
import com.oracle.svm.core.annotate.RestrictHeapAccess.Access;
import com.oracle.svm.core.heap.RestrictHeapAccessCallees;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.hosted.code.RestrictHeapAccessCalleesImpl.RestrictionInfo;
import com.oracle.svm.hosted.meta.HostedMethod;
import com.oracle.svm.hosted.meta.HostedUniverse;
import jdk.vm.ci.meta.Constant;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.JavaKind;
public final class RestrictHeapAccessAnnotationChecker {
/*
* Command line options so errors are not fatal to the build.
*/
public static class Options {
@Option(help = "Print warnings for @RestrictHeapAccess annotations.")//
public static final HostedOptionKey PrintRestrictHeapAccessWarnings = new HostedOptionKey<>(true);
@Option(help = "Print path for @RestrictHeapAccess warnings.")//
public static final HostedOptionKey PrintRestrictHeapAccessPath = new HostedOptionKey<>(true);
}
/** Entry point method. */
public static void check(DebugContext debug, HostedUniverse universe, Collection methods) {
final RestrictHeapAccessWarningVisitor visitor = new RestrictHeapAccessWarningVisitor(universe);
for (HostedMethod method : methods) {
visitor.visitMethod(debug, method);
}
}
static Node checkViolatingNode(StructuredGraph graph, Access access) {
if (graph != null) {
for (Node node : graph.getNodes()) {
if (!isViolatingNode(node, access)) {
return node;
}
}
}
return null;
}
private static boolean isViolatingNode(Node node, Access access) {
assert access != Access.UNRESTRICTED : "does not require checks";
return !isAllocationNode(node) && (access != Access.NO_HEAP_ACCESS || !isHeapAccess(node));
}
private static boolean isAllocationNode(Node node) {
return (node instanceof AbstractNewObjectNode || node instanceof NewMultiArrayNode);
}
private static boolean isHeapAccess(Node node) {
if (node instanceof AccessFieldNode || node instanceof AccessArrayNode || node instanceof UnsafeAccessNode) {
return true;
} else if (node instanceof ConstantNode) {
Constant constant = ((ConstantNode) node).getValue();
if (constant instanceof JavaConstant) {
/*
* Loading an object constant other than null is suspicious and can cause pointer
* compression or uncompression which can be undesirable in some contexts, for
* example when the heap is not set up.
*/
JavaConstant javaConstant = (JavaConstant) constant;
return javaConstant.getJavaKind() == JavaKind.Object && javaConstant.isNonNull();
}
} else if (node instanceof Invoke) {
/* Virtual invokes do type checks and vtable lookups that access the heap */
return ((Invoke) node).callTarget().invokeKind() == InvokeKind.Virtual;
}
return false;
}
/** A HostedMethod visitor that checks for violations of heap access restrictions. */
static class RestrictHeapAccessWarningVisitor {
private final HostedUniverse universe;
private final RestrictHeapAccessCalleesImpl restrictHeapAccessCallees;
RestrictHeapAccessWarningVisitor(HostedUniverse universe) {
this.universe = universe;
this.restrictHeapAccessCallees = (RestrictHeapAccessCalleesImpl) ImageSingletons.lookup(RestrictHeapAccessCallees.class);
}
@SuppressWarnings("try")
public void visitMethod(DebugContext debug, HostedMethod method) {
/* If this is not a method that must not allocate, then everything is fine. */
RestrictionInfo info = restrictHeapAccessCallees.getRestrictionInfo(method);
if (info == null || info.getAccess() == Access.UNRESTRICTED) {
return;
}
/* Look through the graph for this method and see if it allocates. */
final StructuredGraph graph = method.compilationInfo.getGraph();
if (RestrictHeapAccessAnnotationChecker.checkViolatingNode(graph, info.getAccess()) != null) {
try (DebugContext.Scope s = debug.scope("RestrictHeapAccessAnnotationChecker", graph, method, this)) {
postRestrictHeapAccessWarning(method.getWrapped(), restrictHeapAccessCallees.getCallerMap());
} catch (Throwable t) {
throw debug.handle(t);
}
}
}
private void postRestrictHeapAccessWarning(AnalysisMethod violatingCallee, Map callerMap) {
if (Options.PrintRestrictHeapAccessWarnings.getValue()) {
Access violatedAccess = callerMap.get(violatingCallee).getAccess();
String message = "@RestrictHeapAccess warning: ";
/* Walk from callee to caller building a list I can walk from caller to callee. */
final Deque callChain = new ArrayDeque<>();
AnalysisMethod current = violatingCallee;
while (current != null) {
final RestrictionInfo info = callerMap.get(current);
callChain.addFirst(info);
current = info.getCaller();
}
/* Walk from caller to callee building a list to the nearest violating method. */
final Deque allocationList = new ArrayDeque<>();
for (RestrictionInfo element : callChain) {
allocationList.addLast(element);
if (checkHostedViolatingNode(element.getMethod(), element.getAccess()) != null) {
break;
}
}
assert !allocationList.isEmpty();
if (allocationList.size() == 1) {
final StackTraceElement allocationStackTraceElement = getViolatingStackTraceElement(violatingCallee, violatedAccess);
if (allocationStackTraceElement != null) {
message += "Restricted method '" + allocationStackTraceElement.toString() + "' directly violates restriction " + violatedAccess + ".";
} else {
message += "Restricted method '" + violatingCallee.format("%H.%n(%p)") + "' directly violates restriction " + violatedAccess + ".";
}
} else {
final RestrictionInfo first = allocationList.getFirst();
final RestrictionInfo last = allocationList.getLast();
message += "Restricted method: '" + first.getMethod().format("%h.%n(%p)") + "' calls '" +
last.getMethod().format("%h.%n(%p)") + "' that violates restriction " + violatedAccess + ".";
if (Options.PrintRestrictHeapAccessPath.getValue()) {
message += "\n" + " [Path:";
for (RestrictionInfo element : allocationList) {
if (element != first) { // first element has no caller
message += "\n" + " " + element.getInvocationStackTraceElement().toString();
}
}
final StackTraceElement allocationStackTraceElement = getViolatingStackTraceElement(last.getMethod(), last.getAccess());
if (allocationStackTraceElement != null) {
message += "\n" + " " + allocationStackTraceElement.toString();
} else {
message += "\n" + " " + last.getMethod().format("%H.%n(%p)");
}
message += "]";
}
}
throw UserError.abort(message);
}
}
Node checkHostedViolatingNode(AnalysisMethod method, Access access) {
final HostedMethod hostedMethod = universe.optionalLookup(method);
if (hostedMethod != null) {
final StructuredGraph graph = hostedMethod.compilationInfo.getGraph();
return checkViolatingNode(graph, access);
}
return null;
}
/**
* Look through the graph of the corresponding HostedMethod to see if it allocates.
*/
private StackTraceElement getViolatingStackTraceElement(AnalysisMethod method, Access access) {
final HostedMethod hostedMethod = universe.optionalLookup(method);
if (hostedMethod != null) {
final StructuredGraph graph = hostedMethod.compilationInfo.getGraph();
Node node = checkViolatingNode(graph, access);
if (node != null) {
final NodeSourcePosition sourcePosition = node.getNodeSourcePosition();
if (sourcePosition != null && sourcePosition.getBCI() != -1) {
return method.asStackTraceElement(sourcePosition.getBCI());
}
}
}
return null;
}
}
}