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 java.io.File;
  26 import java.io.IOException;
  27 import java.io.PrintWriter;
  28 import java.io.StringWriter;
  29 import java.lang.annotation.Annotation;
  30 import java.lang.reflect.Method;
  31 import java.lang.reflect.Modifier;
  32 import java.util.ArrayList;
  33 import java.util.Collections;
  34 import java.util.Enumeration;
  35 import java.util.List;
  36 import java.util.concurrent.LinkedBlockingQueue;
  37 import java.util.concurrent.ThreadPoolExecutor;
  38 import java.util.concurrent.TimeUnit;
  39 import java.util.zip.ZipEntry;
  40 import java.util.zip.ZipFile;
  41 
  42 import org.graalvm.compiler.api.replacements.Snippet;
  43 import org.graalvm.compiler.api.replacements.Snippet.ConstantParameter;
  44 import org.graalvm.compiler.api.replacements.Snippet.NonNullParameter;
  45 import org.graalvm.compiler.api.replacements.Snippet.VarargsParameter;
  46 import org.graalvm.compiler.api.test.Graal;
  47 import org.graalvm.compiler.bytecode.BridgeMethodUtils;
  48 import org.graalvm.compiler.core.CompilerThreadFactory;
  49 import org.graalvm.compiler.core.common.LIRKind;
  50 import org.graalvm.compiler.core.common.type.ArithmeticOpTable;
  51 import org.graalvm.compiler.debug.DebugCloseable;
  52 import org.graalvm.compiler.debug.DebugHandlersFactory;
  53 import org.graalvm.compiler.debug.DebugContext;
  54 import org.graalvm.compiler.debug.GraalError;
  55 import org.graalvm.compiler.graph.Node;
  56 import org.graalvm.compiler.graph.NodeClass;
  57 import org.graalvm.compiler.java.GraphBuilderPhase;
  58 import org.graalvm.compiler.nodeinfo.NodeInfo;
  59 import org.graalvm.compiler.nodes.PhiNode;
  60 import org.graalvm.compiler.nodes.StructuredGraph;
  61 import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
  62 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
  63 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins;
  64 import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
  65 import org.graalvm.compiler.options.OptionValues;
  66 import org.graalvm.compiler.phases.OptimisticOptimizations;
  67 import org.graalvm.compiler.phases.PhaseSuite;
  68 import org.graalvm.compiler.phases.VerifyPhase;
  69 import org.graalvm.compiler.phases.VerifyPhase.VerificationError;
  70 import org.graalvm.compiler.phases.contract.VerifyNodeCosts;
  71 import org.graalvm.compiler.phases.tiers.HighTierContext;
  72 import org.graalvm.compiler.phases.util.Providers;
  73 import org.graalvm.compiler.phases.verify.VerifyBailoutUsage;
  74 import org.graalvm.compiler.phases.verify.VerifyCallerSensitiveMethods;
  75 import org.graalvm.compiler.phases.verify.VerifyDebugUsage;
  76 import org.graalvm.compiler.phases.verify.VerifyInstanceOfUsage;
  77 import org.graalvm.compiler.phases.verify.VerifyUpdateUsages;
  78 import org.graalvm.compiler.phases.verify.VerifyUsageWithEquals;
  79 import org.graalvm.compiler.phases.verify.VerifyVirtualizableUsage;
  80 import org.graalvm.compiler.runtime.RuntimeProvider;
  81 import org.graalvm.word.LocationIdentity;
  82 import org.junit.Assert;
  83 import org.junit.Assume;
  84 import org.junit.Test;
  85 
  86 import jdk.vm.ci.code.BailoutException;
  87 import jdk.vm.ci.code.Register;
  88 import jdk.vm.ci.code.Register.RegisterCategory;
  89 import jdk.vm.ci.meta.JavaField;
  90 import jdk.vm.ci.meta.JavaMethod;
  91 import jdk.vm.ci.meta.JavaType;
  92 import jdk.vm.ci.meta.MetaAccessProvider;
  93 import jdk.vm.ci.meta.ResolvedJavaMethod;
  94 import jdk.vm.ci.meta.ResolvedJavaType;
  95 import jdk.vm.ci.meta.Value;
  96 
  97 /**
  98  * Checks that all classes in *graal*.jar and *jvmci*.jar entries on the boot class path comply with
  99  * global invariants such as using {@link Object#equals(Object)} to compare certain types instead of
 100  * identity comparisons.
 101  */
 102 public class CheckGraalInvariants extends GraalCompilerTest {
 103 
 104     private static boolean shouldVerifyEquals(ResolvedJavaMethod m) {
 105         if (m.getName().equals("identityEquals")) {
 106             ResolvedJavaType c = m.getDeclaringClass();
 107             if (c.getName().equals("Ljdk/vm/ci/meta/AbstractValue;") || c.getName().equals("jdk/vm/ci/meta/Value")) {
 108                 return false;
 109             }
 110         }
 111 
 112         return true;
 113     }
 114 
 115     public static String relativeFileName(String absolutePath) {
 116         int lastFileSeparatorIndex = absolutePath.lastIndexOf(File.separator);
 117         return absolutePath.substring(lastFileSeparatorIndex >= 0 ? lastFileSeparatorIndex : 0);
 118     }
 119 
 120     public static class InvariantsTool {
 121 
 122         protected boolean shouldProcess(String classpathEntry) {
 123             if (classpathEntry.endsWith(".jar")) {
 124                 String name = new File(classpathEntry).getName();
 125                 return name.contains("jvmci") || name.contains("graal") || name.contains("jdk.internal.vm.compiler");
 126             }
 127             return false;
 128         }
 129 
 130         protected String getClassPath() {
 131             String bootclasspath;
 132             if (Java8OrEarlier) {
 133                 bootclasspath = System.getProperty("sun.boot.class.path");
 134             } else {
 135                 bootclasspath = System.getProperty("jdk.module.path") + File.pathSeparatorChar + System.getProperty("jdk.module.upgrade.path");
 136             }
 137             return bootclasspath;
 138         }
 139 
 140         protected boolean shouldLoadClass(String className) {
 141             return !className.equals("module-info");
 142         }
 143 
 144         protected void handleClassLoadingException(Throwable t) {
 145             GraalError.shouldNotReachHere(t);
 146         }
 147 
 148         protected void handleParsingException(Throwable t) {
 149             GraalError.shouldNotReachHere(t);
 150         }
 151     }
 152 
 153     @Test
 154     @SuppressWarnings("try")
 155     public void test() {
 156         runTest(new InvariantsTool());
 157     }
 158 
 159     @SuppressWarnings("try")
 160     public static void runTest(InvariantsTool tool) {
 161         RuntimeProvider rt = Graal.getRequiredCapability(RuntimeProvider.class);
 162         Providers providers = rt.getHostBackend().getProviders();
 163         MetaAccessProvider metaAccess = providers.getMetaAccess();
 164 
 165         PhaseSuite<HighTierContext> graphBuilderSuite = new PhaseSuite<>();
 166         Plugins plugins = new Plugins(new InvocationPlugins());
 167         GraphBuilderConfiguration config = GraphBuilderConfiguration.getDefault(plugins).withEagerResolving(true);
 168         graphBuilderSuite.appendPhase(new GraphBuilderPhase(config));
 169         HighTierContext context = new HighTierContext(providers, graphBuilderSuite, OptimisticOptimizations.NONE);
 170 
 171         Assume.assumeTrue(VerifyPhase.class.desiredAssertionStatus());
 172 
 173         String bootclasspath = tool.getClassPath();
 174         Assert.assertNotNull("Cannot find boot class path", bootclasspath);
 175 
 176         final List<String> classNames = new ArrayList<>();
 177         for (String path : bootclasspath.split(File.pathSeparator)) {
 178             if (tool.shouldProcess(path)) {
 179                 try {
 180                     final ZipFile zipFile = new ZipFile(new File(path));
 181                     for (final Enumeration<? extends ZipEntry> entry = zipFile.entries(); entry.hasMoreElements();) {
 182                         final ZipEntry zipEntry = entry.nextElement();
 183                         String name = zipEntry.getName();
 184                         if (name.endsWith(".class")) {
 185                             String className = name.substring(0, name.length() - ".class".length()).replace('/', '.');
 186                             classNames.add(className);
 187                         }
 188                     }
 189                 } catch (IOException ex) {
 190                     Assert.fail(ex.toString());
 191                 }
 192             }
 193         }
 194         Assert.assertFalse("Could not find graal jars on boot class path: " + bootclasspath, classNames.isEmpty());
 195 
 196         // Allows a subset of methods to be checked through use of a system property
 197         String property = System.getProperty(CheckGraalInvariants.class.getName() + ".filters");
 198         String[] filters = property == null ? null : property.split(",");
 199 
 200         OptionValues options = getInitialOptions();
 201         CompilerThreadFactory factory = new CompilerThreadFactory("CheckInvariantsThread");
 202         int availableProcessors = Runtime.getRuntime().availableProcessors();
 203         ThreadPoolExecutor executor = new ThreadPoolExecutor(availableProcessors, availableProcessors, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), factory);
 204 
 205         List<String> errors = Collections.synchronizedList(new ArrayList<>());
 206 
 207         for (Method m : BadUsageWithEquals.class.getDeclaredMethods()) {
 208             ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m);
 209             try (DebugContext debug = DebugContext.create(options, DebugHandlersFactory.LOADER)) {
 210                 StructuredGraph graph = new StructuredGraph.Builder(options, debug, AllowAssumptions.YES).method(method).build();
 211                 try (DebugCloseable s = debug.disableIntercept(); DebugContext.Scope ds = debug.scope("CheckingGraph", graph, method)) {
 212                     graphBuilderSuite.apply(graph, context);
 213                     // update phi stamps
 214                     graph.getNodes().filter(PhiNode.class).forEach(PhiNode::inferStamp);
 215                     checkGraph(context, graph);
 216                     errors.add(String.format("Expected error while checking %s", m));
 217                 } catch (VerificationError e) {
 218                     // expected!
 219                 } catch (Throwable e) {
 220                     errors.add(String.format("Error while checking %s:%n%s", m, printStackTraceToString(e)));
 221                 }
 222             }
 223         }
 224         if (errors.isEmpty()) {
 225             // Order outer classes before the inner classes
 226             classNames.sort((String a, String b) -> a.compareTo(b));
 227             // Initialize classes in single thread to avoid deadlocking issues during initialization
 228             List<Class<?>> classes = initializeClasses(tool, classNames);
 229             for (Class<?> c : classes) {
 230                 String className = c.getName();
 231                 executor.execute(() -> {
 232                     try {
 233                         checkClass(c, metaAccess);
 234                     } catch (Throwable e) {
 235                         errors.add(String.format("Error while checking %s:%n%s", className, printStackTraceToString(e)));
 236                     }
 237                 });
 238 
 239                 for (Method m : c.getDeclaredMethods()) {
 240                     if (Modifier.isNative(m.getModifiers()) || Modifier.isAbstract(m.getModifiers())) {
 241                         // ignore
 242                     } else {
 243                         String methodName = className + "." + m.getName();
 244                         if (matches(filters, methodName)) {
 245                             executor.execute(() -> {
 246                                 try (DebugContext debug = DebugContext.create(options, DebugHandlersFactory.LOADER)) {
 247                                     ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m);
 248                                     StructuredGraph graph = new StructuredGraph.Builder(options, debug).method(method).build();
 249                                     try (DebugCloseable s = debug.disableIntercept(); DebugContext.Scope ds = debug.scope("CheckingGraph", graph, method)) {
 250                                         checkMethod(method);
 251                                         graphBuilderSuite.apply(graph, context);
 252                                         // update phi stamps
 253                                         graph.getNodes().filter(PhiNode.class).forEach(PhiNode::inferStamp);
 254                                         checkGraph(context, graph);
 255                                     } catch (VerificationError e) {
 256                                         errors.add(e.getMessage());
 257                                     } catch (LinkageError e) {
 258                                         // suppress linkages errors resulting from eager resolution
 259                                     } catch (BailoutException e) {
 260                                         // Graal bail outs on certain patterns in Java bytecode
 261                                         // (e.g.,
 262                                         // unbalanced monitors introduced by jacoco).
 263                                     } catch (Throwable e) {
 264                                         try {
 265                                             tool.handleParsingException(e);
 266                                         } catch (Throwable t) {
 267                                             errors.add(String.format("Error while checking %s:%n%s", methodName, printStackTraceToString(e)));
 268                                         }
 269                                     }
 270                                 }
 271                             });
 272                         }
 273                     }
 274                 }
 275             }
 276             executor.shutdown();
 277             try {
 278                 executor.awaitTermination(1, TimeUnit.HOURS);
 279             } catch (InterruptedException e1) {
 280                 throw new RuntimeException(e1);
 281             }
 282         }
 283         if (!errors.isEmpty()) {
 284             StringBuilder msg = new StringBuilder();
 285             String nl = String.format("%n");
 286             for (String e : errors) {
 287                 if (msg.length() != 0) {
 288                     msg.append(nl);
 289                 }
 290                 msg.append(e);
 291             }
 292             Assert.fail(msg.toString());
 293         }
 294     }
 295 
 296     private static List<Class<?>> initializeClasses(InvariantsTool tool, List<String> classNames) {
 297         List<Class<?>> classes = new ArrayList<>(classNames.size());
 298         for (String className : classNames) {
 299             if (!tool.shouldLoadClass(className)) {
 300                 continue;
 301             }
 302             try {
 303                 Class<?> c = Class.forName(className, true, CheckGraalInvariants.class.getClassLoader());
 304                 classes.add(c);
 305             } catch (Throwable t) {
 306                 tool.handleClassLoadingException(t);
 307             }
 308         }
 309         return classes;
 310     }
 311 
 312     /**
 313      * @param metaAccess
 314      */
 315     private static void checkClass(Class<?> c, MetaAccessProvider metaAccess) {
 316         if (Node.class.isAssignableFrom(c)) {
 317             if (c.getAnnotation(NodeInfo.class) == null) {
 318                 throw new AssertionError(String.format("Node subclass %s requires %s annotation", c.getName(), NodeClass.class.getSimpleName()));
 319             }
 320             VerifyNodeCosts.verifyNodeClass(c);
 321         }
 322     }
 323 
 324     private static void checkMethod(ResolvedJavaMethod method) {
 325         if (method.getAnnotation(Snippet.class) == null) {
 326             Annotation[][] parameterAnnotations = method.getParameterAnnotations();
 327             for (int i = 0; i < parameterAnnotations.length; i++) {
 328                 for (Annotation a : parameterAnnotations[i]) {
 329                     Class<? extends Annotation> annotationType = a.annotationType();
 330                     if (annotationType == ConstantParameter.class || annotationType == VarargsParameter.class || annotationType == NonNullParameter.class) {
 331                         VerificationError verificationError = new VerificationError("Parameter %d of %s is annotated with %s but the method is not annotated with %s", i, method,
 332                                         annotationType.getSimpleName(),
 333                                         Snippet.class.getSimpleName());
 334                         throw verificationError;
 335                     }
 336                 }
 337             }
 338         }
 339     }
 340 
 341     /**
 342      * Checks the invariants for a single graph.
 343      */
 344     private static void checkGraph(HighTierContext context, StructuredGraph graph) {
 345         if (shouldVerifyEquals(graph.method())) {
 346             // If you add a new type to test here, be sure to add appropriate
 347             // methods to the BadUsageWithEquals class below
 348             new VerifyUsageWithEquals(Value.class).apply(graph, context);
 349             new VerifyUsageWithEquals(Register.class).apply(graph, context);
 350             new VerifyUsageWithEquals(RegisterCategory.class).apply(graph, context);
 351             new VerifyUsageWithEquals(JavaType.class).apply(graph, context);
 352             new VerifyUsageWithEquals(JavaMethod.class).apply(graph, context);
 353             new VerifyUsageWithEquals(JavaField.class).apply(graph, context);
 354             new VerifyUsageWithEquals(LocationIdentity.class).apply(graph, context);
 355             new VerifyUsageWithEquals(LIRKind.class).apply(graph, context);
 356             new VerifyUsageWithEquals(ArithmeticOpTable.class).apply(graph, context);
 357             new VerifyUsageWithEquals(ArithmeticOpTable.Op.class).apply(graph, context);
 358         }
 359         new VerifyDebugUsage().apply(graph, context);
 360         new VerifyCallerSensitiveMethods().apply(graph, context);
 361         new VerifyVirtualizableUsage().apply(graph, context);
 362         new VerifyUpdateUsages().apply(graph, context);
 363         new VerifyBailoutUsage().apply(graph, context);
 364         new VerifyInstanceOfUsage().apply(graph, context);
 365         if (graph.method().isBridge()) {
 366             BridgeMethodUtils.getBridgedMethod(graph.method());
 367         }
 368     }
 369 
 370     private static boolean matches(String[] filters, String s) {
 371         if (filters == null || filters.length == 0) {
 372             return true;
 373         }
 374         for (String filter : filters) {
 375             if (s.contains(filter)) {
 376                 return true;
 377             }
 378         }
 379         return false;
 380     }
 381 
 382     private static String printStackTraceToString(Throwable t) {
 383         StringWriter sw = new StringWriter();
 384         t.printStackTrace(new PrintWriter(sw));
 385         return sw.toString();
 386     }
 387 
 388     static class BadUsageWithEquals {
 389         Value aValue;
 390         Register aRegister;
 391         RegisterCategory aRegisterCategory;
 392         JavaType aJavaType;
 393         JavaField aJavaField;
 394         JavaMethod aJavaMethod;
 395         LocationIdentity aLocationIdentity;
 396         LIRKind aLIRKind;
 397         ArithmeticOpTable anArithmeticOpTable;
 398         ArithmeticOpTable.Op anArithmeticOpTableOp;
 399 
 400         static Value aStaticValue;
 401         static Register aStaticRegister;
 402         static RegisterCategory aStaticRegisterCategory;
 403         static JavaType aStaticJavaType;
 404         static JavaField aStaticJavaField;
 405         static JavaMethod aStaticJavaMethod;
 406         static LocationIdentity aStaticLocationIdentity;
 407         static LIRKind aStaticLIRKind;
 408         static ArithmeticOpTable aStaticArithmeticOpTable;
 409         static ArithmeticOpTable.Op aStaticArithmeticOpTableOp;
 410 
 411         boolean test01(Value f) {
 412             return aValue == f;
 413         }
 414 
 415         boolean test02(Register f) {
 416             return aRegister == f;
 417         }
 418 
 419         boolean test03(RegisterCategory f) {
 420             return aRegisterCategory == f;
 421         }
 422 
 423         boolean test04(JavaType f) {
 424             return aJavaType == f;
 425         }
 426 
 427         boolean test05(JavaField f) {
 428             return aJavaField == f;
 429         }
 430 
 431         boolean test06(JavaMethod f) {
 432             return aJavaMethod == f;
 433         }
 434 
 435         boolean test07(LocationIdentity f) {
 436             return aLocationIdentity == f;
 437         }
 438 
 439         boolean test08(LIRKind f) {
 440             return aLIRKind == f;
 441         }
 442 
 443         boolean test09(ArithmeticOpTable f) {
 444             return anArithmeticOpTable == f;
 445         }
 446 
 447         boolean test10(ArithmeticOpTable.Op f) {
 448             return anArithmeticOpTableOp == f;
 449         }
 450 
 451         boolean test12(Value f) {
 452             return aStaticValue == f;
 453         }
 454 
 455         boolean test13(Register f) {
 456             return aStaticRegister == f;
 457         }
 458 
 459         boolean test14(RegisterCategory f) {
 460             return aStaticRegisterCategory == f;
 461         }
 462 
 463         boolean test15(JavaType f) {
 464             return aStaticJavaType == f;
 465         }
 466 
 467         boolean test16(JavaField f) {
 468             return aStaticJavaField == f;
 469         }
 470 
 471         boolean test17(JavaMethod f) {
 472             return aStaticJavaMethod == f;
 473         }
 474 
 475         boolean test18(LocationIdentity f) {
 476             return aStaticLocationIdentity == f;
 477         }
 478 
 479         boolean test19(LIRKind f) {
 480             return aStaticLIRKind == f;
 481         }
 482 
 483         boolean test20(ArithmeticOpTable f) {
 484             return aStaticArithmeticOpTable == f;
 485         }
 486 
 487         boolean test21(ArithmeticOpTable.Op f) {
 488             return aStaticArithmeticOpTableOp == f;
 489         }
 490     }
 491 }