src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/CheckGraalInvariants.java
changeset 47216 71c04702a3d5
parent 46807 8b2c620d7092
child 47667 390896759aa2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/CheckGraalInvariants.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,510 @@
+/*
+ * Copyright (c) 2014, 2016, 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.
+ *
+ * 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 org.graalvm.compiler.core.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.graalvm.compiler.api.replacements.Snippet;
+import org.graalvm.compiler.api.replacements.Snippet.ConstantParameter;
+import org.graalvm.compiler.api.replacements.Snippet.NonNullParameter;
+import org.graalvm.compiler.api.replacements.Snippet.VarargsParameter;
+import org.graalvm.compiler.api.test.Graal;
+import org.graalvm.compiler.bytecode.BridgeMethodUtils;
+import org.graalvm.compiler.core.CompilerThreadFactory;
+import org.graalvm.compiler.core.common.LIRKind;
+import org.graalvm.compiler.core.common.type.ArithmeticOpTable;
+import org.graalvm.compiler.debug.DebugCloseable;
+import org.graalvm.compiler.debug.DebugHandlersFactory;
+import org.graalvm.compiler.debug.DebugContext;
+import org.graalvm.compiler.debug.GraalError;
+import org.graalvm.compiler.graph.Node;
+import org.graalvm.compiler.graph.NodeClass;
+import org.graalvm.compiler.java.GraphBuilderPhase;
+import org.graalvm.compiler.nodeinfo.NodeInfo;
+import org.graalvm.compiler.nodes.PhiNode;
+import org.graalvm.compiler.nodes.StructuredGraph;
+import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
+import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
+import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins;
+import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
+import org.graalvm.compiler.options.OptionValues;
+import org.graalvm.compiler.phases.OptimisticOptimizations;
+import org.graalvm.compiler.phases.PhaseSuite;
+import org.graalvm.compiler.phases.VerifyPhase;
+import org.graalvm.compiler.phases.VerifyPhase.VerificationError;
+import org.graalvm.compiler.phases.contract.VerifyNodeCosts;
+import org.graalvm.compiler.phases.tiers.HighTierContext;
+import org.graalvm.compiler.phases.util.Providers;
+import org.graalvm.compiler.phases.verify.VerifyBailoutUsage;
+import org.graalvm.compiler.phases.verify.VerifyCallerSensitiveMethods;
+import org.graalvm.compiler.phases.verify.VerifyDebugUsage;
+import org.graalvm.compiler.phases.verify.VerifyInstanceOfUsage;
+import org.graalvm.compiler.phases.verify.VerifyUpdateUsages;
+import org.graalvm.compiler.phases.verify.VerifyUsageWithEquals;
+import org.graalvm.compiler.phases.verify.VerifyVirtualizableUsage;
+import org.graalvm.compiler.runtime.RuntimeProvider;
+import org.graalvm.word.LocationIdentity;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+
+import jdk.vm.ci.code.BailoutException;
+import jdk.vm.ci.code.Register;
+import jdk.vm.ci.code.Register.RegisterCategory;
+import jdk.vm.ci.meta.JavaField;
+import jdk.vm.ci.meta.JavaMethod;
+import jdk.vm.ci.meta.JavaType;
+import jdk.vm.ci.meta.MetaAccessProvider;
+import jdk.vm.ci.meta.ResolvedJavaMethod;
+import jdk.vm.ci.meta.ResolvedJavaType;
+import jdk.vm.ci.meta.Value;
+
+/**
+ * Checks that all classes in *graal*.jar and *jvmci*.jar entries on the boot class path comply with
+ * global invariants such as using {@link Object#equals(Object)} to compare certain types instead of
+ * identity comparisons.
+ */
+public class CheckGraalInvariants extends GraalCompilerTest {
+
+    public CheckGraalInvariants() {
+        try {
+            Class.forName("java.lang.management.ManagementFactory");
+        } catch (ClassNotFoundException ex) {
+            Assume.assumeNoException("cannot run without java.management JDK9 module", ex);
+        }
+    }
+
+    private static boolean shouldVerifyEquals(ResolvedJavaMethod m) {
+        if (m.getName().equals("identityEquals")) {
+            ResolvedJavaType c = m.getDeclaringClass();
+            if (c.getName().equals("Ljdk/vm/ci/meta/AbstractValue;") || c.getName().equals("jdk/vm/ci/meta/Value")) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static String relativeFileName(String absolutePath) {
+        int lastFileSeparatorIndex = absolutePath.lastIndexOf(File.separator);
+        return absolutePath.substring(lastFileSeparatorIndex >= 0 ? lastFileSeparatorIndex : 0);
+    }
+
+    public static class InvariantsTool {
+
+        protected boolean shouldProcess(String classpathEntry) {
+            if (classpathEntry.endsWith(".jar")) {
+                String name = new File(classpathEntry).getName();
+                return name.contains("jvmci") || name.contains("graal") || name.contains("jdk.internal.vm.compiler");
+            }
+            return false;
+        }
+
+        protected String getClassPath() {
+            String bootclasspath;
+            if (Java8OrEarlier) {
+                bootclasspath = System.getProperty("sun.boot.class.path");
+            } else {
+                bootclasspath = System.getProperty("jdk.module.path") + File.pathSeparatorChar + System.getProperty("jdk.module.upgrade.path");
+            }
+            return bootclasspath;
+        }
+
+        protected boolean shouldLoadClass(String className) {
+            return !className.equals("module-info");
+        }
+
+        protected void handleClassLoadingException(Throwable t) {
+            GraalError.shouldNotReachHere(t);
+        }
+
+        protected void handleParsingException(Throwable t) {
+            GraalError.shouldNotReachHere(t);
+        }
+    }
+
+    @Test
+    @SuppressWarnings("try")
+    public void test() {
+        runTest(new InvariantsTool());
+    }
+
+    @SuppressWarnings("try")
+    public static void runTest(InvariantsTool tool) {
+        RuntimeProvider rt = Graal.getRequiredCapability(RuntimeProvider.class);
+        Providers providers = rt.getHostBackend().getProviders();
+        MetaAccessProvider metaAccess = providers.getMetaAccess();
+
+        PhaseSuite<HighTierContext> graphBuilderSuite = new PhaseSuite<>();
+        Plugins plugins = new Plugins(new InvocationPlugins());
+        GraphBuilderConfiguration config = GraphBuilderConfiguration.getDefault(plugins).withEagerResolving(true);
+        graphBuilderSuite.appendPhase(new GraphBuilderPhase(config));
+        HighTierContext context = new HighTierContext(providers, graphBuilderSuite, OptimisticOptimizations.NONE);
+
+        Assume.assumeTrue(VerifyPhase.class.desiredAssertionStatus());
+
+        String bootclasspath = tool.getClassPath();
+        Assert.assertNotNull("Cannot find boot class path", bootclasspath);
+
+        final List<String> classNames = new ArrayList<>();
+        for (String path : bootclasspath.split(File.pathSeparator)) {
+            if (tool.shouldProcess(path)) {
+                try {
+                    final ZipFile zipFile = new ZipFile(new File(path));
+                    for (final Enumeration<? extends ZipEntry> entry = zipFile.entries(); entry.hasMoreElements();) {
+                        final ZipEntry zipEntry = entry.nextElement();
+                        String name = zipEntry.getName();
+                        if (name.endsWith(".class")) {
+                            String className = name.substring(0, name.length() - ".class".length()).replace('/', '.');
+                            if (isInNativeImage(className)) {
+                                /*
+                                 * Native Image is an external tool and does not need to follow the
+                                 * Graal invariants.
+                                 */
+                                continue;
+                            }
+                            classNames.add(className);
+                        }
+                    }
+                } catch (IOException ex) {
+                    Assert.fail(ex.toString());
+                }
+            }
+        }
+        Assert.assertFalse("Could not find graal jars on boot class path: " + bootclasspath, classNames.isEmpty());
+
+        // Allows a subset of methods to be checked through use of a system property
+        String property = System.getProperty(CheckGraalInvariants.class.getName() + ".filters");
+        String[] filters = property == null ? null : property.split(",");
+
+        OptionValues options = getInitialOptions();
+        CompilerThreadFactory factory = new CompilerThreadFactory("CheckInvariantsThread");
+        int availableProcessors = Runtime.getRuntime().availableProcessors();
+        ThreadPoolExecutor executor = new ThreadPoolExecutor(availableProcessors, availableProcessors, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), factory);
+
+        List<String> errors = Collections.synchronizedList(new ArrayList<>());
+
+        for (Method m : BadUsageWithEquals.class.getDeclaredMethods()) {
+            ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m);
+            try (DebugContext debug = DebugContext.create(options, DebugHandlersFactory.LOADER)) {
+                StructuredGraph graph = new StructuredGraph.Builder(options, debug, AllowAssumptions.YES).method(method).build();
+                try (DebugCloseable s = debug.disableIntercept(); DebugContext.Scope ds = debug.scope("CheckingGraph", graph, method)) {
+                    graphBuilderSuite.apply(graph, context);
+                    // update phi stamps
+                    graph.getNodes().filter(PhiNode.class).forEach(PhiNode::inferStamp);
+                    checkGraph(context, graph);
+                    errors.add(String.format("Expected error while checking %s", m));
+                } catch (VerificationError e) {
+                    // expected!
+                } catch (Throwable e) {
+                    errors.add(String.format("Error while checking %s:%n%s", m, printStackTraceToString(e)));
+                }
+            }
+        }
+        if (errors.isEmpty()) {
+            // Order outer classes before the inner classes
+            classNames.sort((String a, String b) -> a.compareTo(b));
+            // Initialize classes in single thread to avoid deadlocking issues during initialization
+            List<Class<?>> classes = initializeClasses(tool, classNames);
+            for (Class<?> c : classes) {
+                String className = c.getName();
+                executor.execute(() -> {
+                    try {
+                        checkClass(c, metaAccess);
+                    } catch (Throwable e) {
+                        errors.add(String.format("Error while checking %s:%n%s", className, printStackTraceToString(e)));
+                    }
+                });
+
+                for (Method m : c.getDeclaredMethods()) {
+                    if (Modifier.isNative(m.getModifiers()) || Modifier.isAbstract(m.getModifiers())) {
+                        // ignore
+                    } else {
+                        String methodName = className + "." + m.getName();
+                        if (matches(filters, methodName)) {
+                            executor.execute(() -> {
+                                try (DebugContext debug = DebugContext.create(options, DebugHandlersFactory.LOADER)) {
+                                    ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m);
+                                    StructuredGraph graph = new StructuredGraph.Builder(options, debug).method(method).build();
+                                    try (DebugCloseable s = debug.disableIntercept(); DebugContext.Scope ds = debug.scope("CheckingGraph", graph, method)) {
+                                        checkMethod(method);
+                                        graphBuilderSuite.apply(graph, context);
+                                        // update phi stamps
+                                        graph.getNodes().filter(PhiNode.class).forEach(PhiNode::inferStamp);
+                                        checkGraph(context, graph);
+                                    } catch (VerificationError e) {
+                                        errors.add(e.getMessage());
+                                    } catch (LinkageError e) {
+                                        // suppress linkages errors resulting from eager resolution
+                                    } catch (BailoutException e) {
+                                        // Graal bail outs on certain patterns in Java bytecode
+                                        // (e.g.,
+                                        // unbalanced monitors introduced by jacoco).
+                                    } catch (Throwable e) {
+                                        try {
+                                            tool.handleParsingException(e);
+                                        } catch (Throwable t) {
+                                            errors.add(String.format("Error while checking %s:%n%s", methodName, printStackTraceToString(e)));
+                                        }
+                                    }
+                                }
+                            });
+                        }
+                    }
+                }
+            }
+            executor.shutdown();
+            try {
+                executor.awaitTermination(1, TimeUnit.HOURS);
+            } catch (InterruptedException e1) {
+                throw new RuntimeException(e1);
+            }
+        }
+        if (!errors.isEmpty()) {
+            StringBuilder msg = new StringBuilder();
+            String nl = String.format("%n");
+            for (String e : errors) {
+                if (msg.length() != 0) {
+                    msg.append(nl);
+                }
+                msg.append(e);
+            }
+            Assert.fail(msg.toString());
+        }
+    }
+
+    private static boolean isInNativeImage(String className) {
+        return className.startsWith("org.graalvm.nativeimage");
+    }
+
+    private static List<Class<?>> initializeClasses(InvariantsTool tool, List<String> classNames) {
+        List<Class<?>> classes = new ArrayList<>(classNames.size());
+        for (String className : classNames) {
+            if (!tool.shouldLoadClass(className)) {
+                continue;
+            }
+            try {
+                Class<?> c = Class.forName(className, true, CheckGraalInvariants.class.getClassLoader());
+                classes.add(c);
+            } catch (Throwable t) {
+                tool.handleClassLoadingException(t);
+            }
+        }
+        return classes;
+    }
+
+    /**
+     * @param metaAccess
+     */
+    private static void checkClass(Class<?> c, MetaAccessProvider metaAccess) {
+        if (Node.class.isAssignableFrom(c)) {
+            if (c.getAnnotation(NodeInfo.class) == null) {
+                throw new AssertionError(String.format("Node subclass %s requires %s annotation", c.getName(), NodeClass.class.getSimpleName()));
+            }
+            VerifyNodeCosts.verifyNodeClass(c);
+        }
+    }
+
+    private static void checkMethod(ResolvedJavaMethod method) {
+        if (method.getAnnotation(Snippet.class) == null) {
+            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
+            for (int i = 0; i < parameterAnnotations.length; i++) {
+                for (Annotation a : parameterAnnotations[i]) {
+                    Class<? extends Annotation> annotationType = a.annotationType();
+                    if (annotationType == ConstantParameter.class || annotationType == VarargsParameter.class || annotationType == NonNullParameter.class) {
+                        VerificationError verificationError = new VerificationError("Parameter %d of %s is annotated with %s but the method is not annotated with %s", i, method,
+                                        annotationType.getSimpleName(),
+                                        Snippet.class.getSimpleName());
+                        throw verificationError;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks the invariants for a single graph.
+     */
+    private static void checkGraph(HighTierContext context, StructuredGraph graph) {
+        if (shouldVerifyEquals(graph.method())) {
+            // If you add a new type to test here, be sure to add appropriate
+            // methods to the BadUsageWithEquals class below
+            new VerifyUsageWithEquals(Value.class).apply(graph, context);
+            new VerifyUsageWithEquals(Register.class).apply(graph, context);
+            new VerifyUsageWithEquals(RegisterCategory.class).apply(graph, context);
+            new VerifyUsageWithEquals(JavaType.class).apply(graph, context);
+            new VerifyUsageWithEquals(JavaMethod.class).apply(graph, context);
+            new VerifyUsageWithEquals(JavaField.class).apply(graph, context);
+            new VerifyUsageWithEquals(LocationIdentity.class).apply(graph, context);
+            new VerifyUsageWithEquals(LIRKind.class).apply(graph, context);
+            new VerifyUsageWithEquals(ArithmeticOpTable.class).apply(graph, context);
+            new VerifyUsageWithEquals(ArithmeticOpTable.Op.class).apply(graph, context);
+        }
+        new VerifyDebugUsage().apply(graph, context);
+        new VerifyCallerSensitiveMethods().apply(graph, context);
+        new VerifyVirtualizableUsage().apply(graph, context);
+        new VerifyUpdateUsages().apply(graph, context);
+        new VerifyBailoutUsage().apply(graph, context);
+        new VerifyInstanceOfUsage().apply(graph, context);
+        if (graph.method().isBridge()) {
+            BridgeMethodUtils.getBridgedMethod(graph.method());
+        }
+    }
+
+    private static boolean matches(String[] filters, String s) {
+        if (filters == null || filters.length == 0) {
+            return true;
+        }
+        for (String filter : filters) {
+            if (s.contains(filter)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static String printStackTraceToString(Throwable t) {
+        StringWriter sw = new StringWriter();
+        t.printStackTrace(new PrintWriter(sw));
+        return sw.toString();
+    }
+
+    static class BadUsageWithEquals {
+        Value aValue;
+        Register aRegister;
+        RegisterCategory aRegisterCategory;
+        JavaType aJavaType;
+        JavaField aJavaField;
+        JavaMethod aJavaMethod;
+        LocationIdentity aLocationIdentity;
+        LIRKind aLIRKind;
+        ArithmeticOpTable anArithmeticOpTable;
+        ArithmeticOpTable.Op anArithmeticOpTableOp;
+
+        static Value aStaticValue;
+        static Register aStaticRegister;
+        static RegisterCategory aStaticRegisterCategory;
+        static JavaType aStaticJavaType;
+        static JavaField aStaticJavaField;
+        static JavaMethod aStaticJavaMethod;
+        static LocationIdentity aStaticLocationIdentity;
+        static LIRKind aStaticLIRKind;
+        static ArithmeticOpTable aStaticArithmeticOpTable;
+        static ArithmeticOpTable.Op aStaticArithmeticOpTableOp;
+
+        boolean test01(Value f) {
+            return aValue == f;
+        }
+
+        boolean test02(Register f) {
+            return aRegister == f;
+        }
+
+        boolean test03(RegisterCategory f) {
+            return aRegisterCategory == f;
+        }
+
+        boolean test04(JavaType f) {
+            return aJavaType == f;
+        }
+
+        boolean test05(JavaField f) {
+            return aJavaField == f;
+        }
+
+        boolean test06(JavaMethod f) {
+            return aJavaMethod == f;
+        }
+
+        boolean test07(LocationIdentity f) {
+            return aLocationIdentity == f;
+        }
+
+        boolean test08(LIRKind f) {
+            return aLIRKind == f;
+        }
+
+        boolean test09(ArithmeticOpTable f) {
+            return anArithmeticOpTable == f;
+        }
+
+        boolean test10(ArithmeticOpTable.Op f) {
+            return anArithmeticOpTableOp == f;
+        }
+
+        boolean test12(Value f) {
+            return aStaticValue == f;
+        }
+
+        boolean test13(Register f) {
+            return aStaticRegister == f;
+        }
+
+        boolean test14(RegisterCategory f) {
+            return aStaticRegisterCategory == f;
+        }
+
+        boolean test15(JavaType f) {
+            return aStaticJavaType == f;
+        }
+
+        boolean test16(JavaField f) {
+            return aStaticJavaField == f;
+        }
+
+        boolean test17(JavaMethod f) {
+            return aStaticJavaMethod == f;
+        }
+
+        boolean test18(LocationIdentity f) {
+            return aStaticLocationIdentity == f;
+        }
+
+        boolean test19(LIRKind f) {
+            return aStaticLIRKind == f;
+        }
+
+        boolean test20(ArithmeticOpTable f) {
+            return aStaticArithmeticOpTable == f;
+        }
+
+        boolean test21(ArithmeticOpTable.Op f) {
+            return aStaticArithmeticOpTableOp == f;
+        }
+    }
+}