All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.jpattern.org.cojen.classfile.CodeDisassembler Maven / Gradle / Ivy

Go to download

This is a copy of the good Cojen project from http://cojen.sourceforge.net/ with package name changed

The newest version!
/*
 *  Copyright 2004-2010 Brian S O'Neill
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.googlecode.jpattern.org.cojen.classfile;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import com.googlecode.jpattern.org.cojen.classfile.attribute.CodeAttr;
import com.googlecode.jpattern.org.cojen.classfile.constant.ConstantClassInfo;
import com.googlecode.jpattern.org.cojen.classfile.constant.ConstantDoubleInfo;
import com.googlecode.jpattern.org.cojen.classfile.constant.ConstantFieldInfo;
import com.googlecode.jpattern.org.cojen.classfile.constant.ConstantFloatInfo;
import com.googlecode.jpattern.org.cojen.classfile.constant.ConstantIntegerInfo;
import com.googlecode.jpattern.org.cojen.classfile.constant.ConstantInterfaceMethodInfo;
import com.googlecode.jpattern.org.cojen.classfile.constant.ConstantLongInfo;
import com.googlecode.jpattern.org.cojen.classfile.constant.ConstantMethodInfo;
import com.googlecode.jpattern.org.cojen.classfile.constant.ConstantNameAndTypeInfo;
import com.googlecode.jpattern.org.cojen.classfile.constant.ConstantStringInfo;
import com.googlecode.jpattern.org.cojen.util.IntHashMap;

/**
 * Disassembles a method into a CodeAssembler, which acts as a visitor.
 *
 * @author Brian S O'Neill
 */
public class CodeDisassembler {
    private final MethodInfo mMethod;
    private final String mEnclosingClassName;
    private final String mSuperClassName;
    private final CodeAttr mCode;
    private final ConstantPool mCp;
    private final byte[] mByteCodes;
    private final ExceptionHandler[] mExceptionHandlers;

    // Current CodeAssembler in use for disassembly.
    private CodeAssembler mAssembler;

    // List of all the LocalVariable objects in use.
    private Vector mLocals;

    // True if the method being decompiled still has a "this" reference.
    private boolean mHasThis;

    private Location mReturnLocation;

    // Maps int address keys to itself, but to Label objects after first
    // needed.
    private IntHashMap mLabels;

    // Maps int catch locations to Lists of ExceptionHandler objects.
    private IntHashMap> mCatchLocations;

    // Current address being decompiled.
    private int mAddress;

    /**
     * @throws IllegalArgumentException if method has no code
     */
    public CodeDisassembler(MethodInfo method) throws IllegalArgumentException {
        mMethod = method;
        mEnclosingClassName = method.getClassFile().getClassName();
        mSuperClassName = method.getClassFile().getSuperClassName();
        if ((mCode = method.getCodeAttr()) == null) {
            throw new IllegalArgumentException("Method defines no code");
        }
        mCp = mCode.getConstantPool();
        CodeBuffer buffer = mCode.getCodeBuffer();
        mByteCodes = buffer.getByteCodes();
        mExceptionHandlers = buffer.getExceptionHandlers();
    }

    /**
     * Disassemble the MethodInfo into the given assembler.
     *
     * @see CodeAssemblerPrinter
     */
    public void disassemble(CodeAssembler assembler) {
        disassemble(assembler, null, null);
    }

