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 }