blob: b56b4b324d43af0dc1eed8bb743b88866a8c1e3f [file] [log] [blame]
/*
* 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;
}
}