view src/main/java/org/icedrobot/daneel/rewriter/DexRewriter.java @ 77:a5ab6534a6a6

Add try/catch and move_exception support. Refactor in Rewriter fixStackAfterMethodCall to fixStackAfterMethodCallOrAnExceptionHandler.
author forax
date Fri, 25 Mar 2011 09:20:08 +0100
parents 15cfcc7a8756
children 6ea46c928791
line wrap: on
line source

/*
 * Daneel - Dalvik to Java bytecode compiler
 * Copyright (C) 2011  IcedRobot team
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * This file is subject to the "Classpath" exception:
 *
 * Linking this library statically or dynamically with other modules is
 * making a combined work based on this library.  Thus, the terms and
 * conditions of the GNU General Public License cover the whole
 * combination.
 *
 * As a special exception, the copyright holders of this library give you
 * permission to link this library with independent modules to produce an
 * executable, regardless of the license terms of these independent
 * modules, and to copy and distribute the resulting executable under terms
 * of your choice, provided that you also meet, for each linked independent
 * module, the terms and conditions of the license of that module.  An
 * independent module is a module which is not derived from or based on
 * this library.  If you modify this library, you may extend this exception
 * to your version of the library, but you are not obligated to do so.  If
 * you do not wish to do so, delete this exception statement from your
 * version.
 */

package org.icedrobot.daneel.rewriter;

import static org.objectweb.asm.Opcodes.*;

import static org.icedrobot.daneel.rewriter.Register.*;
import static org.icedrobot.daneel.rewriter.Registers.*;

import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;

import org.icedrobot.daneel.dex.DexAnnotationVisitor;
import org.icedrobot.daneel.dex.DexClassVisitor;
import org.icedrobot.daneel.dex.DexFieldVisitor;
import org.icedrobot.daneel.dex.DexMethodVisitor;
import org.icedrobot.daneel.dex.DexReader;
import org.icedrobot.daneel.dex.Label;
import org.icedrobot.daneel.dex.Opcode;
import org.icedrobot.daneel.util.TypeUtil;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

public class DexRewriter implements DexClassVisitor {
    private final ClassVisitor cv;

    /**
     * Rewrites a class from Dalvik bytecode into Java bytecode representation.
     * The source class is given by specifying its name and the DEX source it is
     * originating from.
     * 
     * @param name The name of the class to rewrite as a normal fully qualified
     *        class name.
     * @param reader The reader for the DEX source to read from.
     * @return The Java bytecode for the given class.
     * @throws ClassNotFoundException In case the source does not define a class
     *         by that name.
     */
    public static byte[] rewrite(String name, DexReader reader)
            throws ClassNotFoundException {
        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        reader.accept("L" + name.replace('.', '/') + ";", new DexRewriter(writer));
        return writer.toByteArray();
    }

    public DexRewriter(ClassVisitor cv) {
        this.cv = cv;
    }

    @Override
    public void visit(int access, String name, String supername,
            String[] interfaces) {
        name = TypeUtil.convertDescToInternal(name);
        supername = TypeUtil.convertDescToInternal(supername);
        interfaces = (interfaces == null) ? null : TypeUtil
                .convertDescToInternals(interfaces);
        cv.visit(V1_6, access, name, null, supername, interfaces);
    }

    @Override
    public void visitSource(String source) {
        cv.visitSource(source, null);
    }

    @Override
    public DexAnnotationVisitor visitAnnotation(int visibility, String type) {
        // XXX Ignore annotations for now.
        return null;
    }

    @Override
    public DexFieldVisitor visitField(int access, String name, String type,
            Object value) {
        final FieldVisitor fv = cv.visitField(access, name, type, null, value);
        if (fv == null) {
            return null;
        }

        return new DexFieldVisitor() {
            @Override
            public void visitEnd() {
                fv.visitEnd();
            }

            @Override
            public DexAnnotationVisitor visitAnnotation(int visibility,
                    String type) {
                // XXX Ignore annotations for now.
                return null;
            }
        };
    }

    @Override
    public DexMethodVisitor visitMethod(int access, String name, String shorty,
            String returnType, String[] parameterTypes) {
        String desc = TypeUtil.convertProtoToDesc(returnType, parameterTypes);
        MethodVisitor mv = cv.visitMethod(access, name, desc, null, null);
        if (mv == null) {
            return null;
        }

        return new MethodRewriter(mv, desc);
    }

    @Override
    public void visitEnd() {
        cv.visitEnd();
    }

    static class MethodRewriter implements DexMethodVisitor {
        final PatchMethodVisitor mv;
        final String desc;

