1 /*
   2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * Asm plugin testing.
  26  * @test
  27  * @summary Test resource transformation.
  28  * @author Andrei Eremeev
  29  * @modules java.base/jdk.internal.org.objectweb.asm
  30  *          jdk.jlink/jdk.tools.jlink.internal
  31  *          jdk.jlink/jdk.tools.jlink.internal.plugins.asm
  32  *          jdk.jdeps/com.sun.tools.classfile
  33  * @build AsmPluginTestBase
  34  * @run main AddForgetResourcesTest
  35 */
  36 
  37 import java.io.ByteArrayInputStream;
  38 import java.io.IOException;
  39 import java.nio.ByteBuffer;
  40 import java.util.Collection;
  41 import java.util.List;
  42 import java.util.Map;
  43 
  44 import com.sun.tools.classfile.ClassFile;
  45 import com.sun.tools.classfile.Method;
  46 import java.io.UncheckedIOException;
  47 import java.util.Set;
  48 import jdk.internal.org.objectweb.asm.ClassReader;
  49 import jdk.internal.org.objectweb.asm.ClassVisitor;
  50 import jdk.internal.org.objectweb.asm.ClassWriter;
  51 import jdk.internal.org.objectweb.asm.Opcodes;
  52 import jdk.tools.jlink.internal.plugins.asm.AsmGlobalPool;
  53 import jdk.tools.jlink.internal.plugins.asm.AsmModulePool;
  54 import jdk.tools.jlink.internal.plugins.asm.AsmPool.ResourceFile;
  55 import jdk.tools.jlink.internal.plugins.asm.AsmPool.WritableClassPool;
  56 import jdk.tools.jlink.internal.plugins.asm.AsmPool.WritableResourcePool;
  57 import jdk.tools.jlink.internal.plugins.asm.AsmPools;
  58 import jdk.tools.jlink.plugin.ModuleEntry;
  59 import jdk.tools.jlink.plugin.ModulePool;
  60 
  61 public class AddForgetResourcesTest extends AsmPluginTestBase {
  62 
  63     public static void main(String[] args) throws Exception {
  64         if (!isImageBuild()) {
  65             System.err.println("Test not run. Not image build.");
  66             return;
  67         }
  68         new AddForgetResourcesTest().test();
  69     }
  70 
  71     @Override
  72     public void test() throws Exception {
  73         TestPlugin[] plugins = new TestPlugin[] {
  74                 new AddClassesPlugin(),
  75                 new AddResourcesPlugin(),
  76                 new ReplaceClassesPlugin(),
  77                 new ReplaceResourcesPlugin(),
  78                 new ForgetClassesPlugin(),
  79                 new ForgetResourcesPlugin(),
  80                 new AddForgetClassesPlugin(),
  81                 new AddForgetResourcesPlugin(),
  82                 new ComboPlugin()
  83         };
  84         for (TestPlugin p : plugins) {
  85             ModulePool out = p.visit(getPool());
  86             p.test(getPool(), out);
  87         }
  88     }
  89 
  90     private static final String SUFFIX = "HELLOWORLD";
  91 
  92     private static class RenameClassVisitor extends ClassVisitor {
  93 
  94         public RenameClassVisitor(ClassWriter cv) {
  95             super(Opcodes.ASM5, cv);
  96         }
  97 
  98         @Override
  99         public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
 100             super.visit(version, access, name + SUFFIX, signature, superName, interfaces);
 101         }
 102     }
 103 
 104     private static class AddMethodClassVisitor extends ClassVisitor {
 105 
 106         public AddMethodClassVisitor(ClassWriter cv) {
 107             super(Opcodes.ASM5, cv);
 108         }
 109 
 110         @Override
 111         public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
 112             this.visitMethod(0, SUFFIX, "()V", null, null);
 113             super.visit(version, access, name, signature, superName, interfaces);
 114         }
 115     }
 116 
 117     private class AddClassesPlugin extends TestPlugin {
 118 
 119         private int expected = 0;
 120 
 121         @Override
 122         public void visit() {
 123             AsmPools pools = getPools();
 124             AsmGlobalPool globalPool = pools.getGlobalPool();
 125             WritableClassPool transformedClasses = globalPool.getTransformedClasses();
 126             expected = globalPool.getClasses().size();
 127             for (ModuleEntry res : globalPool.getClasses()) {
 128                 ClassReader reader = globalPool.getClassReader(res);
 129                 String className = reader.getClassName();
 130                 if (!className.endsWith("module-info")) {
 131                     ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
 132                     reader.accept(new RenameClassVisitor(writer), ClassReader.EXPAND_FRAMES);
 133                     transformedClasses.addClass(writer);
 134                     ++expected;
 135                 }
 136             }
 137         }
 138 
 139         @Override
 140         public void test(ModulePool inResources, ModulePool outResources) {
 141             Collection<ModuleEntry> inClasses = extractClasses(inResources);
 142             Collection<ModuleEntry> outClasses = extractClasses(outResources);
 143             if (expected != outClasses.size()) {
 144                 throw new AssertionError("Classes were not added. Expected: " + expected
 145                         + ", got: " + outClasses.size());
 146             }
 147             for (ModuleEntry in : inClasses) {
 148                 String path = in.getPath();
 149                 if (!outClasses.contains(in)) {
 150                     throw new AssertionError("Class not found: " + path);
 151                 }
 152                 if (path.endsWith("module-info.class")) {
 153                     continue;
 154                 }
 155                 String modifiedPath = path.replace(".class", SUFFIX + ".class");
 156                 if (!outClasses.contains(ModuleEntry.create(modifiedPath, new byte[0]))) {
 157                     throw new AssertionError("Class not found: " + modifiedPath);
 158                 }
 159             }
 160         }
 161     }
 162 
 163     private class AddResourcesPlugin extends TestPlugin {
 164 
 165         @Override
 166         public void visit() {
 167             AsmPools pools = getPools();
 168             AsmGlobalPool globalPool = pools.getGlobalPool();
 169             for (ModuleEntry res : globalPool.getResourceFiles()) {
 170                 String path = res.getPath();
 171                 String moduleName = getModule(path);
 172                 AsmModulePool modulePool = pools.getModulePool(moduleName);
 173                 WritableResourcePool resourcePool = modulePool.getTransformedResourceFiles();
 174                 resourcePool.addResourceFile(new ResourceFile(removeModule(res.getPath()) + SUFFIX,
 175                         res.getBytes()));
 176             }
 177         }
 178 
 179         @Override
 180         public void test(ModulePool in, ModulePool out) throws Exception {
 181             Collection<ModuleEntry> inResources = extractResources(in);
 182             Collection<ModuleEntry> outResources = extractResources(out);
 183             if (2 * inResources.size() != outResources.size()) {
 184                 throw new AssertionError("Classes were not added. Expected: " + (2 * inResources.size())
 185                         + ", got: " + outResources.size());
 186             }
 187             for (ModuleEntry r : inResources) {
 188                 String path = r.getPath();
 189                 if (!outResources.contains(r)) {
 190                     throw new AssertionError("Class not found: " + path);
 191                 }
 192                 String modifiedPath = path + SUFFIX;
 193                 if (!outResources.contains(ModuleEntry.create(modifiedPath, new byte[0]))) {
 194                     throw new AssertionError("Class not found: " + modifiedPath);
 195                 }
 196             }
 197         }
 198     }
 199 
 200     private class ReplaceClassesPlugin extends TestPlugin {
 201 
 202         @Override
 203         public void visit() {
 204             AsmPools pools = getPools();
 205             AsmGlobalPool globalPool = pools.getGlobalPool();
 206             WritableClassPool transformedClasses = globalPool.getTransformedClasses();
 207             for (ModuleEntry res : globalPool.getClasses()) {
 208                 ClassReader reader = globalPool.getClassReader(res);
 209                 ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
 210                 reader.accept(new AddMethodClassVisitor(writer), ClassReader.EXPAND_FRAMES);
 211                 transformedClasses.addClass(writer);
 212             }
 213         }
 214 
 215         @Override
 216         public void test(ModulePool inResources, ModulePool outResources) throws Exception {
 217             Collection<ModuleEntry> inClasses = extractClasses(inResources);
 218             Collection<ModuleEntry> outClasses = extractClasses(outResources);
 219             if (inClasses.size() != outClasses.size()) {
 220                 throw new AssertionError("Number of classes. Expected: " + (inClasses.size())
 221                         + ", got: " + outClasses.size());
 222             }
 223             for (ModuleEntry out : outClasses) {
 224                 String path = out.getPath();
 225                 if (!inClasses.contains(out)) {
 226                     throw new AssertionError("Class not found: " + path);
 227                 }
 228                 ClassFile cf = ClassFile.read(new ByteArrayInputStream(out.getBytes()));
 229                 if (path.endsWith("module-info.class")) {
 230                     continue;
 231                 }
 232                 boolean failed = true;
 233                 for (Method m : cf.methods) {
 234                     if (m.getName(cf.constant_pool).equals(SUFFIX)) {
 235                         failed = false;
 236                     }
 237                 }
 238                 if (failed) {
 239                     throw new AssertionError("Not found method with name " + SUFFIX + " in class " + path);
 240                 }
 241             }
 242         }
 243     }
 244 
 245     private class ReplaceResourcesPlugin extends TestPlugin {
 246 
 247         @Override
 248         public void visit() {
 249             AsmPools pools = getPools();
 250             AsmGlobalPool globalPool = pools.getGlobalPool();
 251             for (ModuleEntry res : globalPool.getResourceFiles()) {
 252                 String path = res.getPath();
 253                 AsmModulePool modulePool = pools.getModulePool(getModule(path));
 254                 modulePool.getTransformedResourceFiles().addResourceFile(new ResourceFile(removeModule(path),
 255                         "HUI".getBytes()));
 256             }
 257         }
 258 
 259         @Override
 260         public void test(ModulePool in, ModulePool out) throws Exception {
 261             Collection<ModuleEntry> inResources = extractResources(in);
 262             Collection<ModuleEntry> outResources = extractResources(out);
 263             if (inResources.size() != outResources.size()) {
 264                 throw new AssertionError("Number of resources. Expected: " + inResources.size()
 265                         + ", got: " + outResources.size());
 266             }
 267             for (ModuleEntry r : outResources) {
 268                 String path = r.getPath();
 269                 if (!inResources.contains(r)) {
 270                     throw new AssertionError("Resource not found: " + path);
 271                 }
 272                 String content = new String(r.getBytes());
 273                 if (!"HUI".equals(content)) {
 274                     throw new AssertionError("Content expected: 'HUI', got: " + content);
 275                 }
 276             }
 277         }
 278     }
 279 
 280     private class ForgetClassesPlugin extends TestPlugin {
 281 
 282         private int expected = 0;
 283 
 284         @Override
 285         public void visit() {
 286             AsmPools pools = getPools();
 287             AsmGlobalPool globalPool = pools.getGlobalPool();
 288             WritableClassPool transformedClasses = globalPool.getTransformedClasses();
 289             int i = 0;
 290             for (ModuleEntry res : globalPool.getClasses()) {
 291                 String path = removeModule(res.getPath());
 292                 String className = path.replace(".class", "");
 293                 if ((i & 1) == 0 && !className.endsWith("module-info")) {
 294                     transformedClasses.forgetClass(className);
 295                 } else {
 296                     ++expected;
 297                 }
 298                 i ^= 1;
 299             }
 300         }
 301 
 302         @Override
 303         public void test(ModulePool inResources, ModulePool outResources) throws Exception {
 304             Collection<ModuleEntry> outClasses = extractClasses(outResources);
 305             if (expected != outClasses.size()) {
 306                 throw new AssertionError("Number of classes. Expected: " + expected +
 307                         ", got: " + outClasses.size());
 308             }
 309         }
 310     }
 311 
 312     private class ForgetResourcesPlugin extends TestPlugin {
 313 
 314         private int expectedAmount = 0;
 315 
 316         @Override
 317         public void visit() {
 318             AsmPools pools = getPools();
 319             AsmGlobalPool globalPool = pools.getGlobalPool();
 320             int i = 0;
 321             for (ModuleEntry res : globalPool.getResourceFiles()) {
 322                 String path = res.getPath();
 323                 if (!path.contains("META-INF/services")) {
 324                     if ((i & 1) == 0) {
 325                         AsmModulePool modulePool = pools.getModulePool(getModule(path));
 326                         modulePool.getTransformedResourceFiles().forgetResourceFile(removeModule(res.getPath()));
 327                     } else {
 328                         ++expectedAmount;
 329                     }
 330                     i ^= 1;
 331                 } else {
 332                     ++expectedAmount;
 333                 }
 334             }
 335         }
 336 
 337         @Override
 338         public void test(ModulePool in, ModulePool out) throws Exception {
 339             Collection<ModuleEntry> outResources = extractResources(out);
 340             if (expectedAmount != outResources.size()) {
 341                 throw new AssertionError("Number of classes. Expected: " + expectedAmount
 342                         + ", got: " + outResources.size());
 343             }
 344         }
 345     }
 346 
 347     private class AddForgetClassesPlugin extends TestPlugin {
 348 
 349         private int expected = 0;
 350 
 351         @Override
 352         public void visit() {
 353             AsmPools pools = getPools();
 354             AsmGlobalPool globalPool = pools.getGlobalPool();
 355             WritableClassPool transformedClasses = globalPool.getTransformedClasses();
 356             int i = 0;
 357             for (ModuleEntry res : globalPool.getClasses()) {
 358                 ClassReader reader = globalPool.getClassReader(res);
 359                 String className = reader.getClassName();
 360                 ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
 361                 if (!className.endsWith("module-info")) {
 362                     reader.accept(new RenameClassVisitor(writer), ClassReader.EXPAND_FRAMES);
 363                     transformedClasses.addClass(writer);
 364                     ++expected;
 365                 }
 366 
 367                 if ((i & 1) == 0 && !className.endsWith("module-info")) {
 368                     transformedClasses.forgetClass(className);
 369                 } else {
 370                     ++expected;
 371                 }
 372                 i ^= 1;
 373             }
 374         }
 375 
 376         @Override
 377         public void test(ModulePool inResources, ModulePool outResources) throws Exception {
 378             Collection<ModuleEntry> outClasses = extractClasses(outResources);
 379             if (expected != outClasses.size()) {
 380                 throw new AssertionError("Number of classes. Expected: " + expected
 381                         + ", got: " + outClasses.size());
 382             }
 383         }
 384     }
 385 
 386     private class AddForgetResourcesPlugin extends TestPlugin {
 387 
 388         private int expectedAmount = 0;
 389 
 390         @Override
 391         public void visit() {
 392             AsmPools pools = getPools();
 393             AsmGlobalPool globalPool = pools.getGlobalPool();
 394             int i = 0;
 395             for (ModuleEntry res : globalPool.getResourceFiles()) {
 396                 String path = res.getPath();
 397                 String moduleName = getModule(path);
 398                 if (!path.contains("META-INF")) {
 399                     AsmModulePool modulePool = pools.getModulePool(moduleName);
 400                     WritableResourcePool transformedResourceFiles = modulePool.getTransformedResourceFiles();
 401                     String newPath = removeModule(path) + SUFFIX;
 402                     transformedResourceFiles.addResourceFile(new ResourceFile(newPath, res.getBytes()));
 403                     if ((i & 1) == 0) {
 404                         transformedResourceFiles.forgetResourceFile(newPath);
 405                     } else {
 406                         ++expectedAmount;
 407                     }
 408                     i ^= 1;
 409                 }
 410                 ++expectedAmount;
 411             }
 412         }
 413 
 414         @Override
 415         public void test(ModulePool inResources, ModulePool out) throws Exception {
 416             Collection<ModuleEntry> outResources = extractResources(out);
 417             if (expectedAmount != outResources.size()) {
 418                 throw new AssertionError("Number of classes. Expected: " + expectedAmount
 419                         + ", got: " + outResources.size());
 420             }
 421         }
 422     }
 423 
 424     private class ComboPlugin extends TestPlugin {
 425 
 426         private class RenameClassVisitor extends ClassVisitor {
 427 
 428             public RenameClassVisitor(ClassWriter cv) {
 429                 super(Opcodes.ASM5, cv);
 430             }
 431 
 432             @Override
 433             public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
 434                 super.visit(version, access, name + SUFFIX, signature, superName, interfaces);
 435             }
 436         }
 437 
 438         @Override
 439         public void visit() {
 440             try {
 441                 renameClasses();
 442                 renameResources();
 443             } catch (IOException ex) {
 444                 throw new UncheckedIOException(ex);
 445             }
 446         }
 447 
 448         @Override
 449         public void test(ModulePool inResources, ModulePool outResources) throws Exception {
 450             if (!isVisitCalled()) {
 451                 throw new AssertionError("Resources not visited");
 452             }
 453             AsmGlobalPool globalPool = getPools().getGlobalPool();
 454             if (globalPool.getTransformedClasses().getClasses().size() != getClasses().size()) {
 455                 throw new AssertionError("Number of transformed classes not equal to expected");
 456             }
 457             // Check that only renamed classes and resource files are in the result.
 458             outResources.entries().forEach(r -> {
 459                 String resourceName = r.getPath();
 460                 if (resourceName.endsWith(".class") && !resourceName.endsWith("module-info.class")) {
 461                     if (!resourceName.endsWith(SUFFIX + ".class")) {
 462                         throw new AssertionError("Class not renamed " + resourceName);
 463                     }
 464                 } else if (resourceName.contains("META-INF/services/") && MODULES.containsKey(r.getModule())) {
 465                     String newClassName = new String(r.getBytes());
 466                     if(!newClassName.endsWith(SUFFIX)) {
 467                         throw new AssertionError("Resource file not renamed " + resourceName);
 468                     }
 469                 }
 470             });
 471         }
 472 
 473         private void renameResources() throws IOException {
 474             AsmPools pools = getPools();
 475             // Rename the resource Files
 476             for (Map.Entry<String, List<String>> mod : MODULES.entrySet()) {
 477                 String moduleName = mod.getKey();
 478                 AsmModulePool modulePool = pools.getModulePool(moduleName);
 479                 for (ModuleEntry res : modulePool.getResourceFiles()) {
 480                     ResourceFile resFile = modulePool.getResourceFile(res);
 481                     if (resFile.getPath().startsWith("META-INF/services/")) {
 482                         String newContent = new String(resFile.getContent()) + SUFFIX;
 483                         ResourceFile newResourceFile = new ResourceFile(resFile.getPath(),
 484                                 newContent.getBytes());
 485                         modulePool.getTransformedResourceFiles().addResourceFile(newResourceFile);
 486                     }
 487                 }
 488             }
 489         }
 490 
 491         private void renameClasses() throws IOException {
 492             AsmPools pools = getPools();
 493             AsmGlobalPool globalPool = pools.getGlobalPool();
 494             WritableClassPool transformedClasses = globalPool.getTransformedClasses();
 495             for (ModuleEntry res : globalPool.getClasses()) {
 496                 if (res.getPath().endsWith("module-info.class")) {
 497                     continue;
 498                 }
 499                 ClassReader reader = globalPool.getClassReader(res);
 500                 ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
 501                 RenameClassVisitor visitor = new RenameClassVisitor(writer);
 502                 reader.accept(visitor, ClassReader.EXPAND_FRAMES);
 503 
 504                 transformedClasses.forgetClass(reader.getClassName());
 505                 transformedClasses.addClass(writer);
 506             }
 507         }
 508     }
 509 }