| |
| 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); |
| } |
| } |
| } |
| |
| } |