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  * @test
  26  * @summary Test StripDebugPlugin
  27  * @author Jean-Francois Denise
  28  * @library ../../lib
  29  * @build tests.*
  30  * @modules java.base/jdk.internal.jimage
  31  *          jdk.jlink/jdk.tools.jlink.internal
  32  *          jdk.jlink/jdk.tools.jlink.internal.plugins
  33  *          jdk.jlink/jdk.tools.jlink.plugin
  34  *          jdk.jlink/jdk.tools.jimage
  35  *          jdk.jlink/jdk.tools.jmod
  36  *          jdk.jdeps/com.sun.tools.classfile
  37  *          jdk.compiler
  38  * @run main StripDebugPluginTest
  39  */
  40 
  41 import java.io.ByteArrayInputStream;
  42 import java.io.IOException;
  43 import java.nio.file.Files;
  44 import java.nio.file.Path;
  45 import java.util.ArrayList;
  46 import java.util.Arrays;
  47 import java.util.Iterator;
  48 import java.util.List;
  49 import java.util.stream.Stream;
  50 
  51 import com.sun.tools.classfile.Attribute;
  52 import com.sun.tools.classfile.ClassFile;
  53 import com.sun.tools.classfile.Code_attribute;
  54 import com.sun.tools.classfile.ConstantPoolException;
  55 import com.sun.tools.classfile.Method;
  56 import java.util.HashMap;
  57 import java.util.Map;
  58 import jdk.tools.jlink.internal.ResourcePoolManager;
  59 import jdk.tools.jlink.internal.plugins.StripDebugPlugin;
  60 import jdk.tools.jlink.plugin.ResourcePoolEntry;
  61 import jdk.tools.jlink.plugin.ResourcePool;
  62 import jdk.tools.jlink.plugin.Plugin;
  63 import tests.Helper;
  64 
  65 public class StripDebugPluginTest {
  66     public static void main(String[] args) throws Exception {
  67         new StripDebugPluginTest().test();
  68     }
  69 
  70     public void test() throws Exception {
  71         // JPRT not yet ready for jmods
  72         Helper helper = Helper.newHelper();
  73         if (helper == null) {
  74             System.err.println("Test not run, NO jmods directory");
  75             return;
  76         }
  77 
  78         List<String> classes = Arrays.asList("toto.Main", "toto.com.foo.bar.X");
  79         Path moduleFile = helper.generateModuleCompiledClasses(
  80                 helper.getJmodSrcDir(), helper.getJmodClassesDir(), "leaf1", classes);
  81         Path moduleInfo = moduleFile.resolve("module-info.class");
  82 
  83         // Classes have been compiled in debug.
  84         List<Path> covered = new ArrayList<>();
  85         byte[] infoContent = Files.readAllBytes(moduleInfo);
  86         try (Stream<Path> stream = Files.walk(moduleFile)) {
  87             for (Iterator<Path> iterator = stream.iterator(); iterator.hasNext(); ) {
  88                 Path p = iterator.next();
  89                 if (Files.isRegularFile(p) && p.toString().endsWith(".class")) {
  90                     byte[] content = Files.readAllBytes(p);
  91                     String path = "/" + helper.getJmodClassesDir().relativize(p).toString();
  92                     String moduleInfoPath = path + "/module-info.class";
  93                     check(path, content, moduleInfoPath, infoContent);
  94                     covered.add(p);
  95                 }
  96             }
  97         }
  98         if (covered.isEmpty()) {
  99             throw new AssertionError("No class to compress");
 100         } else {
 101             System.err.println("removed debug attributes from "
 102                     + covered.size() + " classes");
 103         }
 104     }
 105 
 106     private void check(String path, byte[] content, String infoPath, byte[] moduleInfo) throws Exception {
 107         path = path.replace('\\', '/');
 108         StripDebugPlugin debug = new StripDebugPlugin();
 109         debug.configure(new HashMap<>());
 110         ResourcePoolEntry result1 = stripDebug(debug, ResourcePoolEntry.create(path,content), path, infoPath, moduleInfo);
 111 
 112         if (!path.endsWith("module-info.class")) {
 113             if (result1.contentLength() >= content.length) {
 114                 throw new AssertionError("Class size not reduced, debug info not "
 115                         + "removed for " + path);
 116             }
 117             checkDebugAttributes(result1.contentBytes());
 118         }
 119 
 120         ResourcePoolEntry result2 = stripDebug(debug, result1, path, infoPath, moduleInfo);
 121         if (result1.contentLength() != result2.contentLength()) {
 122             throw new AssertionError("removing debug info twice reduces class size of "
 123                     + path);
 124         }
 125         checkDebugAttributes(result1.contentBytes());
 126     }
 127 
 128     private ResourcePoolEntry stripDebug(Plugin debug, ResourcePoolEntry classResource,
 129             String path, String infoPath, byte[] moduleInfo) throws Exception {
 130         ResourcePoolManager resources = new ResourcePoolManager();
 131         resources.add(classResource);
 132         if (!path.endsWith("module-info.class")) {
 133             ResourcePoolEntry res2 = ResourcePoolEntry.create(infoPath, moduleInfo);
 134             resources.add(res2);
 135         }
 136         ResourcePoolManager results = new ResourcePoolManager();
 137         ResourcePool resPool = debug.transform(resources.resourcePool(),
 138                 results.resourcePoolBuilder());
 139         System.out.println(classResource.path());
 140 
 141         return resPool.findEntry(classResource.path()).get();
 142     }
 143 
 144     private void checkDebugAttributes(byte[] strippedClassFile) throws IOException, ConstantPoolException {
 145         ClassFile classFile = ClassFile.read(new ByteArrayInputStream(strippedClassFile));
 146         String[] debugAttributes = new String[]{
 147                 Attribute.LineNumberTable,
 148                 Attribute.LocalVariableTable,
 149                 Attribute.LocalVariableTypeTable
 150         };
 151         for (Method method : classFile.methods) {
 152             String methodName = method.getName(classFile.constant_pool);
 153             Code_attribute code = (Code_attribute) method.attributes.get(Attribute.Code);
 154             for (String attr : debugAttributes) {
 155                 if (code.attributes.get(attr) != null) {
 156                     throw new AssertionError("Debug attribute was not removed: " + attr +
 157                             " from method " + classFile.getName() + "#" + methodName);
 158                 }
 159             }
 160         }
 161     }
 162 }