All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.svm.hosted.code.RestrictHeapAccessAnnotationChecker Maven / Gradle / Ivy

The newest version!
/*
 * 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;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy