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 
  68     private static final int[] SIZES = new int[20];
  69 
  70     static {
  71 
  72         //SIZES[CONSTANT_Utf8] = XXX;
  73         SIZES[CONSTANT_Integer] = 4;
  74         SIZES[CONSTANT_Float] = 4;
  75         SIZES[CONSTANT_Long] = 8;
  76         SIZES[CONSTANT_Double] = 8;
  77         SIZES[CONSTANT_Class] = 2;
  78         SIZES[CONSTANT_String] = 2;
  79         SIZES[CONSTANT_Fieldref] = 4;
  80         SIZES[CONSTANT_Methodref] = 4;
  81         SIZES[CONSTANT_InterfaceMethodref] = 4;
  82         SIZES[CONSTANT_NameAndType] = 4;
  83         SIZES[CONSTANT_MethodHandle] = 3;
  84         SIZES[CONSTANT_MethodType] = 2;
  85         SIZES[CONSTANT_InvokeDynamic] = 4;
  86     }
  87 
  88     public static int[] getSizes() {
  89         return SIZES.clone();
  90     }
  91 
  92     @SuppressWarnings("fallthrough")
  93     public static byte[] normalize(StringsProvider provider, byte[] transformed,
  94             int offset) throws IOException {
  95         DataInputStream stream = new DataInputStream(new ByteArrayInputStream(transformed,
  96                 offset, transformed.length - offset));
  97         ByteArrayOutputStream outStream = new ByteArrayOutputStream(transformed.length);
  98         DataOutputStream out = new DataOutputStream(outStream);
  99         byte[] header = new byte[8]; //maginc/4, minor/2, major/2
 100         stream.readFully(header);
 101         out.write(header);
 102         int count = stream.readUnsignedShort();
 103         out.writeShort(count);
 104         for (int i = 1; i < count; i++) {
 105             int tag = stream.readUnsignedByte();
 106             byte[] arr;
 107             switch (tag) {
 108                 case CONSTANT_Utf8: {
 109                     out.write(tag);
 110                     String utf = stream.readUTF();
 111                     out.writeUTF(utf);
 112                     break;
 113                 }
 114 
 115                 case EXTERNALIZED_STRING: {
 116                     int index = CompressIndexes.readInt(stream);
 117                     String orig = provider.getString(index);
 118                     out.write(CONSTANT_Utf8);
 119                     out.writeUTF(orig);
 120                     break;
 121                 }
 122 
 123                 case EXTERNALIZED_STRING_DESCRIPTOR: {
 124                     String orig = reconstruct(provider, stream);
 125                     out.write(CONSTANT_Utf8);
 126                     out.writeUTF(orig);
 127                     break;
 128                 }
 129                 case CONSTANT_Long:
 130                 case CONSTANT_Double: {
 131                     i++;
 132                 }
 133                 default: {
 134                     out.write(tag);
 135                     int size = SIZES[tag];
 136                     arr = new byte[size];
 137                     stream.readFully(arr);
 138                     out.write(arr);
 139                 }
 140             }
 141         }
 142         out.write(transformed, transformed.length - stream.available(),
 143                 stream.available());
 144         out.flush();
 145 
 146         return outStream.toByteArray();
 147     }
 148 
 149     private static String reconstruct(StringsProvider reader, DataInputStream cr)
 150             throws IOException {
 151         int descIndex = CompressIndexes.readInt(cr);
 152         String desc = reader.getString(descIndex);
 153         byte[] encodedDesc = getEncoded(desc);
 154         int indexes_length = CompressIndexes.readInt(cr);
 155         byte[] bytes = new byte[indexes_length];
 156         cr.readFully(bytes);
 157         List<Integer> indices = CompressIndexes.decompressFlow(bytes);
 158         ByteBuffer buffer = ByteBuffer.allocate(encodedDesc.length * 2);
 159         buffer.order(ByteOrder.BIG_ENDIAN);
 160         int argIndex = 0;
 161         for (byte c : encodedDesc) {
 162             if (c == 'L') {
 163                 buffer = safeAdd(buffer, c);
 164                 int index = indices.get(argIndex);
 165                 argIndex += 1;
 166                 String pkg = reader.getString(index);
 167                 if (pkg.length() > 0) {
 168                     pkg = pkg + "/";
 169                     byte[] encoded = getEncoded(pkg);
 170                     buffer = safeAdd(buffer, encoded);
 171                 }
 172                 int classIndex = indices.get(argIndex);
 173                 argIndex += 1;
 174                 String clazz = reader.getString(classIndex);
 175                 byte[] encoded = getEncoded(clazz);
 176                 buffer = safeAdd(buffer, encoded);
 177             } else {
 178                 buffer = safeAdd(buffer, c);
 179             }
 180         }
 181 
 182         byte[] encoded = buffer.array();
 183         ByteBuffer result = ByteBuffer.allocate(encoded.length + 2);
 184         result.order(ByteOrder.BIG_ENDIAN);
 185         result.putShort((short) buffer.position());
 186         result.put(encoded, 0, buffer.position());
 187         ByteArrayInputStream stream = new ByteArrayInputStream(result.array());
 188         DataInputStream inStream = new DataInputStream(stream);
 189         String str = inStream.readUTF();
 190         return str;
 191     }
 192 
 193     public static byte[] getEncoded(String pre) throws IOException {
 194         ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
 195         DataOutputStream resultOut = new DataOutputStream(resultStream);
 196         resultOut.writeUTF(pre);
 197         byte[] content = resultStream.toByteArray();
 198         // first 2 items are length;
 199         if (content.length <= 2) {
 200             return new byte[0];
 201         }
 202         return Arrays.copyOfRange(content, 2, content.length);
 203     }
 204 
 205     private static ByteBuffer safeAdd(ByteBuffer current, byte b) {
 206         byte[] bytes = {b};
 207         return safeAdd(current, bytes);
 208     }
 209 
 210     private static ByteBuffer safeAdd(ByteBuffer current, byte[] bytes) {
 211         if (current.remaining() < bytes.length) {
 212             ByteBuffer newBuffer = ByteBuffer.allocate((current.capacity()
 213                     + bytes.length) * 2);
 214             newBuffer.order(ByteOrder.BIG_ENDIAN);
 215             newBuffer.put(current.array(), 0, current.position());
 216             current = newBuffer;
 217         }
 218         current.put(bytes);
 219         return current;
 220     }
 221 
 222     @Override
 223     public String getName() {
 224         return StringSharingDecompressorFactory.NAME;
 225     }
 226 
 227     public StringSharingDecompressor(Properties properties) {
 228 
 229     }
 230 
 231     @Override
 232     public byte[] decompress(StringsProvider reader, byte[] content,
 233             int offset, long originalSize) throws Exception {
 234         return normalize(reader, content, offset);
 235     }
 236 }