org.jephyr.easyflow.instrument.NewRelocatorMethodAdapter.java Source code

Java tutorial

Introduction

Here is the source code for org.jephyr.easyflow.instrument.NewRelocatorMethodAdapter.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Igor Konev
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.jephyr.easyflow.instrument;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AnalyzerAdapter;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

import static org.objectweb.asm.Opcodes.AALOAD;
import static org.objectweb.asm.Opcodes.AASTORE;
import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ANEWARRAY;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.ARRAYLENGTH;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.ATHROW;
import static org.objectweb.asm.Opcodes.BALOAD;
import static org.objectweb.asm.Opcodes.BASTORE;
import static org.objectweb.asm.Opcodes.BIPUSH;
import static org.objectweb.asm.Opcodes.CALOAD;
import static org.objectweb.asm.Opcodes.CASTORE;
import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.D2F;
import static org.objectweb.asm.Opcodes.D2I;
import static org.objectweb.asm.Opcodes.D2L;
import static org.objectweb.asm.Opcodes.DADD;
import static org.objectweb.asm.Opcodes.DALOAD;
import static org.objectweb.asm.Opcodes.DASTORE;
import static org.objectweb.asm.Opcodes.DCMPG;
import static org.objectweb.asm.Opcodes.DCMPL;
import static org.objectweb.asm.Opcodes.DCONST_0;
import static org.objectweb.asm.Opcodes.DCONST_1;
import static org.objectweb.asm.Opcodes.DDIV;
import static org.objectweb.asm.Opcodes.DLOAD;
import static org.objectweb.asm.Opcodes.DMUL;
import static org.objectweb.asm.Opcodes.DNEG;
import static org.objectweb.asm.Opcodes.DOUBLE;
import static org.objectweb.asm.Opcodes.DREM;
import static org.objectweb.asm.Opcodes.DRETURN;
import static org.objectweb.asm.Opcodes.DSTORE;
import static org.objectweb.asm.Opcodes.DSUB;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.DUP2;
import static org.objectweb.asm.Opcodes.DUP2_X1;
import static org.objectweb.asm.Opcodes.DUP2_X2;
import static org.objectweb.asm.Opcodes.DUP_X1;
import static org.objectweb.asm.Opcodes.DUP_X2;
import static org.objectweb.asm.Opcodes.F2D;
import static org.objectweb.asm.Opcodes.F2I;
import static org.objectweb.asm.Opcodes.F2L;
import static org.objectweb.asm.Opcodes.FADD;
import static org.objectweb.asm.Opcodes.FALOAD;
import static org.objectweb.asm.Opcodes.FASTORE;
import static org.objectweb.asm.Opcodes.FCMPG;
import static org.objectweb.asm.Opcodes.FCMPL;
import static org.objectweb.asm.Opcodes.FCONST_0;
import static org.objectweb.asm.Opcodes.FCONST_1;
import static org.objectweb.asm.Opcodes.FCONST_2;
import static org.objectweb.asm.Opcodes.FDIV;
import static org.objectweb.asm.Opcodes.FLOAD;
import static org.objectweb.asm.Opcodes.FLOAT;
import static org.objectweb.asm.Opcodes.FMUL;
import static org.objectweb.asm.Opcodes.FNEG;
import static org.objectweb.asm.Opcodes.FREM;
import static org.objectweb.asm.Opcodes.FRETURN;
import static org.objectweb.asm.Opcodes.FSTORE;
import static org.objectweb.asm.Opcodes.FSUB;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.GETSTATIC;
import static org.objectweb.asm.Opcodes.GOTO;
import static org.objectweb.asm.Opcodes.I2B;
import static org.objectweb.asm.Opcodes.I2C;
import static org.objectweb.asm.Opcodes.I2D;
import static org.objectweb.asm.Opcodes.I2F;
import static org.objectweb.asm.Opcodes.I2L;
import static org.objectweb.asm.Opcodes.I2S;
import static org.objectweb.asm.Opcodes.IADD;
import static org.objectweb.asm.Opcodes.IALOAD;
import static org.objectweb.asm.Opcodes.IAND;
import static org.objectweb.asm.Opcodes.IASTORE;
import static org.objectweb.asm.Opcodes.ICONST_0;
import static org.objectweb.asm.Opcodes.ICONST_1;
import static org.objectweb.asm.Opcodes.ICONST_2;
import static org.objectweb.asm.Opcodes.ICONST_3;
import static org.objectweb.asm.Opcodes.ICONST_4;
import static org.objectweb.asm.Opcodes.ICONST_5;
import static org.objectweb.asm.Opcodes.ICONST_M1;
import static org.objectweb.asm.Opcodes.IDIV;
import static org.objectweb.asm.Opcodes.IFEQ;
import static org.objectweb.asm.Opcodes.IFGE;
import static org.objectweb.asm.Opcodes.IFGT;
import static org.objectweb.asm.Opcodes.IFLE;
import static org.objectweb.asm.Opcodes.IFLT;
import static org.objectweb.asm.Opcodes.IFNE;
import static org.objectweb.asm.Opcodes.IFNULL;
import static org.objectweb.asm.Opcodes.IF_ACMPEQ;
import static org.objectweb.asm.Opcodes.IF_ACMPNE;
import static org.objectweb.asm.Opcodes.IF_ICMPEQ;
import static org.objectweb.asm.Opcodes.IF_ICMPGE;
import static org.objectweb.asm.Opcodes.IF_ICMPGT;
import static org.objectweb.asm.Opcodes.IF_ICMPLE;
import static org.objectweb.asm.Opcodes.IF_ICMPLT;
import static org.objectweb.asm.Opcodes.IF_ICMPNE;
import static org.objectweb.asm.Opcodes.IINC;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.IMUL;
import static org.objectweb.asm.Opcodes.INEG;
import static org.objectweb.asm.Opcodes.INSTANCEOF;
import static org.objectweb.asm.Opcodes.INTEGER;
import static org.objectweb.asm.Opcodes.INVOKEDYNAMIC;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.IOR;
import static org.objectweb.asm.Opcodes.IREM;
import static org.objectweb.asm.Opcodes.IRETURN;
import static org.objectweb.asm.Opcodes.ISHL;
import static org.objectweb.asm.Opcodes.ISHR;
import static org.objectweb.asm.Opcodes.ISTORE;
import static org.objectweb.asm.Opcodes.ISUB;
import static org.objectweb.asm.Opcodes.IUSHR;
import static org.objectweb.asm.Opcodes.IXOR;
import static org.objectweb.asm.Opcodes.JSR;
import static org.objectweb.asm.Opcodes.L2D;
import static org.objectweb.asm.Opcodes.L2F;
import static org.objectweb.asm.Opcodes.L2I;
import static org.objectweb.asm.Opcodes.LADD;
import static org.objectweb.asm.Opcodes.LALOAD;
import static org.objectweb.asm.Opcodes.LAND;
import static org.objectweb.asm.Opcodes.LASTORE;
import static org.objectweb.asm.Opcodes.LCMP;
import static org.objectweb.asm.Opcodes.LCONST_0;
import static org.objectweb.asm.Opcodes.LCONST_1;
import static org.objectweb.asm.Opcodes.LDC;
import static org.objectweb.asm.Opcodes.LDIV;
import static org.objectweb.asm.Opcodes.LLOAD;
import static org.objectweb.asm.Opcodes.LMUL;
import static org.objectweb.asm.Opcodes.LNEG;
import static org.objectweb.asm.Opcodes.LONG;
import static org.objectweb.asm.Opcodes.LOOKUPSWITCH;
import static org.objectweb.asm.Opcodes.LOR;
import static org.objectweb.asm.Opcodes.LREM;
import static org.objectweb.asm.Opcodes.LRETURN;
import static org.objectweb.asm.Opcodes.LSHL;
import static org.objectweb.asm.Opcodes.LSHR;
import static org.objectweb.asm.Opcodes.LSTORE;
import static org.objectweb.asm.Opcodes.LSUB;
import static org.objectweb.asm.Opcodes.LUSHR;
import static org.objectweb.asm.Opcodes.LXOR;
import static org.objectweb.asm.Opcodes.MONITORENTER;
import static org.objectweb.asm.Opcodes.MONITOREXIT;
import static org.objectweb.asm.Opcodes.MULTIANEWARRAY;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.NEWARRAY;
import static org.objectweb.asm.Opcodes.NOP;
import static org.objectweb.asm.Opcodes.NULL;
import static org.objectweb.asm.Opcodes.POP;
import static org.objectweb.asm.Opcodes.POP2;
import static org.objectweb.asm.Opcodes.PUTFIELD;
import static org.objectweb.asm.Opcodes.PUTSTATIC;
import static org.objectweb.asm.Opcodes.RET;
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.SALOAD;
import static org.objectweb.asm.Opcodes.SASTORE;
import static org.objectweb.asm.Opcodes.SIPUSH;
import static org.objectweb.asm.Opcodes.SWAP;
import static org.objectweb.asm.Opcodes.TABLESWITCH;
import static org.objectweb.asm.Opcodes.TOP;

