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