CheckGraalInvariants.java revision 13083:b9a173f12fe6
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 static org.graalvm.compiler.debug.DelegatingDebugConfig.Feature.INTERCEPT;
26
27import java.io.File;
28import java.io.IOException;
29import java.io.PrintWriter;
30import java.io.StringWriter;
31import java.lang.annotation.Annotation;
32import java.lang.reflect.Method;
33import java.lang.reflect.Modifier;
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.Enumeration;
37import java.util.List;
38import java.util.concurrent.LinkedBlockingQueue;
39import java.util.concurrent.ThreadPoolExecutor;
40import java.util.concurrent.TimeUnit;
41import java.util.zip.ZipEntry;
42import java.util.zip.ZipFile;
43
44import org.graalvm.api.word.LocationIdentity;
45import org.graalvm.compiler.api.replacements.Snippet;
46import org.graalvm.compiler.api.replacements.Snippet.ConstantParameter;
47import org.graalvm.compiler.api.replacements.Snippet.NonNullParameter;
48import org.graalvm.compiler.api.replacements.Snippet.VarargsParameter;
49import org.graalvm.compiler.api.test.Graal;
50import org.graalvm.compiler.bytecode.BridgeMethodUtils;
51import org.graalvm.compiler.core.CompilerThreadFactory;
52import org.graalvm.compiler.core.CompilerThreadFactory.DebugConfigAccess;
53import org.graalvm.compiler.core.common.LIRKind;
54import org.graalvm.compiler.core.common.type.ArithmeticOpTable;
55import org.graalvm.compiler.debug.Debug;
56import org.graalvm.compiler.debug.DebugConfigScope;
57import org.graalvm.compiler.debug.DebugEnvironment;
58import org.graalvm.compiler.debug.DelegatingDebugConfig;
59import org.graalvm.compiler.debug.GraalDebugConfig;
60import org.graalvm.compiler.debug.GraalError;
61import org.graalvm.compiler.graph.Node;
62import org.graalvm.compiler.graph.NodeClass;
63import org.graalvm.compiler.java.GraphBuilderPhase;
64import org.graalvm.compiler.nodeinfo.NodeInfo;
65import org.graalvm.compiler.nodes.PhiNode;
66import org.graalvm.compiler.nodes.StructuredGraph;
67import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
68import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
69import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins;
70import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
71import org.graalvm.compiler.options.OptionValues;
72import org.graalvm.compiler.phases.OptimisticOptimizations;
73import org.graalvm.compiler.phases.PhaseSuite;
74import org.graalvm.compiler.phases.VerifyPhase;
75import org.graalvm.compiler.phases.VerifyPhase.VerificationError;
76import org.graalvm.compiler.phases.contract.VerifyNodeCosts;
77import org.graalvm.compiler.phases.tiers.HighTierContext;
78import org.graalvm.compiler.phases.util.Providers;
79import org.graalvm.compiler.phases.verify.VerifyBailoutUsage;
80import org.graalvm.compiler.phases.verify.VerifyCallerSensitiveMethods;
81import org.graalvm.compiler.phases.verify.VerifyDebugUsage;
82import org.graalvm.compiler.phases.verify.VerifyInstanceOfUsage;
83import org.graalvm.compiler.phases.verify.VerifyUpdateUsages;
84import org.graalvm.compiler.phases.verify.VerifyUsageWithEquals;
85import org.graalvm.compiler.phases.verify.VerifyVirtualizableUsage;
86import org.graalvm.compiler.runtime.RuntimeProvider;
87import org.junit.Assert;
88import org.junit.Assume;
89import org.junit.Test;
90
91import jdk.vm.ci.code.BailoutException;
92import jdk.vm.ci.code.Register;
93import jdk.vm.ci.code.Register.RegisterCategory;
94import jdk.vm.ci.meta.JavaField;
95import jdk.vm.ci.meta.JavaMethod;
96import jdk.vm.ci.meta.JavaType;
97import jdk.vm.ci.meta.MetaAccessProvider;
98import jdk.vm.ci.meta.ResolvedJavaMethod;
99import jdk.vm.ci.meta.ResolvedJavaType;
100import 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 */
107public 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            return !className.equals("module-info");
147        }
148
149        protected void handleClassLoadingException(Throwable t) {
150            GraalError.shouldNotReachHere(t);
151        }
152
153        protected void handleParsingException(Throwable t) {
154            GraalError.shouldNotReachHere(t);
155        }
156    }
157
158    @Test
159    @SuppressWarnings("try")
160    public void test() {
161        runTest(new InvariantsTool());
162    }
163
164    @SuppressWarnings("try")
165    public static void runTest(InvariantsTool tool) {
166        RuntimeProvider rt = Graal.getRequiredCapability(RuntimeProvider.class);
167        Providers providers = rt.getHostBackend().getProviders();
168        MetaAccessProvider metaAccess = providers.getMetaAccess();
169
170        PhaseSuite<HighTierContext> graphBuilderSuite = new PhaseSuite<>();
171        Plugins plugins = new Plugins(new InvocationPlugins());
172        GraphBuilderConfiguration config = GraphBuilderConfiguration.getDefault(plugins).withEagerResolving(true);
173        graphBuilderSuite.appendPhase(new GraphBuilderPhase(config));
174        HighTierContext context = new HighTierContext(providers, graphBuilderSuite, OptimisticOptimizations.NONE);
175
176        Assume.assumeTrue(VerifyPhase.class.desiredAssertionStatus());
177
178        String bootclasspath = tool.getClassPath();
179        Assert.assertNotNull("Cannot find boot class path", bootclasspath);
180
181        final List<String> classNames = new ArrayList<>();
182        for (String path : bootclasspath.split(File.pathSeparator)) {
183            if (tool.shouldProcess(path)) {
184                try {
185                    final ZipFile zipFile = new ZipFile(new File(path));
186                    for (final Enumeration<? extends ZipEntry> entry = zipFile.entries(); entry.hasMoreElements();) {
187                        final ZipEntry zipEntry = entry.nextElement();
188                        String name = zipEntry.getName();
189                        if (name.endsWith(".class")) {
190                            String className = name.substring(0, name.length() - ".class".length()).replace('/', '.');
191                            classNames.add(className);
192                        }
193                    }
194                } catch (IOException ex) {
195                    Assert.fail(ex.toString());
196                }
197            }
198        }
199        Assert.assertFalse("Could not find graal jars on boot class path: " + bootclasspath, classNames.isEmpty());
200
201        // Allows a subset of methods to be checked through use of a system property
202        String property = System.getProperty(CheckGraalInvariants.class.getName() + ".filters");
203        String[] filters = property == null ? null : property.split(",");
204
205        OptionValues options = getInitialOptions();
206        CompilerThreadFactory factory = new CompilerThreadFactory("CheckInvariantsThread", new DebugConfigAccess() {
207            @Override
208            public GraalDebugConfig getDebugConfig() {
209                return DebugEnvironment.ensureInitialized(options);
210            }
211        });
212        int availableProcessors = Runtime.getRuntime().availableProcessors();
213        ThreadPoolExecutor executor = new ThreadPoolExecutor(availableProcessors, availableProcessors, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), factory);
214
215        List<String> errors = Collections.synchronizedList(new ArrayList<>());
216
217        for (Method m : BadUsageWithEquals.class.getDeclaredMethods()) {
218            ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m);
219            StructuredGraph graph = new StructuredGraph.Builder(options, AllowAssumptions.YES).method(method).build();
220            try (DebugConfigScope s = Debug.setConfig(new DelegatingDebugConfig().disable(INTERCEPT)); Debug.Scope ds = Debug.scope("CheckingGraph", graph, method)) {
221                graphBuilderSuite.apply(graph, context);
222                // update phi stamps
223                graph.getNodes().filter(PhiNode.class).forEach(PhiNode::inferStamp);
224                checkGraph(context, graph);
225                errors.add(String.format("Expected error while checking %s", m));
226            } catch (VerificationError e) {
227                // expected!
228            } catch (Throwable e) {
229                errors.add(String.format("Error while checking %s:%n%s", m, printStackTraceToString(e)));
230            }
231        }
232        if (errors.isEmpty()) {
233            // Order outer classes before the inner classes
234            classNames.sort((String a, String b) -> a.compareTo(b));
235            // Initialize classes in single thread to avoid deadlocking issues during initialization
236            List<Class<?>> classes = initializeClasses(tool, classNames);
237            for (Class<?> c : classes) {
238                String className = c.getName();
239                executor.execute(() -> {
240                    try {
241                        checkClass(c, metaAccess);
242                    } catch (Throwable e) {
243                        errors.add(String.format("Error while checking %s:%n%s", className, printStackTraceToString(e)));
244                    }
245                });
246
247                for (Method m : c.getDeclaredMethods()) {
248                    if (Modifier.isNative(m.getModifiers()) || Modifier.isAbstract(m.getModifiers())) {
249                        // ignore
250                    } else {
251                        String methodName = className + "." + m.getName();
252                        if (matches(filters, methodName)) {
253                            executor.execute(() -> {
254                                ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m);
255                                StructuredGraph graph = new StructuredGraph.Builder(options).method(method).build();
256                                try (DebugConfigScope s = Debug.setConfig(new DelegatingDebugConfig().disable(INTERCEPT)); Debug.Scope ds = Debug.scope("CheckingGraph", graph, method)) {
257                                    checkMethod(method);
258                                    graphBuilderSuite.apply(graph, context);
259                                    // update phi stamps
260                                    graph.getNodes().filter(PhiNode.class).forEach(PhiNode::inferStamp);
261                                    checkGraph(context, graph);
262                                } catch (VerificationError e) {
263                                    errors.add(e.getMessage());
264                                } catch (LinkageError e) {
265                                    // suppress linkages errors resulting from eager resolution
266                                } catch (BailoutException e) {
267                                    // Graal bail outs on certain patterns in Java bytecode (e.g.,
268                                    // unbalanced monitors introduced by jacoco).
269                                } catch (Throwable e) {
270                                    try {
271                                        tool.handleParsingException(e);
272                                    } catch (Throwable t) {
273                                        errors.add(String.format("Error while checking %s:%n%s", methodName, printStackTraceToString(e)));
274                                    }
275                                }
276                            });
277                        }
278                    }
279                }
280            }
281            executor.shutdown();
282            try {
283                executor.awaitTermination(1, TimeUnit.HOURS);
284            } catch (InterruptedException e1) {
285                throw new RuntimeException(e1);
286            }
287        }
288        if (!errors.isEmpty()) {
289            StringBuilder msg = new StringBuilder();
290            String nl = String.format("%n");
291            for (String e : errors) {
292                if (msg.length() != 0) {
293                    msg.append(nl);
294                }
295                msg.append(e);
296            }
297            Assert.fail(msg.toString());
298        }
299    }
300
301    private static List<Class<?>> initializeClasses(InvariantsTool tool, List<String> classNames) {
302        List<Class<?>> classes = new ArrayList<>(classNames.size());
303        for (String className : classNames) {
304            if (!tool.shouldLoadClass(className)) {
305                continue;
306            }
307            try {
308                Class<?> c = Class.forName(className, true, CheckGraalInvariants.class.getClassLoader());
309                classes.add(c);
310            } catch (Throwable t) {
311                tool.handleClassLoadingException(t);
312            }
313        }
314        return classes;
315    }
316
317    /**
318     * @param metaAccess
319     */
320    private static void checkClass(Class<?> c, MetaAccessProvider metaAccess) {
321        if (Node.class.isAssignableFrom(c)) {
322            if (c.getAnnotation(NodeInfo.class) == null) {
323                throw new AssertionError(String.format("Node subclass %s requires %s annotation", c.getName(), NodeClass.class.getSimpleName()));
324            }
325            VerifyNodeCosts.verifyNodeClass(c);
326        }
327    }
328
329    private static void checkMethod(ResolvedJavaMethod method) {
330        if (method.getAnnotation(Snippet.class) == null) {
331            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
332            for (int i = 0; i < parameterAnnotations.length; i++) {
333                for (Annotation a : parameterAnnotations[i]) {
334                    Class<? extends Annotation> annotationType = a.annotationType();
335                    if (annotationType == ConstantParameter.class || annotationType == VarargsParameter.class || annotationType == NonNullParameter.class) {
336                        VerificationError verificationError = new VerificationError("Parameter %d of %s is annotated with %s but the method is not annotated with %s", i, method,
337                                        annotationType.getSimpleName(),
338                                        Snippet.class.getSimpleName());
339                        throw verificationError;
340                    }
341                }
342            }
343        }
344    }
345
346    /**
347     * Checks the invariants for a single graph.
348     */
349    private static void checkGraph(HighTierContext context, StructuredGraph graph) {
350        if (shouldVerifyEquals(graph.method())) {
351            // If you add a new type to test here, be sure to add appropriate
352            // methods to the BadUsageWithEquals class below
353            new VerifyUsageWithEquals(Value.class).apply(graph, context);
354            new VerifyUsageWithEquals(Register.class).apply(graph, context);
355            new VerifyUsageWithEquals(RegisterCategory.class).apply(graph, context);
356            new VerifyUsageWithEquals(JavaType.class).apply(graph, context);
357            new VerifyUsageWithEquals(JavaMethod.class).apply(graph, context);
358            new VerifyUsageWithEquals(JavaField.class).apply(graph, context);
359            new VerifyUsageWithEquals(LocationIdentity.class).apply(graph, context);
360            new VerifyUsageWithEquals(LIRKind.class).apply(graph, context);
361            new VerifyUsageWithEquals(ArithmeticOpTable.class).apply(graph, context);
362            new VerifyUsageWithEquals(ArithmeticOpTable.Op.class).apply(graph, context);
363        }
364        new VerifyDebugUsage().apply(graph, context);
365        new VerifyCallerSensitiveMethods().apply(graph, context);
366        new VerifyVirtualizableUsage().apply(graph, context);
367        new VerifyUpdateUsages().apply(graph, context);
368        new VerifyBailoutUsage().apply(graph, context);
369        new VerifyInstanceOfUsage().apply(graph, context);
370        if (graph.method().isBridge()) {
371            BridgeMethodUtils.getBridgedMethod(graph.method());
372        }
373    }
374
375    private static boolean matches(String[] filters, String s) {
376        if (filters == null || filters.length == 0) {
377            return true;
378        }
379        for (String filter : filters) {
380            if (s.contains(filter)) {
381                return true;
382            }
383        }
384        return false;
385    }
386
387    private static String printStackTraceToString(Throwable t) {
388        StringWriter sw = new StringWriter();
389        t.printStackTrace(new PrintWriter(sw));
390        return sw.toString();
391    }
392
393    static class BadUsageWithEquals {
394        Value aValue;
395        Register aRegister;
396        RegisterCategory aRegisterCategory;
397        JavaType aJavaType;
398        JavaField aJavaField;
399        JavaMethod aJavaMethod;
400        LocationIdentity aLocationIdentity;
401        LIRKind aLIRKind;
402        ArithmeticOpTable anArithmeticOpTable;
403        ArithmeticOpTable.Op anArithmeticOpTableOp;
404
405        static Value aStaticValue;
406        static Register aStaticRegister;
407        static RegisterCategory aStaticRegisterCategory;
408        static JavaType aStaticJavaType;
409        static JavaField aStaticJavaField;
410        static JavaMethod aStaticJavaMethod;
411        static LocationIdentity aStaticLocationIdentity;
412        static LIRKind aStaticLIRKind;
413        static ArithmeticOpTable aStaticArithmeticOpTable;
414        static ArithmeticOpTable.Op aStaticArithmeticOpTableOp;
415
416        boolean test01(Value f) {
417            return aValue == f;
418        }
419
420        boolean test02(Register f) {
421            return aRegister == f;
422        }
423
424        boolean test03(RegisterCategory f) {
425            return aRegisterCategory == f;
426        }
427
428        boolean test04(JavaType f) {
429            return aJavaType == f;
430        }
431
432        boolean test05(JavaField f) {
433            return aJavaField == f;
434        }
435
436        boolean test06(JavaMethod f) {
437            return aJavaMethod == f;
438        }
439
440        boolean test07(LocationIdentity f) {
441            return aLocationIdentity == f;
442        }
443
444        boolean test08(LIRKind f) {
445            return aLIRKind == f;
446        }
447
448        boolean test09(ArithmeticOpTable f) {
449            return anArithmeticOpTable == f;
450        }
451
452        boolean test10(ArithmeticOpTable.Op f) {
453            return anArithmeticOpTableOp == f;
454        }
455
456        boolean test12(Value f) {
457            return aStaticValue == f;
458        }
459
460        boolean test13(Register f) {
461            return aStaticRegister == f;
462        }
463
464        boolean test14(RegisterCategory f) {
465            return aStaticRegisterCategory == f;
466        }
467
468        boolean test15(JavaType f) {
469            return aStaticJavaType == f;
470        }
471
472        boolean test16(JavaField f) {
473            return aStaticJavaField == f;
474        }
475
476        boolean test17(JavaMethod f) {
477            return aStaticJavaMethod == f;
478        }
479
480        boolean test18(LocationIdentity f) {
481            return aStaticLocationIdentity == f;
482        }
483
484        boolean test19(LIRKind f) {
485            return aStaticLIRKind == f;
486        }
487
488        boolean test20(ArithmeticOpTable f) {
489            return aStaticArithmeticOpTable == f;
490        }
491
492        boolean test21(ArithmeticOpTable.Op f) {
493            return aStaticArithmeticOpTableOp == f;
494        }
495    }
496}
497