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