1 /*
  2  * Copyright (c) 2016, 2019, 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 package gc.stress.gcbasher;
 26 
 27 class Decompiler {
 28     private ByteCursor cursor;
 29     private ClassInfo ci;
 30 
 31     public Decompiler(byte[] classData) {
 32         cursor = new ByteCursor(classData);
 33 
 34         int magicNumber = cursor.readInt();
 35         if (magicNumber != 0xCAFEBABE) {
 36             throw new IllegalArgumentException("Bad magic number " + magicNumber);
 37         }
 38 
 39         cursor.readUnsignedShort(); // Minor version
 40         cursor.readUnsignedShort(); // Major version
 41 
 42         ConstantPoolEntry[] constantPool = decodeConstantPool();
 43 
 44         cursor.readUnsignedShort(); // Access flags
 45 
 46         // this class index in constant pool;
 47         int classInfo = cursor.readUnsignedShort();
 48         int classInfoNameIndex = constantPool[classInfo].getNameIndex();
 49         ci = new ClassInfo(constantPool[classInfoNameIndex].getValue());
 50 
 51         cursor.readUnsignedShort(); // superclass
 52 
 53         int numInterfaces = cursor.readUnsignedShort();
 54         for (int i = 0; i < numInterfaces; i++) {
 55             cursor.readUnsignedShort(); // interface
 56         }
 57 
 58         decodeFields();
 59         MethodInfo[] methods = decodeMethods(constantPool);
 60         decodeMethodDependencies(methods, constantPool);
 61     }
 62 
 63     public ClassInfo getClassInfo() {
 64         return ci;
 65     }
 66 
 67     private boolean isDependency(String name, String className) {
 68         return !name.equals(className) && !name.startsWith("[");
 69     }
 70 
 71     private void addDependency(MethodInfo m, String name) {
 72         Dependency d = new Dependency(m.getName(), m.getDescriptor(), name);
 73         ci.addResolutionDep(d);
 74     }
 75 
 76     private String resolveName(ConstantPoolEntry[] constantPool, int cpi) {
 77         int nameIndex = constantPool[cpi].getNameIndex();
 78         return constantPool[nameIndex].getValue();
 79     }
 80 
 81     private void decodeMethodDependencies(MethodInfo[] methods, ConstantPoolEntry[] constantPool) {
 82         for (int i = 0; i < methods.length; i++) {
 83             MethodInfo m = methods[i];
 84             final int stopCheck = m.getCodeStart() + m.getCodeLength();
 85 
 86             int byteCodeIndex = m.getCodeStart();
 87             while (byteCodeIndex < stopCheck) {
 88                 int bc = cursor.readUnsignedByteAt(byteCodeIndex);
 89 
 90                 switch (bc) {
 91                     // These opcodes cause name resolution or initialization
 92                     // Their index bytes all point to a CONSTANT_Class (4.4.1)
 93                     case Bytecode.ANEWARRAY:
 94                     case Bytecode.CHECKCAST:
 95                     case Bytecode.INSTANCEOF:
 96                     case Bytecode.MULTIANEWARRAY:
 97                     case Bytecode.NEW: {
 98                         int cpi = cursor.readUnsignedShortAt(byteCodeIndex + 1);
 99                         String name = resolveName(constantPool, cpi);
100 
101                         if (isDependency(name, ci.getName())) {
102                             addDependency(m, name);
103                         }
104                         break;
105                     }
106 
107                     // These opcodes cause name resolution or initialization
108                     // Their index bytes all point to a CONSTANT_Field/Methodref (4.4.2)
109                     case Bytecode.GETFIELD:
110                     case Bytecode.INVOKEINTERFACE:
111                     case Bytecode.INVOKESPECIAL:
112                     case Bytecode.INVOKEVIRTUAL:
113                     case Bytecode.PUTFIELD:
114                     case Bytecode.PUTSTATIC:
115                     case Bytecode.GETSTATIC:
116                     case Bytecode.INVOKESTATIC: {
117                         int cpi = cursor.readUnsignedShortAt(byteCodeIndex + 1);
118                         int classIndex = constantPool[cpi].getClassIndex();
119                         String name = resolveName(constantPool, classIndex);
120 
121                         if (isDependency(name, ci.getName())) {
122                             addDependency(m, name);
123                         }
124                         break;
125                     }
126 
127                     case Bytecode.LOOKUPSWITCH: {
128                         byteCodeIndex++;
129                         int offset = byteCodeIndex - m.getCodeStart();
130                         while (offset % 4 != 0) {
131                             offset++;
132                             byteCodeIndex++;
133                         }
134 
135                         int def = cursor.readIntAt(byteCodeIndex);
136                         byteCodeIndex +=4;
137 
138                         int npairs = cursor.readIntAt(byteCodeIndex);
139                         byteCodeIndex +=4;
140                         byteCodeIndex += (8 * npairs);
141                         continue;
142                     }
143 
144                     case Bytecode.TABLESWITCH: {
145                         byteCodeIndex++;
146                         int offset = byteCodeIndex - m.getCodeStart();
147                         while (offset % 4 != 0) {
148                             offset++;
149                             byteCodeIndex++;
150                         }
151 
152                         int def = cursor.readIntAt(byteCodeIndex);
153                         byteCodeIndex +=4;
154 
155                         int low = cursor.readIntAt(byteCodeIndex);
156                         byteCodeIndex +=4;
157                         int high = cursor.readIntAt(byteCodeIndex);
158                         byteCodeIndex +=4;
159                         byteCodeIndex += (4 * (high - low + 1));
160                         continue;
161                     }
162 
163                     case Bytecode.WIDE: {
164                         bc = cursor.readUnsignedByteAt(++byteCodeIndex);
165                         if (bc == Bytecode.IINC) {
166                             byteCodeIndex += 5;
167                         } else {
168                             byteCodeIndex += 3;
169                         }
170                         continue;
171                     }
172                 }
173 
174                 byteCodeIndex += Bytecode.getLength(bc);
175             }
176 
177             if (byteCodeIndex - stopCheck > 1) {
178                 String err = "bad finish for method " + m.getName() +
179                              "End + "  + (byteCodeIndex - stopCheck);
180                 throw new IllegalArgumentException(err);
181             }
182         }
183     }
184 
185     private MethodInfo[] decodeMethods(ConstantPoolEntry[] constantPool) {
186         MethodInfo[] methods = new MethodInfo[cursor.readUnsignedShort()];
187 
188         for (int i = 0; i < methods.length; i++) {
189             cursor.readUnsignedShort(); // access flags
190 
191             String name = constantPool[cursor.readUnsignedShort()].getValue();
192             String descriptor = constantPool[cursor.readUnsignedShort()].getValue();
193 
194             int codeLength = 0;
195             int codeStart = 0;
196 
197             int numAttributes = cursor.readUnsignedShort(); // attributes count
198             for (int j = 0; j < numAttributes; j++) {
199                 int type = cursor.readUnsignedShort(); // attrib nameIndex
200                 int aLen = cursor.readInt(); // attrib length
201 
202                 if (constantPool[type].getValue().equals("Code")) {
203                     cursor.readUnsignedShort(); // Max stack
204                     cursor.readUnsignedShort(); // Max locals
205 
206                     codeLength = cursor.readInt();
207                     codeStart = cursor.getOffset();
208 
209                     cursor.skipBytes(codeLength); // Need to skip the code bytes
210                     cursor.skipBytes(cursor.readUnsignedShort() * 8); // Skip exception table
211 
212                     int numSubAttributes = cursor.readUnsignedShort();
213                     for (int k = 0; k < numSubAttributes; k++) {
214                         cursor.readUnsignedShort(); // sub name
215                         cursor.skipBytes(cursor.readInt()); // sub attrib data
216                     }
217                 } else {
218                     cursor.skipBytes(aLen); // unknown attrib data
219                 }
220             }
221 
222             methods[i] = new MethodInfo(name, descriptor, codeLength, codeStart);
223         }
224 
225         return methods;
226     }
227 
228     private void decodeFields() {
229         // Looks like we dont need any field info, throw it away!
230         int numFields = cursor.readUnsignedShort();
231 
232         for (int i = 0; i < numFields; i++) {
233             cursor.readUnsignedShort(); // access flags
234             cursor.readUnsignedShort(); // nameIndex
235             cursor.readUnsignedShort(); // descriptorIndex
236 
237             int numAttributes = cursor.readUnsignedShort();
238             for (int j = 0; j < numAttributes; j++) {
239                 cursor.readUnsignedShort(); // nameIndex
240                 int length = cursor.readInt();
241                 cursor.skipBytes(length); // data
242             }
243         }
244     }
245 
246     private ConstantPoolEntry[] decodeConstantPool() {
247         final int CONSTANT_Utf8 = 1;
248         final int CONSTANT_Unicode = 2;
249         final int CONSTANT_Integer = 3;
250         final int CONSTANT_Float = 4;
251         final int CONSTANT_Long = 5;
252         final int CONSTANT_Double = 6;
253         final int CONSTANT_Class = 7;
254         final int CONSTANT_String = 8;
255         final int CONSTANT_Fieldref = 9;
256         final int CONSTANT_Methodref = 10;
257         final int CONSTANT_InterfaceMethodref = 11;
258         final int CONSTANT_NameAndType = 12;
259         final int CONSTANT_MethodHandle = 15;
260         final int CONSTANT_MethodType = 16;
261         final int CONSTANT_InvokeDynamic = 18;
262 
263         ConstantPoolEntry[] constantPool = new ConstantPoolEntry[cursor.readUnsignedShort()];
264 
265         // The constant pool starts at index 1
266         for (int i = 1; i < constantPool.length; i++) {
267             int type = cursor.readUnsignedByte();
268 
269             switch (type) {
270                 case CONSTANT_Class:
271                     constantPool[i] = new ConstantPoolEntry(cursor.readUnsignedShort()); // name_index
272                     break;
273 
274                 case CONSTANT_Fieldref: case CONSTANT_Methodref: case CONSTANT_InterfaceMethodref:
275                     constantPool[i] = new ConstantPoolEntry(cursor.readUnsignedShort()); // class_index
276                     cursor.readUnsignedShort(); // name_and_type_index
277                     break;
278 
279                 case CONSTANT_String:
280                     cursor.readUnsignedShort(); // string_index
281                     break;
282 
283                 case CONSTANT_Integer:
284                     cursor.readInt(); // bytes
285                     break;
286 
287                 case CONSTANT_Float:
288                     cursor.readInt(); // bytes
289                     break;
290 
291                 case CONSTANT_Long:
292                     cursor.readInt(); // high_bytes
293                     cursor.readInt(); // low_bytes
294                     i++; // 8 byte constants use 2 constant pool slots.
295                     break;
296 
297                 case CONSTANT_Double:
298                     cursor.readInt(); // high_bytes
299                     cursor.readInt(); // low_bytes
300                     i++; // 8 byte constants use 2 constant pool slots.
301                     break;
302 
303                 case CONSTANT_NameAndType:
304                     constantPool[i] = new ConstantPoolEntry(cursor.readUnsignedShort()); // name_index
305                     cursor.readUnsignedShort(); // descriptor_index
306                     break;
307 
308                 case CONSTANT_Utf8:
309                     int length = cursor.readUnsignedShort(); // length
310                     constantPool[i] = new ConstantPoolEntry(cursor.readUtf8(length)); // bytes[length]
311                     break;
312 
313                 case CONSTANT_MethodHandle:
314                     cursor.readUnsignedByte(); // reference_kind
315                     cursor.readUnsignedShort(); // reference_index
316                     break;
317 
318                 case CONSTANT_MethodType:
319                     cursor.readUnsignedShort(); // descriptor_index
320                     break;
321 
322                 case CONSTANT_InvokeDynamic:
323                     cursor.readUnsignedShort(); // bootstrap_method_attr_index
324                     cursor.readUnsignedShort(); // name_and_type_index
325                     break;
326 
327                 default:
328                     String err = "Unknown constant pool type " + String.valueOf(type) + "\n" +
329                                  "CPE " + i + " of " + constantPool.length + "\n" +
330                                  "Byte offset " + Integer.toHexString(cursor.getOffset());
331                     throw new IllegalArgumentException(err);
332             }
333         }
334         return constantPool;
335     }
336 }