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