1 
   2 import java.lang.reflect.Method;
   3 import java.net.URI;
   4 import java.nio.file.FileSystem;
   5 import java.nio.file.FileSystems;
   6 import java.nio.file.Files;
   7 import java.nio.file.Path;
   8 import java.nio.file.Paths;
   9 import java.util.ArrayList;
  10 import java.util.Collections;
  11 import java.util.HashMap;
  12 import java.util.Iterator;
  13 import java.util.List;
  14 import java.util.Map;
  15 import java.util.stream.Stream;
  16 import jdk.internal.org.objectweb.asm.ClassReader;
  17 import jdk.internal.org.objectweb.asm.Opcodes;
  18 import jdk.internal.org.objectweb.asm.tree.AbstractInsnNode;
  19 import jdk.internal.org.objectweb.asm.tree.ClassNode;
  20 import jdk.internal.org.objectweb.asm.tree.MethodInsnNode;
  21 import jdk.internal.org.objectweb.asm.tree.MethodNode;
  22 import jdk.internal.org.objectweb.asm.tree.TryCatchBlockNode;
  23 import jdk.tools.jlink.internal.PluginRepository;
  24 import jdk.tools.jlink.internal.ModulePoolImpl;
  25 import jdk.tools.jlink.internal.plugins.OptimizationPlugin;
  26 import jdk.tools.jlink.internal.plugins.asm.AsmModulePool;
  27 import jdk.tools.jlink.internal.plugins.asm.AsmPlugin;
  28 import jdk.tools.jlink.internal.plugins.asm.AsmPools;
  29 import jdk.tools.jlink.internal.plugins.optim.ControlFlow;
  30 import jdk.tools.jlink.internal.plugins.optim.ControlFlow.Block;
  31 import jdk.tools.jlink.plugin.ModuleEntry;
  32 import jdk.tools.jlink.plugin.ModulePool;
  33 
  34 import tests.Helper;
  35 import tests.JImageGenerator;
  36 
  37 /*
  38  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
  39  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  40  *
  41  * This code is free software; you can redistribute it and/or modify it
  42  * under the terms of the GNU General Public License version 2 only, as
  43  * published by the Free Software Foundation.
  44  *
  45  * This code is distributed in the hope that it will be useful, but WITHOUT
  46  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  47  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  48  * version 2 for more details (a copy is included in the LICENSE file that
  49  * accompanied this code).
  50  *
  51  * You should have received a copy of the GNU General Public License version
  52  * 2 along with this work; if not, write to the Free Software Foundation,
  53  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  54  *
  55  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  56  * or visit www.oracle.com if you need additional information or have any
  57  * questions.
  58  */
  59 
  60  /*
  61  * @test
  62  * @summary Test image creation with class optimization
  63  * @author Jean-Francois Denise
  64  * @library ../lib
  65  * @modules java.base/jdk.internal.jimage
  66  *          jdk.jdeps/com.sun.tools.classfile
  67  *          jdk.jlink/jdk.tools.jlink.internal
  68  *          jdk.jlink/jdk.tools.jmod
  69  *          jdk.jlink/jdk.tools.jimage
  70  *          jdk.jlink/jdk.tools.jlink.internal.plugins
  71  *          jdk.jlink/jdk.tools.jlink.internal.plugins.asm
  72  *          jdk.jlink/jdk.tools.jlink.internal.plugins.optim
  73  *          java.base/jdk.internal.org.objectweb.asm
  74  *          java.base/jdk.internal.org.objectweb.asm.tree
  75  *          java.base/jdk.internal.org.objectweb.asm.util
  76  *          jdk.compiler
  77  * @build tests.*
  78  * @run main JLinkOptimTest
  79  */
  80 public class JLinkOptimTest {
  81 
  82     private static final String EXPECTED = "expected";
  83     private static Helper helper;
  84 
  85     public static class ControlFlowPlugin extends AsmPlugin {
  86 
  87         private boolean called;
  88         private int numMethods;
  89         private int numBlocks;
  90 
  91         private static final String NAME = "test-optim";
  92 
  93         private ControlFlowPlugin() {
  94         }
  95 
  96         @Override
  97         public void visit(AsmPools pools) {
  98             called = true;
  99             for (AsmModulePool p : pools.getModulePools()) {
 100 
 101                 p.visitClassReaders((reader) -> {
 102                     ClassNode cn = new ClassNode();
 103                     if ((reader.getAccess() & Opcodes.ACC_INTERFACE) == 0) {
 104                         reader.accept(cn, ClassReader.EXPAND_FRAMES);
 105                         for (MethodNode m : cn.methods) {
 106                             if ((m.access & Opcodes.ACC_ABSTRACT) == 0
 107                                     && (m.access & Opcodes.ACC_NATIVE) == 0) {
 108                                 numMethods += 1;
 109                                 try {
 110                                     ControlFlow f
 111                                             = ControlFlow.createControlFlow(cn.name, m);
 112                                     for (Block b : f.getBlocks()) {
 113                                         numBlocks += 1;
 114                                         f.getClosure(b);
 115                                     }
 116                                 } catch (Throwable ex) {
 117                                     //ex.printStackTrace();
 118                                     throw new RuntimeException("Exception in "
 119                                             + cn.name + "." + m.name, ex);
 120                                 }
 121                             }
 122                         }
 123                     }
 124                     return null;
 125                 });
 126             }
 127         }
 128 
 129         @Override
 130         public String getName() {
 131             return NAME;
 132         }
 133     }
 134 
 135     private static void testForName() throws Exception {
 136         String moduleName = "optimplugin";
 137         Path src = Paths.get(System.getProperty("test.src")).resolve(moduleName);
 138         Path classes = helper.getJmodClassesDir().resolve(moduleName);
 139         JImageGenerator.compile(src, classes);
 140 
 141         FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
 142         Path root = fs.getPath("/modules/java.base");
 143         // Access module-info.class to be reused as fake module-info.class
 144         List<ModuleEntry> javabaseResources = new ArrayList<>();
 145         try (Stream<Path> stream = Files.walk(root)) {
 146             for (Iterator<Path> iterator = stream.iterator(); iterator.hasNext();) {
 147                 Path p = iterator.next();
 148                 if (Files.isRegularFile(p)) {
 149                     try {
 150                         javabaseResources.add(ModuleEntry.create(p.toString().
 151                                 substring("/modules".length()), Files.readAllBytes(p)));
 152                     } catch (Exception ex) {
 153                         throw new RuntimeException(ex);
 154                     }
 155                 }
 156             }
 157         }
 158 
 159         //forName folding
 160         ModulePoolImpl pool = new ModulePoolImpl();
 161         byte[] content = Files.readAllBytes(classes.
 162                 resolve("optim").resolve("ForNameTestCase.class"));
 163         byte[] content2 = Files.readAllBytes(classes.
 164                 resolve("optim").resolve("AType.class"));
 165         byte[] mcontent = Files.readAllBytes(classes.resolve("module-info.class"));
 166 
 167         pool.add(ModuleEntry.create("/optimplugin/optim/ForNameTestCase.class", content));
 168         pool.add(ModuleEntry.create("/optimplugin/optim/AType.class", content2));
 169         pool.add(ModuleEntry.create("/optimplugin/module-info.class", mcontent));
 170 
 171         for (ModuleEntry r : javabaseResources) {
 172             pool.add(r);
 173         }
 174 
 175         OptimizationPlugin plugin = new OptimizationPlugin();
 176         Map<String, String> optional = new HashMap<>();
 177         optional.put(OptimizationPlugin.NAME, OptimizationPlugin.FORNAME_REMOVAL);
 178         optional.put(OptimizationPlugin.LOG, "forName.log");
 179         plugin.configure(optional);
 180         ModulePool out = new ModulePoolImpl();
 181         plugin.visit(pool, out);
 182 
 183         ModuleEntry result = out.entries().iterator().next();
 184 
 185         ClassReader optimReader = new ClassReader(result.getBytes());
 186         ClassNode optimClass = new ClassNode();
 187         optimReader.accept(optimClass, ClassReader.EXPAND_FRAMES);
 188 
 189         if (!optimClass.name.equals("optim/ForNameTestCase")) {
 190             throw new Exception("Invalid class " + optimClass.name);
 191         }
 192         if (optimClass.methods.size() < 2) {
 193             throw new Exception("Not enough methods in new class");
 194         }
 195         for (MethodNode mn : optimClass.methods) {
 196             if (!mn.name.contains("forName") && !mn.name.contains("<clinit>")) {
 197                 continue;
 198             }
 199             if (mn.name.startsWith("negative")) {
 200                 checkForName(mn);
 201             } else {
 202                 checkNoForName(mn);
 203             }
 204         }
 205         Map<String, byte[]> newClasses = new HashMap<>();
 206         newClasses.put("optim.ForNameTestCase", result.getBytes());
 207         newClasses.put("optim.AType", content2);
 208         MemClassLoader loader = new MemClassLoader(newClasses);
 209         Class<?> loaded = loader.loadClass("optim.ForNameTestCase");
 210         if (loaded.getDeclaredMethods().length < 2) {
 211             throw new Exception("Not enough methods in new class");
 212         }
 213         for (Method m : loaded.getDeclaredMethods()) {
 214             if (m.getName().contains("Exception")) {
 215                 try {
 216                     m.invoke(null);
 217                 } catch (Exception ex) {
 218                     //ex.getCause().printStackTrace();
 219                     if (!ex.getCause().getMessage().equals(EXPECTED)) {
 220                         throw new Exception("Unexpected exception " + ex);
 221                     }
 222                 }
 223             } else if (!m.getName().startsWith("negative")) {
 224                 Class<?> clazz = (Class<?>) m.invoke(null);
 225                 if (clazz != String.class && clazz != loader.findClass("optim.AType")) {
 226                     throw new Exception("Invalid class " + clazz);
 227                 }
 228             }
 229         }
 230     }
 231 
 232     private static void checkNoForName(MethodNode m) throws Exception {
 233         Iterator<AbstractInsnNode> it = m.instructions.iterator();
 234         while (it.hasNext()) {
 235             AbstractInsnNode n = it.next();
 236             if (n instanceof MethodInsnNode) {
 237                 MethodInsnNode met = (MethodInsnNode) n;
 238                 if (met.name.equals("forName")
 239                         && met.owner.equals("java/lang/Class")
 240                         && met.desc.equals("(Ljava/lang/String;)Ljava/lang/Class;")) {
 241                     throw new Exception("forName not removed in " + m.name);
 242                 }
 243             }
 244         }
 245         for (TryCatchBlockNode tcb : m.tryCatchBlocks) {
 246             if (tcb.type.equals(ClassNotFoundException.class.getName().replaceAll("\\.", "/"))) {
 247                 throw new Exception("ClassNotFoundException Block not removed for " + m.name);
 248             }
 249         }
 250     }
 251 
 252     private static void checkForName(MethodNode m) throws Exception {
 253         Iterator<AbstractInsnNode> it = m.instructions.iterator();
 254         boolean found = false;
 255         while (it.hasNext()) {
 256             AbstractInsnNode n = it.next();
 257             if (n instanceof MethodInsnNode) {
 258                 MethodInsnNode met = (MethodInsnNode) n;
 259                 if (met.name.equals("forName")
 260                         && met.owner.equals("java/lang/Class")
 261                         && met.desc.equals("(Ljava/lang/String;)Ljava/lang/Class;")) {
 262                     found = true;
 263                     break;
 264                 }
 265             }
 266         }
 267         if (!found) {
 268             throw new Exception("forName removed but shouldn't have");
 269         }
 270         found = false;
 271         for (TryCatchBlockNode tcb : m.tryCatchBlocks) {
 272             if (tcb.type.equals(ClassNotFoundException.class.getName().replaceAll("\\.", "/"))) {
 273                 found = true;
 274                 break;
 275             }
 276         }
 277         if (!found) {
 278             throw new Exception("tryCatchBlocks removed but shouldn't have");
 279         }
 280     }
 281 
 282     static class MemClassLoader extends ClassLoader {
 283 
 284         private final Map<String, byte[]> classes;
 285         private final Map<String, Class<?>> cache = new HashMap<>();
 286 
 287         MemClassLoader(Map<String, byte[]> classes) {
 288             super(null);
 289             this.classes = classes;
 290         }
 291 
 292         @Override
 293         public Class findClass(String name) throws ClassNotFoundException {
 294             Class<?> clazz = cache.get(name);
 295             if (clazz == null) {
 296                 byte[] b = classes.get(name);
 297                 if (b == null) {
 298                     return super.findClass(name);
 299                 } else {
 300                     clazz = defineClass(name, b, 0, b.length);
 301                     cache.put(name, clazz);
 302                 }
 303             }
 304             return clazz;
 305         }
 306     }
 307 
 308     public static void main(String[] args) throws Exception {
 309         helper = Helper.newHelper();
 310         if (helper == null) {
 311             System.err.println("Test not run");
 312             return;
 313         }
 314 
 315         testForName();
 316 
 317         helper.generateDefaultModules();
 318         helper.generateDefaultJModule("optim1", "java.se");
 319         {
 320             String[] userOptions = {"--class-optim=all:log=./class-optim-log.txt"};
 321 
 322             Path imageDir = helper.generateDefaultImage(userOptions, "optim1").assertSuccess();
 323             helper.checkImage(imageDir, "optim1", null, null);
 324         }
 325 
 326         {
 327             String[] userOptions = {"--class-optim=forName-folding:log=./class-optim-log.txt"};
 328             Path imageDir = helper.generateDefaultImage(userOptions, "optim1").assertSuccess();
 329             helper.checkImage(imageDir, "optim1", null, null);
 330         }
 331 
 332         {
 333             ControlFlowPlugin plugin = new ControlFlowPlugin();
 334             PluginRepository.registerPlugin(plugin);
 335             String[] userOptions = {"--test-optim"};
 336             Path imageDir = helper.generateDefaultImage(userOptions, "optim1").assertSuccess();
 337             helper.checkImage(imageDir, "optim1", null, null);
 338             //System.out.println("Num methods analyzed " + provider.numMethods
 339             //        + "num blocks " + provider.numBlocks);
 340             if (!plugin.called) {
 341                 throw new Exception("Plugin not called");
 342             }
 343             if (plugin.numMethods < 1000) {
 344                 throw new Exception("Not enough method called,  should be "
 345                         + "around 10000 but is " + plugin.numMethods);
 346             }
 347             if (plugin.numBlocks < 100000) {
 348                 throw new Exception("Not enough blocks,  should be "
 349                         + "around 640000 but is " + plugin.numMethods);
 350             }
 351         }
 352     }
 353 
 354 }