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         runTest(new InvariantsTool());
 185     }
 186 
 187     @SuppressWarnings("try")
 188     public static void runTest(InvariantsTool tool) {
 189         RuntimeProvider rt = Graal.getRequiredCapability(RuntimeProvider.class);
 190         Providers providers = rt.getHostBackend().getProviders();
 191         MetaAccessProvider metaAccess = providers.getMetaAccess();
 192 
 193         PhaseSuite<HighTierContext> graphBuilderSuite = new PhaseSuite<>();
 194         Plugins plugins = new Plugins(new InvocationPlugins());
 195         GraphBuilderConfiguration config = GraphBuilderConfiguration.getDefault(plugins).withEagerResolving(true).withUnresolvedIsError(true);
 196         graphBuilderSuite.appendPhase(new GraphBuilderPhase(config));
 197         HighTierContext context = new HighTierContext(providers, graphBuilderSuite, OptimisticOptimizations.NONE);
 198 
 199         Assume.assumeTrue(VerifyPhase.class.desiredAssertionStatus());
 200 
 201         String bootclasspath = tool.getClassPath();
 202         Assert.assertNotNull("Cannot find boot class path", bootclasspath);
 203 
 204         final List<String> classNames = new ArrayList<>();
 205         for (String path : bootclasspath.split(File.pathSeparator)) {
 206             if (tool.shouldProcess(path)) {
 207                 try {
 208                     final ZipFile zipFile = new ZipFile(new File(path));
 209                     for (final Enumeration<? extends ZipEntry> entry = zipFile.entries(); entry.hasMoreElements();) {
 210                         final ZipEntry zipEntry = entry.nextElement();
 211                         String name = zipEntry.getName();
 212                         if (name.endsWith(".class") && !name.startsWith("META-INF/versions/")) {
 213                             String className = name.substring(0, name.length() - ".class".length()).replace('/', '.');
 214                             if (isInNativeImage(className)) {
 215                                 /*
 216                                  * Native Image is an external tool and does not need to follow the
 217                                  * Graal invariants.
 218                                  */
 219                                 continue;
 220                             }
 221                             classNames.add(className);
 222                         }
 223                     }
 224                 } catch (IOException ex) {
 225                     Assert.fail(ex.toString());
 226                 }
 227             }
 228         }
 229         Assert.assertFalse("Could not find graal jars on boot class path: " + bootclasspath, classNames.isEmpty());
 230 
 231         // Allows a subset of methods to be checked through use of a system property
 232         String property = System.getProperty(CheckGraalInvariants.class.getName() + ".filters");
 233         String[] filters = property == null ? null : property.split(",");
 234 
 235         OptionValues options = getInitialOptions();
 236         CompilerThreadFactory factory = new CompilerThreadFactory("CheckInvariantsThread");
 237         int availableProcessors = Runtime.getRuntime().availableProcessors();
 238         ThreadPoolExecutor executor = new ThreadPoolExecutor(availableProcessors, availableProcessors, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), factory);
 239 
 240         List<String> errors = Collections.synchronizedList(new ArrayList<>());
 241 
 242         for (Method m : BadUsageWithEquals.class.getDeclaredMethods()) {
 243             ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m);
 244             try (DebugContext debug = DebugContext.create(options, DebugHandlersFactory.LOADER)) {
 245                 StructuredGraph graph = new StructuredGraph.Builder(options, debug, AllowAssumptions.YES).method(method).build();
 246                 try (DebugCloseable s = debug.disableIntercept(); DebugContext.Scope ds = debug.scope("CheckingGraph", graph, method)) {
 247                     graphBuilderSuite.apply(graph, context);
 248                     // update phi stamps
 249                     graph.getNodes().filter(PhiNode.class).forEach(PhiNode::inferStamp);
 250                     checkGraph(context, graph);
 251                     errors.add(String.format("Expected error while checking %s", m));
 252                 } catch (VerificationError e) {
 253                     // expected!
 254                 } catch (Throwable e) {
 255                     errors.add(String.format("Error while checking %s:%n%s", m, printStackTraceToString(e)));
 256                 }
 257             }
 258         }
 259         if (errors.isEmpty()) {
 260             // Order outer classes before the inner classes
 261             classNames.sort((String a, String b) -> a.compareTo(b));
 262             // Initialize classes in single thread to avoid deadlocking issues during initialization
 263             List<Class<?>> classes = initializeClasses(tool, classNames);
 264             for (Class<?> c : classes) {
 265                 String className = c.getName();
 266                 executor.execute(() -> {
 267                     try {
 268                         checkClass(c, metaAccess);
 269                     } catch (Throwable e) {
 270                         errors.add(String.format("Error while checking %s:%n%s", className, printStackTraceToString(e)));
 271                     }
 272                 });
 273 
 274                 for (Method m : c.getDeclaredMethods()) {
 275                     if (Modifier.isNative(m.getModifiers()) || Modifier.isAbstract(m.getModifiers())) {
 276                         // ignore
 277                     } else {
 278                         String methodName = className + "." + m.getName();
 279                         if (matches(filters, methodName)) {
 280                             executor.execute(() -> {
 281                                 try (DebugContext debug = DebugContext.create(options, DebugHandlersFactory.LOADER)) {
 282                                     ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m);
 283                                     boolean isSubstitution = method.getAnnotation(Snippet.class) != null || method.getAnnotation(MethodSubstitution.class) != null;
 284                                     StructuredGraph graph = new StructuredGraph.Builder(options, debug).method(method).setIsSubstitution(isSubstitution).build();
 285                                     try (DebugCloseable s = debug.disableIntercept(); DebugContext.Scope ds = debug.scope("CheckingGraph", graph, method)) {
 286                                         checkMethod(method);
 287                                         graphBuilderSuite.apply(graph, context);
 288                                         // update phi stamps
 289                                         graph.getNodes().filter(PhiNode.class).forEach(PhiNode::inferStamp);
 290                                         checkGraph(context, graph);
 291                                     } catch (VerificationError e) {
 292                                         errors.add(e.getMessage());
 293                                     } catch (LinkageError e) {
 294                                         // suppress linkages errors resulting from eager resolution
 295                                     } catch (BailoutException e) {
 296                                         // Graal bail outs on certain patterns in Java bytecode
 297                                         // (e.g.,
 298                                         // unbalanced monitors introduced by jacoco).
 299                                     } catch (Throwable e) {
 300                                         try {
 301                                             tool.handleParsingException(e);
 302                                         } catch (Throwable t) {
 303                                             errors.add(String.format("Error while checking %s:%n%s", methodName, printStackTraceToString(e)));
 304                                         }
 305                                     }
 306                                 }
 307                             });
 308                         }
 309                     }
 310                 }
 311             }
 312             executor.shutdown();
 313             try {
 314                 executor.awaitTermination(1, TimeUnit.HOURS);
 315             } catch (InterruptedException e1) {
 316                 throw new RuntimeException(e1);
 317             }
 318         }
 319         if (!errors.isEmpty()) {
 320             StringBuilder msg = new StringBuilder();
 321             String nl = String.format("%n");
 322             for (String e : errors) {
 323                 if (msg.length() != 0) {
 324                     msg.append(nl);
 325                 }
 326                 msg.append(e);
 327             }
 328             Assert.fail(msg.toString());
 329         }
 330     }
 331 
 332     private static boolean isInNativeImage(String className) {
 333         return className.startsWith("org.graalvm.nativeimage");
 334     }
 335 
 336     private static List<Class<?>> initializeClasses(InvariantsTool tool, List<String> classNames) {
 337         List<Class<?>> classes = new ArrayList<>(classNames.size());
 338         for (String className : classNames) {
 339             if (!tool.shouldLoadClass(className)) {
 340                 continue;
 341             }
 342             try {
 343                 Class<?> c = Class.forName(className, true, CheckGraalInvariants.class.getClassLoader());
 344                 classes.add(c);
 345             } catch (Throwable t) {
 346                 tool.handleClassLoadingException(t);
 347             }
 348         }
 349         return classes;
 350     }
 351 
 352     /**
 353      * @param metaAccess
 354      */
 355     private static void checkClass(Class<?> c, MetaAccessProvider metaAccess) {
 356         if (Node.class.isAssignableFrom(c)) {
 357             if (c.getAnnotation(NodeInfo.class) == null) {
 358                 throw new AssertionError(String.format("Node subclass %s requires %s annotation", c.getName(), NodeClass.class.getSimpleName()));
 359             }
 360             VerifyNodeCosts.verifyNodeClass(c);
 361         }
 362     }
 363 
 364     private static void checkMethod(ResolvedJavaMethod method) {
 365         if (method.getAnnotation(Snippet.class) == null) {
 366             Annotation[][] parameterAnnotations = method.getParameterAnnotations();
 367             for (int i = 0; i < parameterAnnotations.length; i++) {
 368                 for (Annotation a : parameterAnnotations[i]) {
 369                     Class<? extends Annotation> annotationType = a.annotationType();
 370                     if (annotationType == ConstantParameter.class || annotationType == VarargsParameter.class || annotationType == NonNullParameter.class) {
 371                         VerificationError verificationError = new VerificationError("Parameter %d of %s is annotated with %s but the method is not annotated with %s", i, method,
 372                                         annotationType.getSimpleName(),
 373                                         Snippet.class.getSimpleName());
 374                         throw verificationError;
 375                     }
 376                 }
 377             }
 378         }
 379     }
 380 
 381     /**
 382      * Checks the invariants for a single graph.
 383      */
 384     private static void checkGraph(HighTierContext context, StructuredGraph graph) {
 385         if (shouldVerifyEquals(graph.method())) {
 386             // If you add a new type to test here, be sure to add appropriate
 387             // methods to the BadUsageWithEquals class below
 388             new VerifyUsageWithEquals(Value.class).apply(graph, context);
 389             new VerifyUsageWithEquals(Register.class).apply(graph, context);
 390             new VerifyUsageWithEquals(RegisterCategory.class).apply(graph, context);
 391             new VerifyUsageWithEquals(JavaType.class).apply(graph, context);
 392             new VerifyUsageWithEquals(JavaMethod.class).apply(graph, context);
 393             new VerifyUsageWithEquals(JavaField.class).apply(graph, context);
 394             new VerifyUsageWithEquals(LocationIdentity.class).apply(graph, context);
 395             new VerifyUsageWithEquals(LIRKind.class).apply(graph, context);
 396             new VerifyUsageWithEquals(ArithmeticOpTable.class).apply(graph, context);
 397             new VerifyUsageWithEquals(ArithmeticOpTable.Op.class).apply(graph, context);
 398         }
 399         new VerifyDebugUsage().apply(graph, context);
 400         new VerifyCallerSensitiveMethods().apply(graph, context);
 401         new VerifyVirtualizableUsage().apply(graph, context);
 402         new VerifyUpdateUsages().apply(graph, context);
 403         new VerifyBailoutUsage().apply(graph, context);
 404         new VerifyInstanceOfUsage().apply(graph, context);
 405         new VerifyGraphAddUsage().apply(graph, context);
 406         new VerifyGetOptionsUsage().apply(graph, context);
 407         if (graph.method().isBridge()) {
 408             BridgeMethodUtils.getBridgedMethod(graph.method());
 409         }
 410     }
 411 
 412     private static boolean matches(String[] filters, String s) {
 413         if (filters == null || filters.length == 0) {
 414             return true;
 415         }
 416         for (String filter : filters) {
 417             if (s.contains(filter)) {
 418                 return true;
 419             }
 420         }
 421         return false;
 422     }
 423 
 424     private static String printStackTraceToString(Throwable t) {
 425         StringWriter sw = new StringWriter();
 426         t.printStackTrace(new PrintWriter(sw));
 427         return sw.toString();
 428     }
 429 
 430     static class BadUsageWithEquals {
 431         Value aValue;
 432         Register aRegister;
 433         RegisterCategory aRegisterCategory;
 434         JavaType aJavaType;
 435         JavaField aJavaField;
 436         JavaMethod aJavaMethod;
 437         LocationIdentity aLocationIdentity;
 438         LIRKind aLIRKind;
 439         ArithmeticOpTable anArithmeticOpTable;
 440         ArithmeticOpTable.Op anArithmeticOpTableOp;
 441 
 442         static Value aStaticValue;
 443         static Register aStaticRegister;
 444         static RegisterCategory aStaticRegisterCategory;
 445         static JavaType aStaticJavaType;
 446         static JavaField aStaticJavaField;
 447         static JavaMethod aStaticJavaMethod;
 448         static LocationIdentity aStaticLocationIdentity;
 449         static LIRKind aStaticLIRKind;
 450         static ArithmeticOpTable aStaticArithmeticOpTable;
 451         static ArithmeticOpTable.Op aStaticArithmeticOpTableOp;
 452 
 453         boolean test01(Value f) {
 454             return aValue == f;
 455         }
 456 
 457         boolean test02(Register f) {
 458             return aRegister == f;
 459         }
 460 
 461         boolean test03(RegisterCategory f) {
 462             return aRegisterCategory == f;
 463         }
 464 
 465         boolean test04(JavaType f) {
 466             return aJavaType == f;
 467         }
 468 
 469         boolean test05(JavaField f) {
 470             return aJavaField == f;
 471         }
 472 
 473         boolean test06(JavaMethod f) {
 474             return aJavaMethod == f;
 475         }
 476 
 477         boolean test07(LocationIdentity f) {
 478             return aLocationIdentity == f;
 479         }
 480 
 481         boolean test08(LIRKind f) {
 482             return aLIRKind == f;
 483         }
 484 
 485         boolean test09(ArithmeticOpTable f) {
 486             return anArithmeticOpTable == f;
 487         }
 488 
 489         boolean test10(ArithmeticOpTable.Op f) {
 490             return anArithmeticOpTableOp == f;
 491         }
 492 
 493         boolean test12(Value f) {
 494             return aStaticValue == f;
 495         }
 496 
 497         boolean test13(Register f) {
 498             return aStaticRegister == f;
 499         }
 500 
 501         boolean test14(RegisterCategory f) {
 502             return aStaticRegisterCategory == f;
 503         }
 504 
 505         boolean test15(JavaType f) {
 506             return aStaticJavaType == f;
 507         }
 508 
 509         boolean test16(JavaField f) {
 510             return aStaticJavaField == f;
 511         }
 512 
 513         boolean test17(JavaMethod f) {
 514             return aStaticJavaMethod == f;
 515         }
 516 
 517         boolean test18(LocationIdentity f) {
 518             return aStaticLocationIdentity == f;
 519         }
 520 
 521         boolean test19(LIRKind f) {
 522             return aStaticLIRKind == f;
 523         }
 524 
 525         boolean test20(ArithmeticOpTable f) {
 526             return aStaticArithmeticOpTable == f;
 527         }
 528 
 529         boolean test21(ArithmeticOpTable.Op f) {
 530             return aStaticArithmeticOpTableOp == f;
 531         }
 532     }
 533 }