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  * @build tests.*
  87  * @run main JLinkOptimTest
  88  */
  89 public class JLinkOptimTest {
  90 
  91     private static final String EXPECTED = "expected";
  92     private static Helper helper;
  93 
  94     private static class ControlFlowProvider extends CmdResourcePluginProvider {
  95 
  96         private boolean called;
  97         private int numMethods;
  98         private int numBlocks;
  99 
 100         private class ControlFlowPlugin extends AsmPlugin {
 101 
 102             private ControlFlowPlugin() {
 103             }
 104 
 105             @Override
 106             public void visit(AsmPools pools, StringTable strings) throws IOException {
 107                 called = true;
 108                 for (AsmModulePool p : pools.getModulePools()) {
 109 
 110                     p.visitClassReaders((reader) -> {
 111                         ClassNode cn = new ClassNode();
 112                         if ((reader.getAccess() & Opcodes.ACC_INTERFACE) == 0) {
 113                             reader.accept(cn, ClassReader.EXPAND_FRAMES);
 114                             for (MethodNode m : cn.methods) {
 115                                 if ((m.access & Opcodes.ACC_ABSTRACT) == 0
 116                                         && (m.access & Opcodes.ACC_NATIVE) == 0) {
 117                                     numMethods += 1;
 118                                     try {
 119                                         ControlFlow f
 120                                                 = ControlFlow.createControlFlow(cn.name, m);
 121                                         for (Block b : f.getBlocks()) {
 122                                             numBlocks += 1;
 123                                             f.getClosure(b);
 124                                         }
 125                                     } catch (Throwable ex) {
 126                                         //ex.printStackTrace();
 127                                         throw new RuntimeException("Exception in "
 128                                                 + cn.name + "." + m.name, ex);
 129                                     }
 130                                 }
 131                             }
 132                         }
 133                         return null;
 134                     });
 135                 }
 136             }
 137 
 138             @Override
 139             public String getName() {
 140                 return NAME;
 141             }
 142 
 143         }
 144 
 145         private static final String NAME = "test-optim";
 146 
 147         ControlFlowProvider() {
 148             super(NAME, "");
 149         }
 150 
 151         @Override
 152         public ResourcePlugin[] newPlugins(String[] argument,
 153                 Map<String, String> options) throws IOException {
 154             return new ResourcePlugin[]{new ControlFlowPlugin()};
 155         }
 156 
 157         @Override
 158         public String getCategory() {
 159             return PluginProvider.TRANSFORMER;
 160         }
 161 
 162         @Override
 163         public String getToolArgument() {
 164             return null;
 165         }
 166 
 167         @Override
 168         public String getToolOption() {
 169             return NAME;
 170         }
 171 
 172         @Override
 173         public Map<String, String> getAdditionalOptions() {
 174             return null;
 175         }
 176     }
 177 
 178     private static void testForName() throws Exception {
 179         String moduleName = "optimplugin";
 180         Path src = Paths.get(System.getProperty("test.src")).resolve(moduleName);
 181         Path classes = helper.getJmodClassesDir().resolve(moduleName);
 182         JImageGenerator.compile(src, classes);
 183 
 184         FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
 185         Path root = fs.getPath("/modules/java.base");
 186         // Access module-info.class to be reused as fake module-info.class
 187         List<Resource> javabaseResources = new ArrayList<>();
 188         try (Stream<Path> stream = Files.walk(root)) {
 189             for (Iterator<Path> iterator = stream.iterator(); iterator.hasNext();) {
 190                 Path p = iterator.next();
 191                 if (Files.isRegularFile(p)) {
 192                     try {
 193                         javabaseResources.add(
 194                                 new Resource(p.toString().substring("/modules".length()),
 195                                         ByteBuffer.wrap(Files.readAllBytes(p))));
 196                     } catch (Exception ex) {
 197                         throw new RuntimeException(ex);
 198                     }
 199                 }
 200             }
 201         }
 202 
 203         //forName folding
 204         ResourcePool pool = new ResourcePoolImpl(ByteOrder.nativeOrder());
 205         byte[] content = Files.readAllBytes(classes.
 206                 resolve("optim").resolve("ForNameTestCase.class"));
 207         byte[] content2 = Files.readAllBytes(classes.
 208                 resolve("optim").resolve("AType.class"));
 209         byte[] mcontent = Files.readAllBytes(classes.resolve("module-info.class"));
 210 
 211         pool.addResource(new ResourcePool.Resource("/optimplugin/optim/ForNameTestCase.class", ByteBuffer.wrap(content)));
 212         pool.addResource(new ResourcePool.Resource("/optimplugin/optim/AType.class", ByteBuffer.wrap(content2)));
 213         pool.addResource(new ResourcePool.Resource("/optimplugin/module-info.class",
 214                 ByteBuffer.wrap(mcontent)));
 215 
 216         for (Resource r : javabaseResources) {
 217             pool.addResource(r);
 218         }
 219 
 220         OptimizationProvider prov = new OptimizationProvider();
 221         String[] a = {OptimizationProvider.FORNAME_REMOVAL};
 222         Map<String, String> optional = new HashMap<>();
 223         optional.put(OptimizationProvider.LOG_FILE, "forName.log");
 224         ResourcePlugin plug = prov.newPlugins(a, optional)[0];
 225         ResourcePool out = new ResourcePoolImpl(ByteOrder.nativeOrder());
 226         plug.visit(pool, out, new StringTable() {
 227 
 228             @Override
 229             public int addString(String str) {
 230                 throw new UnsupportedOperationException("Not supported yet.");
 231             }
 232 
 233             @Override
 234             public String getString(int id) {
 235                 throw new UnsupportedOperationException("Not supported yet.");
 236             }
 237         });
 238 
 239         Resource result = out.getResources().iterator().next();
 240 
 241         ClassReader optimReader = new ClassReader(result.getByteArray());
 242         ClassNode optimClass = new ClassNode();
 243         optimReader.accept(optimClass, ClassReader.EXPAND_FRAMES);
 244 
 245         if (!optimClass.name.equals("optim/ForNameTestCase")) {
 246             throw new Exception("Invalid class " + optimClass.name);
 247         }
 248         if (optimClass.methods.size() < 2) {
 249             throw new Exception("Not enough methods in new class");
 250         }
 251         for (MethodNode mn : optimClass.methods) {
 252             if (!mn.name.contains("forName") && !mn.name.contains("<clinit>")) {
 253                 continue;
 254             }
 255             if (mn.name.startsWith("negative")) {
 256                 checkForName(mn);
 257             } else {
 258                 checkNoForName(mn);
 259             }
 260         }
 261         Map<String, byte[]> newClasses = new HashMap<>();
 262         newClasses.put("optim.ForNameTestCase", result.getByteArray());
 263         newClasses.put("optim.AType", content2);
 264         MemClassLoader loader = new MemClassLoader(newClasses);
 265         Class<?> loaded = loader.loadClass("optim.ForNameTestCase");
 266         if (loaded.getDeclaredMethods().length < 2) {
 267             throw new Exception("Not enough methods in new class");
 268         }
 269         for (Method m : loaded.getDeclaredMethods()) {
 270             if (m.getName().contains("Exception")) {
 271                 try {
 272                     m.invoke(null);
 273                 } catch (Exception ex) {
 274                     //ex.getCause().printStackTrace();
 275                     if (!ex.getCause().getMessage().equals(EXPECTED)) {
 276                         throw new Exception("Unexpected exception " + ex);
 277                     }
 278                 }
 279             } else {
 280                 if (!m.getName().startsWith("negative")) {
 281                     Class<?> clazz = (Class<?>) m.invoke(null);
 282                     if (clazz != String.class && clazz != loader.findClass("optim.AType")) {
 283                         throw new Exception("Invalid class " + clazz);
 284                     }
 285                 }
 286             }
 287         }
 288     }
 289 
 290     private static void checkNoForName(MethodNode m) throws Exception {
 291         Iterator<AbstractInsnNode> it = m.instructions.iterator();
 292         while (it.hasNext()) {
 293             AbstractInsnNode n = it.next();
 294             if (n instanceof MethodInsnNode) {
 295                 MethodInsnNode met = (MethodInsnNode) n;
 296                 if (met.name.equals("forName")
 297                         && met.owner.equals("java/lang/Class")
 298                         && met.desc.equals("(Ljava/lang/String;)Ljava/lang/Class;")) {
 299                     throw new Exception("forName not removed in " + m.name);
 300                 }
 301             }
 302         }
 303         for (TryCatchBlockNode tcb : m.tryCatchBlocks) {
 304             if (tcb.type.equals(ClassNotFoundException.class.getName().replaceAll("\\.", "/"))) {
 305                 throw new Exception("ClassNotFoundException Block not removed for " + m.name);
 306             }
 307         }
 308     }
 309 
 310     private static void checkForName(MethodNode m) throws Exception {
 311         Iterator<AbstractInsnNode> it = m.instructions.iterator();
 312         boolean found = false;
 313         while (it.hasNext()) {
 314             AbstractInsnNode n = it.next();
 315             if (n instanceof MethodInsnNode) {
 316                 MethodInsnNode met = (MethodInsnNode) n;
 317                 if (met.name.equals("forName")
 318                         && met.owner.equals("java/lang/Class")
 319                         && met.desc.equals("(Ljava/lang/String;)Ljava/lang/Class;")) {
 320                     found = true;
 321                     break;
 322                 }
 323             }
 324         }
 325         if (!found) {
 326             throw new Exception("forName removed but shouldn't have");
 327         }
 328         found = false;
 329         for (TryCatchBlockNode tcb : m.tryCatchBlocks) {
 330             if (tcb.type.equals(ClassNotFoundException.class.getName().replaceAll("\\.", "/"))) {
 331                 found = true;
 332                 break;
 333             }
 334         }
 335         if (!found) {
 336             throw new Exception("tryCatchBlocks removed but shouldn't have");
 337         }
 338     }
 339 
 340     static class MemClassLoader extends ClassLoader {
 341 
 342         private final Map<String, byte[]> classes;
 343         private final Map<String, Class<?>> cache = new HashMap<>();
 344         MemClassLoader(Map<String, byte[]> classes) {
 345             super(null);
 346             this.classes = classes;
 347         }
 348 
 349         @Override
 350         public Class findClass(String name) throws ClassNotFoundException {
 351             Class<?> clazz = cache.get(name);
 352             if (clazz == null) {
 353                 byte[] b = classes.get(name);
 354                 if (b == null) {
 355                     return super.findClass(name);
 356                 } else {
 357                     clazz = defineClass(name, b, 0, b.length);
 358                     cache.put(name, clazz);
 359                 }
 360             }
 361             return clazz;
 362         }
 363     }
 364 
 365     public static void main(String[] args) throws Exception {
 366         helper = Helper.newHelper();
 367         if (helper == null) {
 368             System.err.println("Test not run");
 369             return;
 370         }
 371 
 372         testForName();
 373 
 374         helper.generateDefaultModules();
 375         helper.generateDefaultJModule("optim1", "java.se");
 376         {
 377             String[] userOptions = {"--class-optim", "all",
 378                 "--class-optim-log-file", "./class-optim-log.txt"};
 379 
 380             Path imageDir = helper.generateDefaultImage(userOptions, "optim1").assertSuccess();
 381             helper.checkImage(imageDir, "optim1", null, null);
 382         }
 383 
 384         /*{
 385          Path dir = Paths.get("dir.log");
 386          Files.createDirectory(dir);
 387          String[] userOptions = {"--class-optim", "all", "--class-optim-log-file", dir.toString()};
 388          helper.generateDefaultImage(userOptions, "optim1")
 389          .assertFailure("java.io.FileNotFoundException: dir.log (Is a directory)");
 390          }*/
 391         /*{
 392          String[] userOptions = {"--class-optim", "UNKNOWN"};
 393          helper.generateDefaultImage(userOptions, "optim1").assertFailure("Unknown optimization");
 394          }*/
 395         {
 396             String[] userOptions = {"--class-optim", "forName-folding",
 397                 "--class-optim-log-file", "./class-optim-log.txt"};
 398             Path imageDir = helper.generateDefaultImage(userOptions, "optim1").assertSuccess();
 399             helper.checkImage(imageDir, "optim1", null, null);
 400         }
 401 
 402         {
 403             ControlFlowProvider provider = new ControlFlowProvider();
 404             ImagePluginProviderRepository.registerPluginProvider(provider);
 405             String[] userOptions = {"--test-optim"};
 406             Path imageDir = helper.generateDefaultImage(userOptions, "optim1").assertSuccess();
 407             helper.checkImage(imageDir, "optim1", null, null);
 408             //System.out.println("Num methods analyzed " + provider.numMethods
 409             //        + "num blocks " + provider.numBlocks);
 410             if (!provider.called) {
 411                 throw new Exception("Plugin not called");
 412             }
 413             if (provider.numMethods < 1000) {
 414                 throw new Exception("Not enough method called,  should be "
 415                         + "around 10000 but is " + provider.numMethods);
 416             }
 417             if (provider.numBlocks < 100000) {
 418                 throw new Exception("Not enough blocks,  should be "
 419                         + "around 640000 but is " + provider.numMethods);
 420             }
 421         }
 422     }
 423 
 424 }