        private Interpreter interpreter;
        private int returnRegisterType;   // type of the register used to stored
                                          // the return value
        private boolean exceptionOnTop;   // true if an exception is on top of the stack
        private int locals;               // number of local variables
        private int parameters;           // number of parameters
        
        // map DEX jump label to ASM label
        private final HashMap<Label, org.objectweb.asm.Label> labelMap =
            new HashMap<Label, org.objectweb.asm.Label>();
        // set which contains all labels which is the start of an exception handler
        private final Set<Label> exceptionHandlerSet =
            Collections.newSetFromMap(new IdentityHashMap<Label, Boolean>());

        public MethodRewriter(MethodVisitor mv, String desc) {
            this.mv = new PatchMethodVisitor(mv);
            this.desc = desc;
            this.returnRegisterType = VOID_TYPE;
        }

        /**
         * Translate a register with Dalvik calling convention to a slot with
         * Java calling convention
         * 
         * @param register the register
         * @return the corresponding Java slot.
         */
        private int registerToSlot(int register) {
            if (register >= locals) { // it's a parameter
                return register - locals;
            } // otherwise it's a local variable
            return parameters + register;
        }

        private static int getTypeFromASMType(Type type) {
            switch (type.getSort()) {
            case Type.BOOLEAN:
                return BOOLEAN_TYPE;
            case Type.BYTE:
                return BYTE_TYPE;
            case Type.CHAR:
                return CHAR_TYPE;
            case Type.SHORT:
                return SHORT_TYPE;
            case Type.INT:
                return INT_TYPE;
            case Type.LONG:
                return LONG_TYPE;
            case Type.FLOAT:
                return FLOAT_TYPE;
            case Type.DOUBLE:
                return DOUBLE_TYPE;
            case Type.VOID:
                return VOID_TYPE;
            case Type.OBJECT:
                return OBJECT_TYPE;
            case Type.ARRAY:
                return Register.makeArray(getTypeFromASMType(type.getElementType()), type.getDimensions());
            default:
                throw new AssertionError("bad type "+type);
            }
        }
        
        private static int getNewArrayKindFromASMType(Type type) {
            switch (type.getSort()) {
            case Type.BOOLEAN:
                return T_BOOLEAN;
            case Type.BYTE:
                return T_BYTE;
            case Type.CHAR:
                return T_CHAR;
            case Type.SHORT:
                return T_SHORT;
            case Type.INT:
                return T_INT;
            case Type.LONG:
                return T_LONG;
            case Type.FLOAT:
                return T_FLOAT;
            case Type.DOUBLE:
                return T_DOUBLE;
            default:
                throw new AssertionError("bad type "+type);
            }
        }

        private static int getTypeFromFieldDescriptor(String descriptor) {
            return getTypeFromASMType(Type.getType(descriptor));
        }

        private static int getReturnTypeFromMethodDescriptor(String descriptor) {
            return getTypeFromASMType(Type.getReturnType(descriptor));
        }

        /** Maps DEX label to ASM label
         * @param label a DEX label
         * @return the corresponding ASM label.
         */
        private org.objectweb.asm.Label getASMLabel(Label label) {
            org.objectweb.asm.Label asmLabel = labelMap.get(label);
            if (asmLabel != null) {
                return asmLabel;
            }
            asmLabel = new org.objectweb.asm.Label();
            labelMap.put(label, asmLabel);
            return asmLabel;
        }
        
        /** This method must be called before each instruction unless the instruction
         *  is a MOVE_RESULT* or a MOVE_EXCEPTION.
         *  It checks if the last instruction was an invoke or the start of an exception handler
         *  and corrects the stack.
         */
        private void fixStackAfterAMethodCallOrAnExceptionHandler() {
            int returnRegisterType = this.returnRegisterType;
            if (returnRegisterType != VOID_TYPE) {
                mv.visitInsn((returnRegisterType == LONG_TYPE || returnRegisterType == DOUBLE_TYPE)? POP2: POP);
                this.returnRegisterType = VOID_TYPE;
            }
            if (exceptionOnTop) {
                mv.visitInsn(POP);
                exceptionOnTop = false;
            }
        }
        
        private void visitNotInstr(int type) {
            org.objectweb.asm.Label falsePart = new org.objectweb.asm.Label();
            org.objectweb.asm.Label endPart = new org.objectweb.asm.Label();
            mv.visitJumpInsn(IFNE, falsePart);
            mv.visitInsn((type == INT_TYPE)? ICONST_1: LCONST_1);
            mv.visitJumpInsn(GOTO, endPart);
            mv.visitLabel(falsePart);
            mv.visitInsn((type == INT_TYPE)? ICONST_0: LCONST_0);
            mv.visitLabel(endPart);
        }
        
