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 }