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 */
23package org.graalvm.compiler.core.test;
24
25import java.io.File;
26import java.io.IOException;
27import java.io.PrintWriter;
28import java.io.StringWriter;
29import java.lang.annotation.Annotation;
30import java.lang.reflect.Method;
31import java.lang.reflect.Modifier;
32import java.util.ArrayList;
33import java.util.Collections;
34import java.util.Enumeration;
35import java.util.List;
36import java.util.concurrent.LinkedBlockingQueue;
37import java.util.concurrent.ThreadPoolExecutor;
38import java.util.concurrent.TimeUnit;
39import java.util.zip.ZipEntry;
40import java.util.zip.ZipFile;
41
42import org.graalvm.compiler.api.replacements.Snippet;
43import org.graalvm.compiler.api.replacements.Snippet.ConstantParameter;
44import org.graalvm.compiler.api.replacements.Snippet.NonNullParameter;
45import org.graalvm.compiler.api.replacements.Snippet.VarargsParameter;
46import org.graalvm.compiler.api.test.Graal;
47import org.graalvm.compiler.bytecode.BridgeMethodUtils;
48import org.graalvm.compiler.core.CompilerThreadFactory;
49import org.graalvm.compiler.core.common.LIRKind;
50import org.graalvm.compiler.core.common.type.ArithmeticOpTable;
51import org.graalvm.compiler.debug.DebugCloseable;
52import org.graalvm.compiler.debug.DebugHandlersFactory;
53import org.graalvm.compiler.debug.DebugContext;
54import org.graalvm.compiler.debug.GraalError;
55import org.graalvm.compiler.graph.Node;
56import org.graalvm.compiler.graph.NodeClass;
57import org.graalvm.compiler.java.GraphBuilderPhase;
58import org.graalvm.compiler.nodeinfo.NodeInfo;
59import org.graalvm.compiler.nodes.PhiNode;
60import org.graalvm.compiler.nodes.StructuredGraph;
61import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
62import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
63import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins;
64import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
65import org.graalvm.compiler.options.OptionValues;
66import org.graalvm.compiler.phases.OptimisticOptimizations;
67import org.graalvm.compiler.phases.PhaseSuite;
68import org.graalvm.compiler.phases.VerifyPhase;
69import org.graalvm.compiler.phases.VerifyPhase.VerificationError;
70import org.graalvm.compiler.phases.contract.VerifyNodeCosts;
71import org.graalvm.compiler.phases.tiers.HighTierContext;
72import org.graalvm.compiler.phases.util.Providers;
73import org.graalvm.compiler.phases.verify.VerifyBailoutUsage;
74import org.graalvm.compiler.phases.verify.VerifyCallerSensitiveMethods;
75import org.graalvm.compiler.phases.verify.VerifyDebugUsage;
76import org.graalvm.compiler.phases.verify.VerifyInstanceOfUsage;
77import org.graalvm.compiler.phases.verify.VerifyUpdateUsages;
78import org.graalvm.compiler.phases.verify.VerifyUsageWithEquals;
79import org.graalvm.compiler.phases.verify.VerifyVirtualizableUsage;
80import org.graalvm.compiler.runtime.RuntimeProvider;
81import org.graalvm.word.LocationIdentity;
82import org.junit.Assert;
83import org.junit.Assume;
84import org.junit.Test;
85
86import jdk.vm.ci.code.BailoutException;
87import jdk.vm.ci.code.Register;
88import jdk.vm.ci.code.Register.RegisterCategory;
89import jdk.vm.ci.meta.JavaField;
90import jdk.vm.ci.meta.JavaMethod;
91import jdk.vm.ci.meta.JavaType;
92import jdk.vm.ci.meta.MetaAccessProvider;
93import jdk.vm.ci.meta.ResolvedJavaMethod;
94import jdk.vm.ci.meta.ResolvedJavaType;
95import 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 */
102public 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}
511