        private static AssertionError newAssertionError(Opcode opcode) {
            return new AssertionError("Invalid opcode: " + opcode);
        }

        @Override
        public DexAnnotationVisitor visitAnnotation(int visibility, String type) {
            // XXX Ignore annotations for now.
            return null;
        }

        @Override
        public DexAnnotationVisitor visitParameterAnnotation(int parameter,
                int visibility, String type) {
            // XXX Ignore annotations for now.
            return null;
        }

        @Override
        public void visitLineNumber(String source, int line, Label start) {
            // XXX Ignore debug information for now.
        }

        @Override
        public void visitLocalVariable(String name, String desc, Label start,
                Label end, int reg) {
            // XXX Ignore debug information for now.
        }

        @Override
        public void visitCode(int registerSize, int insSize, int outSize) {
            mv.visitCode();
            this.locals = registerSize - insSize;
            this.parameters = insSize;
            interpreter = new Interpreter(registerSize);
        }

        @Override
        public void visitInstr(Opcode opcode) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            if (opcode == Opcode.RETURN_VOID) {
                mv.visitInsn(RETURN);
                interpreter.setDead();
                return;
            }
            if (opcode == Opcode.NOP) {
                mv.visitInsn(NOP);
                return;
            }
            throw newAssertionError(opcode);
        }

