1 /*
   2  * Copyright (c) 2015, 2016, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package jdk.internal.jimage.decompressor;
  26 
  27 import java.io.ByteArrayInputStream;
  28 import java.io.ByteArrayOutputStream;
  29 import java.io.DataInputStream;
  30 import java.io.DataOutputStream;
  31 import java.io.IOException;
  32 import java.nio.ByteBuffer;
  33 import java.nio.ByteOrder;
  34 import java.util.Arrays;
  35 import java.util.List;
  36 import java.util.Properties;
  37 
  38 /**
  39  *
  40  * A Decompressor that reconstructs the constant pool of classes.
  41  *
  42  * @implNote This class needs to maintain JDK 8 source compatibility.
  43  *
  44  * It is used internally in the JDK to implement jimage/jrtfs access,
  45  * but also compiled and delivered as part of the jrtfs.jar to support access
  46  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
  47  */
  48 public class StringSharingDecompressor implements ResourceDecompressor {
  49 
  50     public static final int EXTERNALIZED_STRING = 23;
  51     public static final int EXTERNALIZED_STRING_DESCRIPTOR = 25;
  52 
  53     private static final int CONSTANT_Utf8 = 1;
  54     private static final int CONSTANT_Integer = 3;
  55     private static final int CONSTANT_Float = 4;
  56     private static final int CONSTANT_Long = 5;
  57     private static final int CONSTANT_Double = 6;
  58     private static final int CONSTANT_Class = 7;
  59     private static final int CONSTANT_String = 8;
  60     private static final int CONSTANT_Fieldref = 9;
  61     private static final int CONSTANT_Methodref = 10;
  62     private static final int CONSTANT_InterfaceMethodref = 11;
  63     private static final int CONSTANT_NameAndType = 12;
  64     private static final int CONSTANT_MethodHandle = 15;
  65     private static final int CONSTANT_MethodType = 16;
  66     private static final int CONSTANT_InvokeDynamic = 18;
  67     private static final int CONSTANT_Module = 19;
  68     private static final int CONSTANT_Package = 20;
  69 
  70     private static final int[] SIZES = new int[21];
  71 
  72     static {
  73 
  74         //SIZES[CONSTANT_Utf8] = XXX;
  75         SIZES[CONSTANT_Integer] = 4;
  76         SIZES[CONSTANT_Float] = 4;
  77         SIZES[CONSTANT_Long] = 8;
  78         SIZES[CONSTANT_Double] = 8;
  79         SIZES[CONSTANT_Class] = 2;
  80         SIZES[CONSTANT_String] = 2;
  81         SIZES[CONSTANT_Fieldref] = 4;
  82         SIZES[CONSTANT_Methodref] = 4;
  83         SIZES[CONSTANT_InterfaceMethodref] = 4;
  84         SIZES[CONSTANT_NameAndType] = 4;
  85         SIZES[CONSTANT_MethodHandle] = 3;
  86         SIZES[CONSTANT_MethodType] = 2;
  87         SIZES[CONSTANT_InvokeDynamic] = 4;
  88         SIZES[CONSTANT_Module] = 2;
  89         SIZES[CONSTANT_Package] = 2;
  90     }
  91 
  92     public static int[] getSizes() {
  93         return SIZES.clone();
  94     }
  95 
  96     @SuppressWarnings("fallthrough")
  97     public static byte[] normalize(StringsProvider provider, byte[] transformed,
  98             int offset) throws IOException {
  99         DataInputStream stream = new DataInputStream(new ByteArrayInputStream(transformed,
 100                 offset, transformed.length - offset));
 101         ByteArrayOutputStream outStream = new ByteArrayOutputStream(transformed.length);
 102         DataOutputStream out = new DataOutputStream(outStream);
 103         byte[] header = new byte[8]; //maginc/4, minor/2, major/2
 104         stream.readFully(header);
 105         out.write(header);
 106         int count = stream.readUnsignedShort();
 107         out.writeShort(count);
 108         for (int i = 1; i < count; i++) {
 109             int tag = stream.readUnsignedByte();
 110             byte[] arr;
 111             switch (tag) {
 112                 case CONSTANT_Utf8: {
 113                     out.write(tag);
 114                     String utf = stream.readUTF();
 115                     out.writeUTF(utf);
 116                     break;
 117                 }
 118 
 119                 case EXTERNALIZED_STRING: {
 120                     int index = CompressIndexes.readInt(stream);
 121                     String orig = provider.getString(index);
 122                     out.write(CONSTANT_Utf8);
 123                     out.writeUTF(orig);
 124                     break;
 125                 }
 126 
 127                 case EXTERNALIZED_STRING_DESCRIPTOR: {
 128                     String orig = reconstruct(provider, stream);
 129                     out.write(CONSTANT_Utf8);
 130                     out.writeUTF(orig);
 131                     break;
 132                 }
 133                 case CONSTANT_Long:
 134                 case CONSTANT_Double: {
 135                     i++;
 136                 }
 137                 default: {
 138                     out.write(tag);
 139                     int size = SIZES[tag];
 140                     arr = new byte[size];
 141                     stream.readFully(arr);
 142                     out.write(arr);
 143                 }
 144             }
 145         }
 146         out.write(transformed, transformed.length - stream.available(),
 147                 stream.available());
 148         out.flush();
 149 
 150         return outStream.toByteArray();
 151     }
 152 
 153     private static String reconstruct(StringsProvider reader, DataInputStream cr)
 154             throws IOException {
 155         int descIndex = CompressIndexes.readInt(cr);
 156         String desc = reader.getString(descIndex);
 157         byte[] encodedDesc = getEncoded(desc);
 158         int indexes_length = CompressIndexes.readInt(cr);
 159         byte[] bytes = new byte[indexes_length];
 160         cr.readFully(bytes);
 161         List<Integer> indices = CompressIndexes.decompressFlow(bytes);
 162         ByteBuffer buffer = ByteBuffer.allocate(encodedDesc.length * 2);
 163         buffer.order(ByteOrder.BIG_ENDIAN);
 164         int argIndex = 0;
 165         for (byte c : encodedDesc) {
 166             if (c == 'L') {
 167                 buffer = safeAdd(buffer, c);
 168                 int index = indices.get(argIndex);
 169                 argIndex += 1;
 170                 String pkg = reader.getString(index);
 171                 if (pkg.length() > 0) {
 172                     pkg = pkg + "/";
 173                     byte[] encoded = getEncoded(pkg);
 174                     buffer = safeAdd(buffer, encoded);
 175                 }
 176                 int classIndex = indices.get(argIndex);
 177                 argIndex += 1;
 178                 String clazz = reader.getString(classIndex);
 179                 byte[] encoded = getEncoded(clazz);
 180                 buffer = safeAdd(buffer, encoded);
 181             } else {
 182                 buffer = safeAdd(buffer, c);
 183             }
 184         }
 185 
 186         byte[] encoded = buffer.array();
 187         ByteBuffer result = ByteBuffer.allocate(encoded.length + 2);
 188         result.order(ByteOrder.BIG_ENDIAN);
 189         result.putShort((short) buffer.position());
 190         result.put(encoded, 0, buffer.position());
 191         ByteArrayInputStream stream = new ByteArrayInputStream(result.array());
 192         DataInputStream inStream = new DataInputStream(stream);
 193         String str = inStream.readUTF();
 194         return str;
 195     }
 196 
 197     public static byte[] getEncoded(String pre) throws IOException {
 198         ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
 199         DataOutputStream resultOut = new DataOutputStream(resultStream);
 200         resultOut.writeUTF(pre);
 201         byte[] content = resultStream.toByteArray();
 202         // first 2 items are length;
 203         if (content.length <= 2) {
 204             return new byte[0];
 205         }
 206         return Arrays.copyOfRange(content, 2, content.length);
 207     }
 208 
 209     private static ByteBuffer safeAdd(ByteBuffer current, byte b) {
 210         byte[] bytes = {b};
 211         return safeAdd(current, bytes);
 212     }
 213 
 214     private static ByteBuffer safeAdd(ByteBuffer current, byte[] bytes) {
 215         if (current.remaining() < bytes.length) {
 216             ByteBuffer newBuffer = ByteBuffer.allocate((current.capacity()
 217                     + bytes.length) * 2);
 218             newBuffer.order(ByteOrder.BIG_ENDIAN);
 219             newBuffer.put(current.array(), 0, current.position());
 220             current = newBuffer;
 221         }
 222         current.put(bytes);
 223         return current;
 224     }
 225 
 226     @Override
 227     public String getName() {
 228         return StringSharingDecompressorFactory.NAME;
 229     }
 230 
 231     public StringSharingDecompressor(Properties properties) {
 232 
 233     }
 234 
 235     @Override
 236     public byte[] decompress(StringsProvider reader, byte[] content,
 237             int offset, long originalSize) throws Exception {
 238         return normalize(reader, content, offset);
 239     }
 240 }