/* * Copyright (c) 2015, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.graalvm.compiler.truffle.test; import static org.graalvm.compiler.core.common.util.Util.JAVA_SPECIFICATION_VERSION; import static org.graalvm.compiler.test.SubprocessUtil.formatExecutedCommand; import static org.graalvm.compiler.test.SubprocessUtil.getVMCommandLine; import static org.graalvm.compiler.test.SubprocessUtil.withoutDebuggerArguments; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.graalvm.compiler.core.CompilerThreadFactory; import org.graalvm.compiler.core.common.util.ModuleAPI; import org.graalvm.compiler.core.common.util.Util; import org.graalvm.compiler.debug.Assertions; import org.graalvm.compiler.nodes.Cancellable; import org.graalvm.compiler.options.OptionDescriptor; import org.graalvm.compiler.options.OptionDescriptors; import org.graalvm.compiler.options.OptionKey; import org.graalvm.compiler.options.OptionValues; import org.graalvm.compiler.options.OptionValuesAccess; import org.graalvm.compiler.options.OptionsParser; import org.junit.Assert; import org.junit.Assume; import org.junit.Test; import jdk.vm.ci.runtime.JVMCICompilerFactory; import jdk.vm.ci.services.JVMCIServiceLocator; /** * Test lazy initialization of Graal in the context of Truffle. When simply executing Truffle code, * Graal should not be initialized unless there is an actual compilation request. */ public class LazyInitializationTest { private final Class<?> hotSpotVMEventListener; private final Class<?> hotSpotGraalCompilerFactoryOptions; private final Class<?> hotSpotGraalJVMCIServiceLocatorShared; private final Class<?> jvmciVersionCheck; private static boolean Java8OrEarlier = System.getProperty("java.specification.version").compareTo("1.9") < 0; public LazyInitializationTest() { hotSpotVMEventListener = forNameOrNull("jdk.vm.ci.hotspot.services.HotSpotVMEventListener"); hotSpotGraalCompilerFactoryOptions = forNameOrNull("org.graalvm.compiler.hotspot.HotSpotGraalCompilerFactory$Options"); hotSpotGraalJVMCIServiceLocatorShared = forNameOrNull("org.graalvm.compiler.hotspot.HotSpotGraalJVMCIServiceLocator$Shared"); jvmciVersionCheck = forNameOrNull("org.graalvm.compiler.hotspot.JVMCIVersionCheck"); } private static Class<?> forNameOrNull(String name) { try { return Class.forName(name); } catch (ClassNotFoundException e) { return null; } } @Test public void testSLTck() throws IOException, InterruptedException { spawnUnitTests("com.oracle.truffle.sl.test.SLFactorialTest"); } private static final Pattern CLASS_INIT_LOG_PATTERN = Pattern.compile("\\[info\\]\\[class,init\\] \\d+ Initializing '([^']+)'"); /** * Extracts the class name from a line of log output. */ private static String extractClass(String line) { if (Java8OrEarlier) { String traceClassLoadingPrefix = "[Loaded "; int index = line.indexOf(traceClassLoadingPrefix); if (index != -1) { int start = index + traceClassLoadingPrefix.length(); int end = line.indexOf(' ', start); return line.substring(start, end); } } else { Matcher matcher = CLASS_INIT_LOG_PATTERN.matcher(line); if (matcher.find()) { return matcher.group(1).replace('/', '.'); } } return null; } /** * Spawn a new VM, execute unit tests, and check which classes are loaded. */ private void spawnUnitTests(String... tests) throws IOException, InterruptedException { List<String> args = withoutDebuggerArguments(getVMCommandLine()); boolean usesJvmciCompiler = args.contains("-jvmci") || args.contains("-XX:+UseJVMCICompiler"); Assume.assumeFalse("This test can only run if JVMCI is not one of the default compilers", usesJvmciCompiler); args.add(Java8OrEarlier ? "-XX:+TraceClassLoading" : "-Xlog:class+init=info"); args.add("-dsa"); args.add("-da"); args.add("com.oracle.mxtool.junit.MxJUnitWrapper"); args.addAll(Arrays.asList(tests)); ArrayList<String> loadedGraalClassNames = new ArrayList<>(); ProcessBuilder processBuilder = new ProcessBuilder(args); processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); int testCount = 0; BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream())); List<String> outputLines = new ArrayList<>(); String line; while ((line = stdout.readLine()) != null) { outputLines.add(line); String loadedClass = extractClass(line); if (loadedClass != null) { if (isGraalClass(loadedClass)) { loadedGraalClassNames.add(loadedClass); } } else if (line.startsWith("OK (")) { Assert.assertTrue(testCount == 0); int start = "OK (".length(); int end = line.indexOf(' ', start); testCount = Integer.parseInt(line.substring(start, end)); } } String dashes = "-------------------------------------------------------"; int exitCode = process.waitFor(); if (exitCode != 0) { Assert.fail(String.format("non-zero exit code %d for command:%n%s", exitCode, formatExecutedCommand(args, outputLines, dashes, dashes))); } if (testCount == 0) { Assert.fail(String.format("no tests found in output of command:%n%s", testCount, formatExecutedCommand(args, outputLines, dashes, dashes))); } try { checkAllowedGraalClasses(loadedGraalClassNames); } catch (AssertionError e) { throw new AssertionError(String.format("Failure for command:%n%s", formatExecutedCommand(args, outputLines, dashes, dashes)), e); } } private static boolean isGraalClass(String className) { if (className.startsWith("org.graalvm.compiler.truffle.") || className.startsWith("org.graalvm.compiler.serviceprovider.")) { // Ignore classes in the com.oracle.graal.truffle package, they are all allowed. // Also ignore classes in the Graal service provider package, as they might not be // lazily loaded. return false; } else { return className.startsWith("org.graalvm.compiler."); } } private void checkAllowedGraalClasses(List<String> loadedGraalClassNames) { HashSet<Class<?>> whitelist = new HashSet<>(); List<Class<?>> loadedGraalClasses = new ArrayList<>(); for (String name : loadedGraalClassNames) { try { loadedGraalClasses.add(Class.forName(name)); } catch (ClassNotFoundException e) { Assert.fail("loaded class " + name + " not found"); } } /* * Look for all loaded OptionDescriptors classes, and whitelist the classes that declare the * options. They may be loaded by the option parsing code. */ for (Class<?> cls : loadedGraalClasses) { if (OptionDescriptors.class.isAssignableFrom(cls)) { try { OptionDescriptors optionDescriptors = cls.asSubclass(OptionDescriptors.class).newInstance(); for (OptionDescriptor option : optionDescriptors) { whitelist.add(option.getDeclaringClass()); whitelist.add(option.getType()); } } catch (ReflectiveOperationException e) { } } } whitelist.add(Cancellable.class); List<String> forbiddenClasses = new ArrayList<>(); for (Class<?> cls : loadedGraalClasses) { if (whitelist.contains(cls)) { continue; } if (!isGraalClassAllowed(cls)) { forbiddenClasses.add(cls.getName()); } } if (!forbiddenClasses.isEmpty()) { Assert.fail("loaded forbidden classes:\n " + forbiddenClasses.stream().collect(Collectors.joining("\n ")) + "\n"); } } private boolean isGraalClassAllowed(Class<?> cls) { if (CompilerThreadFactory.class.equals(cls) || CompilerThreadFactory.DebugConfigAccess.class.equals(cls)) { // The HotSpotTruffleRuntime creates a CompilerThreadFactory for Truffle. return true; } if (cls.equals(hotSpotGraalCompilerFactoryOptions)) { // Graal initialization needs to access this class. return true; } if (cls.equals(Util.class)) { // Provider of the Java runtime check utility used during Graal initialization. return true; } if (JAVA_SPECIFICATION_VERSION >= 9 && cls.equals(ModuleAPI.class)) { // Graal initialization needs access to Module API on JDK 9. return true; } if (cls.equals(jvmciVersionCheck)) { // The Graal initialization needs to check the JVMCI version. return true; } if (JVMCICompilerFactory.class.isAssignableFrom(cls)) { // The compiler factories have to be loaded and instantiated by the JVMCI. return true; } if (JVMCIServiceLocator.class.isAssignableFrom(cls) || cls == hotSpotGraalJVMCIServiceLocatorShared) { return true; } if (OptionDescriptors.class.isAssignableFrom(cls) || OptionDescriptor.class.isAssignableFrom(cls)) { // If options are specified, the corresponding *_OptionDescriptors classes are loaded. return true; } if (cls == Assertions.class || cls == OptionsParser.class || cls == OptionValues.class || OptionValuesAccess.class.isAssignableFrom(cls)) { // Classes implementing Graal option loading return true; } if (OptionKey.class.isAssignableFrom(cls)) { // If options are specified, that may implicitly load a custom OptionKey subclass. return true; } if (hotSpotVMEventListener != null && hotSpotVMEventListener.isAssignableFrom(cls)) { // HotSpotVMEventListeners need to be loaded on JVMCI startup. return true; } // No other class from the org.graalvm.compiler package should be loaded. return false; } }