        @Override
        public void visitInstrField(Opcode opcode, int vsrcOrDest, int vref,
                String owner, String name, String desc) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            vsrcOrDest = registerToSlot(vsrcOrDest);
            vref = registerToSlot(vref);
            owner = TypeUtil.convertDescToInternal(owner);
            if (opcode.compareTo(Opcode.IGET) >= 0
                    && opcode.compareTo(Opcode.IGET_SHORT) <= 0) {
                mv.visitVarInsn(ALOAD, vref);
                mv.visitFieldInsn(GETFIELD, owner, name, desc);
                int type = getTypeFromFieldDescriptor(desc);
                interpreter.store(vsrcOrDest, type);
                mv.visitVarInsn(Register.getJavaOpcode(type, ISTORE),
                        vsrcOrDest);
            } else if (opcode.compareTo(Opcode.SGET) >= 0
                    && opcode.compareTo(Opcode.SGET_SHORT) <= 0) {
                mv.visitFieldInsn(GETSTATIC, owner, name, desc);
                int type = getTypeFromFieldDescriptor(desc);
                interpreter.store(vsrcOrDest, type);
                mv.visitVarInsn(Register.getJavaOpcode(type, ISTORE),
                        vsrcOrDest);
            } else if (opcode.compareTo(Opcode.IPUT) >= 0
                    && opcode.compareTo(Opcode.IPUT_SHORT) <= 0) {
                mv.visitVarInsn(ALOAD, vref);
                int type = getTypeFromFieldDescriptor(desc);
                mv.visitVarInsn(Register.getJavaOpcode(type, ILOAD), vsrcOrDest);
                mv.visitFieldInsn(PUTFIELD, owner, name, desc);
                interpreter.load(vsrcOrDest, type);
            } else if (opcode.compareTo(Opcode.SPUT) >= 0
                    && opcode.compareTo(Opcode.SPUT_SHORT) <= 0) {
                int type = getTypeFromFieldDescriptor(desc);
                mv.visitVarInsn(Register.getJavaOpcode(type, ILOAD), vsrcOrDest);
                mv.visitFieldInsn(PUTSTATIC, owner, name, desc);
                interpreter.load(vsrcOrDest, type);
            } else {
                throw newAssertionError(opcode);
            }
        }

        @Override
        public void visitInstrConstString(Opcode opcode, int vdest, String value) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            vdest = registerToSlot(vdest);
            mv.visitLdcInsn(value);
            mv.visitVarInsn(ASTORE, vdest);
            interpreter.store(vdest, OBJECT_TYPE);
        }
        
        @Override
        public void visitInstrClass(Opcode opcode, int vsrcOrDest, String typeDesc) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            vsrcOrDest = registerToSlot(vsrcOrDest);
            switch(opcode) {
            case CONST_CLASS:
                mv.visitLdcInsn(Type.getType(typeDesc));
                mv.visitVarInsn(ASTORE, vsrcOrDest);
                interpreter.store(vsrcOrDest, OBJECT_TYPE);
                break;
            case NEW_INSTANCE:
                mv.visitTypeInsn(NEW, TypeUtil
                        .convertDescToInternal(typeDesc));
                mv.visitVarInsn(ASTORE, vsrcOrDest);
                interpreter.store(vsrcOrDest, OBJECT_TYPE);
                break;
            case CHECK_CAST:
                mv.visitVarInsn(ALOAD, vsrcOrDest);
                interpreter.load(vsrcOrDest, OBJECT_TYPE);
                mv.visitTypeInsn(CHECKCAST, TypeUtil
                        .convertDescToInternal(typeDesc));
                mv.visitVarInsn(ASTORE, vsrcOrDest);
                interpreter.store(vsrcOrDest, getTypeFromASMType(Type.getType(typeDesc)));
                break;
            default:
                throw newAssertionError(opcode);
            }
        }

        @Override
        public void visitInstrMethod(Opcode opcode, int num, int va,
                int vpacked, String owner, String name, String desc) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            Opcode erasedRangeOpcode;
            int[] registers;
            if (opcode.compareTo(Opcode.INVOKE_VIRTUAL_RANGE) >= 0) { // *_range
                registers = null;
                erasedRangeOpcode = Opcode.getOpcode(opcode.ordinal()
                        - INVOKE_RANGE_SHIFT);
            } else {
                registers = new int[] { getRegisterD(vpacked),
                        getRegisterE(vpacked), getRegisterF(vpacked),
                        getRegisterG(vpacked), va };
                erasedRangeOpcode = opcode;
            }

            int javaOpcode;
            switch (erasedRangeOpcode) {
            case INVOKE_STATIC:
                javaOpcode = INVOKESTATIC;
                break;
            case INVOKE_SUPER:
                javaOpcode = INVOKESPECIAL;
                break;
            case INVOKE_VIRTUAL:
                javaOpcode = INVOKEVIRTUAL;
                break;
            case INVOKE_INTERFACE:
                javaOpcode = INVOKEINTERFACE;
                break;
            case INVOKE_DIRECT:
                javaOpcode = ("<init>".equals(name)) ? INVOKESPECIAL
                        : INVOKEVIRTUAL;
                break;
            default:
                throw newAssertionError(opcode);
            }
            
            // We need to load the receiver first.
            int r;
            if (erasedRangeOpcode != Opcode.INVOKE_STATIC) {
                r = 1;
                int register = registerToSlot((registers == null) ? va
                        : registers[0]);
                mv.visitVarInsn(ALOAD, register);
                interpreter.load(register, getTypeFromASMType(Type.getType(owner)));
            } else {
                r = 0;
            }
            
            Type[] types = Type.getArgumentTypes(desc);
            for (Type asmType : types) {
                int type = getTypeFromASMType(asmType);
                int register = registerToSlot((registers == null) ? va + r
                        : registers[r]);
                mv.visitVarInsn(Register.getJavaOpcode(type, ILOAD), register);
                interpreter.load(register, type);
                r += asmType.getSize();
            }

            owner = TypeUtil.convertDescToInternal(owner);
            mv.visitMethodInsn(javaOpcode, owner, name, desc);
            returnRegisterType = getReturnTypeFromMethodDescriptor(desc);
            return;
        }

        private static final int INVOKE_RANGE_SHIFT =
                Opcode.INVOKE_VIRTUAL_RANGE.ordinal() -
                Opcode.INVOKE_VIRTUAL.ordinal();

        @Override
        public void visitInstrConstU32(Opcode opcode, int vdest,
                final int value) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            final int sdest = registerToSlot(vdest);
            mv.setPatchMode();
            mv.visitLdcInsn(value);
            final AbstractInsnNode ldc = mv.getLastInsnNode();
            mv.visitVarInsn(ISTORE, sdest);
            final AbstractInsnNode istore = mv.getLastInsnNode();
            interpreter.storeUntypedType(sdest, U32_TYPE, new Patchable() {
                @Override
                protected void patch(int registerType) {
                    if (registerType == FLOAT_TYPE) {
                        mv.patch(ldc, new LdcInsnNode(Float
                                .intBitsToFloat(value)));
                        mv.patch(istore, new VarInsnNode(FSTORE, sdest));
                        return;
                    }
                    if (registerType == OBJECT_TYPE || Register.isArray(registerType)) {
                        mv.patch(ldc, new InsnNode(ACONST_NULL));
                        mv.patch(istore, new VarInsnNode(ASTORE, sdest));
                        return;
                    }
                }
            });
        }

        @Override
        public void visitInstrConstU64(Opcode opcode, int vdest,
                final long value) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            final int sdest = registerToSlot(vdest);
            mv.setPatchMode();
            mv.visitLdcInsn(value);
            final AbstractInsnNode ldc = mv.getLastInsnNode();
            mv.visitVarInsn(LSTORE, sdest);
            final AbstractInsnNode lstore = mv.getLastInsnNode();
            interpreter.storeUntypedType(sdest, U64_TYPE, new Patchable() {
                @Override
                protected void patch(int registerType) {
                    if (registerType == DOUBLE_TYPE) {
                        mv.patch(ldc,
                                new LdcInsnNode(Double.longBitsToDouble(value)));
                        mv.patch(lstore, new VarInsnNode(DSTORE, sdest));
                        return;
                    }
                }
            });
        }

        @Override
        public void visitInstrOp(Opcode opcode, int srcOrDst) {
            srcOrDst = registerToSlot(srcOrDst);
            switch (opcode) {
            case RETURN:
            case RETURN_WIDE:
            case RETURN_OBJECT: {
                fixStackAfterAMethodCallOrAnExceptionHandler();
                int type = getReturnTypeFromMethodDescriptor(desc);
                mv.visitVarInsn(Register.getJavaOpcode(type, ILOAD), srcOrDst);
                interpreter.load(srcOrDst, type);
                mv.visitInsn(Register.getJavaOpcode(type, IRETURN));
                break;
            }
            case MOVE_RESULT:
            case MOVE_RESULT_WIDE:
            case MOVE_RESULT_OBJECT: {
                // INVOKE + MOVE_RESULT is equivalent to INVOKE + STORE
                if (returnRegisterType != VOID_TYPE) {
                    mv.visitVarInsn(
                            Register.getJavaOpcode(returnRegisterType, ISTORE),
                            srcOrDst);
                    interpreter.store(srcOrDst, returnRegisterType);
                    returnRegisterType = VOID_TYPE;
                }
                break;
            }
            case MOVE_EXCEPTION: {
                mv.visitVarInsn(ASTORE, srcOrDst);
                interpreter.store(srcOrDst, OBJECT_TYPE);
                exceptionOnTop = false;
                break;   
            }
            default: //MONITOR_ENTER, MONITOR_EXIT, THROW.
                fixStackAfterAMethodCallOrAnExceptionHandler();
                mv.visitVarInsn(ALOAD, srcOrDst);
                interpreter.load(srcOrDst, OBJECT_TYPE);
                mv.visitInsn(toJavaOpcode[opcode.ordinal()]);
            }
        }
        
        @Override
        public void visitInstrUnaryOp(Opcode opcode, int dest, int src) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            final int vdest = registerToSlot(dest);
            final int vsrc = registerToSlot(src);
            int srcType = INT_TYPE, dstType;
            switch(opcode) {
            case MOVE:
            case MOVE_FROM16:
            case MOVE_16:
            case MOVE_WIDE:
            case MOVE_WIDE_FROM16:
            case MOVE_WIDE_16: 
            case MOVE_OBJECT:
            case MOVE_OBJECT_FROM16:
            case MOVE_OBJECT_16: {
                final Register register  = interpreter.getRegister(vsrc);
                int type = register.getType();
                if (Register.isUntyped(type)) {
                    int runtimeType = Register.asDefaultTypedType(type);
                    mv.setPatchMode();
                    mv.visitVarInsn(Register.getJavaOpcode(runtimeType, ILOAD), vsrc);
                    final AbstractInsnNode load = mv.getLastInsnNode();
                    mv.visitVarInsn(Register.getJavaOpcode(runtimeType, ISTORE), vdest);
                    final AbstractInsnNode store = mv.getLastInsnNode();
                    final Patchable patchable = register.getPatchable();
                    interpreter.storeUntypedType(vdest, type, new Patchable() {
                        @Override
                        protected void patch(int registerType) {
                            patchable.doPatch(registerType);
                            if (registerType == FLOAT_TYPE) {
                                mv.patch(load, new VarInsnNode(FLOAD, vsrc));
                                mv.patch(store, new VarInsnNode(FSTORE, vdest));
                                return;
                            }
                            if (registerType == DOUBLE_TYPE) {
                                mv.patch(load, new VarInsnNode(DLOAD, vsrc));
                                mv.patch(store, new VarInsnNode(DSTORE, vdest));
                                return;
                            }
                            if (registerType == OBJECT_TYPE || Register.isArray(registerType)) {
                                mv.patch(load, new VarInsnNode(ALOAD, vsrc));
                                mv.patch(store, new VarInsnNode(ASTORE, vdest));
                                return;
                            }
                        }
                    });
                } else {
                    mv.visitVarInsn(Register.getJavaOpcode(type, ILOAD), vsrc);
                    interpreter.load(vsrc, type);
                    mv.visitVarInsn(Register.getJavaOpcode(type, ISTORE), vdest);
                    interpreter.store(vdest, type);
                }
                return;
            }
            
            case NEG_INT: case NOT_INT:
                dstType = INT_TYPE;
                break;
            case NEG_LONG: case NOT_LONG:
                srcType = dstType = LONG_TYPE;
                break;
            case NEG_FLOAT:
                srcType = dstType = FLOAT_TYPE;
                break;
            case NEG_DOUBLE:
                srcType = dstType = DOUBLE_TYPE;
                break;
            
            case INT_TO_BYTE:
            case INT_TO_CHAR:
            case INT_TO_SHORT:
                dstType = BYTE_TYPE + opcode.ordinal() - Opcode.INT_TO_BYTE.ordinal();
                break;
                
            default: {
                int conversion = opcode.ordinal() - Opcode.INT_TO_LONG.ordinal();
                int div = conversion / 3;
                int modulo = conversion % 3;
                srcType = INT_TYPE + div;
                dstType = INT_TYPE + ((modulo < div)? modulo: modulo + 1);
            }
            }
            
            mv.visitVarInsn(Register.getJavaOpcode(srcType, ILOAD), vsrc);
            interpreter.load(vsrc, srcType);
            if (opcode == Opcode.NOT_INT || opcode == Opcode.NOT_LONG) {
                visitNotInstr(srcType);
            } else {
                mv.visitInsn(toJavaOpcode[opcode.ordinal()]);   
            }
            mv.visitVarInsn(Register.getJavaOpcode(dstType, ISTORE), vdest);
            interpreter.store(vdest, dstType);
        }

        @Override
        public void visitInstrBinOp(Opcode opcode, int vdest, int vsrc1,
                int vsrc2) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            vdest = registerToSlot(vdest);
            vsrc1 = registerToSlot(vsrc1);
            vsrc2 = registerToSlot(vsrc2);
            
            // 2 addresses -> simple address opcode
            if (opcode.compareTo(Opcode.ADD_INT_2ADDR) >= 0 &&
                opcode.compareTo(Opcode.REM_DOUBLE_2ADDR) <= 0) {
                opcode = Opcode.getOpcode(opcode.ordinal() + OP_2ADDR_SHIFT);
            }
            
            int srcType, dstType;
            switch(opcode) {
            case CMPL_FLOAT: case CMPG_FLOAT: case ADD_FLOAT: case SUB_FLOAT:
            case MUL_FLOAT: case DIV_FLOAT: case REM_FLOAT:
                srcType = FLOAT_TYPE;
                dstType = (opcode == Opcode.CMPL_FLOAT ||
                           opcode == Opcode.CMPG_FLOAT) ? INT_TYPE : FLOAT_TYPE;
                break;
            
            case CMPL_DOUBLE: case CMPG_DOUBLE: case ADD_DOUBLE: case SUB_DOUBLE:
            case MUL_DOUBLE:  case DIV_DOUBLE: case REM_DOUBLE:
                srcType = DOUBLE_TYPE;
                dstType = (opcode == Opcode.CMPL_DOUBLE ||
                           opcode == Opcode.CMPG_DOUBLE) ? INT_TYPE : DOUBLE_TYPE;
                break;
                
            case CMP_LONG: case ADD_LONG: case SUB_LONG: case MUL_LONG:
            case DIV_LONG: case REM_LONG: case AND_LONG: case OR_LONG:
            case XOR_LONG: case SHL_LONG: case SHR_LONG: case USHR_LONG:
                srcType = LONG_TYPE;
                dstType = (opcode == Opcode.CMP_LONG) ? INT_TYPE : LONG_TYPE;
                break;
              
            // ADD_INT, SUB_INT, MUL_INT, DIV_INT, REM_INT,
            // AND_INT, OR_INT, XOR_INT, SHL_INT, SHR_INT, USHR_INT,
            default: 
                srcType = dstType = INT_TYPE;
            }
            
            mv.visitVarInsn(Register.getJavaOpcode(srcType, ILOAD), vsrc1);
            interpreter.load(vsrc1, srcType);
            mv.visitVarInsn(Register.getJavaOpcode(srcType, ILOAD), vsrc2);
            interpreter.load(vsrc2, srcType);
            mv.visitInsn(toJavaOpcode[opcode.ordinal()]);
            mv.visitVarInsn(Register.getJavaOpcode(dstType, ISTORE), vdest);
            interpreter.store(vdest, dstType);
        }
        
        private static final int OP_2ADDR_SHIFT =
            Opcode.ADD_INT.ordinal() - Opcode.ADD_INT_2ADDR.ordinal();

        @Override
        public void visitInstrBinOpAndLiteral(Opcode opcode, int vdest,
                int vsrc, int value) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            vdest = registerToSlot(vdest);
            vsrc = registerToSlot(vsrc);
            boolean rsub = opcode == Opcode.RSUB_INT_LIT16
                    || opcode == Opcode.RSUB_INT_LIT8;
            if (rsub) {
                mv.visitLdcInsn(value);
            }
            mv.visitVarInsn(ILOAD, vsrc);
            interpreter.load(vsrc, INT_TYPE);
            if (!rsub) {
                mv.visitLdcInsn(value);
            }
            mv.visitInsn(toJavaOpcode[opcode.ordinal()]);
            mv.visitVarInsn(ISTORE, vdest);
            interpreter.store(vdest, INT_TYPE);
        }
        
        @Override
        public void visitLabel(Label label) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            exceptionOnTop = exceptionHandlerSet.contains(label);
            
            Register[] registers = interpreter.getJoinPoint(label);   
            if (registers != null) { // target of an already seen jump
                interpreter.merge(registers);
            } else {  // perhaps a control flow join point, save state
                interpreter.cloneJoinPoint(label);
            }
            mv.visitLabel(getASMLabel(label));
        }
        
        @Override
        public void visitInstrGoto(Opcode opcode, Label label) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            Register[] registers = interpreter.getJoinPoint(label);
            if (registers != null) { // backward goto
                interpreter.merge(registers);
            } else {  // forward goto
                interpreter.cloneJoinPoint(label);    
            }
            
            mv.visitJumpInsn(GOTO, getASMLabel(label));
            interpreter.setDead();
        }
        
        @Override
        public void visitInstrIfTestZ(Opcode opcode, int vsrc, Label label) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            vsrc = registerToSlot(vsrc);
            mv.visitVarInsn(ILOAD, vsrc);
            interpreter.load(vsrc, INT_TYPE);
            
            Register[] registers = interpreter.getJoinPoint(label);
            if (registers != null) { // backward jump
                interpreter.merge(registers);
            } else {  // forward jump
                interpreter.cloneJoinPoint(label);    
            }
            
            org.objectweb.asm.Label asmLabel = getASMLabel(label);
            mv.visitJumpInsn(toJavaOpcode[opcode.ordinal()], asmLabel);
        }
        
        @Override
        public void visitInstrNewArray(Opcode opcode, int vdest, int vsize,
                String typeDesc) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            vdest = registerToSlot(vdest);
            vsize = registerToSlot(vsize);
            mv.visitVarInsn(ILOAD, vsize);
            interpreter.load(vsize, INT_TYPE);
            mv.visitIntInsn(NEWARRAY, getNewArrayKindFromASMType(Type.getType(typeDesc).getElementType()));
            mv.visitVarInsn(ASTORE, vdest);
            interpreter.store(vdest, getTypeFromASMType(Type.getType(typeDesc)));
        }
        
        @Override
        public void visitInstrArray(Opcode opcode, int vsrcOrDest, int varray,
                int vindex) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            vsrcOrDest = registerToSlot(vsrcOrDest);
            varray = registerToSlot(varray);
            vindex = registerToSlot(vindex);
            int type = interpreter.getRegister(varray).getType();
            int componentType = Register.getComponentType(type);
            interpreter.load(vindex, INT_TYPE);
            switch(opcode) {
            case APUT: case APUT_WIDE: case APUT_OBJECT:
            case APUT_BOOLEAN: case APUT_BYTE: case APUT_CHAR: case APUT_SHORT:
                mv.visitVarInsn(Register.getJavaOpcode(type, ILOAD), varray);
                mv.visitVarInsn(ILOAD, vindex);
                mv.visitVarInsn(Register.getJavaOpcode(componentType, ILOAD), vsrcOrDest);
                mv.visitInsn(Register.getJavaOpcode(componentType, IASTORE));
                interpreter.load(vsrcOrDest, componentType);
                break;
            default:
            //case AGET: case AGET_WIDE: case AGET_OBJECT:
            //case AGET_BOOLEAN: case AGET_BYTE: case AGET_CHAR: case AGET_SHORT:
                mv.visitVarInsn(Register.getJavaOpcode(type, ILOAD), varray);
                mv.visitVarInsn(ILOAD, vindex);
                mv.visitInsn(Register.getJavaOpcode(componentType, IALOAD));
                mv.visitVarInsn(Register.getJavaOpcode(componentType, ISTORE), vsrcOrDest);
                interpreter.store(vsrcOrDest, componentType);
                break;
            }
        }
        
        @Override
        public void visitInstrInstanceof(Opcode opcode, int vdest, int vsrc,
                String type) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            vdest = registerToSlot(vdest);
            vsrc = registerToSlot(vsrc);
            mv.visitVarInsn(ALOAD, vsrc);
            Type asmType = Type.getType(type);
            interpreter.load(vsrc, getTypeFromASMType(asmType));
            mv.visitTypeInsn(INSTANCEOF, asmType.getInternalName());
            mv.visitVarInsn(ISTORE, vdest);
            interpreter.store(vdest, BOOLEAN_TYPE);
        }
        
        @Override
        public void visitInstrIfTest(Opcode opcode, int vsrc1, int vsrc2,
                Label label) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            vsrc1 = registerToSlot(vsrc1);
            vsrc2 = registerToSlot(vsrc2);
            org.objectweb.asm.Label asmLabel = getASMLabel(label);
            if (opcode == Opcode.IF_EQ || opcode == Opcode.IF_NE) {
                int src1Type = interpreter.getRegister(vsrc1).getType();
                int src2Type = interpreter.getRegister(vsrc2).getType();
                int javaOpcode;
                int type;
                if (src1Type == INT_TYPE || src2Type == INT_TYPE) {
                    type = INT_TYPE;
                    javaOpcode = (opcode == Opcode.IF_EQ)? IF_ICMPEQ: IF_ICMPNE;
                } else { // OBJECT_TYPE or array
                    type = OBJECT_TYPE;
                    javaOpcode = (opcode == Opcode.IF_EQ)? IF_ACMPEQ: IF_ACMPNE;
                }
                mv.visitVarInsn(Register.getJavaOpcode(type, ILOAD), vsrc1);
                interpreter.load(vsrc1, OBJECT_TYPE);
                mv.visitVarInsn(Register.getJavaOpcode(type, ILOAD), vsrc2);
                interpreter.load(vsrc2, OBJECT_TYPE);
                mv.visitJumpInsn(javaOpcode, asmLabel);
            } else {   // IF_LT, IF_GE, IF_GT, IF_LE
                mv.visitVarInsn(ILOAD, vsrc1);
                interpreter.load(vsrc1, INT_TYPE);
                mv.visitVarInsn(ILOAD, vsrc2);
                interpreter.load(vsrc2, INT_TYPE);
                mv.visitJumpInsn(toJavaOpcode[opcode.ordinal()], asmLabel);
            }
        }
        
        @Override
        public void visitTryCatch(Label start, Label end, Label handler, String type) {
            exceptionHandlerSet.add(handler);
            type = (type == null)? null: TypeUtil.convertDescToInternal(type);
            mv.visitTryCatchBlock(getASMLabel(start), getASMLabel(end),
                    getASMLabel(handler),
                    type);
        }
        
        
        // unimplemented
        
        @Override
        public void visitInstrFillArrayData(Opcode opcode, int vsrc,
                Object arrayData) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            vsrc = registerToSlot(vsrc);
            throw new UnsupportedOperationException("NYI " + opcode);
        }

        @Override
        public void visitInstrFilledNewArray(Opcode opcode, int num, int va,
                int vpacked, String type) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            // don't forget to use registerToSlot
            throw new UnsupportedOperationException("NYI " + opcode);
        }

        @Override
        public void visitInstrSwitch(Opcode opcode, int vsrc,
                Map<Integer, Label> labels) {
            fixStackAfterAMethodCallOrAnExceptionHandler();
            vsrc = registerToSlot(vsrc);
            throw new UnsupportedOperationException("NYI " + opcode);
        }

        @Override
        public void visitEnd() {
            mv.visitMaxs(-1, -1);
            mv.visitEnd();
        }
    }
    
    static final int[] toJavaOpcode;
    static { // this string is generated using DalvikToJVMEncoder
        String text = "AA@@@@@@@@@@@@@@@@@@@@@@@@@@LB@@@@@@@@@@@@@@@@@@@@@@@@@@@@MCMDMAMBLOLLLM@@@@@@LPKHKHKH@@@@JFJGJHJIJE@@@@KBKCKDKEJJJKJLJMJNJO@@@@@@@@@@@@@@@@DCDDDDDEDF@@@@FDFEFEFFFG@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@HE@@HF@@HGHHIFIGIHIIIJIKILIMINIOIPJAJBJCJDGAGEGIGMHAGAIAICHIHKHMGBGFGJGNHBHPIBIDHJHLHNGCGGGKGOHCGDGHGLGPHDGAGEGIGMHAHOIAICHIHKHMGBGFGJGNHBHPIBIDHJHLHNGCGGGKGOHCGDGHGLGPHDGAGEGIGMHAGAIAICGAGEGIGMHAHOIAICHIHKHM@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@";
        int[] array = new int[256];
        int length = text.length();
        for (int i = 0; i < length; i += 2) {
            int index = ((text.charAt(i) - 'A') << 4)
                    | (text.charAt(i + 1) - 'A');
            array[i >> 1] = index;
        }
        toJavaOpcode = array;
    }
}