| /* |
| * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| * |
| */ |
| |
| class Decompiler { |
| private ByteCursor cursor; |
| private ClassInfo ci; |
| |
| public Decompiler(byte[] classData) { |
| cursor = new ByteCursor(classData); |
| |
| int magicNumber = cursor.readInt(); |
| if (magicNumber != 0xCAFEBABE) { |
| throw new IllegalArgumentException("Bad magic number " + magicNumber); |
| } |
| |
| cursor.readUnsignedShort(); // Minor version |
| cursor.readUnsignedShort(); // Major version |
| |
| ConstantPoolEntry[] constantPool = decodeConstantPool(); |
| |
| cursor.readUnsignedShort(); // Access flags |
| |
| // this class index in constant pool; |
| int classInfo = cursor.readUnsignedShort(); |
| int classInfoNameIndex = constantPool[classInfo].getNameIndex(); |
| ci = new ClassInfo(constantPool[classInfoNameIndex].getValue()); |
| |
| cursor.readUnsignedShort(); // superclass |
| |
| int numInterfaces = cursor.readUnsignedShort(); |
| for (int i = 0; i < numInterfaces; i++) { |
| cursor.readUnsignedShort(); // interface |
| } |
| |
| decodeFields(); |
| MethodInfo[] methods = decodeMethods(constantPool); |
| decodeMethodDependencies(methods, constantPool); |
| } |
| |
| public ClassInfo getClassInfo() { |
| return ci; |
| } |
| |
| private boolean isDependency(String name, String className) { |
| return !name.equals(className) && !name.startsWith("["); |
| } |
| |
| private void addDependency(MethodInfo m, String name) { |
| Dependency d = new Dependency(m.getName(), m.getDescriptor(), name); |
| ci.addResolutionDep(d); |
| } |
| |
| private String resolveName(ConstantPoolEntry[] constantPool, int cpi) { |
| int nameIndex = constantPool[cpi].getNameIndex(); |
| return constantPool[nameIndex].getValue(); |
| } |
| |
| private void decodeMethodDependencies(MethodInfo[] methods, ConstantPoolEntry[] constantPool) { |
| for (int i = 0; i < methods.length; i++) { |
| MethodInfo m = methods[i]; |
| final int stopCheck = m.getCodeStart() + m.getCodeLength(); |
| |
| int byteCodeIndex = m.getCodeStart(); |
| while (byteCodeIndex < stopCheck) { |
| int bc = cursor.readUnsignedByteAt(byteCodeIndex); |
| |
| switch (bc) { |
| // These opcodes cause name resolution or initialization |
| // Their index bytes all point to a CONSTANT_Class (4.4.1) |
| case Bytecode.ANEWARRAY: |
| case Bytecode.CHECKCAST: |
| case Bytecode.INSTANCEOF: |
| case Bytecode.MULTIANEWARRAY: |
| case Bytecode.NEW: { |
| int cpi = cursor.readUnsignedShortAt(byteCodeIndex + 1); |
| String name = resolveName(constantPool, cpi); |
| |
| if (isDependency(name, ci.getName())) { |
| addDependency(m, name); |
| } |
| break; |
| } |
| |
| // These opcodes cause name resolution or initialization |
| // Their index bytes all point to a CONSTANT_Field/Methodref (4.4.2) |
| case Bytecode.GETFIELD: |
| case Bytecode.INVOKEINTERFACE: |
| case Bytecode.INVOKESPECIAL: |
| case Bytecode.INVOKEVIRTUAL: |
| case Bytecode.PUTFIELD: |
| case Bytecode.PUTSTATIC: |
| case Bytecode.GETSTATIC: |
| case Bytecode.INVOKESTATIC: { |
| int cpi = cursor.readUnsignedShortAt(byteCodeIndex + 1); |
| int classIndex = constantPool[cpi].getClassIndex(); |
| String name = resolveName(constantPool, classIndex); |
| |
| if (isDependency(name, ci.getName())) { |
| addDependency(m, name); |
| } |
| break; |
| } |
| |
| case Bytecode.LOOKUPSWITCH: { |
| byteCodeIndex++; |
| int offset = byteCodeIndex - m.getCodeStart(); |
| while (offset % 4 != 0) { |
| offset++; |
| byteCodeIndex++; |
| } |
| |
| int def = cursor.readIntAt(byteCodeIndex); |
| byteCodeIndex +=4; |
| |
| int npairs = cursor.readIntAt(byteCodeIndex); |
| byteCodeIndex +=4; |
| byteCodeIndex += (8 * npairs); |
| continue; |
| } |
| |
| case Bytecode.TABLESWITCH: { |
| byteCodeIndex++; |
| int offset = byteCodeIndex - m.getCodeStart(); |
| while (offset % 4 != 0) { |
| offset++; |
| byteCodeIndex++; |
| } |
| |
| int def = cursor.readIntAt(byteCodeIndex); |
| byteCodeIndex +=4; |
| |
| int low = cursor.readIntAt(byteCodeIndex); |
| byteCodeIndex +=4; |
| int high = cursor.readIntAt(byteCodeIndex); |
| byteCodeIndex +=4; |
| byteCodeIndex += (4 * (high - low + 1)); |
| continue; |
| } |
| |
| case Bytecode.WIDE: { |
| bc = cursor.readUnsignedByteAt(++byteCodeIndex); |
| if (bc == Bytecode.IINC) { |
| byteCodeIndex += 5; |
| } else { |
| byteCodeIndex += 3; |
| } |
| continue; |
| } |
| } |
| |
| byteCodeIndex += Bytecode.getLength(bc); |
| } |
| |
| if (byteCodeIndex - stopCheck > 1) { |
| String err = "bad finish for method " + m.getName() + |
| "End + " + (byteCodeIndex - stopCheck); |
| throw new IllegalArgumentException(err); |
| } |
| } |
| } |
| |
| private MethodInfo[] decodeMethods(ConstantPoolEntry[] constantPool) { |
| MethodInfo[] methods = new MethodInfo[cursor.readUnsignedShort()]; |
| |
| for (int i = 0; i < methods.length; i++) { |
| cursor.readUnsignedShort(); // access flags |
| |
| String name = constantPool[cursor.readUnsignedShort()].getValue(); |
| String descriptor = constantPool[cursor.readUnsignedShort()].getValue(); |
| |
| int codeLength = 0; |
| int codeStart = 0; |
| |
| int numAttributes = cursor.readUnsignedShort(); // attributes count |
| for (int j = 0; j < numAttributes; j++) { |
| int type = cursor.readUnsignedShort(); // attrib nameIndex |
| int aLen = cursor.readInt(); // attrib length |
| |
| if (constantPool[type].getValue().equals("Code")) { |
| cursor.readUnsignedShort(); // Max stack |
| cursor.readUnsignedShort(); // Max locals |
| |
| codeLength = cursor.readInt(); |
| codeStart = cursor.getOffset(); |
| |
| cursor.skipBytes(codeLength); // Need to skip the code bytes |
| cursor.skipBytes(cursor.readUnsignedShort() * 8); // Skip exception table |
| |
| int numSubAttributes = cursor.readUnsignedShort(); |
| for (int k = 0; k < numSubAttributes; k++) { |
| cursor.readUnsignedShort(); // sub name |
| cursor.skipBytes(cursor.readInt()); // sub attrib data |
| } |
| } else { |
| cursor.skipBytes(aLen); // unknown attrib data |
| } |
| } |
| |
| methods[i] = new MethodInfo(name, descriptor, codeLength, codeStart); |
| } |
| |
| return methods; |
| } |
| |
| private void decodeFields() { |
| // Looks like we dont need any field info, throw it away! |
| int numFields = cursor.readUnsignedShort(); |
| |
| for (int i = 0; i < numFields; i++) { |
| cursor.readUnsignedShort(); // access flags |
| cursor.readUnsignedShort(); // nameIndex |
| cursor.readUnsignedShort(); // descriptorIndex |
| |
| int numAttributes = cursor.readUnsignedShort(); |
| for (int j = 0; j < numAttributes; j++) { |
| cursor.readUnsignedShort(); // nameIndex |
| int length = cursor.readInt(); |
| cursor.skipBytes(length); // data |
| } |
| } |
| } |
| |
| private ConstantPoolEntry[] decodeConstantPool() { |
| final int CONSTANT_Utf8 = 1; |
| final int CONSTANT_Unicode = 2; |
| final int CONSTANT_Integer = 3; |
| final int CONSTANT_Float = 4; |
| final int CONSTANT_Long = 5; |
| final int CONSTANT_Double = 6; |
| final int CONSTANT_Class = 7; |
| final int CONSTANT_String = 8; |
| final int CONSTANT_Fieldref = 9; |
| final int CONSTANT_Methodref = 10; |
| final int CONSTANT_InterfaceMethodref = 11; |
| final int CONSTANT_NameAndType = 12; |
| final int CONSTANT_MethodHandle = 15; |
| final int CONSTANT_MethodType = 16; |
| final int CONSTANT_InvokeDynamic = 18; |
| |
| ConstantPoolEntry[] constantPool = new ConstantPoolEntry[cursor.readUnsignedShort()]; |
| |
| // The constant pool starts at index 1 |
| for (int i = 1; i < constantPool.length; i++) { |
| int type = cursor.readUnsignedByte(); |
| |
| switch (type) { |
| case CONSTANT_Class: |
| constantPool[i] = new ConstantPoolEntry(cursor.readUnsignedShort()); // name_index |
| break; |
| |
| case CONSTANT_Fieldref: case CONSTANT_Methodref: case CONSTANT_InterfaceMethodref: |
| constantPool[i] = new ConstantPoolEntry(cursor.readUnsignedShort()); // class_index |
| cursor.readUnsignedShort(); // name_and_type_index |
| break; |
| |
| case CONSTANT_String: |
| cursor.readUnsignedShort(); // string_index |
| break; |
| |
| case CONSTANT_Integer: |
| cursor.readInt(); // bytes |
| break; |
| |
| case CONSTANT_Float: |
| cursor.readInt(); // bytes |
| break; |
| |
| case CONSTANT_Long: |
| cursor.readInt(); // high_bytes |
| cursor.readInt(); // low_bytes |
| i++; // 8 byte constants use 2 constant pool slots. |
| break; |
| |
| case CONSTANT_Double: |
| cursor.readInt(); // high_bytes |
| cursor.readInt(); // low_bytes |
| i++; // 8 byte constants use 2 constant pool slots. |
| break; |
| |
| case CONSTANT_NameAndType: |
| constantPool[i] = new ConstantPoolEntry(cursor.readUnsignedShort()); // name_index |
| cursor.readUnsignedShort(); // descriptor_index |
| break; |
| |
| case CONSTANT_Utf8: |
| int length = cursor.readUnsignedShort(); // length |
| constantPool[i] = new ConstantPoolEntry(cursor.readUtf8(length)); // bytes[length] |
| break; |
| |
| case CONSTANT_MethodHandle: |
| cursor.readUnsignedByte(); // reference_kind |
| cursor.readUnsignedShort(); // reference_index |
| break; |
| |
| case CONSTANT_MethodType: |
| cursor.readUnsignedShort(); // descriptor_index |
| break; |
| |
| case CONSTANT_InvokeDynamic: |
| cursor.readUnsignedShort(); // bootstrap_method_attr_index |
| cursor.readUnsignedShort(); // name_and_type_index |
| break; |
| |
| default: |
| String err = "Unknown constant pool type " + String.valueOf(type) + "\n" + |
| "CPE " + i + " of " + constantPool.length + "\n" + |
| "Byte offset " + Integer.toHexString(cursor.getOffset()); |
| throw new IllegalArgumentException(err); |
| } |
| } |
| return constantPool; |
| } |
| } |