001/*
002 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.compiler.test;
024
025import static com.oracle.graal.debug.DelegatingDebugConfig.Feature.*;
026
027import java.io.*;
028import java.lang.reflect.*;
029import java.util.*;
030import java.util.concurrent.*;
031import java.util.zip.*;
032
033import jdk.internal.jvmci.code.*;
034import jdk.internal.jvmci.code.Register.RegisterCategory;
035import jdk.internal.jvmci.meta.*;
036
037import org.junit.*;
038
039import com.oracle.graal.api.runtime.*;
040import com.oracle.graal.compiler.*;
041import com.oracle.graal.compiler.CompilerThreadFactory.DebugConfigAccess;
042import com.oracle.graal.compiler.common.type.*;
043import com.oracle.graal.debug.*;
044import com.oracle.graal.graph.*;
045import com.oracle.graal.graphbuilderconf.*;
046import com.oracle.graal.graphbuilderconf.GraphBuilderConfiguration.Plugins;
047import com.oracle.graal.java.*;
048import com.oracle.graal.nodeinfo.*;
049import com.oracle.graal.nodes.*;
050import com.oracle.graal.nodes.StructuredGraph.AllowAssumptions;
051import com.oracle.graal.phases.*;
052import com.oracle.graal.phases.VerifyPhase.VerificationError;
053import com.oracle.graal.phases.tiers.*;
054import com.oracle.graal.phases.util.*;
055import com.oracle.graal.phases.verify.*;
056import com.oracle.graal.runtime.*;
057import com.oracle.graal.test.*;
058
059/**
060 * Checks that all classes in *graal*.jar and *jvmci*.jar entries on the boot class path comply with
061 * global invariants such as using {@link Object#equals(Object)} to compare certain types instead of
062 * identity comparisons.
063 */
064public class CheckGraalInvariants extends GraalTest {
065
066    private static boolean shouldVerifyEquals(ResolvedJavaMethod m) {
067        if (m.getName().equals("identityEquals")) {
068            ResolvedJavaType c = m.getDeclaringClass();
069            if (c.getName().equals("Ljdk/internal/jvmci/meta/AbstractValue;") || c.getName().equals("jdk/internal/jvmci/meta/Value")) {
070                return false;
071            }
072        }
073
074        return true;
075    }
076
077    private static boolean shouldProcess(String classpathEntry) {
078        if (classpathEntry.endsWith(".jar")) {
079            String name = new File(classpathEntry).getName();
080            return name.contains("jvmci") || name.contains("graal");
081        }
082        return false;
083    }
084
085    @Test
086    public void test() {
087        RuntimeProvider rt = Graal.getRequiredCapability(RuntimeProvider.class);
088        Providers providers = rt.getHostBackend().getProviders();
089        MetaAccessProvider metaAccess = providers.getMetaAccess();
090
091        PhaseSuite<HighTierContext> graphBuilderSuite = new PhaseSuite<>();
092        GraphBuilderConfiguration config = GraphBuilderConfiguration.getEagerDefault(new Plugins(new InvocationPlugins(metaAccess)));
093        graphBuilderSuite.appendPhase(new GraphBuilderPhase(config));
094        HighTierContext context = new HighTierContext(providers, graphBuilderSuite, OptimisticOptimizations.NONE);
095
096        Assume.assumeTrue(VerifyPhase.class.desiredAssertionStatus());
097
098        String bootclasspath = System.getProperty("sun.boot.class.path");
099        Assert.assertNotNull("Cannot find value of boot class path", bootclasspath);
100
101        bootclasspath.split(File.pathSeparator);
102
103        final List<String> classNames = new ArrayList<>();
104        for (String path : bootclasspath.split(File.pathSeparator)) {
105            if (shouldProcess(path)) {
106                try {
107                    final ZipFile zipFile = new ZipFile(new File(path));
108                    for (final Enumeration<? extends ZipEntry> entry = zipFile.entries(); entry.hasMoreElements();) {
109                        final ZipEntry zipEntry = entry.nextElement();
110                        String name = zipEntry.getName();
111                        if (name.endsWith(".class")) {
112                            String className = name.substring(0, name.length() - ".class".length()).replace('/', '.');
113                            classNames.add(className);
114                        }
115                    }
116                } catch (IOException ex) {
117                    Assert.fail(ex.toString());
118                }
119            }
120        }
121        Assert.assertFalse("Could not find graal jars on boot class path: " + bootclasspath, classNames.isEmpty());
122
123        // Allows a subset of methods to be checked through use of a system property
124        String property = System.getProperty(CheckGraalInvariants.class.getName() + ".filters");
125        String[] filters = property == null ? null : property.split(",");
126
127        CompilerThreadFactory factory = new CompilerThreadFactory("CheckInvariantsThread", new DebugConfigAccess() {
128            public GraalDebugConfig getDebugConfig() {
129                return DebugEnvironment.initialize(System.out);
130            }
131        });
132        int availableProcessors = Runtime.getRuntime().availableProcessors();
133        ThreadPoolExecutor executor = new ThreadPoolExecutor(availableProcessors, availableProcessors, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), factory);
134
135        List<String> errors = Collections.synchronizedList(new ArrayList<>());
136        for (String className : classNames) {
137            try {
138                Class<?> c = Class.forName(className, false, CheckGraalInvariants.class.getClassLoader());
139                executor.execute(() -> {
140                    try {
141                        checkClass(c, metaAccess);
142                    } catch (Throwable e) {
143                        errors.add(String.format("Error while checking %s:%n%s", className, printStackTraceToString(e)));
144                    }
145                });
146
147                for (Method m : c.getDeclaredMethods()) {
148                    if (Modifier.isNative(m.getModifiers()) || Modifier.isAbstract(m.getModifiers())) {
149                        // ignore
150                    } else {
151                        String methodName = className + "." + m.getName();
152                        if (matches(filters, methodName)) {
153                            executor.execute(() -> {
154                                ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m);
155                                StructuredGraph graph = new StructuredGraph(method, AllowAssumptions.NO);
156                                try (DebugConfigScope s = Debug.setConfig(new DelegatingDebugConfig().disable(INTERCEPT)); Debug.Scope ds = Debug.scope("CheckingGraph", graph, method)) {
157                                    graphBuilderSuite.apply(graph, context);
158                                    // update phi stamps
159                                    graph.getNodes().filter(PhiNode.class).forEach(PhiNode::inferStamp);
160                                    checkGraph(context, graph);
161                                } catch (VerificationError e) {
162                                    errors.add(e.getMessage());
163                                } catch (LinkageError e) {
164                                    // suppress linkages errors resulting from eager resolution
165                                } catch (BailoutException e) {
166                                    // Graal bail outs on certain patterns in Java bytecode (e.g.,
167                                    // unbalanced monitors introduced by jacoco).
168                                } catch (Throwable e) {
169                                    errors.add(String.format("Error while checking %s:%n%s", methodName, printStackTraceToString(e)));
170                                }
171                            });
172                        }
173                    }
174                }
175
176            } catch (ClassNotFoundException e) {
177                e.printStackTrace();
178            }
179        }
180        executor.shutdown();
181        try {
182            executor.awaitTermination(1, TimeUnit.HOURS);
183        } catch (InterruptedException e1) {
184            throw new RuntimeException(e1);
185        }
186
187        if (!errors.isEmpty()) {
188            StringBuilder msg = new StringBuilder();
189            String nl = String.format("%n");
190            for (String e : errors) {
191                if (msg.length() != 0) {
192                    msg.append(nl);
193                }
194                msg.append(e);
195            }
196            Assert.fail(msg.toString());
197        }
198    }
199
200    /**
201     * @param metaAccess
202     */
203    private static void checkClass(Class<?> c, MetaAccessProvider metaAccess) {
204        if (Node.class.isAssignableFrom(c)) {
205            if (c.getAnnotation(NodeInfo.class) == null) {
206                throw new AssertionError(String.format("Node subclass %s requires %s annotation", c.getName(), NodeClass.class.getSimpleName()));
207            }
208        }
209    }
210
211    /**
212     * Checks the invariants for a single graph.
213     */
214    private static void checkGraph(HighTierContext context, StructuredGraph graph) {
215        if (shouldVerifyEquals(graph.method())) {
216            new VerifyUsageWithEquals(Value.class).apply(graph, context);
217            new VerifyUsageWithEquals(Register.class).apply(graph, context);
218            new VerifyUsageWithEquals(RegisterCategory.class).apply(graph, context);
219            new VerifyUsageWithEquals(JavaType.class).apply(graph, context);
220            new VerifyUsageWithEquals(JavaMethod.class).apply(graph, context);
221            new VerifyUsageWithEquals(JavaField.class).apply(graph, context);
222            new VerifyUsageWithEquals(LocationIdentity.class).apply(graph, context);
223            new VerifyUsageWithEquals(LIRKind.class).apply(graph, context);
224            new VerifyUsageWithEquals(ArithmeticOpTable.class).apply(graph, context);
225            new VerifyUsageWithEquals(ArithmeticOpTable.Op.class).apply(graph, context);
226        }
227        new VerifyDebugUsage().apply(graph, context);
228    }
229
230    private static boolean matches(String[] filters, String s) {
231        if (filters == null || filters.length == 0) {
232            return true;
233        }
234        for (String filter : filters) {
235            if (s.contains(filter)) {
236                return true;
237            }
238        }
239        return false;
240    }
241
242    private static String printStackTraceToString(Throwable t) {
243        StringWriter sw = new StringWriter();
244        t.printStackTrace(new PrintWriter(sw));
245        return sw.toString();
246    }
247}