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