905d21fe76
Reviewed-by: mchung
375 lines
15 KiB
Java
375 lines
15 KiB
Java
|
|
import java.lang.reflect.Method;
|
|
import java.net.URI;
|
|
import java.nio.file.FileSystem;
|
|
import java.nio.file.FileSystems;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.stream.Stream;
|
|
import jdk.internal.org.objectweb.asm.ClassReader;
|
|
import jdk.internal.org.objectweb.asm.Opcodes;
|
|
import jdk.internal.org.objectweb.asm.tree.AbstractInsnNode;
|
|
import jdk.internal.org.objectweb.asm.tree.ClassNode;
|
|
import jdk.internal.org.objectweb.asm.tree.MethodInsnNode;
|
|
import jdk.internal.org.objectweb.asm.tree.MethodNode;
|
|
import jdk.internal.org.objectweb.asm.tree.TryCatchBlockNode;
|
|
import jdk.tools.jlink.internal.PluginRepository;
|
|
import jdk.tools.jlink.internal.ModulePoolImpl;
|
|
import jdk.tools.jlink.internal.plugins.OptimizationPlugin;
|
|
import jdk.tools.jlink.internal.plugins.asm.AsmModulePool;
|
|
import jdk.tools.jlink.internal.plugins.asm.AsmPlugin;
|
|
import jdk.tools.jlink.internal.plugins.asm.AsmPools;
|
|
import jdk.tools.jlink.internal.plugins.optim.ControlFlow;
|
|
import jdk.tools.jlink.internal.plugins.optim.ControlFlow.Block;
|
|
import jdk.tools.jlink.plugin.ModuleEntry;
|
|
import jdk.tools.jlink.plugin.ModulePool;
|
|
|
|
import tests.Helper;
|
|
import tests.JImageGenerator;
|
|
|
|
/*
|
|
* Copyright (c) 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.
|
|
*/
|
|
|
|
/*
|
|
* @test
|
|
* @summary Test image creation with class optimization
|
|
* @author Jean-Francois Denise
|
|
* @library ../lib
|
|
* @modules java.base/jdk.internal.jimage
|
|
* jdk.jdeps/com.sun.tools.classfile
|
|
* jdk.jlink/jdk.tools.jlink.internal
|
|
* jdk.jlink/jdk.tools.jmod
|
|
* jdk.jlink/jdk.tools.jimage
|
|
* jdk.jlink/jdk.tools.jlink.internal.plugins
|
|
* jdk.jlink/jdk.tools.jlink.internal.plugins.asm
|
|
* jdk.jlink/jdk.tools.jlink.internal.plugins.optim
|
|
* java.base/jdk.internal.org.objectweb.asm
|
|
* java.base/jdk.internal.org.objectweb.asm.tree
|
|
* java.base/jdk.internal.org.objectweb.asm.util
|
|
* jdk.compiler
|
|
* @build tests.*
|
|
* @run main JLinkOptimTest
|
|
*/
|
|
public class JLinkOptimTest {
|
|
|
|
private static final String EXPECTED = "expected";
|
|
private static Helper helper;
|
|
|
|
public static class ControlFlowPlugin extends AsmPlugin {
|
|
|
|
private boolean called;
|
|
private int numMethods;
|
|
private int numBlocks;
|
|
|
|
private static final String NAME = "test-optim";
|
|
|
|
private ControlFlowPlugin() {
|
|
}
|
|
|
|
@Override
|
|
public void visit(AsmPools pools) {
|
|
called = true;
|
|
for (AsmModulePool p : pools.getModulePools()) {
|
|
|
|
p.visitClassReaders((reader) -> {
|
|
ClassNode cn = new ClassNode();
|
|
if ((reader.getAccess() & Opcodes.ACC_INTERFACE) == 0) {
|
|
reader.accept(cn, ClassReader.EXPAND_FRAMES);
|
|
for (MethodNode m : cn.methods) {
|
|
if ((m.access & Opcodes.ACC_ABSTRACT) == 0
|
|
&& (m.access & Opcodes.ACC_NATIVE) == 0) {
|
|
numMethods += 1;
|
|
try {
|
|
ControlFlow f
|
|
= ControlFlow.createControlFlow(cn.name, m);
|
|
for (Block b : f.getBlocks()) {
|
|
numBlocks += 1;
|
|
f.getClosure(b);
|
|
}
|
|
} catch (Throwable ex) {
|
|
//ex.printStackTrace();
|
|
throw new RuntimeException("Exception in "
|
|
+ cn.name + "." + m.name, ex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
});
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String getName() {
|
|
return NAME;
|
|
}
|
|
|
|
@Override
|
|
public Set<Category> getType() {
|
|
Set<Category> set = new HashSet<>();
|
|
set.add(Category.TRANSFORMER);
|
|
return Collections.unmodifiableSet(set);
|
|
}
|
|
}
|
|
|
|
private static void testForName() throws Exception {
|
|
String moduleName = "optimplugin";
|
|
Path src = Paths.get(System.getProperty("test.src")).resolve(moduleName);
|
|
Path classes = helper.getJmodClassesDir().resolve(moduleName);
|
|
JImageGenerator.compile(src, classes);
|
|
|
|
FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
|
|
Path root = fs.getPath("/modules/java.base");
|
|
// Access module-info.class to be reused as fake module-info.class
|
|
List<ModuleEntry> javabaseResources = new ArrayList<>();
|
|
try (Stream<Path> stream = Files.walk(root)) {
|
|
for (Iterator<Path> iterator = stream.iterator(); iterator.hasNext();) {
|
|
Path p = iterator.next();
|
|
if (Files.isRegularFile(p)) {
|
|
try {
|
|
javabaseResources.add(ModuleEntry.create(p.toString().
|
|
substring("/modules".length()), Files.readAllBytes(p)));
|
|
} catch (Exception ex) {
|
|
throw new RuntimeException(ex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//forName folding
|
|
ModulePoolImpl pool = new ModulePoolImpl();
|
|
byte[] content = Files.readAllBytes(classes.
|
|
resolve("optim").resolve("ForNameTestCase.class"));
|
|
byte[] content2 = Files.readAllBytes(classes.
|
|
resolve("optim").resolve("AType.class"));
|
|
byte[] mcontent = Files.readAllBytes(classes.resolve("module-info.class"));
|
|
|
|
pool.add(ModuleEntry.create("/optimplugin/optim/ForNameTestCase.class", content));
|
|
pool.add(ModuleEntry.create("/optimplugin/optim/AType.class", content2));
|
|
pool.add(ModuleEntry.create("/optimplugin/module-info.class", mcontent));
|
|
|
|
for (ModuleEntry r : javabaseResources) {
|
|
pool.add(r);
|
|
}
|
|
|
|
OptimizationPlugin plugin = new OptimizationPlugin();
|
|
Map<String, String> optional = new HashMap<>();
|
|
optional.put(OptimizationPlugin.NAME, OptimizationPlugin.FORNAME_REMOVAL);
|
|
optional.put(OptimizationPlugin.LOG, "forName.log");
|
|
plugin.configure(optional);
|
|
ModulePool out = new ModulePoolImpl();
|
|
plugin.visit(pool, out);
|
|
|
|
ModuleEntry result = out.entries().iterator().next();
|
|
|
|
ClassReader optimReader = new ClassReader(result.getBytes());
|
|
ClassNode optimClass = new ClassNode();
|
|
optimReader.accept(optimClass, ClassReader.EXPAND_FRAMES);
|
|
|
|
if (!optimClass.name.equals("optim/ForNameTestCase")) {
|
|
throw new Exception("Invalid class " + optimClass.name);
|
|
}
|
|
if (optimClass.methods.size() < 2) {
|
|
throw new Exception("Not enough methods in new class");
|
|
}
|
|
for (MethodNode mn : optimClass.methods) {
|
|
if (!mn.name.contains("forName") && !mn.name.contains("<clinit>")) {
|
|
continue;
|
|
}
|
|
if (mn.name.startsWith("negative")) {
|
|
checkForName(mn);
|
|
} else {
|
|
checkNoForName(mn);
|
|
}
|
|
}
|
|
Map<String, byte[]> newClasses = new HashMap<>();
|
|
newClasses.put("optim.ForNameTestCase", result.getBytes());
|
|
newClasses.put("optim.AType", content2);
|
|
MemClassLoader loader = new MemClassLoader(newClasses);
|
|
Class<?> loaded = loader.loadClass("optim.ForNameTestCase");
|
|
if (loaded.getDeclaredMethods().length < 2) {
|
|
throw new Exception("Not enough methods in new class");
|
|
}
|
|
for (Method m : loaded.getDeclaredMethods()) {
|
|
if (m.getName().contains("Exception")) {
|
|
try {
|
|
m.invoke(null);
|
|
} catch (Exception ex) {
|
|
//ex.getCause().printStackTrace();
|
|
if (!ex.getCause().getMessage().equals(EXPECTED)) {
|
|
throw new Exception("Unexpected exception " + ex);
|
|
}
|
|
}
|
|
} else if (!m.getName().startsWith("negative")) {
|
|
Class<?> clazz = (Class<?>) m.invoke(null);
|
|
if (clazz != String.class && clazz != loader.findClass("optim.AType")) {
|
|
throw new Exception("Invalid class " + clazz);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void checkNoForName(MethodNode m) throws Exception {
|
|
Iterator<AbstractInsnNode> it = m.instructions.iterator();
|
|
while (it.hasNext()) {
|
|
AbstractInsnNode n = it.next();
|
|
if (n instanceof MethodInsnNode) {
|
|
MethodInsnNode met = (MethodInsnNode) n;
|
|
if (met.name.equals("forName")
|
|
&& met.owner.equals("java/lang/Class")
|
|
&& met.desc.equals("(Ljava/lang/String;)Ljava/lang/Class;")) {
|
|
throw new Exception("forName not removed in " + m.name);
|
|
}
|
|
}
|
|
}
|
|
for (TryCatchBlockNode tcb : m.tryCatchBlocks) {
|
|
if (tcb.type.equals(ClassNotFoundException.class.getName().replaceAll("\\.", "/"))) {
|
|
throw new Exception("ClassNotFoundException Block not removed for " + m.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void checkForName(MethodNode m) throws Exception {
|
|
Iterator<AbstractInsnNode> it = m.instructions.iterator();
|
|
boolean found = false;
|
|
while (it.hasNext()) {
|
|
AbstractInsnNode n = it.next();
|
|
if (n instanceof MethodInsnNode) {
|
|
MethodInsnNode met = (MethodInsnNode) n;
|
|
if (met.name.equals("forName")
|
|
&& met.owner.equals("java/lang/Class")
|
|
&& met.desc.equals("(Ljava/lang/String;)Ljava/lang/Class;")) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!found) {
|
|
throw new Exception("forName removed but shouldn't have");
|
|
}
|
|
found = false;
|
|
for (TryCatchBlockNode tcb : m.tryCatchBlocks) {
|
|
if (tcb.type.equals(ClassNotFoundException.class.getName().replaceAll("\\.", "/"))) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
throw new Exception("tryCatchBlocks removed but shouldn't have");
|
|
}
|
|
}
|
|
|
|
static class MemClassLoader extends ClassLoader {
|
|
|
|
private final Map<String, byte[]> classes;
|
|
private final Map<String, Class<?>> cache = new HashMap<>();
|
|
|
|
MemClassLoader(Map<String, byte[]> classes) {
|
|
super(null);
|
|
this.classes = classes;
|
|
}
|
|
|
|
@Override
|
|
public Class findClass(String name) throws ClassNotFoundException {
|
|
Class<?> clazz = cache.get(name);
|
|
if (clazz == null) {
|
|
byte[] b = classes.get(name);
|
|
if (b == null) {
|
|
return super.findClass(name);
|
|
} else {
|
|
clazz = defineClass(name, b, 0, b.length);
|
|
cache.put(name, clazz);
|
|
}
|
|
}
|
|
return clazz;
|
|
}
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
helper = Helper.newHelper();
|
|
if (helper == null) {
|
|
System.err.println("Test not run");
|
|
return;
|
|
}
|
|
|
|
testForName();
|
|
|
|
helper.generateDefaultModules();
|
|
helper.generateDefaultJModule("optim1", "java.se");
|
|
{
|
|
String[] userOptions = {"--class-optim=all:log=./class-optim-log.txt"};
|
|
|
|
Path imageDir = helper.generateDefaultImage(userOptions, "optim1").assertSuccess();
|
|
helper.checkImage(imageDir, "optim1", null, null);
|
|
}
|
|
|
|
/*{
|
|
Path dir = Paths.get("dir.log");
|
|
Files.createDirectory(dir);
|
|
String[] userOptions = {"--class-optim=all:log=" + dir.toString()};
|
|
helper.generateDefaultImage(userOptions, "optim1")
|
|
.assertFailure("java.io.FileNotFoundException: dir.log (Is a directory)");
|
|
}*/
|
|
/*{
|
|
String[] userOptions = {"--class-optim", "UNKNOWN"};
|
|
helper.generateDefaultImage(userOptions, "optim1").assertFailure("Unknown optimization");
|
|
}*/
|
|
{
|
|
String[] userOptions = {"--class-optim=forName-folding:log=./class-optim-log.txt"};
|
|
Path imageDir = helper.generateDefaultImage(userOptions, "optim1").assertSuccess();
|
|
helper.checkImage(imageDir, "optim1", null, null);
|
|
}
|
|
|
|
{
|
|
ControlFlowPlugin plugin = new ControlFlowPlugin();
|
|
PluginRepository.registerPlugin(plugin);
|
|
String[] userOptions = {"--test-optim"};
|
|
Path imageDir = helper.generateDefaultImage(userOptions, "optim1").assertSuccess();
|
|
helper.checkImage(imageDir, "optim1", null, null);
|
|
//System.out.println("Num methods analyzed " + provider.numMethods
|
|
// + "num blocks " + provider.numBlocks);
|
|
if (!plugin.called) {
|
|
throw new Exception("Plugin not called");
|
|
}
|
|
if (plugin.numMethods < 1000) {
|
|
throw new Exception("Not enough method called, should be "
|
|
+ "around 10000 but is " + plugin.numMethods);
|
|
}
|
|
if (plugin.numBlocks < 100000) {
|
|
throw new Exception("Not enough blocks, should be "
|
|
+ "around 640000 but is " + plugin.numMethods);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|