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 }