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