1 /*
   2  * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.core.test;
  24 
  25 import static org.graalvm.compiler.core.common.CompilationIdentifier.INVALID_COMPILATION_ID;
  26 import static org.graalvm.compiler.debug.DelegatingDebugConfig.Feature.INTERCEPT;
  27 
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.io.PrintWriter;
  31 import java.io.StringWriter;
  32 import java.lang.reflect.Method;
  33 import java.lang.reflect.Modifier;
  34 import java.util.ArrayList;
  35 import java.util.Collections;
  36 import java.util.Enumeration;
  37 import java.util.List;
  38 import java.util.concurrent.LinkedBlockingQueue;
  39 import java.util.concurrent.ThreadPoolExecutor;
  40 import java.util.concurrent.TimeUnit;
  41 import java.util.zip.ZipEntry;
  42 import java.util.zip.ZipFile;
  43 
  44 import org.junit.Assert;
  45 import org.junit.Assume;
  46 import org.junit.Test;
  47 
  48 import org.graalvm.compiler.api.test.Graal;
  49 import org.graalvm.compiler.bytecode.BridgeMethodUtils;
  50 import org.graalvm.compiler.core.CompilerThreadFactory;
  51 import org.graalvm.compiler.core.CompilerThreadFactory.DebugConfigAccess;
  52 import org.graalvm.compiler.core.common.LIRKind;
  53 import org.graalvm.compiler.core.common.LocationIdentity;
  54 import org.graalvm.compiler.core.common.type.ArithmeticOpTable;
  55 import org.graalvm.compiler.debug.Debug;
  56 import org.graalvm.compiler.debug.DebugConfigScope;
  57 import org.graalvm.compiler.debug.DebugEnvironment;
  58 import org.graalvm.compiler.debug.DelegatingDebugConfig;
  59 import org.graalvm.compiler.debug.GraalDebugConfig;
  60 import org.graalvm.compiler.graph.Node;
  61 import org.graalvm.compiler.graph.NodeClass;
  62 import org.graalvm.compiler.java.GraphBuilderPhase;
  63 import org.graalvm.compiler.nodeinfo.NodeInfo;
  64 import org.graalvm.compiler.nodes.PhiNode;
  65 import org.graalvm.compiler.nodes.StructuredGraph;
  66 import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
  67 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
  68 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins;
  69 import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
  70 import org.graalvm.compiler.phases.OptimisticOptimizations;
  71 import org.graalvm.compiler.phases.PhaseSuite;
  72 import org.graalvm.compiler.phases.VerifyPhase;
  73 import org.graalvm.compiler.phases.VerifyPhase.VerificationError;
  74 import org.graalvm.compiler.phases.tiers.HighTierContext;
  75 import org.graalvm.compiler.phases.util.Providers;
  76 import org.graalvm.compiler.phases.verify.VerifyBailoutUsage;
  77 import org.graalvm.compiler.phases.verify.VerifyCallerSensitiveMethods;
  78 import org.graalvm.compiler.phases.verify.VerifyDebugUsage;
  79 import org.graalvm.compiler.phases.verify.VerifyUpdateUsages;
  80 import org.graalvm.compiler.phases.verify.VerifyUsageWithEquals;
  81 import org.graalvm.compiler.phases.verify.VerifyVirtualizableUsage;
  82 import org.graalvm.compiler.runtime.RuntimeProvider;
  83 import org.graalvm.compiler.test.GraalTest;
  84 
  85 import jdk.vm.ci.code.BailoutException;
  86 import jdk.vm.ci.code.Register;
  87 import jdk.vm.ci.code.Register.RegisterCategory;
  88 import jdk.vm.ci.meta.JavaField;
  89 import jdk.vm.ci.meta.JavaMethod;
  90 import jdk.vm.ci.meta.JavaType;
  91 import jdk.vm.ci.meta.MetaAccessProvider;
  92 import jdk.vm.ci.meta.ResolvedJavaMethod;
  93 import jdk.vm.ci.meta.ResolvedJavaType;
  94 import jdk.vm.ci.meta.Value;
  95 
  96 /**
  97  * Checks that all classes in *graal*.jar and *jvmci*.jar entries on the boot class path comply with
  98  * global invariants such as using {@link Object#equals(Object)} to compare certain types instead of
  99  * identity comparisons.
 100  */
 101 public class CheckGraalInvariants extends GraalTest {
 102 
 103     private static boolean shouldVerifyEquals(ResolvedJavaMethod m) {
 104         if (m.getName().equals("identityEquals")) {
 105             ResolvedJavaType c = m.getDeclaringClass();
 106             if (c.getName().equals("Ljdk/vm/ci/meta/AbstractValue;") || c.getName().equals("jdk/vm/ci/meta/Value")) {
 107                 return false;
 108             }
 109         }
 110 
 111         return true;
 112     }
 113 
 114     private static boolean shouldProcess(String classpathEntry) {
 115         if (classpathEntry.endsWith(".jar")) {
 116             String name = new File(classpathEntry).getName();
 117             return name.contains("jvmci") || name.contains("graal");
 118         }
 119         return false;
 120     }
 121 
 122     @Test
 123     @SuppressWarnings("try")
 124     public void test() {
 125         RuntimeProvider rt = Graal.getRequiredCapability(RuntimeProvider.class);
 126         Providers providers = rt.getHostBackend().getProviders();
 127         MetaAccessProvider metaAccess = providers.getMetaAccess();
 128 
 129         PhaseSuite<HighTierContext> graphBuilderSuite = new PhaseSuite<>();
 130         Plugins plugins = new Plugins(new InvocationPlugins(metaAccess));
 131         GraphBuilderConfiguration config = GraphBuilderConfiguration.getDefault(plugins).withEagerResolving(true);
 132         graphBuilderSuite.appendPhase(new GraphBuilderPhase(config));
 133         HighTierContext context = new HighTierContext(providers, graphBuilderSuite, OptimisticOptimizations.NONE);
 134 
 135         Assume.assumeTrue(VerifyPhase.class.desiredAssertionStatus());
 136 
 137         String propertyName = Java8OrEarlier ? "sun.boot.class.path" : "jdk.module.path";
 138         String bootclasspath = System.getProperty(propertyName);
 139         Assert.assertNotNull("Cannot find value of " + propertyName, bootclasspath);
 140 
 141         final List<String> classNames = new ArrayList<>();
 142         for (String path : bootclasspath.split(File.pathSeparator)) {
 143             if (shouldProcess(path)) {
 144                 try {
 145                     final ZipFile zipFile = new ZipFile(new File(path));
 146                     for (final Enumeration<? extends ZipEntry> entry = zipFile.entries(); entry.hasMoreElements();) {
 147                         final ZipEntry zipEntry = entry.nextElement();
 148                         String name = zipEntry.getName();
 149                         if (name.endsWith(".class")) {
 150                             String className = name.substring(0, name.length() - ".class".length()).replace('/', '.');
 151                             classNames.add(className);
 152                         }
 153                     }
 154                 } catch (IOException ex) {
 155                     Assert.fail(ex.toString());
 156                 }
 157             }
 158         }
 159         Assert.assertFalse("Could not find graal jars on boot class path: " + bootclasspath, classNames.isEmpty());
 160 
 161         // Allows a subset of methods to be checked through use of a system property
 162         String property = System.getProperty(CheckGraalInvariants.class.getName() + ".filters");
 163         String[] filters = property == null ? null : property.split(",");
 164 
 165         CompilerThreadFactory factory = new CompilerThreadFactory("CheckInvariantsThread", new DebugConfigAccess() {
 166             @Override
 167             public GraalDebugConfig getDebugConfig() {
 168                 return DebugEnvironment.initialize(System.out);
 169             }
 170         });
 171         int availableProcessors = Runtime.getRuntime().availableProcessors();
 172         ThreadPoolExecutor executor = new ThreadPoolExecutor(availableProcessors, availableProcessors, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), factory);
 173 
 174         List<String> errors = Collections.synchronizedList(new ArrayList<>());
 175         // Order outer classes before the inner classes
 176         classNames.sort((String a, String b) -> a.compareTo(b));
 177         // Initialize classes in single thread to avoid deadlocking issues during initialization
 178         List<Class<?>> classes = initializeClasses(classNames);
 179         for (Class<?> c : classes) {
 180             String className = c.getName();
 181             executor.execute(() -> {
 182                 try {
 183                     checkClass(c, metaAccess);
 184                 } catch (Throwable e) {
 185                     errors.add(String.format("Error while checking %s:%n%s", className, printStackTraceToString(e)));
 186                 }
 187             });
 188 
 189             for (Method m : c.getDeclaredMethods()) {
 190                 if (Modifier.isNative(m.getModifiers()) || Modifier.isAbstract(m.getModifiers())) {
 191                     // ignore
 192                 } else {
 193                     String methodName = className + "." + m.getName();
 194                     if (matches(filters, methodName)) {
 195                         executor.execute(() -> {
 196                             ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m);
 197                             StructuredGraph graph = new StructuredGraph(method, AllowAssumptions.NO, INVALID_COMPILATION_ID);
 198                             try (DebugConfigScope s = Debug.setConfig(new DelegatingDebugConfig().disable(INTERCEPT)); Debug.Scope ds = Debug.scope("CheckingGraph", graph, method)) {
 199                                 graphBuilderSuite.apply(graph, context);
 200                                 // update phi stamps
 201                                 graph.getNodes().filter(PhiNode.class).forEach(PhiNode::inferStamp);
 202                                 checkGraph(context, graph);
 203                             } catch (VerificationError e) {
 204                                 errors.add(e.getMessage());
 205                             } catch (LinkageError e) {
 206                                 // suppress linkages errors resulting from eager resolution
 207                             } catch (BailoutException e) {
 208                                 // Graal bail outs on certain patterns in Java bytecode (e.g.,
 209                                 // unbalanced monitors introduced by jacoco).
 210                             } catch (Throwable e) {
 211                                 errors.add(String.format("Error while checking %s:%n%s", methodName, printStackTraceToString(e)));
 212                             }
 213                         });
 214                     }
 215                 }
 216             }
 217         }
 218         executor.shutdown();
 219         try {
 220             executor.awaitTermination(1, TimeUnit.HOURS);
 221         } catch (InterruptedException e1) {
 222             throw new RuntimeException(e1);
 223         }
 224 
 225         if (!errors.isEmpty()) {
 226             StringBuilder msg = new StringBuilder();
 227             String nl = String.format("%n");
 228             for (String e : errors) {
 229                 if (msg.length() != 0) {
 230                     msg.append(nl);
 231                 }
 232                 msg.append(e);
 233             }
 234             Assert.fail(msg.toString());
 235         }
 236     }
 237 
 238     private static List<Class<?>> initializeClasses(List<String> classNames) {
 239         List<Class<?>> classes = new ArrayList<>(classNames.size());
 240         for (String className : classNames) {
 241             if (className.equals("module-info")) {
 242                 continue;
 243             }
 244             try {
 245                 Class<?> c = Class.forName(className, true, CheckGraalInvariants.class.getClassLoader());
 246                 classes.add(c);
 247             } catch (ClassNotFoundException e) {
 248                 e.printStackTrace();
 249             }
 250         }
 251         return classes;
 252     }
 253 
 254     /**
 255      * @param metaAccess
 256      */
 257     private static void checkClass(Class<?> c, MetaAccessProvider metaAccess) {
 258         if (Node.class.isAssignableFrom(c)) {
 259             if (c.getAnnotation(NodeInfo.class) == null) {
 260                 throw new AssertionError(String.format("Node subclass %s requires %s annotation", c.getName(), NodeClass.class.getSimpleName()));
 261             }
 262         }
 263     }
 264 
 265     /**
 266      * Checks the invariants for a single graph.
 267      */
 268     private static void checkGraph(HighTierContext context, StructuredGraph graph) {
 269         if (shouldVerifyEquals(graph.method())) {
 270             new VerifyUsageWithEquals(Value.class).apply(graph, context);
 271             new VerifyUsageWithEquals(Register.class).apply(graph, context);
 272             new VerifyUsageWithEquals(RegisterCategory.class).apply(graph, context);
 273             new VerifyUsageWithEquals(JavaType.class).apply(graph, context);
 274             new VerifyUsageWithEquals(JavaMethod.class).apply(graph, context);
 275             new VerifyUsageWithEquals(JavaField.class).apply(graph, context);
 276             new VerifyUsageWithEquals(LocationIdentity.class).apply(graph, context);
 277             new VerifyUsageWithEquals(LIRKind.class).apply(graph, context);
 278             new VerifyUsageWithEquals(ArithmeticOpTable.class).apply(graph, context);
 279             new VerifyUsageWithEquals(ArithmeticOpTable.Op.class).apply(graph, context);
 280         }
 281         new VerifyDebugUsage().apply(graph, context);
 282         new VerifyCallerSensitiveMethods().apply(graph, context);
 283         new VerifyVirtualizableUsage().apply(graph, context);
 284         new VerifyUpdateUsages().apply(graph, context);
 285         new VerifyBailoutUsage().apply(graph, context);
 286         if (graph.method().isBridge()) {
 287             BridgeMethodUtils.getBridgedMethod(graph.method());
 288         }
 289     }
 290 
 291     private static boolean matches(String[] filters, String s) {
 292         if (filters == null || filters.length == 0) {
 293             return true;
 294         }
 295         for (String filter : filters) {
 296             if (s.contains(filter)) {
 297                 return true;
 298             }
 299         }
 300         return false;
 301     }
 302 
 303     private static String printStackTraceToString(Throwable t) {
 304         StringWriter sw = new StringWriter();
 305         t.printStackTrace(new PrintWriter(sw));
 306         return sw.toString();
 307     }
 308 }