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