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
  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.ByteBuffer;
  44 import java.nio.ByteOrder;
  45 import java.nio.file.Files;
  46 import java.nio.file.Path;
  47 import java.util.ArrayList;
  48 import java.util.Arrays;
  49 import java.util.Iterator;
  50 import java.util.List;
  51 import java.util.stream.Stream;
  52 
  53 import com.sun.tools.classfile.Attribute;
  54 import com.sun.tools.classfile.ClassFile;
  55 import com.sun.tools.classfile.Code_attribute;
  56 import com.sun.tools.classfile.ConstantPoolException;
  57 import com.sun.tools.classfile.Method;
  58 import java.util.HashMap;
  59 import java.util.Map;
  60 import jdk.tools.jlink.internal.ResourcePoolImpl;
  61 import jdk.tools.jlink.internal.plugins.StripDebugProvider;
  62 import jdk.tools.jlink.plugins.CmdPluginProvider;
  63 import jdk.tools.jlink.plugins.OnOffPluginProvider;
  64 import jdk.tools.jlink.plugins.ResourcePlugin;
  65 import jdk.tools.jlink.plugins.ResourcePool;
  66 import jdk.tools.jlink.plugins.ResourcePool.Resource;
  67 import jdk.tools.jlink.plugins.StringTable;
  68 import tests.Helper;
  69 
  70 public class StripDebugPluginTest {
  71     public static void main(String[] args) throws Exception {
  72         new StripDebugPluginTest().test();
  73     }
  74 
  75     public void test() throws Exception {
  76         // JPRT not yet ready for jmods
  77         Helper helper = Helper.newHelper();
  78         if (helper == null) {
  79             System.err.println("Test not run, NO jmods directory");
  80             return;
  81         }
  82 
  83         List<String> classes = Arrays.asList("toto.Main", "toto.com.foo.bar.X");
  84         Path moduleFile = helper.generateModuleCompiledClasses(
  85                 helper.getJmodSrcDir(), helper.getJmodClassesDir(), "leaf1", classes);
  86         Path moduleInfo = moduleFile.resolve("module-info.class");
  87 
  88         // Classes have been compiled in debug.
  89         List<Path> covered = new ArrayList<>();
  90         byte[] infoContent = Files.readAllBytes(moduleInfo);
  91         try (Stream<Path> stream = Files.walk(moduleFile)) {
  92             for (Iterator<Path> iterator = stream.iterator(); iterator.hasNext(); ) {
  93                 Path p = iterator.next();
  94                 if (Files.isRegularFile(p) && p.toString().endsWith(".class")) {
  95                     byte[] content = Files.readAllBytes(p);
  96                     String path = "/" + helper.getJmodClassesDir().relativize(p).toString();
  97                     String moduleInfoPath = path + "/module-info.class";
  98                     check(path, content, moduleInfoPath, infoContent);
  99                     covered.add(p);
 100                 }
 101             }
 102         }
 103         if (covered.isEmpty()) {
 104             throw new AssertionError("No class to compress");
 105         } else {
 106             System.err.println("removed debug attributes from "
 107                     + covered.size() + " classes");
 108         }
 109     }
 110 
 111     private void check(String path, byte[] content, String infoPath, byte[] moduleInfo) throws Exception {
 112         StripDebugProvider prov = new StripDebugProvider();
 113         Map<String, Object> options = new HashMap<>();
 114         options.put(CmdPluginProvider.TOOL_ARGUMENT_PROPERTY,
 115                 OnOffPluginProvider.ON_ARGUMENT);
 116         ResourcePlugin debug = (ResourcePlugin) prov.newPlugins(options)[0];
 117         Resource result1 = stripDebug(debug, new Resource(path, ByteBuffer.wrap(content)), path, infoPath, moduleInfo);
 118 
 119         if (!path.endsWith("module-info.class")) {
 120             if (result1.getLength() >= content.length) {
 121                 throw new AssertionError("Class size not reduced, debug info not "
 122                         + "removed for " + path);
 123             }
 124             checkDebugAttributes(result1.getByteArray());
 125         }
 126 
 127         Resource result2 = stripDebug(debug, result1, path, infoPath, moduleInfo);
 128         if (result1.getLength() != result2.getLength()) {
 129             throw new AssertionError("removing debug info twice reduces class size of "
 130                     + path);
 131         }
 132         checkDebugAttributes(result1.getByteArray());
 133     }
 134 
 135     private Resource stripDebug(ResourcePlugin debug, Resource classResource, String path, String infoPath, byte[] moduleInfo) throws Exception {
 136         ResourcePool resources = new ResourcePoolImpl(ByteOrder.nativeOrder());
 137         resources.addResource(classResource);
 138         if (!path.endsWith("module-info.class")) {
 139             Resource res2 = new Resource(infoPath, ByteBuffer.wrap(moduleInfo));
 140             resources.addResource(res2);
 141         }
 142         ResourcePool results = new ResourcePoolImpl(resources.getByteOrder());
 143         debug.visit(resources, results, new StringTable() {
 144             @Override
 145             public int addString(String str) {
 146                 return -1;
 147             }
 148 
 149             @Override
 150             public String getString(int id) {
 151                 throw new UnsupportedOperationException("Not supported yet.");
 152             }
 153         });
 154         System.out.println(classResource.getPath());
 155         return results.getResource(classResource.getPath());
 156     }
 157 
 158     private void checkDebugAttributes(byte[] strippedClassFile) throws IOException, ConstantPoolException {
 159         ClassFile classFile = ClassFile.read(new ByteArrayInputStream(strippedClassFile));
 160         String[] debugAttributes = new String[]{
 161                 Attribute.LineNumberTable,
 162                 Attribute.LocalVariableTable,
 163                 Attribute.LocalVariableTypeTable
 164         };
 165         for (Method method : classFile.methods) {
 166             String methodName = method.getName(classFile.constant_pool);
 167             Code_attribute code = (Code_attribute) method.attributes.get(Attribute.Code);
 168             for (String attr : debugAttributes) {
 169                 if (code.attributes.get(attr) != null) {
 170                     throw new AssertionError("Debug attribute was not removed: " + attr +
 171                             " from method " + classFile.getName() + "#" + methodName);
 172                 }
 173             }
 174         }
 175     }
 176 }