    /**
     * Disassemble the MethodInfo into the given assembler.
     *
     * @param params if not null, override the local variables which hold parameter values
     * @param returnLocation if not null, disassemble will branch to this location upon seeing
     * a return, leaving any arguments on the stack
     * @see CodeAssemblerPrinter
     */
    public synchronized void disassemble(CodeAssembler assembler,
                                         LocalVariable[] params, Location returnLocation) {
        mAssembler = assembler;
        mLocals = new Vector();
        if (mHasThis = !mMethod.getModifiers().isStatic()) {
            // Reserve a slot for "this" parameter.
            mLocals.add(null);
        }

        gatherLabels();

        // Gather the local variables of the parameters.
        {
            TypeDesc[] paramTypes = mMethod.getMethodDescriptor().getParameterTypes();
            
            if (params == null) {
                params = new LocalVariable[assembler.getParameterCount()];
                for (int i=params.length; --i>=0; ) {
                    params[i] = assembler.getParameter(i);
                }
            }
            if (paramTypes.length != params.length) {
                throw new IllegalArgumentException
                    ("Method parameter count doesn't match given parameter count: "
                     + paramTypes.length + " != " + params.length);
            }
        
            for (int i=0; i".equals(methodName)) {
                        if (className == null) {
                            assembler.invokeConstructor(paramTypes);
                        } else {
                            if ("".equals(mMethod.getName())
                                && className.equals(mSuperClassName)) {
                                assembler.invokeSuperConstructor(paramTypes);
                            } else {
                                assembler.invokeConstructor(className, paramTypes);
                            }
                        }
                    } else {
                        if (className == null) {
                            assembler.invokePrivate(methodName, ret, paramTypes);
                        } else {
                            assembler.invokeSuper(className, methodName, ret, paramTypes);
                        }
                    }
                    break;
                case Opcode.INVOKESTATIC:
                    if (className == null) {
                        assembler.invokeStatic(methodName, ret, paramTypes);
                    } else {
                        assembler.invokeStatic(className, methodName, ret, paramTypes);
                    }
                    break;
                case Opcode.INVOKEINTERFACE:
                    assembler.invokeInterface(className, methodName, ret, paramTypes);
                    break;
                }
                break;

                // End opcodes that load a constant from the constant pool.

                // Opcodes that load or store local variables...

            case Opcode.ILOAD: case Opcode.ISTORE:
            case Opcode.LLOAD: case Opcode.LSTORE:
            case Opcode.FLOAD: case Opcode.FSTORE:
            case Opcode.DLOAD: case Opcode.DSTORE:
            case Opcode.ALOAD: case Opcode.ASTORE:
            case Opcode.ILOAD_0: case Opcode.ISTORE_0:
            case Opcode.ILOAD_1: case Opcode.ISTORE_1:
            case Opcode.ILOAD_2: case Opcode.ISTORE_2:
            case Opcode.ILOAD_3: case Opcode.ISTORE_3:
            case Opcode.LLOAD_0: case Opcode.LSTORE_0:
            case Opcode.LLOAD_1: case Opcode.LSTORE_1:
            case Opcode.LLOAD_2: case Opcode.LSTORE_2:
            case Opcode.LLOAD_3: case Opcode.LSTORE_3:
            case Opcode.FLOAD_0: case Opcode.FSTORE_0:
            case Opcode.FLOAD_1: case Opcode.FSTORE_1:
            case Opcode.FLOAD_2: case Opcode.FSTORE_2:
            case Opcode.FLOAD_3: case Opcode.FSTORE_3:
            case Opcode.DLOAD_0: case Opcode.DSTORE_0:
            case Opcode.DLOAD_1: case Opcode.DSTORE_1:
            case Opcode.DLOAD_2: case Opcode.DSTORE_2:
            case Opcode.DLOAD_3: case Opcode.DSTORE_3:
            case Opcode.ALOAD_0: case Opcode.ASTORE_0:
            case Opcode.ALOAD_1: case Opcode.ASTORE_1:
            case Opcode.ALOAD_2: case Opcode.ASTORE_2:
            case Opcode.ALOAD_3: case Opcode.ASTORE_3:
                switch (opcode) {
                case Opcode.ILOAD: case Opcode.ISTORE:
                    index = readUnsignedByte();
                    type = TypeDesc.INT;
                    break;
                case Opcode.LLOAD: case Opcode.LSTORE:
                    index = readUnsignedByte();
                    type = TypeDesc.LONG;
                    break;
                case Opcode.FLOAD: case Opcode.FSTORE:
                    index = readUnsignedByte();
                    type = TypeDesc.FLOAT;
                    break;
                case Opcode.DLOAD: case Opcode.DSTORE:
                    index = readUnsignedByte();
                    type = TypeDesc.DOUBLE;
                    break;
                case Opcode.ALOAD: case Opcode.ASTORE:
                    index = readUnsignedByte();
                    type = TypeDesc.OBJECT;
                    break;
                case Opcode.ILOAD_0: case Opcode.ISTORE_0:
                    index = 0;
                    type = TypeDesc.INT;
                    break;
                case Opcode.ILOAD_1: case Opcode.ISTORE_1:
                    index = 1;
                    type = TypeDesc.INT;
                    break;
                case Opcode.ILOAD_2: case Opcode.ISTORE_2:
                    index = 2;
                    type = TypeDesc.INT;
                    break;
                case Opcode.ILOAD_3: case Opcode.ISTORE_3:
                    index = 3;
                    type = TypeDesc.INT;
                    break;
                case Opcode.LLOAD_0: case Opcode.LSTORE_0:
                    index = 0;
                    type = TypeDesc.LONG;
                    break;
                case Opcode.LLOAD_1: case Opcode.LSTORE_1:
                    index = 1;
                    type = TypeDesc.LONG;
                    break;
                case Opcode.LLOAD_2: case Opcode.LSTORE_2:
                    index = 2;
                    type = TypeDesc.LONG;
                    break;
                case Opcode.LLOAD_3: case Opcode.LSTORE_3:
                    index = 3;
                    type = TypeDesc.LONG;
                    break;
                case Opcode.FLOAD_0: case Opcode.FSTORE_0:
                    index = 0;
                    type = TypeDesc.FLOAT;
                    break;
                case Opcode.FLOAD_1: case Opcode.FSTORE_1:
                    index = 1;
                    type = TypeDesc.FLOAT;
                    break;
                case Opcode.FLOAD_2: case Opcode.FSTORE_2:
                    index = 2;
                    type = TypeDesc.FLOAT;
                    break;
                case Opcode.FLOAD_3: case Opcode.FSTORE_3:
                    index = 3;
                    type = TypeDesc.FLOAT;
                    break;
                case Opcode.DLOAD_0: case Opcode.DSTORE_0:
                    index = 0;
                    type = TypeDesc.DOUBLE;
                    break;
                case Opcode.DLOAD_1: case Opcode.DSTORE_1:
                    index = 1;
                    type = TypeDesc.DOUBLE;
                    break;
                case Opcode.DLOAD_2: case Opcode.DSTORE_2:
                    index = 2;
                    type = TypeDesc.DOUBLE;
                    break;
                case Opcode.DLOAD_3: case Opcode.DSTORE_3:
                    index = 3;
                    type = TypeDesc.DOUBLE;
                    break;
                case Opcode.ALOAD_0: case Opcode.ASTORE_0:
                    index = 0;
                    type = TypeDesc.OBJECT;
                    break;
                case Opcode.ALOAD_1: case Opcode.ASTORE_1:
                    index = 1;
                    type = TypeDesc.OBJECT;
                    break;
                case Opcode.ALOAD_2: case Opcode.ASTORE_2:
                    index = 2;
                    type = TypeDesc.OBJECT;
                    break;
                case Opcode.ALOAD_3: case Opcode.ASTORE_3:
                    index = 3;
                    type = TypeDesc.OBJECT;
                    break;
                default:
                    index = 0;
                    type = null;
                    break;
                }

                switch (opcode) {
                case Opcode.ILOAD:
                case Opcode.LLOAD:
                case Opcode.FLOAD:
                case Opcode.DLOAD:
                case Opcode.ALOAD:
                case Opcode.ILOAD_0:
                case Opcode.ILOAD_1:
                case Opcode.ILOAD_2:
                case Opcode.ILOAD_3:
                case Opcode.LLOAD_0:
                case Opcode.LLOAD_1:
                case Opcode.LLOAD_2:
                case Opcode.LLOAD_3:
                case Opcode.FLOAD_0:
                case Opcode.FLOAD_1:
                case Opcode.FLOAD_2:
                case Opcode.FLOAD_3:
                case Opcode.DLOAD_0:
                case Opcode.DLOAD_1:
                case Opcode.DLOAD_2:
                case Opcode.DLOAD_3:
                case Opcode.ALOAD_0:
                case Opcode.ALOAD_1:
                case Opcode.ALOAD_2:
                case Opcode.ALOAD_3:
                    if (index == 0 && mHasThis) {
                        assembler.loadThis();
                    } else {
                        assembler.loadLocal(getLocalVariable(index, type));
                    }
                    break;
                case Opcode.ISTORE:
                case Opcode.LSTORE:
                case Opcode.FSTORE:
                case Opcode.DSTORE:
                case Opcode.ASTORE:
                case Opcode.ISTORE_0:
                case Opcode.ISTORE_1:
                case Opcode.ISTORE_2:
                case Opcode.ISTORE_3:
                case Opcode.LSTORE_0:
                case Opcode.LSTORE_1:
                case Opcode.LSTORE_2:
                case Opcode.LSTORE_3:
                case Opcode.FSTORE_0:
                case Opcode.FSTORE_1:
                case Opcode.FSTORE_2:
                case Opcode.FSTORE_3:
                case Opcode.DSTORE_0:
                case Opcode.DSTORE_1:
                case Opcode.DSTORE_2:
                case Opcode.DSTORE_3:
                case Opcode.ASTORE_0:
                case Opcode.ASTORE_1:
                case Opcode.ASTORE_2:
                case Opcode.ASTORE_3:
                    if (index == 0 && mHasThis) {
                        // The "this" reference just got blown away.
                        mHasThis = false;
                    }
                    assembler.storeLocal(getLocalVariable(index, type));
                    break;
                }
                break;

            case Opcode.RET:
                LocalVariable local = getLocalVariable
                    (readUnsignedByte(), TypeDesc.OBJECT);
                assembler.ret(local);
                break;

            case Opcode.IINC:
                local = getLocalVariable(readUnsignedByte(), TypeDesc.INT);
                assembler.integerIncrement(local, readByte());
                break;

                // End opcodes that load or store local variables.

                // Opcodes that branch to another address.

            case Opcode.GOTO:
                loc = getLabel(mAddress + readShort());
                assembler.branch(loc);
                break;
            case Opcode.JSR:
                loc = getLabel(mAddress + readShort());
                assembler.jsr(loc);
                break;
            case Opcode.GOTO_W:
                loc = getLabel(mAddress + readInt());
                assembler.branch(loc);
                break;
            case Opcode.JSR_W:
                loc = getLabel(mAddress + readInt());
                assembler.jsr(loc);
                break;

            case Opcode.IFNULL:
                loc = getLabel(mAddress + readShort());
                assembler.ifNullBranch(loc, true);
                break;
            case Opcode.IFNONNULL:
                loc = getLabel(mAddress + readShort());
                assembler.ifNullBranch(loc, false);
                break;

            case Opcode.IF_ACMPEQ:
                loc = getLabel(mAddress + readShort());
                assembler.ifEqualBranch(loc, true);
                break;
            case Opcode.IF_ACMPNE:
                loc = getLabel(mAddress + readShort());
                assembler.ifEqualBranch(loc, false);
                break;

            case Opcode.IFEQ:
            case Opcode.IFNE:
            case Opcode.IFLT:
            case Opcode.IFGE:
            case Opcode.IFGT:
            case Opcode.IFLE:
                loc = getLabel(mAddress + readShort());
                String choice;
                switch (opcode) {
                case Opcode.IFEQ:
                    choice = "==";
                    break;
                case Opcode.IFNE:
                    choice = "!=";
                    break;
                case Opcode.IFLT:
                    choice = "<";
                    break;
                case Opcode.IFGE:
                    choice = ">=";
                    break;
                case Opcode.IFGT:
                    choice = ">";
                    break;
                case Opcode.IFLE:
                    choice = "<=";
                    break;
                default:
                    choice = null;
                    break;
                }
                assembler.ifZeroComparisonBranch(loc, choice);
                break;

            case Opcode.IF_ICMPEQ:
            case Opcode.IF_ICMPNE:
            case Opcode.IF_ICMPLT:
            case Opcode.IF_ICMPGE:
            case Opcode.IF_ICMPGT:
            case Opcode.IF_ICMPLE:
                loc = getLabel(mAddress + readShort());
                switch (opcode) {
                case Opcode.IF_ICMPEQ:
                    choice = "==";
                    break;
                case Opcode.IF_ICMPNE:
                    choice = "!=";
                    break;
                case Opcode.IF_ICMPLT:
                    choice = "<";
                    break;
                case Opcode.IF_ICMPGE:
                    choice = ">=";
                    break;
                case Opcode.IF_ICMPGT:
                    choice = ">";
                    break;
                case Opcode.IF_ICMPLE:
                    choice = "<=";
                    break;
                default:
                    choice = null;
                    break;
                }
                assembler.ifComparisonBranch(loc, choice);
                break;

                // End opcodes that branch to another address.

                // Miscellaneous opcodes...

            case Opcode.BIPUSH:
                assembler.loadConstant(readByte());
                break;
            case Opcode.SIPUSH:
                assembler.loadConstant(readShort());
                break;

            case Opcode.NEWARRAY:
                int atype = readByte();
                type = null;
                switch (atype) {
                case 4: // T_BOOLEAN
                    type = TypeDesc.BOOLEAN;
                    break;
                case 5: // T_CHAR
                    type = TypeDesc.CHAR;
                    break;
                case 6: // T_FLOAT
                    type = TypeDesc.FLOAT;
                    break;
                case 7: // T_DOUBLE
                    type = TypeDesc.DOUBLE;
                    break;
                case 8: // T_BYTE
                    type = TypeDesc.BYTE;
                    break;
                case 9: // T_SHORT
                    type = TypeDesc.SHORT;
                    break;
                case 10: // T_INT
                    type = TypeDesc.INT;
                    break;
                case 11: // T_LONG
                    type = TypeDesc.LONG;
                    break;
                }

                if (type == null) {
                    error(opcode, "Unknown primitive type for new array: " + atype);
                    break;
                }

                assembler.newObject(type.toArrayType());
                break;

            case Opcode.TABLESWITCH:
            case Opcode.LOOKUPSWITCH:
                int opcodeAddress = mAddress;
                // Read padding until address is 32 bit word aligned.
                while (((mAddress + 1) & 3) != 0) {
                    ++mAddress;
                }
                Location defaultLocation = getLabel(opcodeAddress + readInt());
                int[] cases;
                Location[] locations;
                
                if (opcode == Opcode.TABLESWITCH) {
                    int lowValue = readInt();
                    int highValue = readInt();
                    int caseCount = highValue - lowValue + 1;
                    try {
                        cases = new int[caseCount];
                    } catch (NegativeArraySizeException e) {
                        error(opcode, "Negative case count for switch: " + caseCount);
                        break;
                    }
                    locations = new Location[caseCount];
                    for (int i=0; i();
        mCatchLocations = new IntHashMap>
            (mExceptionHandlers.length * 2 + 1);
        int labelKey;

        // Gather labels for any exception handlers.
        for (int i = mExceptionHandlers.length - 1; i >= 0; i--) {
            ExceptionHandler handler = mExceptionHandlers[i];
            labelKey = handler.getStartLocation().getLocation();
            mLabels.put(labelKey, (Object) labelKey);
            labelKey = handler.getEndLocation().getLocation();
            mLabels.put(labelKey, (Object) labelKey);
            labelKey = handler.getCatchLocation().getLocation();
            List list = mCatchLocations.get(labelKey);
            if (list == null) {
                list = new ArrayList(2);
                mCatchLocations.put(labelKey, list);
            }
            list.add(handler);
        }

        // Now gather labels that occur within byte code.
        for (mAddress = 0; mAddress < mByteCodes.length; mAddress++) {
            byte opcode = mByteCodes[mAddress];

            switch (opcode) {

            default:
                error(opcode, "Unknown opcode: " + (opcode & 0xff));
                break;

                // Opcodes that use labels.

            case Opcode.GOTO:
            case Opcode.JSR:
            case Opcode.IFNULL:
            case Opcode.IFNONNULL:
            case Opcode.IF_ACMPEQ:
            case Opcode.IF_ACMPNE:
            case Opcode.IFEQ:
            case Opcode.IFNE:
            case Opcode.IFLT:
            case Opcode.IFGE:
            case Opcode.IFGT:
            case Opcode.IFLE:
            case Opcode.IF_ICMPEQ:
            case Opcode.IF_ICMPNE:
            case Opcode.IF_ICMPLT:
            case Opcode.IF_ICMPGE:
            case Opcode.IF_ICMPGT:
            case Opcode.IF_ICMPLE:
                labelKey = mAddress + readShort();
                mLabels.put(labelKey, (Object) labelKey);
                break;

            case Opcode.GOTO_W:
            case Opcode.JSR_W:
                labelKey = mAddress + readInt();
                mLabels.put(labelKey, (Object) labelKey);
                break;

            case Opcode.TABLESWITCH:
            case Opcode.LOOKUPSWITCH:
                int opcodeAddress = mAddress;
                // Read padding until address is 32 bit word aligned.
                while (((mAddress + 1) & 3) != 0) {
                    ++mAddress;
                }
                
                // Read the default location.
                labelKey = opcodeAddress + readInt();
                mLabels.put(labelKey, (Object) labelKey);
                
                if (opcode == Opcode.TABLESWITCH) {
                    int lowValue = readInt();
                    int highValue = readInt();
                    int caseCount = highValue - lowValue + 1;

                    for (int i=0; i= mLocals.size()) {
            mLocals.setSize(index + 1);
            local = mAssembler.createLocalVariable(null, type);
            mLocals.set(index, local);
            return local;
        }

        Object obj = mLocals.get(index);

        if (obj == null) {
            local = mAssembler.createLocalVariable(null, type);
            mLocals.set(index, local);
            return local;
        }

        if (obj instanceof LocalVariable) {
            local = (LocalVariable)obj;
            if (compatibleType(type, local.getType())) {
                return local;
            }
            // Variable takes on multiple types, so convert entry to a list.
            List locals = new ArrayList(4);
            locals.add(local);
            local = mAssembler.createLocalVariable(null, type);
            locals.add(local);
            mLocals.set(index, locals);
            return local;
        }
        
        List locals = (List)obj;
        for (int i=locals.size(); --i>=0; ) {
            local = locals.get(i);
            if (compatibleType(type, local.getType())) {
                return local;
            }
        }
        
        local = mAssembler.createLocalVariable(null, type);
        locals.add(local);
        return local;
    }

    private boolean compatibleType(TypeDesc a, TypeDesc b) {
        if (a == b  || (!a.isPrimitive() && !b.isPrimitive())) {
            return true;
        }
        if (isIntType(a) && isIntType(b)) {
            return true;
        }
        return false;
    }

    private boolean isIntType(TypeDesc type) {
        switch (type.getTypeCode()) {
        case TypeDesc.INT_CODE:
        case TypeDesc.BOOLEAN_CODE:
        case TypeDesc.BYTE_CODE:
        case TypeDesc.SHORT_CODE:
        case TypeDesc.CHAR_CODE:
            return true;
        }
        return false;
    }

    private void locateLabel() {
        int labelKey = mAddress;
        Object labelValue = mLabels.get(labelKey);
        if (labelValue != null) {
            if (labelValue instanceof Label) {
                ((Label)labelValue).setLocation();
            } else {
                labelValue = mAssembler.createLabel().setLocation();
                mLabels.put(labelKey, labelValue);
            }
        }

        List handlers = mCatchLocations.get(labelKey);

        if (handlers != null) {
            for (int i=0; i