CheckGraalInvariants.java revision 12748:fbb9c8026495
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.core.common.CompilationIdentifier.INVALID_COMPILATION_ID;
26import static org.graalvm.compiler.debug.DelegatingDebugConfig.Feature.INTERCEPT;
27
28import java.io.File;
29import java.io.IOException;
30import java.io.PrintWriter;
31import java.io.StringWriter;
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.junit.Assert;
45import org.junit.Assume;
46import org.junit.Test;
47
48import org.graalvm.compiler.api.test.Graal;
49import org.graalvm.compiler.bytecode.BridgeMethodUtils;
50import org.graalvm.compiler.core.CompilerThreadFactory;
51import org.graalvm.compiler.core.CompilerThreadFactory.DebugConfigAccess;
52import org.graalvm.compiler.core.common.LIRKind;
53import org.graalvm.compiler.core.common.LocationIdentity;
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.graph.Node;
61import org.graalvm.compiler.graph.NodeClass;
62import org.graalvm.compiler.java.GraphBuilderPhase;
63import org.graalvm.compiler.nodeinfo.NodeInfo;
64import org.graalvm.compiler.nodes.PhiNode;
65import org.graalvm.compiler.nodes.StructuredGraph;
66import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
67import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
68import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins;
69import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
70import org.graalvm.compiler.phases.OptimisticOptimizations;
71import org.graalvm.compiler.phases.PhaseSuite;
72import org.graalvm.compiler.phases.VerifyPhase;
73import org.graalvm.compiler.phases.VerifyPhase.VerificationError;
74import org.graalvm.compiler.phases.tiers.HighTierContext;
75import org.graalvm.compiler.phases.util.Providers;
76import org.graalvm.compiler.phases.verify.VerifyBailoutUsage;
77import org.graalvm.compiler.phases.verify.VerifyCallerSensitiveMethods;
78import org.graalvm.compiler.phases.verify.VerifyDebugUsage;
79import org.graalvm.compiler.phases.verify.VerifyUpdateUsages;
80import org.graalvm.compiler.phases.verify.VerifyUsageWithEquals;
81import org.graalvm.compiler.phases.verify.VerifyVirtualizableUsage;
82import org.graalvm.compiler.runtime.RuntimeProvider;
83import org.graalvm.compiler.test.GraalTest;
84
85import jdk.vm.ci.code.BailoutException;
86import jdk.vm.ci.code.Register;
87import jdk.vm.ci.code.Register.RegisterCategory;
88import jdk.vm.ci.meta.JavaField;
89import jdk.vm.ci.meta.JavaMethod;
90import jdk.vm.ci.meta.JavaType;
91import jdk.vm.ci.meta.MetaAccessProvider;
92import jdk.vm.ci.meta.ResolvedJavaMethod;
93import jdk.vm.ci.meta.ResolvedJavaType;
94import jdk.vm.ci.meta.Value;
95
96/**
97 * Checks that all classes in *graal*.jar and *jvmci*.jar entries on the boot class path comply with
98 * global invariants such as using {@link Object#equals(Object)} to compare certain types instead of
99 * identity comparisons.
100 */
101public class CheckGraalInvariants extends GraalTest {
102
103    private static boolean shouldVerifyEquals(ResolvedJavaMethod m) {
104        if (m.getName().equals("identityEquals")) {
105            ResolvedJavaType c = m.getDeclaringClass();
106            if (c.getName().equals("Ljdk/vm/ci/meta/AbstractValue;") || c.getName().equals("jdk/vm/ci/meta/Value")) {
107                return false;
108            }
109        }
110
111        return true;
112    }
113
114    private static boolean shouldProcess(String classpathEntry) {
115        if (classpathEntry.endsWith(".jar")) {
116            String name = new File(classpathEntry).getName();
117            return name.contains("jvmci") || name.contains("graal");
118        }
119        return false;
120    }
121
122    @Test
123    @SuppressWarnings("try")
124    public void test() {
125        RuntimeProvider rt = Graal.getRequiredCapability(RuntimeProvider.class);
126        Providers providers = rt.getHostBackend().getProviders();
127        MetaAccessProvider metaAccess = providers.getMetaAccess();
128
129        PhaseSuite<HighTierContext> graphBuilderSuite = new PhaseSuite<>();
130        Plugins plugins = new Plugins(new InvocationPlugins());
131        GraphBuilderConfiguration config = GraphBuilderConfiguration.getDefault(plugins).withEagerResolving(true);
132        graphBuilderSuite.appendPhase(new GraphBuilderPhase(config));
133        HighTierContext context = new HighTierContext(providers, graphBuilderSuite, OptimisticOptimizations.NONE);
134
135        Assume.assumeTrue(VerifyPhase.class.desiredAssertionStatus());
136
137        String propertyName = Java8OrEarlier ? "sun.boot.class.path" : "jdk.module.path";
138        String bootclasspath = System.getProperty(propertyName);
139        Assert.assertNotNull("Cannot find value of " + propertyName, bootclasspath);
140
141        final List<String> classNames = new ArrayList<>();
142        for (String path : bootclasspath.split(File.pathSeparator)) {
143            if (shouldProcess(path)) {
144                try {
145                    final ZipFile zipFile = new ZipFile(new File(path));
146                    for (final Enumeration<? extends ZipEntry> entry = zipFile.entries(); entry.hasMoreElements();) {
147                        final ZipEntry zipEntry = entry.nextElement();
148                        String name = zipEntry.getName();
149                        if (name.endsWith(".class")) {
150                            String className = name.substring(0, name.length() - ".class".length()).replace('/', '.');
151                            classNames.add(className);
152                        }
153                    }
154                } catch (IOException ex) {
155                    Assert.fail(ex.toString());
156                }
157            }
158        }
159        Assert.assertFalse("Could not find graal jars on boot class path: " + bootclasspath, classNames.isEmpty());
160
161        // Allows a subset of methods to be checked through use of a system property
162        String property = System.getProperty(CheckGraalInvariants.class.getName() + ".filters");
163        String[] filters = property == null ? null : property.split(",");
164
165        CompilerThreadFactory factory = new CompilerThreadFactory("CheckInvariantsThread", new DebugConfigAccess() {
166            @Override
167            public GraalDebugConfig getDebugConfig() {
168                return DebugEnvironment.initialize(System.out);
169            }
170        });
171        int availableProcessors = Runtime.getRuntime().availableProcessors();
172        ThreadPoolExecutor executor = new ThreadPoolExecutor(availableProcessors, availableProcessors, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), factory);
173
174        List<String> errors = Collections.synchronizedList(new ArrayList<>());
175        // Order outer classes before the inner classes
176        classNames.sort((String a, String b) -> a.compareTo(b));
177        // Initialize classes in single thread to avoid deadlocking issues during initialization
178        List<Class<?>> classes = initializeClasses(classNames);
179        for (Class<?> c : classes) {
180            String className = c.getName();
181            executor.execute(() -> {
182                try {
183                    checkClass(c, metaAccess);
184                } catch (Throwable e) {
185                    errors.add(String.format("Error while checking %s:%n%s", className, printStackTraceToString(e)));
186                }
187            });
188
189            for (Method m : c.getDeclaredMethods()) {
190                if (Modifier.isNative(m.getModifiers()) || Modifier.isAbstract(m.getModifiers())) {
191                    // ignore
192                } else {
193                    String methodName = className + "." + m.getName();
194                    if (matches(filters, methodName)) {
195                        executor.execute(() -> {
196                            ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m);
197                            StructuredGraph graph = new StructuredGraph(method, AllowAssumptions.NO, INVALID_COMPILATION_ID);
198                            try (DebugConfigScope s = Debug.setConfig(new DelegatingDebugConfig().disable(INTERCEPT)); Debug.Scope ds = Debug.scope("CheckingGraph", graph, method)) {
199                                graphBuilderSuite.apply(graph, context);
200                                // update phi stamps
201                                graph.getNodes().filter(PhiNode.class).forEach(PhiNode::inferStamp);
202                                checkGraph(context, graph);
203                            } catch (VerificationError e) {
204                                errors.add(e.getMessage());
205                            } catch (LinkageError e) {
206                                // suppress linkages errors resulting from eager resolution
207                            } catch (BailoutException e) {
208                                // Graal bail outs on certain patterns in Java bytecode (e.g.,
209                                // unbalanced monitors introduced by jacoco).
210                            } catch (Throwable e) {
211                                errors.add(String.format("Error while checking %s:%n%s", methodName, printStackTraceToString(e)));
212                            }
213                        });
214                    }
215                }
216            }
217        }
218        executor.shutdown();
219        try {
220            executor.awaitTermination(1, TimeUnit.HOURS);
221        } catch (InterruptedException e1) {
222            throw new RuntimeException(e1);
223        }
224
225        if (!errors.isEmpty()) {
226            StringBuilder msg = new StringBuilder();
227            String nl = String.format("%n");
228            for (String e : errors) {
229                if (msg.length() != 0) {
230                    msg.append(nl);
231                }
232                msg.append(e);
233            }
234            Assert.fail(msg.toString());
235        }
236    }
237
238    private static List<Class<?>> initializeClasses(List<String> classNames) {
239        List<Class<?>> classes = new ArrayList<>(classNames.size());
240        for (String className : classNames) {
241            if (className.equals("module-info")) {
242                continue;
243            }
244            try {
245                Class<?> c = Class.forName(className, true, CheckGraalInvariants.class.getClassLoader());
246                classes.add(c);
247            } catch (ClassNotFoundException e) {
248                e.printStackTrace();
249            }
250        }
251        return classes;
252    }
253
254    /**
255     * @param metaAccess
256     */
257    private static void checkClass(Class<?> c, MetaAccessProvider metaAccess) {
258        if (Node.class.isAssignableFrom(c)) {
259            if (c.getAnnotation(NodeInfo.class) == null) {
260                throw new AssertionError(String.format("Node subclass %s requires %s annotation", c.getName(), NodeClass.class.getSimpleName()));
261            }
262        }
263    }
264
265    /**
266     * Checks the invariants for a single graph.
267     */
268    private static void checkGraph(HighTierContext context, StructuredGraph graph) {
269        if (shouldVerifyEquals(graph.method())) {
270            new VerifyUsageWithEquals(Value.class).apply(graph, context);
271            new VerifyUsageWithEquals(Register.class).apply(graph, context);
272            new VerifyUsageWithEquals(RegisterCategory.class).apply(graph, context);
273            new VerifyUsageWithEquals(JavaType.class).apply(graph, context);
274            new VerifyUsageWithEquals(JavaMethod.class).apply(graph, context);
275            new VerifyUsageWithEquals(JavaField.class).apply(graph, context);
276            new VerifyUsageWithEquals(LocationIdentity.class).apply(graph, context);
277            new VerifyUsageWithEquals(LIRKind.class).apply(graph, context);
278            new VerifyUsageWithEquals(ArithmeticOpTable.class).apply(graph, context);
279            new VerifyUsageWithEquals(ArithmeticOpTable.Op.class).apply(graph, context);
280        }
281        new VerifyDebugUsage().apply(graph, context);
282        new VerifyCallerSensitiveMethods().apply(graph, context);
283        new VerifyVirtualizableUsage().apply(graph, context);
284        new VerifyUpdateUsages().apply(graph, context);
285        new VerifyBailoutUsage().apply(graph, context);
286        if (graph.method().isBridge()) {
287            BridgeMethodUtils.getBridgedMethod(graph.method());
288        }
289    }
290
291    private static boolean matches(String[] filters, String s) {
292        if (filters == null || filters.length == 0) {
293            return true;
294        }
295        for (String filter : filters) {
296            if (s.contains(filter)) {
297                return true;
298            }
299        }
300        return false;
301    }
302
303    private static String printStackTraceToString(Throwable t) {
304        StringWriter sw = new StringWriter();
305        t.printStackTrace(new PrintWriter(sw));
306        return sw.toString();
307    }
308}
309