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 }