final class NewRelocatorMethodAdapter extends AnalyzingMethodNode {

    private final MethodVisitor mv;

    private NewRelocatorMethodAdapter(int access, String name, String desc, String signature, String[] exceptions,
            MethodVisitor mv) {
        super(access, name, desc, signature, exceptions);
        this.mv = mv;
    }

    static MethodVisitor create(String owner, int access, String name, String desc, String signature,
            String[] exceptions, MethodVisitor mv) {
        NewRelocatorMethodAdapter adapter = new NewRelocatorMethodAdapter(access, name, desc, signature, exceptions,
                mv);
        AnalyzerAdapter analyzerAdapter = new AnalyzerAdapter(owner, access, name, desc, adapter);
        adapter.adapter = analyzerAdapter;
        return analyzerAdapter;
    }

    @Override
    public void visitEnd() {
        AbstractInsnNode next = instructions.getFirst();
        boolean removeFrame = false;
        int stackSize = 0;

        while (next != null) {
            AbstractInsnNode node = next;
            next = next.getNext();
            Object[] stack;

            switch (node.getOpcode()) {
            case -1:
                if (node instanceof FrameNode) {
                    if (removeFrame) {
                        instructions.remove(node);
                    } else {
                        stackSize = handleFrame((FrameNode) node);
                        removeFrame = true;
                    }
                }
                break;
            case NOP:
            case LALOAD:
            case DALOAD:
            case INEG:
            case LNEG:
            case FNEG:
            case DNEG:
            case IINC:
            case I2F:
            case L2D:
            case F2I:
            case D2L:
            case I2B:
            case I2C:
            case I2S:
            case GOTO:
            case RET:
            case NEWARRAY:
            case ANEWARRAY:
            case ARRAYLENGTH:
            case CHECKCAST:
            case INSTANCEOF:
                removeFrame = false;
                break;
            case ACONST_NULL:
            case ICONST_M1:
            case ICONST_0:
            case ICONST_1:
            case ICONST_2:
            case ICONST_3:
            case ICONST_4:
            case ICONST_5:
            case FCONST_0:
            case FCONST_1:
            case FCONST_2:
            case BIPUSH:
            case SIPUSH:
            case ILOAD:
            case FLOAD:
            case I2L:
            case I2D:
            case F2L:
            case F2D:
            case JSR:
                stackSize += 1;
                updateMaxStack(stackSize);
                removeFrame = false;
                break;
            case LCONST_0:
            case LCONST_1:
            case DCONST_0:
            case DCONST_1:
            case LLOAD:
            case DLOAD:
                stackSize += 2;
                updateMaxStack(stackSize);
                removeFrame = false;
                break;
            case LDC:
                Object cst = ((LdcInsnNode) node).cst;
                stackSize += cst instanceof Long || cst instanceof Double ? 2 : 1;
                updateMaxStack(stackSize);
                removeFrame = false;
                break;
            case ALOAD:
                if (frames.get(node).locals[((VarInsnNode) node).var] instanceof AbstractInsnNode) {
                    instructions.remove(node);
                } else {
                    stackSize += 1;
                    updateMaxStack(stackSize);
                    removeFrame = false;
                }
                break;
            case IALOAD:
            case FALOAD:
            case AALOAD:
            case BALOAD:
            case CALOAD:
            case SALOAD:
            case ISTORE:
            case FSTORE:
            case IADD:
            case FADD:
            case ISUB:
            case FSUB:
            case IMUL:
            case FMUL:
            case IDIV:
            case FDIV:
            case IREM:
            case FREM:
            case ISHL:
            case ISHR:
            case IUSHR:
            case IAND:
            case IOR:
            case IXOR:
            case L2I:
            case L2F:
            case D2I:
            case D2F:
            case IFEQ:
            case IFNE:
            case IFLT:
            case IFGE:
            case IFGT:
            case IFLE:
            case TABLESWITCH:
            case LOOKUPSWITCH:
            case MONITORENTER:
            case MONITOREXIT:
                stackSize -= 1;
                removeFrame = false;
                break;
            case LSTORE:
            case DSTORE:
            case LADD:
            case DADD:
            case LSUB:
            case DSUB:
            case LMUL:
            case DMUL:
            case LDIV:
            case DDIV:
            case LREM:
            case DREM:
            case LSHL:
            case LSHR:
            case LUSHR:
            case LAND:
            case LOR:
            case LXOR:
            case FCMPL:
            case FCMPG:
            case IF_ICMPEQ:
            case IF_ICMPNE:
            case IF_ICMPLT:
            case IF_ICMPGE:
            case IF_ICMPGT:
            case IF_ICMPLE:
                stackSize -= 2;
                removeFrame = false;
                break;
            case ASTORE:
                stack = frames.get(node).stack;
                if (stack[stack.length - 1] instanceof AbstractInsnNode) {
                    instructions.remove(node);
                } else {
                    stackSize -= 1;
                    removeFrame = false;
                }
                break;
            case IASTORE:
            case FASTORE:
            case AASTORE:
            case BASTORE:
            case CASTORE:
            case SASTORE:
            case LCMP:
            case DCMPL:
            case DCMPG:
                stackSize -= 3;
                removeFrame = false;
                break;
            case LASTORE:
            case DASTORE:
                stackSize -= 4;
                removeFrame = false;
                break;
            case POP:
                stack = frames.get(node).stack;
                if (stack[stack.length - 1] instanceof AbstractInsnNode) {
                    instructions.remove(node);
                } else {
                    stackSize -= 1;
                    removeFrame = false;
                }
                break;
            case POP2:
                stack = frames.get(node).stack;
                if (stack[stack.length - 1] instanceof AbstractInsnNode) {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        instructions.remove(node);
                    } else {
                        instructions.set(node, new InsnNode(POP));
                        stackSize -= 1;
                        removeFrame = false;
                    }
                } else {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        instructions.set(node, new InsnNode(POP));
                        stackSize -= 1;
                    } else {
                        stackSize -= 2;
                    }
                    removeFrame = false;
                }
                break;
            case DUP:
                stack = frames.get(node).stack;
                if (stack[stack.length - 1] instanceof AbstractInsnNode) {
                    instructions.remove(node);
                } else {
                    stackSize += 1;
                    updateMaxStack(stackSize);
                    removeFrame = false;
                }
                break;
            case DUP_X1:
                stack = frames.get(node).stack;
                if (stack[stack.length - 1] instanceof AbstractInsnNode) {
                    instructions.remove(node);
                } else {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        instructions.set(node, new InsnNode(DUP));
                    }
                    stackSize += 1;
                    updateMaxStack(stackSize);
                    removeFrame = false;
                }
                break;
            case DUP_X2:
                stack = frames.get(node).stack;
                if (stack[stack.length - 1] instanceof AbstractInsnNode) {
                    instructions.remove(node);
                } else {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        if (stack[stack.length - 3] instanceof AbstractInsnNode) {
                            instructions.set(node, new InsnNode(DUP));
                        } else {
                            instructions.set(node, new InsnNode(DUP_X1));
                        }
                    } else {
                        if (stack[stack.length - 3] instanceof AbstractInsnNode) {
                            instructions.set(node, new InsnNode(DUP_X1));
                        }
                    }
                    stackSize += 1;
                    updateMaxStack(stackSize);
                    removeFrame = false;
                }
                break;
            case DUP2:
                stack = frames.get(node).stack;
                if (stack[stack.length - 1] instanceof AbstractInsnNode) {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        instructions.remove(node);
                    } else {
                        instructions.set(node, new InsnNode(DUP));
                        stackSize += 1;
                        updateMaxStack(stackSize);
                        removeFrame = false;
                    }
                } else {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        instructions.set(node, new InsnNode(DUP));
                        stackSize += 1;
                    } else {
                        stackSize += 2;
                    }
                    updateMaxStack(stackSize);
                    removeFrame = false;
                }
                break;
            case DUP2_X1:
                stack = frames.get(node).stack;
                if (stack[stack.length - 1] instanceof AbstractInsnNode) {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        instructions.remove(node);
                    } else {
                        if (stack[stack.length - 3] instanceof AbstractInsnNode) {
                            instructions.set(node, new InsnNode(DUP));
                        } else {
                            instructions.set(node, new InsnNode(DUP_X1));
                        }
                        stackSize += 1;
                        updateMaxStack(stackSize);
                        removeFrame = false;
                    }
                } else {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        if (stack[stack.length - 3] instanceof AbstractInsnNode) {
                            instructions.set(node, new InsnNode(DUP));
                        } else {
                            instructions.set(node, new InsnNode(DUP_X1));
                        }
                        stackSize += 1;
                    } else {
                        if (stack[stack.length - 3] instanceof AbstractInsnNode) {
                            instructions.set(node, new InsnNode(DUP2));
                        }
                        stackSize += 2;
                    }
                    updateMaxStack(stackSize);
                    removeFrame = false;
                }
                break;
            case DUP2_X2:
                stack = frames.get(node).stack;
                if (stack[stack.length - 1] instanceof AbstractInsnNode) {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        instructions.remove(node);
                    } else {
                        if (stack[stack.length - 3] instanceof AbstractInsnNode) {
                            if (stack[stack.length - 4] instanceof AbstractInsnNode) {
                                instructions.set(node, new InsnNode(DUP));
                            } else {
                                instructions.set(node, new InsnNode(DUP_X1));
                            }
                        } else {
                            if (stack[stack.length - 3] instanceof AbstractInsnNode) {
                                instructions.set(node, new InsnNode(DUP_X1));
                            } else {
                                instructions.set(node, new InsnNode(DUP_X2));
                            }
                        }
                        stackSize += 1;
                        updateMaxStack(stackSize);
                        removeFrame = false;
                    }
                } else {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        if (stack[stack.length - 3] instanceof AbstractInsnNode) {
                            if (stack[stack.length - 4] instanceof AbstractInsnNode) {
                                instructions.set(node, new InsnNode(DUP));
                            } else {
                                instructions.set(node, new InsnNode(DUP_X1));
                            }
                        } else {
                            if (stack[stack.length - 4] instanceof AbstractInsnNode) {
                                instructions.set(node, new InsnNode(DUP_X1));
                            } else {
                                instructions.set(node, new InsnNode(DUP_X2));
                            }
                        }
                        stackSize += 1;
                    } else {
                        if (stack[stack.length - 3] instanceof AbstractInsnNode) {
                            if (stack[stack.length - 4] instanceof AbstractInsnNode) {
                                instructions.set(node, new InsnNode(DUP2));
                            } else {
                                instructions.set(node, new InsnNode(DUP2_X1));
                            }
                        } else {
                            if (stack[stack.length - 4] instanceof AbstractInsnNode) {
                                instructions.set(node, new InsnNode(DUP2_X1));
                            }
                        }
                        stackSize += 2;
                    }
                    updateMaxStack(stackSize);
                    removeFrame = false;
                }
                break;
            case SWAP:
                stack = frames.get(node).stack;
                if (stack[stack.length - 1] instanceof AbstractInsnNode
                        || stack[stack.length - 2] instanceof AbstractInsnNode) {
                    instructions.remove(node);
                } else {
                    removeFrame = false;
                }
                break;
            case IF_ACMPEQ:
                stack = frames.get(node).stack;
                if (stack[stack.length - 1] instanceof AbstractInsnNode) {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        if (stack[stack.length - 1] == stack[stack.length - 2]) {
                            instructions.set(node, new JumpInsnNode(GOTO, ((JumpInsnNode) node).label));
                            removeFrame = false;
                        } else {
                            instructions.remove(node);
                        }
                    } else {
                        instructions.set(node, new InsnNode(POP));
                        stackSize -= 1;
                        removeFrame = false;
                    }
                } else {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        instructions.set(node, new InsnNode(POP));
                        stackSize -= 1;
                    } else {
                        stackSize -= 2;
                    }
                    removeFrame = false;
                }
                break;
            case IF_ACMPNE:
                stack = frames.get(node).stack;
                if (stack[stack.length - 1] instanceof AbstractInsnNode) {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        if (stack[stack.length - 1] == stack[stack.length - 2]) {
                            instructions.remove(node);
                        } else {
                            instructions.set(node, new JumpInsnNode(GOTO, ((JumpInsnNode) node).label));
                            removeFrame = false;
                        }
                    } else {
                        instructions.insertBefore(node, new InsnNode(POP));
                        stackSize -= 1;
                        instructions.set(node, new JumpInsnNode(GOTO, ((JumpInsnNode) node).label));
                        removeFrame = false;
                    }
                } else {
                    if (stack[stack.length - 2] instanceof AbstractInsnNode) {
                        instructions.insertBefore(node, new InsnNode(POP));
                        stackSize -= 1;
                        instructions.set(node, new JumpInsnNode(GOTO, ((JumpInsnNode) node).label));
                    } else {
                        stackSize -= 2;
                    }
                    removeFrame = false;
                }
                break;
            case IRETURN:
            case LRETURN:
            case FRETURN:
            case DRETURN:
            case ARETURN:
            case RETURN:
                stackSize = 0;
                removeFrame = false;
                break;
            case GETSTATIC:
                stackSize += getTypeSize(((FieldInsnNode) node).desc);
                updateMaxStack(stackSize);
                removeFrame = false;
                break;
            case PUTSTATIC:
                stackSize -= getTypeSize(((FieldInsnNode) node).desc);
                removeFrame = false;
                break;
            case GETFIELD:
                stackSize += getTypeSize(((FieldInsnNode) node).desc) - 1;
                updateMaxStack(stackSize);
                removeFrame = false;
                break;
            case PUTFIELD:
                stackSize -= getTypeSize(((FieldInsnNode) node).desc) + 1;
                removeFrame = false;
                break;
            case INVOKEVIRTUAL:
            case INVOKEINTERFACE:
                stackSize += getInvokeDelta(((MethodInsnNode) node).desc);
                updateMaxStack(stackSize);
                removeFrame = false;
                break;
            case INVOKESPECIAL:
                stackSize = handleInvokeSpecial((MethodInsnNode) node, stackSize);
                removeFrame = false;
                break;
            case INVOKESTATIC:
                stackSize += getInvokeDelta(((MethodInsnNode) node).desc) + 1;
                updateMaxStack(stackSize);
                removeFrame = false;
                break;
            case INVOKEDYNAMIC:
                stackSize += getInvokeDelta(((InvokeDynamicInsnNode) node).desc) + 1;
                updateMaxStack(stackSize);
                removeFrame = false;
                break;
            case NEW:
                instructions.remove(node);
                break;
            case ATHROW:
                stackSize += 1 - stackSize;
                updateMaxStack(stackSize);
                removeFrame = false;
                break;
            case MULTIANEWARRAY:
                stackSize += 1 - ((MultiANewArrayInsnNode) node).dims;
                updateMaxStack(stackSize);
                removeFrame = false;
                break;
            case IFNULL:
                stack = frames.get(node).stack;
                if (!(stack[stack.length - 1] instanceof AbstractInsnNode)) {
                    stackSize -= 1;
                    removeFrame = false;
                }
                break;
            default: // IFNONNULL:
                stack = frames.get(node).stack;
                if (stack[stack.length - 1] instanceof AbstractInsnNode) {
                    instructions.set(node, new JumpInsnNode(GOTO, ((JumpInsnNode) node).label));
                } else {
                    stackSize -= 1;
                }
                removeFrame = false;
            }
        }

        accept(mv);
    }

    private int handleFrame(FrameNode node) {
        List<Object> local = node.local;
        Collection<Object> local1 = new ArrayList<>(local.size());
        for (Object value : local) {
            if (value instanceof LabelNode) {
                local1.add(TOP);
            } else {
                local1.add(value);
            }
        }

        int stackSize = 0;

        List<Object> stack = node.stack;
        Collection<Object> stack1 = new ArrayList<>(stack.size());
        for (Object value : stack) {
            if (!(value instanceof LabelNode)) {
                stack1.add(value);
                stackSize += isLong(value) ? 2 : 1;
            }
        }

        instructions.set(node,
                new FrameNode(node.type, local1.size(), local1.toArray(), stack1.size(), stack1.toArray()));

        updateMaxStack(stackSize);

        return stackSize;
    }

    private static int getTypeSize(String desc) {
        char c = desc.charAt(0);
        return c == 'J' || c == 'D' ? 2 : 1;
    }

    private static int getInvokeDelta(String desc) {
        int sizes = Type.getArgumentsAndReturnSizes(desc);
        return (sizes & 0x03) - (sizes >> 2);
    }

    private int handleInvokeSpecial(MethodInsnNode node, int stackSize) {
        int stackSize1 = stackSize;
        int sizes = Type.getArgumentsAndReturnSizes(node.desc);
        int argSize = sizes >> 2;

        if (node.name.charAt(0) == '<') {
            Frame frame = frames.get(node);
            Object[] locals = frame.locals;
            Object[] stack = frame.stack;
            AbstractInsnNode newNode = (AbstractInsnNode) stack[stack.length - argSize];

            boolean noLocalNews = true;
            for (Object value : locals) {
                if (value == newNode) {
                    noLocalNews = false;
                    break;
                }
            }

            int stackNewCount = 0;
            for (Object value : stack) {
                if (value == newNode) {
                    stackNewCount++;
                }
            }

            if (noLocalNews && stackNewCount == 2 && stack[stack.length - argSize - 1] == newNode) {
                if (argSize == 1) {
                    instructions.insertBefore(node, new TypeInsnNode(NEW, node.owner));
                    instructions.insertBefore(node, new InsnNode(DUP));
                    stackSize1 += 2;
                    updateMaxStack(stackSize1);
                } else if (argSize == 2 || argSize >= 4 && isLong(stack[stack.length - argSize + 2])) {
                    int varIndex = locals.length;

                    for (int i = stack.length - 1, k = stack.length - argSize + 2; i >= k; i--) {
                        Object value = stack[i];
                        if (value == INTEGER) {
                            instructions.insertBefore(node, new VarInsnNode(ISTORE, varIndex));
                            stackSize1 -= 1;
                            varIndex += 1;
                        } else if (value == FLOAT) {
                            instructions.insertBefore(node, new VarInsnNode(FSTORE, varIndex));
                            stackSize1 -= 1;
                            varIndex += 1;
                        } else if (value == DOUBLE) {
                            instructions.insertBefore(node, new VarInsnNode(DSTORE, varIndex));
                            stackSize1 -= 2;
                            varIndex += 2;
                        } else if (value == LONG) {
                            instructions.insertBefore(node, new VarInsnNode(LSTORE, varIndex));
                            stackSize1 -= 2;
                            varIndex += 2;
                        } else if (value == NULL) {
                            instructions.insertBefore(node, new InsnNode(POP));
                            stackSize1 -= 1;
                        } else if (value instanceof String) {
                            instructions.insertBefore(node, new VarInsnNode(ASTORE, varIndex));
                            stackSize1 -= 1;
                            varIndex += 1;
                        }
                    }

                    if (maxLocals < varIndex) {
                        maxLocals = varIndex;
                    }

                    instructions.insertBefore(node, new TypeInsnNode(NEW, node.owner));
                    instructions.insertBefore(node, new InsnNode(DUP));
                    instructions.insertBefore(node, new InsnNode(DUP2_X1));
                    instructions.insertBefore(node, new InsnNode(POP2));
                    stackSize1 += 2;
                    updateMaxStack(stackSize1 + 2);

                    for (int i = stack.length - argSize + 2, n = stack.length; i < n; i++) {
                        Object value = stack[i];
                        if (value == INTEGER) {
                            varIndex -= 1;
                            instructions.insertBefore(node, new VarInsnNode(ILOAD, varIndex));
                            stackSize1 += 1;
                        } else if (value == FLOAT) {
                            varIndex -= 1;
                            instructions.insertBefore(node, new VarInsnNode(FLOAD, varIndex));
                            stackSize1 += 1;
                        } else if (value == DOUBLE) {
                            varIndex -= 2;
                            instructions.insertBefore(node, new VarInsnNode(DLOAD, varIndex));
                            stackSize1 += 2;
                        } else if (value == LONG) {
                            varIndex -= 2;
                            instructions.insertBefore(node, new VarInsnNode(LLOAD, varIndex));
                            stackSize1 += 2;
                        } else if (value == NULL) {
                            instructions.insertBefore(node, new InsnNode(ACONST_NULL));
                            stackSize1 += 1;
                        } else if (value instanceof String) {
                            varIndex -= 1;
                            instructions.insertBefore(node, new VarInsnNode(ALOAD, varIndex));
                            stackSize1 += 1;
                        }
                    }

                    updateMaxStack(stackSize1);
                } else {
                    int varIndex = locals.length;

                    for (int i = stack.length - 1, k = stack.length - argSize + 3; i >= k; i--) {
                        Object value = stack[i];
                        if (value == INTEGER) {
                            instructions.insertBefore(node, new VarInsnNode(ISTORE, varIndex));
                            stackSize1 -= 1;
                            varIndex += 1;
                        } else if (value == FLOAT) {
                            instructions.insertBefore(node, new VarInsnNode(FSTORE, varIndex));
                            stackSize1 -= 1;
                            varIndex += 1;
                        } else if (value == DOUBLE) {
                            instructions.insertBefore(node, new VarInsnNode(DSTORE, varIndex));
                            stackSize1 -= 2;
                            varIndex += 2;
                        } else if (value == LONG) {
                            instructions.insertBefore(node, new VarInsnNode(LSTORE, varIndex));
                            stackSize1 -= 2;
                            varIndex += 2;
                        } else if (value == NULL) {
                            instructions.insertBefore(node, new InsnNode(POP));
                            stackSize1 -= 1;
                        } else if (value instanceof String) {
                            instructions.insertBefore(node, new VarInsnNode(ASTORE, varIndex));
                            stackSize1 -= 1;
                            varIndex += 1;
                        }
                    }

                    if (maxLocals < varIndex) {
                        maxLocals = varIndex;
                    }

                    instructions.insertBefore(node, new TypeInsnNode(NEW, node.owner));
                    instructions.insertBefore(node, new InsnNode(DUP));
                    instructions.insertBefore(node, new InsnNode(DUP2_X2));
                    instructions.insertBefore(node, new InsnNode(POP2));
                    stackSize1 += 2;
                    updateMaxStack(stackSize1 + 2);

                    for (int i = stack.length - argSize + 3, n = stack.length; i < n; i++) {
                        Object value = stack[i];
                        if (value == INTEGER) {
                            varIndex -= 1;
                            instructions.insertBefore(node, new VarInsnNode(ILOAD, varIndex));
                            stackSize1 += 1;
                        } else if (value == FLOAT) {
                            varIndex -= 1;
                            instructions.insertBefore(node, new VarInsnNode(FLOAD, varIndex));
                            stackSize1 += 1;
                        } else if (value == DOUBLE) {
                            varIndex -= 2;
                            instructions.insertBefore(node, new VarInsnNode(DLOAD, varIndex));
                            stackSize1 += 2;
                        } else if (value == LONG) {
                            varIndex -= 2;
                            instructions.insertBefore(node, new VarInsnNode(LLOAD, varIndex));
                            stackSize1 += 2;
                        } else if (value == NULL) {
                            instructions.insertBefore(node, new InsnNode(ACONST_NULL));
                            stackSize1 += 1;
                        } else if (value instanceof String) {
                            varIndex -= 1;
                            instructions.insertBefore(node, new VarInsnNode(ALOAD, varIndex));
                            stackSize1 += 1;
                        }
                    }

                    updateMaxStack(stackSize1);
                }
            } else {
                int k = 0;
                for (Object value : stack) {
                    if (value == newNode) {
                        break;
                    }
                    k++;
                }

                int newVarIndex = locals.length;
                int varIndex = newVarIndex + 1;

                for (int i = stack.length - 1; i >= k; i--) {
                    Object value = stack[i];
                    if (value == INTEGER) {
                        instructions.insertBefore(node, new VarInsnNode(ISTORE, varIndex));
                        stackSize1 -= 1;
                        varIndex += 1;
                    } else if (value == FLOAT) {
                        instructions.insertBefore(node, new VarInsnNode(FSTORE, varIndex));
                        stackSize1 -= 1;
                        varIndex += 1;
                    } else if (value == DOUBLE) {
                        instructions.insertBefore(node, new VarInsnNode(DSTORE, varIndex));
                        stackSize1 -= 2;
                        varIndex += 2;
                    } else if (value == LONG) {
                        instructions.insertBefore(node, new VarInsnNode(LSTORE, varIndex));
                        stackSize1 -= 2;
                        varIndex += 2;
                    } else if (value == NULL) {
                        instructions.insertBefore(node, new InsnNode(POP));
                        stackSize1 -= 1;
                    } else if (value instanceof String) {
                        instructions.insertBefore(node, new VarInsnNode(ASTORE, varIndex));
                        stackSize1 -= 1;
                        varIndex += 1;
                    }
                }

                if (maxLocals < varIndex) {
                    maxLocals = varIndex;
                }

                instructions.insertBefore(node, new TypeInsnNode(NEW, node.owner));
                instructions.insertBefore(node, new VarInsnNode(ASTORE, newVarIndex));

                for (int i = 0; i < newVarIndex; i++) {
                    if (locals[i] == newNode) {
                        instructions.insertBefore(node, new VarInsnNode(ALOAD, newVarIndex));
                        instructions.insertBefore(node, new VarInsnNode(ASTORE, i));
                    }
                }

                updateMaxStack(stackSize1 + 1);

                for (int i = k, n = stack.length; i < n; i++) {
                    Object value = stack[i];
                    if (value == INTEGER) {
                        varIndex -= 1;
                        instructions.insertBefore(node, new VarInsnNode(ILOAD, varIndex));
                        stackSize1 += 1;
                    } else if (value == FLOAT) {
                        varIndex -= 1;
                        instructions.insertBefore(node, new VarInsnNode(FLOAD, varIndex));
                        stackSize1 += 1;
                    } else if (value == DOUBLE) {
                        varIndex -= 2;
                        instructions.insertBefore(node, new VarInsnNode(DLOAD, varIndex));
                        stackSize1 += 2;
                    } else if (value == LONG) {
                        varIndex -= 2;
                        instructions.insertBefore(node, new VarInsnNode(LLOAD, varIndex));
                        stackSize1 += 2;
                    } else if (value == NULL) {
                        instructions.insertBefore(node, new InsnNode(ACONST_NULL));
                        stackSize1 += 1;
                    } else if (value instanceof String) {
                        varIndex -= 1;
                        instructions.insertBefore(node, new VarInsnNode(ALOAD, varIndex));
                        stackSize1 += 1;
                    } else if (value == newNode) {
                        instructions.insertBefore(node, new VarInsnNode(ALOAD, newVarIndex));
                        stackSize1 += 1;
                    }
                }

                updateMaxStack(stackSize1);
            }
        }

        stackSize1 += (sizes & 0x03) - argSize;
        updateMaxStack(stackSize1);

        return stackSize1;
    }

    private static boolean isLong(Object value) {
        return value == LONG || value == DOUBLE;
    }

    private void updateMaxStack(int stackSize) {
        if (maxStack < stackSize) {
            maxStack = stackSize;
        }
    }
}