blob: 5453ac3662dc158b60ffdb499621e6e378c073c6 [file] [log] [blame]
/*
* Copyright (c) 2010, 2018, 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 vm.mlvm.anonloader.share;
import java.io.File;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.nio.file.Files;
import java.nio.file.Paths;
import nsk.share.test.Stresser;
import vm.share.options.Option;
import vm.share.options.OptionSupport;
import vm.share.options.IgnoreUnknownArgumentsHandler;
import vm.mlvm.share.Env;
import vm.mlvm.share.MlvmTest;
import vm.mlvm.share.CustomClassLoaders;
import vm.share.FileUtils;
import vm.share.UnsafeAccess;
/**
* Does stress-testing of class loading subsystem.
* This class should be subclassed by the tests
* to provide test class data.
*
* <p>StressClassLoadingTest performs a number of iterations
* (the default value is 100 000).
* Each iteration gets class bytes from the subclass
* and loads it into JVM using either:
* <ul>
* <li>a custom {@link java.lang.ClassLoader} implementation or
* <li>{@link sun.misc.Unsafe#defineAnonymousClass} call.
* </ul>
*
* <p>Loading is done in a separate thread. If this thread is stuck,
* it is killed after some timeout (default is 10 seconds, please see
* -parseTimeout option). The class file is saved as hangXX.class, where XX
* starts at 00 and is increased on every hangup.
* A prefix can be added to the file name using {@link #setFileNamePrefix}
*
* <p>The test fails, if there were hangups.
*
* <p>By default, before loading class, the bytes are
* saved to {@code _AnonkTestee01.class} file in the current directory.
* If JVM crashes, the bytecodes can be analysed.
* Class saving is controlled by -saveClassFile option.
* A prefix can be added to the file name using {@link #setFileNamePrefix}
* function.
*
* <p>There is a tool to load the saved .class file.
* The tool tries to load class using a number of class loaders. For more
* information, please see tool documentation: {@link vm.mlvm.tools.LoadClass}.
*
* @see vm.mlvm.tools.LoadClass
*/
public abstract class StressClassLoadingTest extends MlvmTest {
private static final String RESCUE_FILE_NAME = "_AnonkTestee01.class";
private static final String HUNG_CLASS_FILE_NAME = "hang%02d.class";
@Option(name = "iterations", default_value = "100000",
description = "How many times generate a class and parse it")
private static int iterations;
@Option(name = "saveClassFile", default_value = "true",
description = "Save generated class file before loading."
+ " Useful when VM crashes on loading")
private static boolean saveClassFile;
@Option(name = "parseTimeout", default_value = "10000",
description = "Timeout in millisectionds to detect hung parser"
+ " thread. The parser thread is killed after the timeout")
private static int parseTimeout;
@Option(name = "unsafeLoad", default_value = "false",
description = "An option for adhoc experiments: load class via "
+ "Unsafe.defineAnonymousClass(). Since in this way the "
+ "loading process skips several security checks, if the "
+ "class is not valid, crashes and assertions are normal.")
private static boolean unsafeLoad;
private String fileNamePrefix = "";
private final static AtomicBoolean classFileMessagePrinted
= new AtomicBoolean(false);
/**
* Sets prefix for names of the files, created by test:
* _AnonkTestee01.class and hangXX.class.
*
* @param p a prefix to add before file name.
* @throws java.lang.NullPointerException if p is null
*/
public void setFileNamePrefix(String p) {
Objects.requireNonNull(p);
fileNamePrefix = p;
}
static volatile boolean optionsSetup = false;
public static void setupOptions(Object instance) {
if (!optionsSetup) {
synchronized (StressClassLoadingTest.class) {
if (!optionsSetup) {
OptionSupport.setup(instance, Env.getArgParser().getRawArguments(), new IgnoreUnknownArgumentsHandler());
optionsSetup = true;
Env.traceNormal("StressClassLoadingTest options: iterations: " + iterations);
Env.traceNormal("StressClassLoadingTest options: unsafeLoad: " + unsafeLoad);
Env.traceNormal("StressClassLoadingTest options: parseTimeout: " + parseTimeout);
Env.traceNormal("StressClassLoadingTest options: saveClassFile: " + saveClassFile);
}
}
}
}
public boolean run() throws Exception {
setupOptions(this);
int hangNum = 0;
Stresser stresser = createStresser();
stresser.start(iterations);
while (stresser.continueExecution()) {
stresser.iteration();
byte[] classBytes = generateClassBytes();
Class<?> hostClass = getHostClass();
String className = hostClass.getName();
File rescueFile = new File(String.format("%s_%d_%s",
fileNamePrefix, stresser.getIteration(), RESCUE_FILE_NAME));
if (saveClassFile) {
// Write out the class file being loaded. It's useful
// to have if the JVM crashes.
FileUtils.writeBytesToFile(rescueFile, classBytes);
if (classFileMessagePrinted.compareAndSet(false, true)) {
Env.traceImportant("If the JVM crashes then "
+ "the class file causing the crash is saved as *_*_"
+ RESCUE_FILE_NAME);
}
}
Thread parserThread = new Thread() {
public void run() {
try {
Class<?> c;
if (unsafeLoad) {
c = UnsafeAccess.unsafe.defineAnonymousClass(hostClass, classBytes, null);
} else {
c = CustomClassLoaders.makeClassBytesLoader(classBytes, className)
.loadClass(className);
}
c.newInstance();
} catch (Throwable e) {
Env.traceVerbose(e, "parser caught exception");
}
}
};
parserThread.setDaemon(true);
parserThread.start();
parserThread.join(parseTimeout);
if (parserThread.isAlive()) {
Env.complain("Killing parser thread");
StackTraceElement[] stack = parserThread.getStackTrace();
Env.traceImportant(parserThread + " stack trace:");
for (int i = 0; i < stack.length; ++i) {
Env.traceImportant(parserThread + "\tat " + stack[i]);
}
if (saveClassFile) {
Files.move(rescueFile.toPath(), Paths.get(fileNamePrefix
+ String.format(HUNG_CLASS_FILE_NAME, hangNum)));
}
++hangNum;
} else if (saveClassFile) {
rescueFile.delete();
}
}
stresser.finish();
if (hangNum > 0) {
Env.complain("There were " + hangNum + " hangups during parsing."
+ " The class files, which produced hangup were saved as "
+ fileNamePrefix + String.format(HUNG_CLASS_FILE_NAME, 0)
+ "... in the test directory. You may want to analyse them."
+ " Failing this test because of hangups.");
return false;
}
return true;
}
/**
* Generated class bytes. The method is called for each iteration.
*
* @return Byte array with the generated class
*/
protected abstract byte[] generateClassBytes();
/**
* Returns a host class for the generated class.
*
* @return A host class that for the generated class
*/
protected abstract Class<?> getHostClass();
}