org.decojer.cavaj.readers.asm.ReadMethodVisitor.java Source code

Java tutorial

Introduction

Here is the source code for org.decojer.cavaj.readers.asm.ReadMethodVisitor.java

Source

/*
 * $Id$
 *
 * This file is part of the DecoJer project.
 * Copyright (C) 2010-2011  Andr Pankraz
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
    
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * a covered work must retain the producer line in every Java Source Code
 * that is created using DecoJer.
 */
package org.decojer.cavaj.readers.asm;

import static org.decojer.cavaj.readers.asm.ReadUtils.annotateM;
import static org.decojer.cavaj.readers.asm.ReadUtils.annotateT;

import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import org.decojer.cavaj.model.A;
import org.decojer.cavaj.model.DU;
import org.decojer.cavaj.model.code.CFG;
import org.decojer.cavaj.model.code.Exc;
import org.decojer.cavaj.model.code.V;
import org.decojer.cavaj.model.code.ops.ADD;
import org.decojer.cavaj.model.code.ops.ALOAD;
import org.decojer.cavaj.model.code.ops.AND;
import org.decojer.cavaj.model.code.ops.ARRAYLENGTH;
import org.decojer.cavaj.model.code.ops.ASTORE;
import org.decojer.cavaj.model.code.ops.CAST;
import org.decojer.cavaj.model.code.ops.CMP;
import org.decojer.cavaj.model.code.ops.CmpType;
import org.decojer.cavaj.model.code.ops.DIV;
import org.decojer.cavaj.model.code.ops.DUP;
import org.decojer.cavaj.model.code.ops.GET;
import org.decojer.cavaj.model.code.ops.GOTO;
import org.decojer.cavaj.model.code.ops.INC;
import org.decojer.cavaj.model.code.ops.INSTANCEOF;
import org.decojer.cavaj.model.code.ops.INVOKE;
import org.decojer.cavaj.model.code.ops.JCMP;
import org.decojer.cavaj.model.code.ops.JCND;
import org.decojer.cavaj.model.code.ops.JSR;
import org.decojer.cavaj.model.code.ops.LOAD;
import org.decojer.cavaj.model.code.ops.MONITOR;
import org.decojer.cavaj.model.code.ops.MUL;
import org.decojer.cavaj.model.code.ops.NEG;
import org.decojer.cavaj.model.code.ops.NEW;
import org.decojer.cavaj.model.code.ops.NEWARRAY;
import org.decojer.cavaj.model.code.ops.OR;
import org.decojer.cavaj.model.code.ops.Op;
import org.decojer.cavaj.model.code.ops.POP;
import org.decojer.cavaj.model.code.ops.PUSH;
import org.decojer.cavaj.model.code.ops.PUT;
import org.decojer.cavaj.model.code.ops.REM;
import org.decojer.cavaj.model.code.ops.RET;
import org.decojer.cavaj.model.code.ops.RETURN;
import org.decojer.cavaj.model.code.ops.SHL;
import org.decojer.cavaj.model.code.ops.SHR;
import org.decojer.cavaj.model.code.ops.STORE;
import org.decojer.cavaj.model.code.ops.SUB;
import org.decojer.cavaj.model.code.ops.SWAP;
import org.decojer.cavaj.model.code.ops.SWITCH;
import org.decojer.cavaj.model.code.ops.THROW;
import org.decojer.cavaj.model.code.ops.TypedOp;
import org.decojer.cavaj.model.code.ops.XOR;
import org.decojer.cavaj.model.fields.F;
import org.decojer.cavaj.model.methods.M;
import org.decojer.cavaj.model.types.T;
import org.decojer.cavaj.readers.ReadVisitor;
import org.decojer.cavaj.utils.Cursor;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
import org.objectweb.asm.TypeReference;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * ASM read method visitor.
 *
 * @author Andr Pankraz
 */
@Slf4j
public class ReadMethodVisitor extends MethodVisitor implements ReadVisitor {

    /**
     * JDK 8.0 has +1 index to Eclipse! who is wrong? JDK or Eclipse? we try both...
     */
    private static class InsnAnnotationInfo {

        A a;

        int typeRef;

        TypePath typePath;

        public InsnAnnotationInfo(final A a, final int typeRef, final TypePath typePath) {
            this.a = a;
            this.typeRef = typeRef;
            this.typePath = typePath;
        }

    }

    private InsnAnnotationInfo insnAnnotationInfo;

    private A[] as;

    private final List<Exc> excs = Lists.newArrayList();

    private final Map<Label, Integer> label2pc = Maps.newHashMap();

    private final Map<Label, List<Object>> label2unresolved = Maps.newHashMap();

    private int line = -1;

    private int maxLocals;

    private int maxStack;

    private M m;

    private final List<Op> ops = Lists.newArrayList();

    private A[][] paramAss;

    @Nonnull
    private final ReadAnnotationMemberVisitor annotationVisitor;

    private final Map<Integer, List<V>> reg2vs = Maps.newHashMap();

    @Getter
    @Nonnull
    private final ReadClassVisitor parentVisitor;

    /**
     * Constructor.
     *
     * @param parentVisitor
     *            parent visitor
     */
    public ReadMethodVisitor(@Nonnull final ReadClassVisitor parentVisitor) {
        super(Opcodes.ASM5);
        this.parentVisitor = parentVisitor;
        this.annotationVisitor = new ReadAnnotationMemberVisitor(this);
    }

    private final void add(final Op op) {
        this.ops.add(op);
        if (this.insnAnnotationInfo != null && this.insnAnnotationInfo.a != null) {
            // JDK 8.0 has +1 index to Eclipse! who is wrong? JDK or Eclipse? we try both...
            applyOperationAnnotation(this.insnAnnotationInfo.a, this.insnAnnotationInfo.typeRef,
                    this.insnAnnotationInfo.typePath, true);
            this.insnAnnotationInfo = null;
        }
    }

    private boolean applyOperationAnnotation(@Nonnull final A a, final int typeRef,
            @Nullable final TypePath typePath, final boolean logError) {
        final Op op = this.ops.get(this.ops.size() - 1);
        final TypeReference typeReference = new TypeReference(typeRef);
        switch (typeReference.getSort()) {
        case TypeReference.CAST: {
            if (op instanceof CAST) {
                ((CAST) op).setToT(annotateT(((CAST) op).getToT(), a, typePath));
                return true;
            }
            if (logError) {
                log.warn(getM() + ": Wrong operation '" + op + "' for type annotation ref sort 'CAST' : " + typeRef
                        + " : " + typePath + " : " + a);
            }
            return false;
        }
        case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
        case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT: {
            if (op instanceof INVOKE) {
                log.warn(getM()
                        + ": Missing bytecode info, cannot really apply type annotation ref sort 'CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT' or 'METHOD_INVOCATION_TYPE_ARGUMENT' : "
                        + typeRef + " : " + typePath + " : " + a);
                return true;
            }
            if (logError) {
                log.warn(getM() + ": Wrong operation '" + op
                        + "' for type annotation ref sort 'CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT' or 'METHOD_INVOCATION_TYPE_ARGUMENT' : "
                        + typeRef + " : " + typePath + " : " + a);
            }
            return false;
        }
        case TypeReference.CONSTRUCTOR_REFERENCE:
        case TypeReference.METHOD_REFERENCE: {
            if (op instanceof INVOKE) {
                final Object[] bsArgs = ((INVOKE) op).getBsArgs();
                if (bsArgs != null && bsArgs.length > 1) {
                    final Object bsArg = bsArgs[1];
                    if (bsArg instanceof M) {
                        bsArgs[1] = annotateM((M) bsArg, a, typePath);
                        return true;
                    }
                }
            }
            if (logError) {
                log.warn(getM() + ": Wrong operation '" + op
                        + "' for type annotation ref sort 'CONSTRUCTOR_REFERENCE' or 'METHOD_REFERENCE' : "
                        + typeRef + " : " + typePath + " : " + a);
            }
            return false;
        }
        case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
        case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT: {
            if (op instanceof INVOKE) {
                final Object[] bsArgs = ((INVOKE) op).getBsArgs();
                if (bsArgs != null && bsArgs.length > 1 && bsArgs[1] instanceof M) {
                    log.warn(getM()
                            + ": Missing bytecode info, cannot really apply type annotation ref sort 'CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT' or 'METHOD_REFERENCE_TYPE_ARGUMENT' : "
                            + typeRef + " : " + typePath + " : " + a);
                    return true;
                }
            }
            if (logError) {
                log.warn(getM() + ": Wrong operation '" + op
                        + "' for type annotation ref sort 'CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT' or 'METHOD_REFERENCE_TYPE_ARGUMENT' : "
                        + typeRef + " : " + typePath + " : " + a);
            }
            return false;
        }
        case TypeReference.NEW: {
            if (op instanceof NEW || op instanceof NEWARRAY) {
                ((TypedOp) op).setT(annotateT(((TypedOp) op).getT(), a, typePath));
                return true;
            }
            if (logError) {
                log.warn(getM() + ": Wrong operation '" + op + "' for type annotation ref sort 'NEW' : " + typeRef
                        + " : " + typePath + " : " + a);
            }
            return false;
        }
        default:
            log.warn(
                    getM() + ": Unknown type annotation ref sort '0x" + Integer.toHexString(typeReference.getSort())
                            + "' : " + typeRef + " : " + typePath + " : " + a);
        }
        return false;
    }

    @Override
    public DU getDu() {
        return getParentVisitor().getDu();
    }

    /**
     * Get method.
     *
     * @return method
     */
    @Nonnull
    public M getM() {
        assert this.m != null;
        return this.m;
    }

    private int getPc(final Label label) {
        assert label != null;

        final Integer pc = this.label2pc.get(label);
        if (pc != null) {
            return pc;
        }
        final int unresolvedPc = -1 - this.label2unresolved.size();
        this.label2pc.put(label, unresolvedPc);
        return unresolvedPc;
    }

    @Override
    public T getT() {
        return getParentVisitor().getT();
    }

    private List<Object> getUnresolved(final Label label) {
        assert label != null;

        List<Object> unresolved = this.label2unresolved.get(label);
        if (unresolved == null) {
            unresolved = Lists.newArrayList();
            this.label2unresolved.put(label, unresolved);
        }
        return unresolved;
    }

    @Nonnull
    private M handle2m(@Nonnull final Handle handle) {
        final String ownerName = handle.getOwner();
        assert ownerName != null;
        final T ownerT = getDu().getT(ownerName);
        if (handle.getTag() == Opcodes.H_INVOKEINTERFACE) {
            ownerT.setInterface(true); // static also possible in interface since JVM 8
        }
        final String name = handle.getName();
        final String desc = handle.getDesc();
        assert name != null && desc != null;
        final M refM = ownerT.getM(name, desc);
        refM.setStatic(handle.getTag() == Opcodes.H_INVOKESTATIC);
        return refM;
    }

    /**
     * Init and set method.
     *
     * @param m
     *            method
     */
    public void init(@Nonnull final M m) {
        this.m = m;
    }

    @Override
    public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
        if (this.as == null) {
            this.as = new A[1];
        } else {
            final A[] newAs = new A[this.as.length + 1];
            System.arraycopy(this.as, 0, newAs, 0, this.as.length);
            this.as = newAs;
        }
        this.as[this.as.length - 1] = this.annotationVisitor.init(desc,
                visible ? RetentionPolicy.RUNTIME : RetentionPolicy.CLASS);
        return this.annotationVisitor;
    }

    @Override
    public AnnotationVisitor visitAnnotationDefault() {
        return new ReadAnnotationVisitor(this) {

            @Override
            protected void add(final String name, final Object value) {
                ReadMethodVisitor.this.m.setAnnotationDefaultValue(value);
            }

        };
    }

    @Override
    public void visitAttribute(final Attribute attr) {
        if (!attr.type.equals("org.aspectj.weaver.MethodDeclarationLineNumber")) {
            log.warn(getM() + ": Unknown method attribute tag '" + attr.type + "' for field info '" + this.m.getT()
                    + "'!");
        }
    }

    @Override
    public void visitCode() {
        // OK
    }

    @Override
    public void visitEnd() {
        if (this.as != null) {
            this.m.setAs(this.as);
            this.as = null;
        }
        if (this.paramAss != null) {
            this.m.setParamAss(this.paramAss);
            this.paramAss = null;
        }
        if (this.ops.size() > 0) {
            final CFG cfg = new CFG(getM(), this.maxLocals, this.maxStack,
                    this.ops.toArray(new Op[this.ops.size()]));

            this.ops.clear();
            this.label2pc.clear();
            this.label2unresolved.clear();
            this.line = -1;

            if (this.excs.size() > 0) {
                cfg.setExcs(this.excs.toArray(new Exc[this.excs.size()]));
                this.excs.clear();
            }
            if (this.reg2vs.size() > 0) {
                for (final Entry<Integer, List<V>> entry : this.reg2vs.entrySet()) {
                    final int reg = entry.getKey();
                    for (final V var : entry.getValue()) {
                        cfg.addVar(reg, var);
                    }
                }
                this.reg2vs.clear();
            }
            cfg.postProcessVars();
        }
    }

    @Override
    public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
        if (owner == null || name == null || desc == null) {
            log.warn(getM() + ": Cannot read get operation with field '" + owner + "." + name + "' and descriptor '"
                    + desc + "'!");
            return;
        }
        switch (opcode) {
        /*******
         * GET *
         *******/
        case Opcodes.GETFIELD:
        case Opcodes.GETSTATIC: {
            final T ownerT = getDu().getT(owner);
            final F f = ownerT.getF(name, desc);
            f.setStatic(opcode == Opcodes.GETSTATIC);
            add(new GET(this.ops.size(), opcode, this.line, f));
            return;
        }
        /*******
         * PUT *
         *******/
        case Opcodes.PUTFIELD:
        case Opcodes.PUTSTATIC: {
            final T ownerT = getDu().getT(owner);
            final F f = ownerT.getF(name, desc);
            f.setStatic(opcode == Opcodes.PUTSTATIC);
            add(new PUT(this.ops.size(), opcode, this.line, f));
            return;
        }
        default:
            log.warn(getM() + ": Unknown field insn opcode '" + opcode + "'!");
        }
    }

    @Override
    public void visitFrame(final int type, final int nLocal, final Object[] local, final int nStack,
            final Object[] stack) {
        // log.info(getM() + ": " + type + " : " + nLocal
        // + " : " + local + " : " + nStack + " : " + stack);
    }

    @Override
    public void visitIincInsn(final int var, final int increment) {
        /*******
         * INC *
         *******/
        add(new INC(this.ops.size(), Opcodes.IINC, this.line, T.INT, var, increment));
    }

    @Override
    public void visitInsn(final int opcode) {
        T t = null;
        int iValue = Integer.MIN_VALUE;
        Object oValue = null;

        switch (opcode) {
        case Opcodes.NOP:
            // nothing to do, ignore
            break;
        /*******
         * ADD *
         *******/
        case Opcodes.DADD:
            t = T.DOUBLE;
            // fall through
        case Opcodes.FADD:
            if (t == null) {
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.IADD:
            if (t == null) {
                t = T.INT;
            }
            // fall through
        case Opcodes.LADD:
            if (t == null) {
                t = T.LONG;
            }
            add(new ADD(this.ops.size(), opcode, this.line, t));
            break;
        /*********
         * ALOAD *
         *********/
        case Opcodes.AALOAD:
            t = T.REF;
            // fall through
        case Opcodes.BALOAD:
            if (t == null) {
                t = T.SMALL;
            }
            // fall through
        case Opcodes.CALOAD:
            if (t == null) {
                t = T.CHAR;
            }
            // fall through
        case Opcodes.DALOAD:
            if (t == null) {
                t = T.DOUBLE;
            }
            // fall through
        case Opcodes.FALOAD:
            if (t == null) {
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.IALOAD:
            if (t == null) {
                t = T.INT;
            }
            // fall through
        case Opcodes.LALOAD:
            if (t == null) {
                t = T.LONG;
            }
            // fall through
        case Opcodes.SALOAD:
            if (t == null) {
                t = T.SHORT;
            }
            add(new ALOAD(this.ops.size(), opcode, this.line, t));
            break;
        /*******
         * AND *
         *******/
        case Opcodes.IAND:
            t = T.AINT;
            // fall through
        case Opcodes.LAND:
            if (t == null) {
                t = T.LONG;
            }
            add(new AND(this.ops.size(), opcode, this.line, t));
            break;
        /***************
         * ARRAYLENGTH *
         ***************/
        case Opcodes.ARRAYLENGTH:
            add(new ARRAYLENGTH(this.ops.size(), opcode, this.line));
            break;
        /**********
         * ASTORE *
         **********/
        case Opcodes.AASTORE:
            t = T.REF;
            // fall through
        case Opcodes.BASTORE:
            if (t == null) {
                t = T.SMALL;
            }
            // fall through
        case Opcodes.CASTORE:
            if (t == null) {
                t = T.CHAR;
            }
            // fall through
        case Opcodes.DASTORE:
            if (t == null) {
                t = T.DOUBLE;
            }
            // fall through
        case Opcodes.FASTORE:
            if (t == null) {
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.IASTORE:
            if (t == null) {
                t = T.INT;
            }
            // fall through
        case Opcodes.LASTORE:
            if (t == null) {
                t = T.LONG;
            }
            // fall through
        case Opcodes.SASTORE:
            if (t == null) {
                t = T.SHORT;
            }
            add(new ASTORE(this.ops.size(), opcode, this.line, t));
            break;
        /********
         * CAST *
         ********/
        case Opcodes.D2F:
            t = T.DOUBLE;
            oValue = T.FLOAT;
            // fall through
        case Opcodes.D2I:
            if (t == null) {
                t = T.DOUBLE;
                oValue = T.INT;
            }
            // fall through
        case Opcodes.D2L:
            if (t == null) {
                t = T.DOUBLE;
                oValue = T.LONG;
            }
            // fall through
        case Opcodes.F2D:
            if (t == null) {
                t = T.FLOAT;
                oValue = T.DOUBLE;
            }
            // fall through
        case Opcodes.F2I:
            if (t == null) {
                t = T.FLOAT;
                oValue = T.INT;
            }
            // fall through
        case Opcodes.F2L:
            if (t == null) {
                t = T.FLOAT;
                oValue = T.LONG;
            }
            // fall through
        case Opcodes.I2B:
            if (t == null) {
                t = T.INT;
                oValue = T.BYTE;
            }
            // fall through
        case Opcodes.I2C:
            if (t == null) {
                t = T.INT;
                oValue = T.CHAR;
            }
            // fall through
        case Opcodes.I2D:
            if (t == null) {
                t = T.INT;
                oValue = T.DOUBLE;
            }
            // fall through
        case Opcodes.I2F:
            if (t == null) {
                t = T.INT;
                oValue = T.FLOAT;
            }
            // fall through
        case Opcodes.I2L:
            if (t == null) {
                t = T.INT;
                oValue = T.LONG;
            }
            // fall through
        case Opcodes.I2S:
            if (t == null) {
                t = T.INT;
                oValue = T.SHORT;
            }
            // fall through
        case Opcodes.L2D:
            if (t == null) {
                t = T.LONG;
                oValue = T.DOUBLE;
            }
            // fall through
        case Opcodes.L2F:
            if (t == null) {
                t = T.LONG;
                oValue = T.FLOAT;
            }
            // fall through
        case Opcodes.L2I:
            if (t == null) {
                t = T.LONG;
                oValue = T.INT;
            }
            assert oValue instanceof T;
            add(new CAST(this.ops.size(), opcode, this.line, t, (T) oValue));
            break;
        /*******
         * CMP *
         *******/
        case Opcodes.DCMPG:
            t = T.DOUBLE;
            iValue = CMP.T_G;
            // fall through
        case Opcodes.DCMPL:
            if (t == null) {
                t = T.DOUBLE;
                iValue = CMP.T_L;
            }
            // fall through
        case Opcodes.FCMPG:
            if (t == null) {
                t = T.FLOAT;
                iValue = CMP.T_G;
            }
            // fall through
        case Opcodes.FCMPL:
            if (t == null) {
                t = T.FLOAT;
                iValue = CMP.T_L;
            }
            // fall through
        case Opcodes.LCMP:
            if (t == null) {
                t = T.LONG;
                iValue = CMP.T_0;
            }
            add(new CMP(this.ops.size(), opcode, this.line, t, iValue));
            break;
        /*******
         * DIV *
         *******/
        case Opcodes.DDIV:
            t = T.DOUBLE;
            // fall through
        case Opcodes.FDIV:
            if (t == null) {
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.IDIV:
            if (t == null) {
                t = T.INT;
            }
            // fall through
        case Opcodes.LDIV:
            if (t == null) {
                t = T.LONG;
            }
            add(new DIV(this.ops.size(), opcode, this.line, t));
            break;
        /*******
         * DUP *
         *******/
        case Opcodes.DUP:
            oValue = DUP.Kind.DUP;
            // fall through
        case Opcodes.DUP_X1:
            if (oValue == null) {
                oValue = DUP.Kind.DUP_X1;
            }
            // fall through
        case Opcodes.DUP_X2:
            if (oValue == null) {
                oValue = DUP.Kind.DUP_X2;
            }
            // fall through
        case Opcodes.DUP2:
            if (oValue == null) {
                oValue = DUP.Kind.DUP2;
            }
            // fall through
        case Opcodes.DUP2_X1:
            if (oValue == null) {
                oValue = DUP.Kind.DUP2_X1;
            }
            // fall through
        case Opcodes.DUP2_X2:
            if (oValue == null) {
                oValue = DUP.Kind.DUP2_X2;
            }
            add(new DUP(this.ops.size(), opcode, this.line, (DUP.Kind) oValue));
            break;
        /***********
         * MONITOR *
         ***********/
        case Opcodes.MONITORENTER:
            oValue = MONITOR.Kind.ENTER;
            // fall through
        case Opcodes.MONITOREXIT:
            if (oValue == null) {
                oValue = MONITOR.Kind.EXIT;
            }
            add(new MONITOR(this.ops.size(), opcode, this.line, (MONITOR.Kind) oValue));
            break;
        /*******
         * MUL *
         *******/
        case Opcodes.DMUL:
            t = T.DOUBLE;
            // fall through
        case Opcodes.FMUL:
            if (t == null) {
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.IMUL:
            if (t == null) {
                t = T.INT;
            }
            // fall through
        case Opcodes.LMUL:
            if (t == null) {
                t = T.LONG;
            }
            add(new MUL(this.ops.size(), opcode, this.line, t));
            break;
        /*******
         * NEG *
         *******/
        case Opcodes.DNEG:
            t = T.DOUBLE;
            // fall through
        case Opcodes.FNEG:
            if (t == null) {
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.INEG:
            if (t == null) {
                t = T.INT;
            }
            // fall through
        case Opcodes.LNEG:
            if (t == null) {
                t = T.LONG;
            }
            add(new NEG(this.ops.size(), opcode, this.line, t));
            break;
        /******
         * OR *
         ******/
        case Opcodes.IOR:
            t = T.AINT;
            // fall through
        case Opcodes.LOR:
            if (t == null) {
                t = T.LONG;
            }
            add(new OR(this.ops.size(), opcode, this.line, t));
            break;
        /*******
         * POP *
         *******/
        case Opcodes.POP:
            oValue = POP.Kind.POP;
            // fall through
        case Opcodes.POP2:
            if (oValue == null) {
                oValue = POP.Kind.POP2;
            }
            add(new POP(this.ops.size(), opcode, this.line, (POP.Kind) oValue));
            break;
        /********
         * PUSH *
         ********/
        case Opcodes.ACONST_NULL:
            t = T.REF;
            // fall through
        case Opcodes.DCONST_0:
            if (t == null) {
                oValue = 0D;
                t = T.DOUBLE;
            }
            // fall through
        case Opcodes.FCONST_0:
            if (t == null) {
                oValue = 0F;
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.ICONST_0:
            if (t == null) {
                oValue = 0;
                t = T.getJvmIntT(0);
            }
            // fall through
        case Opcodes.LCONST_0:
            if (t == null) {
                oValue = 0L;
                t = T.LONG;
            }
            // fall through
        case Opcodes.DCONST_1:
            if (t == null) {
                oValue = 1D;
                t = T.DOUBLE;
            }
            // fall through
        case Opcodes.FCONST_1:
            if (t == null) {
                oValue = 1F;
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.ICONST_1:
            if (t == null) {
                oValue = 1;
                t = T.getJvmIntT(1);
            }
            // fall through
        case Opcodes.LCONST_1:
            if (t == null) {
                oValue = 1L;
                t = T.LONG;
            }
            // fall through
        case Opcodes.FCONST_2:
            if (t == null) {
                oValue = 2F;
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.ICONST_2:
            if (t == null) {
                oValue = 2;
                t = T.getJvmIntT(2);
            }
            // fall through
        case Opcodes.ICONST_3:
            if (t == null) {
                oValue = 3;
                t = T.getJvmIntT(3);
            }
            // fall through
        case Opcodes.ICONST_4:
            if (t == null) {
                oValue = 4;
                t = T.getJvmIntT(4);
            }
            // fall through
        case Opcodes.ICONST_5:
            if (t == null) {
                oValue = 5;
                t = T.getJvmIntT(5);
            }
            // fall through
        case Opcodes.ICONST_M1:
            if (t == null) {
                oValue = -1;
                t = T.getJvmIntT(-1);
            }
            add(new PUSH(this.ops.size(), opcode, this.line, t, oValue));
            break;
        /*******
         * REM *
         *******/
        case Opcodes.DREM:
            t = T.DOUBLE;
            // fall through
        case Opcodes.FREM:
            if (t == null) {
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.IREM:
            if (t == null) {
                t = T.INT;
            }
            // fall through
        case Opcodes.LREM:
            if (t == null) {
                t = T.LONG;
            }
            add(new REM(this.ops.size(), opcode, this.line, t));
            break;
        /**********
         * RETURN *
         **********/
        case Opcodes.ARETURN:
            t = T.REF;
            // fall through
        case Opcodes.DRETURN:
            if (t == null) {
                t = T.DOUBLE;
            }
            // fall through
        case Opcodes.FRETURN:
            if (t == null) {
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.IRETURN:
            if (t == null) {
                t = T.AINT;
            }
            // fall through
        case Opcodes.LRETURN:
            if (t == null) {
                t = T.LONG;
            }
            // fall through
        case Opcodes.RETURN:
            if (t == null) {
                t = T.VOID;
            }
            add(new RETURN(this.ops.size(), opcode, this.line, t));
            break;
        /*******
         * SHL *
         *******/
        case Opcodes.ISHL:
            t = T.INT;
            // fall through
        case Opcodes.LSHL:
            if (t == null) {
                t = T.LONG;
            }
            add(new SHL(this.ops.size(), opcode, this.line, t, T.INT));
            break;
        /*******
         * SHR *
         *******/
        case Opcodes.ISHR:
        case Opcodes.IUSHR:
            t = T.INT;
            // fall through
        case Opcodes.LSHR:
        case Opcodes.LUSHR:
            if (t == null) {
                t = T.LONG;
            }
            add(new SHR(this.ops.size(), opcode, this.line, t, T.INT,
                    opcode == Opcodes.IUSHR || opcode == Opcodes.LUSHR));
            break;
        /*******
         * SUB *
         *******/
        case Opcodes.DSUB:
            t = T.DOUBLE;
            // fall through
        case Opcodes.FSUB:
            if (t == null) {
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.ISUB:
            if (t == null) {
                t = T.INT;
            }
            // fall through
        case Opcodes.LSUB:
            if (t == null) {
                t = T.LONG;
            }
            add(new SUB(this.ops.size(), opcode, this.line, t));
            break;
        /********
         * SWAP *
         ********/
        case Opcodes.SWAP:
            add(new SWAP(this.ops.size(), opcode, this.line));
            break;
        /*********
         * THROW *
         *********/
        case Opcodes.ATHROW:
            add(new THROW(this.ops.size(), opcode, this.line));
            break;
        /*******
         * XOR *
         *******/
        case Opcodes.IXOR:
            t = T.AINT;
            // fall through
        case Opcodes.LXOR: {
            if (t == null) {
                t = T.LONG;
            }
            add(new XOR(this.ops.size(), opcode, this.line, t));
            break;
        }
        default:
            log.warn(getM() + ": Unknown insn opcode '" + opcode + "'!");
        }
    }

    @Override
    public AnnotationVisitor visitInsnAnnotation(final int typeRef, @Nullable final TypePath typePath,
            final String desc, final boolean visible) {
        final A a = this.annotationVisitor.init(desc, visible ? RetentionPolicy.RUNTIME : RetentionPolicy.CLASS);
        if (a == null) {
            log.warn(getM() + ": Cannot read annotation for descriptor '" + desc + "'!");
            return null;
        }
        if (!applyOperationAnnotation(a, typeRef, typePath, false)) {
            // JDK 8.0 has +1 index to Eclipse! who is wrong? JDK or Eclipse? we try both...
            this.insnAnnotationInfo = new InsnAnnotationInfo(a, typeRef, typePath);
        }
        return this.annotationVisitor;
    }

    @Override
    public void visitIntInsn(final int opcode, final int operand) {
        switch (opcode) {
        /********
         * PUSH *
         ********/
        case Opcodes.BIPUSH:
        case Opcodes.SIPUSH:
            add(new PUSH(this.ops.size(), opcode, this.line, T.getJvmIntT(operand), operand));
            break;
        /************
         * NEWARRAY *
         ************/
        case Opcodes.NEWARRAY: {
            final T t = T.TYPES[operand];
            assert t != null;
            add(new NEWARRAY(this.ops.size(), opcode, this.line, getDu().getArrayT(t), 1));
            break;
        }
        default:
            log.warn(getM() + ": Unknown int insn opcode '" + opcode + "'!");
        }
    }

    @Override
    public void visitInvokeDynamicInsn(final String name, final String desc, final Handle bsm,
            final Object... bsmArgs) {
        assert name != null && desc != null && bsm != null && bsmArgs != null;
        /**********
         * INVOKE *
         **********/
        final M m = getDu().getDynamicM(name, desc);
        final M bsM = handle2m(bsm);
        final Object[] bsArgs = new Object[bsmArgs.length];
        for (int i = 0; i < bsArgs.length; ++i) {
            // don't leak ASM types
            Object arg = bsmArgs[i];
            if (arg instanceof Handle) {
                arg = handle2m((Handle) arg);
            }
            bsArgs[i] = arg;
        }
        add(new INVOKE(this.ops.size(), Opcodes.INVOKEVIRTUAL, this.line, m, bsM, bsArgs));
    }

    @Override
    public void visitJumpInsn(final int opcode, final Label label) {
        T t = null;
        Object oValue = null;

        final int targetPc = getPc(label);

        switch (opcode) {
        /********
         * GOTO *
         ********/
        case Opcodes.GOTO: {
            final GOTO op = new GOTO(this.ops.size(), opcode, this.line);
            add(op);
            op.setTargetPc(targetPc);
            if (targetPc < 0) {
                getUnresolved(label).add(op);
            }
            break;
        }
        /********
         * JCMP *
         ********/
        case Opcodes.IF_ACMPEQ:
            t = T.REF;
            oValue = CmpType.T_EQ;
            // fall through
        case Opcodes.IF_ACMPNE:
            if (t == null) {
                t = T.REF;
                oValue = CmpType.T_NE;
            }
            // fall through
        case Opcodes.IF_ICMPEQ:
            if (t == null) {
                t = T.AINT; // boolean too
                oValue = CmpType.T_EQ;
            }
            // fall through
        case Opcodes.IF_ICMPGE:
            if (t == null) {
                t = T.INT;
                oValue = CmpType.T_GE;
            }
            // fall through
        case Opcodes.IF_ICMPGT:
            if (t == null) {
                t = T.INT;
                oValue = CmpType.T_GT;
            }
            // fall through
        case Opcodes.IF_ICMPLE:
            if (t == null) {
                t = T.INT;
                oValue = CmpType.T_LE;
            }
            // fall through
        case Opcodes.IF_ICMPLT:
            if (t == null) {
                t = T.INT;
                oValue = CmpType.T_LT;
            }
            // fall through
        case Opcodes.IF_ICMPNE:
            if (t == null) {
                t = T.AINT; // boolean too
                oValue = CmpType.T_NE;
            } {
            assert oValue instanceof CmpType;
            final JCMP op = new JCMP(this.ops.size(), opcode, this.line, t, (CmpType) oValue);
            add(op);
            op.setTargetPc(targetPc);
            if (targetPc < 0) {
                getUnresolved(label).add(op);
            }
        }
            break;
        /********
         * JCND *
         ********/
        case Opcodes.IFNULL:
            t = T.REF;
            oValue = CmpType.T_EQ;
            // fall through
        case Opcodes.IFNONNULL:
            if (t == null) {
                t = T.REF;
                oValue = CmpType.T_NE;
            }
            // fall through
        case Opcodes.IFEQ:
            if (t == null) {
                t = T.AINT; // boolean too
                oValue = CmpType.T_EQ;
            }
            // fall through
        case Opcodes.IFGE:
            if (t == null) {
                t = T.INT;
                oValue = CmpType.T_GE;
            }
            // fall through
        case Opcodes.IFGT:
            if (t == null) {
                t = T.INT;
                oValue = CmpType.T_GT;
            }
            // fall through
        case Opcodes.IFLE:
            if (t == null) {
                t = T.INT;
                oValue = CmpType.T_LE;
            }
            // fall through
        case Opcodes.IFLT:
            if (t == null) {
                t = T.INT;
                oValue = CmpType.T_LT;
            }
            // fall through
        case Opcodes.IFNE:
            if (t == null) {
                t = T.AINT; // boolean too
                oValue = CmpType.T_NE;
            } {
            assert oValue instanceof CmpType;
            final JCND op = new JCND(this.ops.size(), opcode, this.line, t, (CmpType) oValue);
            add(op);
            op.setTargetPc(targetPc);
            if (targetPc < 0) {
                getUnresolved(label).add(op);
            }
        }
            break;
        /*******
         * JSR *
         *******/
        case Opcodes.JSR: {
            final JSR op = new JSR(this.ops.size(), opcode, this.line);
            add(op);
            op.setTargetPc(targetPc);
            if (targetPc < 0) {
                getUnresolved(label).add(op);
            }
            break;
        }
        default:
            log.warn(getM() + ": Unknown jump insn opcode '" + opcode + "'!");
        }
    }

    @Override
    public void visitLabel(final Label label) {
        final Integer pc = this.label2pc.put(label, this.ops.size());
        if (pc == null) {
            // fresh new label, never referenced before
            return;
        }
        if (pc > 0) {
            // visited before but is known?!
            log.warn(getM() + ": Label '" + label + "' is not unique, has old PC '" + this.ops.size() + "'!");
            return;
        }
        // unknown and has forward reference
        for (final Object o : this.label2unresolved.get(label)) {
            if (o instanceof GOTO) {
                ((GOTO) o).setTargetPc(this.ops.size());
                continue;
            }
            if (o instanceof JCMP) {
                ((JCMP) o).setTargetPc(this.ops.size());
                continue;
            }
            if (o instanceof JCND) {
                ((JCND) o).setTargetPc(this.ops.size());
                continue;
            }
            if (o instanceof JSR) {
                ((JSR) o).setTargetPc(this.ops.size());
                continue;
            }
            if (o instanceof SWITCH) {
                final SWITCH op = (SWITCH) o;
                if (pc == op.getDefaultPc()) {
                    op.setDefaultPc(this.ops.size());
                }
                final int[] casePcs = op.getCasePcs();
                if (casePcs != null) {
                    for (int i = casePcs.length; i-- > 0;) {
                        if (pc == casePcs[i]) {
                            casePcs[i] = this.ops.size();
                        }
                    }
                }
                continue;
            }
            if (o instanceof Exc) {
                final Exc op = (Exc) o;
                if (pc == op.getStartPc()) {
                    op.setStartPc(this.ops.size());
                }
                if (pc == op.getEndPc()) {
                    op.setEndPc(this.ops.size());
                }
                if (pc == op.getHandlerPc()) {
                    op.setHandlerPc(this.ops.size());
                }
            }
            if (o instanceof V) {
                final int[] pcs = ((V) o).getPcs();
                for (int i = pcs.length; i-- > 0;) {
                    if (pc == pcs[i]) {
                        pcs[i] = this.ops.size();
                    }
                }
            }
        }
    }

    @Override
    public void visitLdcInsn(final Object cst) {
        T t = null;
        Object oValue = null;

        /********
         * PUSH *
         ********/
        if (cst instanceof Type) {
            oValue = getDu().getDescT(((Type) cst).getDescriptor());
            t = getDu().getT(Class.class);
        } else {
            oValue = cst;
            if (cst instanceof Double) {
                t = T.DOUBLE;
            } else if (cst instanceof Float) {
                t = T.FLOAT;
            } else if (cst instanceof Integer) {
                t = T.getJvmIntT((Integer) oValue);
            } else if (cst instanceof Long) {
                t = T.LONG;
            } else if (cst instanceof String) {
                t = getDu().getT(String.class);
            } else {
                log.warn(getM() + ": Unknown ldc insn cst '" + cst + "'!");
                t = T.ANY;
            }
        }
        add(new PUSH(this.ops.size(), Opcodes.LDC, this.line, t, oValue));
    }

    @Override
    public void visitLineNumber(final int line, final Label start) {
        final int pc = getPc(start);
        if (pc < 0) {
            log.warn(getM() + ": Line number '" + line + "' start label '" + start + "' unknown yet?");
        }
        this.line = line;
    }

    @Override
    public void visitLocalVariable(final String name, final String desc, final String signature, final Label start,
            final Label end, final int index) {
        if (name == null) {
            return;
        }
        T vT = getDu().getDescT(desc);
        if (vT == null) {
            return;
        }
        if (signature != null) {
            final T sigT = getDu().parseT(signature, new Cursor(), this.m);
            if (sigT != null) {
                if (!sigT.eraseTo(vT)) {
                    log.info(getM() + ": Cannot reduce signature '" + signature + "' to type '" + vT
                            + "' for method (local variable '" + name + "') " + this.m);
                } else {
                    vT = sigT;
                }
            }
        }
        final int startPc = getPc(start);
        final int endPc = getPc(end);

        final V v = new V(vT, name, startPc, endPc);

        if (startPc < 0) {
            getUnresolved(start).add(v);
        }
        if (endPc < 0) {
            getUnresolved(end).add(v);
        }

        List<V> vs = this.reg2vs.get(index);
        if (vs == null) {
            vs = Lists.newArrayList();
            this.reg2vs.put(index, vs);
        }
        vs.add(v);
    }

    @Override
    public AnnotationVisitor visitLocalVariableAnnotation(final int typeRef, final TypePath typePath,
            final Label[] start, final Label[] end, final int[] index, final String desc, final boolean visible) {
        /*
         * 3.3.7: The table length field specifies the number of entries in the table array;
         * multiple entries are necessary because a compiler is permitted to break a single variable
         * into multiple live ranges with different local variable indices. The start pc and length
         * fields specify the variables live range in the bytecodes of the local variables
         * containing method (from offset start pc, inclusive, to offset start pc + length,
         * exclusive). The index field stores the local variables index in that method. These
         * fields are similar to those of the optional LocalVariableTable attribute [LBBY12,
         * 4.8.12]. Storing local variable type annotations in the class file raises certain
         * challenges. For example, live ranges are not isomorphic to local variables. Note that a
         * local variable with no live range might not appear in the class file; that is OK, because
         * it is irrelevant to the program. A Runtime[In]visibleTypeAnnotations attribute containing
         * a localvar target appears in the attributes table of a Code attribute.
         */
        final A a = this.annotationVisitor.init(desc, visible ? RetentionPolicy.RUNTIME : RetentionPolicy.CLASS);
        if (a == null) {
            log.warn(getM() + ": Cannot read annotation for descriptor '" + desc + "'!");
            return null;
        }
        final TypeReference typeReference = new TypeReference(typeRef);
        switch (typeReference.getSort()) {
        case TypeReference.LOCAL_VARIABLE:
            for (int i = index.length; i-- > 0;) {
                final List<V> vs = this.reg2vs.get(index[i]);
                if (vs != null) {
                    // TODO hmmm, we may have to remember this info (like receiver),
                    // we cannot apply it without variable analysis in none-debug bytecode,
                    // missing local variable tables! this whole new bytecode sucks
                    for (final V v : vs) {
                        if (v.validIn(getPc(start[i]), getPc(end[i]))) {
                            v.setT(annotateT(v.getT(), a, typePath));
                        }
                    }
                }
            }
            break;
        default:
            log.warn(
                    getM() + ": Unknown type annotation ref sort '0x" + Integer.toHexString(typeReference.getSort())
                            + "' : " + typeRef + " : " + typePath + " : " + desc + " : " + visible);
        }
        return this.annotationVisitor;
    }

    @Override
    public void visitLookupSwitchInsn(final Label dflt, final int[] caseKeys, final Label[] labels) {
        /**********
         * SWITCH *
         **********/
        final SWITCH op = new SWITCH(this.ops.size(), Opcodes.LOOKUPSWITCH, this.line);
        add(op);
        // default
        int targetPc = getPc(dflt);
        op.setDefaultPc(targetPc);
        if (targetPc < 0) {
            getUnresolved(dflt).add(op);
        }
        // keys
        final int[] casePcs = new int[labels.length];
        for (int i = labels.length; i-- > 0;) {
            casePcs[i] = targetPc = getPc(labels[i]);
            if (targetPc < 0) {
                getUnresolved(labels[i]).add(op);
            }
        }
        op.setCaseKeys(caseKeys);
        op.setCasePcs(casePcs);
    }

    @Override
    public void visitMaxs(final int maxStack, final int maxLocals) {
        this.maxStack = maxStack;
        this.maxLocals = maxLocals;
    }

    @Override
    @SuppressWarnings("deprecation")
    public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
        log.warn(getM() + ": Shouldn't be called with ASM5!");
        super.visitMethodInsn(opcode, owner, name, desc);
    }

    @Override
    public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc,
            final boolean itf) {
        if (owner == null || name == null || desc == null) {
            log.warn(getM() + ": Cannot read invoke operation with method name '" + owner + "." + name
                    + "' and descriptor '" + desc + "'!");
            return;
        }
        switch (opcode) {
        /**********
         * INVOKE *
         **********/
        case Opcodes.INVOKEINTERFACE:
        case Opcodes.INVOKESPECIAL:
            // Constructor or supermethod (any super) or private method callout.
        case Opcodes.INVOKESTATIC:
        case Opcodes.INVOKEVIRTUAL: {
            final T ownerT = getDu().getT(owner);
            if (opcode == Opcodes.INVOKEINTERFACE) {
                ownerT.setInterface(true); // static also possible in interface since JVM 8
            }
            assert opcode != Opcodes.INVOKEINTERFACE || itf;

            final M refM = ownerT.getM(name, desc);
            refM.setStatic(opcode == Opcodes.INVOKESTATIC);
            add(new INVOKE(this.ops.size(), opcode, this.line, refM, opcode == Opcodes.INVOKESPECIAL));
            break;
        }
        default:
            log.warn(getM() + ": Unknown method insn opcode '" + opcode + "'!");
        }
    }

    @Override
    public void visitMultiANewArrayInsn(final String desc, final int dims) {
        /************
         * NEWARRAY *
         ************/
        // operation works different from other newarrays, descriptor contains array with
        // dimension > given sizes on stack, e.g.: new int[1][2][3][][], dimension is 3 and
        // descriptor is [[[[[I
        T elementT = getDu().getDescT(desc);
        if (elementT == null) {
            log.warn(getM() + ": Cannot read element type from descriptor '" + desc + "' for MULTIANEWARRAY!");
            elementT = T.ANY;
        }
        add(new NEWARRAY(this.ops.size(), Opcodes.MULTIANEWARRAY, this.line, elementT, dims));
    }

    @Override
    public void visitParameter(final String name, final int access) {
        log.warn(getM() + ": " + name + " : " + access);
        super.visitParameter(name, access);
    }

    @Override
    public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc,
            final boolean visible) {
        A[] paramAs = null;
        if (this.paramAss == null) {
            this.paramAss = new A[parameter + 1][];
        } else if (parameter >= this.paramAss.length) {
            final A[][] newParamAss = new A[parameter + 1][];
            System.arraycopy(this.paramAss, 0, newParamAss, 0, this.paramAss.length);
            this.paramAss = newParamAss;
        } else {
            paramAs = this.paramAss[parameter];
        }
        if (paramAs == null) {
            paramAs = new A[1];
        } else {
            final A[] newParamAs = new A[paramAs.length + 1];
            System.arraycopy(newParamAs, 0, paramAs, 0, paramAs.length);
            paramAs = newParamAs;
        }
        this.paramAss[parameter] = paramAs;
        paramAs[paramAs.length - 1] = this.annotationVisitor.init(desc,
                visible ? RetentionPolicy.RUNTIME : RetentionPolicy.CLASS);
        return this.annotationVisitor;
    }

    @Override
    public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) {
        /**********
         * SWITCH *
         **********/
        final SWITCH op = new SWITCH(this.ops.size(), Opcodes.TABLESWITCH, this.line);
        add(op);
        // default
        int targetPc = getPc(dflt);
        op.setDefaultPc(targetPc);
        if (targetPc < 0) {
            getUnresolved(dflt).add(op);
        }
        // keys
        final int[] keys = new int[labels.length];
        final int[] keyTargets = new int[labels.length];
        for (int i = labels.length; i-- > 0;) {
            keys[i] = min + i;
            targetPc = getPc(labels[i]);
            keyTargets[i] = targetPc;
            if (targetPc < 0) {
                getUnresolved(labels[i]).add(op);
            }
        }
        op.setCaseKeys(keys);
        op.setCasePcs(keyTargets);
    }

    @Override
    public AnnotationVisitor visitTryCatchAnnotation(final int typeRef, final TypePath typePath, final String desc,
            final boolean visible) {
        final A a = this.annotationVisitor.init(desc, visible ? RetentionPolicy.RUNTIME : RetentionPolicy.CLASS);
        if (a == null) {
            log.warn(getM() + ": Cannot read annotation for descriptor '" + desc + "'!");
            return null;
        }
        final TypeReference typeReference = new TypeReference(typeRef);
        switch (typeReference.getSort()) {
        case TypeReference.EXCEPTION_PARAMETER: {
            final int tryCatchBlockIndex = typeReference.getTryCatchBlockIndex();
            final Exc exc = this.excs.get(tryCatchBlockIndex);
            if (exc == null) {
                log.warn(getM() + ": Cannot apply type annotation '" + a + "' to missing exception!");
                break;
            }
            final T excT = exc.getT();
            if (excT == null) {
                log.warn(getM() + ": Cannot apply type annotation '" + a + "' to catch all exception!");
                break;
            }
            exc.setT(annotateT(excT, a, typePath));
            break;
        }
        default:
            log.warn(
                    getM() + ": Unknown type annotation ref sort '0x" + Integer.toHexString(typeReference.getSort())
                            + "' : " + typeRef + " : " + typePath + " : " + desc + " : " + visible);
        }
        return this.annotationVisitor;
    }

    @Override
    public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) {
        // type: java/lang/Exception
        final T catchT = type == null ? null : getDu().getT(type);
        final Exc exc = new Exc(catchT);

        int pc = getPc(start);
        exc.setStartPc(pc);
        if (pc < 0) {
            getUnresolved(start).add(exc);
        }
        pc = getPc(end);
        exc.setEndPc(pc);
        if (pc < 0) {
            getUnresolved(end).add(exc);
        }
        pc = getPc(handler);
        exc.setHandlerPc(pc);
        if (pc < 0) {
            getUnresolved(handler).add(exc);
        }
        this.excs.add(exc);
    }

    @Override
    public AnnotationVisitor visitTypeAnnotation(final int typeRef, @Nullable final TypePath typePath,
            final String desc, final boolean visible) {
        final A a = this.annotationVisitor.init(desc, visible ? RetentionPolicy.RUNTIME : RetentionPolicy.CLASS);
        if (a == null) {
            log.warn(getM() + ": Cannot read annotation for descriptor '" + desc + "'!");
            return null;
        }
        final TypeReference typeReference = new TypeReference(typeRef);
        switch (typeReference.getSort()) {
        case TypeReference.METHOD_FORMAL_PARAMETER: {
            final int formalParameterIndex = typeReference.getFormalParameterIndex();
            final T[] paramTs = getM().getParamTs();
            if (paramTs.length <= formalParameterIndex) {
                log.warn(getM() + ": Cannot apply type annotation '" + a
                        + "' to missing method parameter at index '" + formalParameterIndex + "'!");
                break;
            }
            final T paramT = paramTs[formalParameterIndex];
            assert paramT != null;
            paramTs[formalParameterIndex] = annotateT(paramT, a, typePath);
            break;
        }
        case TypeReference.METHOD_RECEIVER: {
            // for type annotations like: void test(@Annots This this, ...) for none-static methods
            // TODO receiver needs full signature, test-method DU#getQualifiedT(T) does't work,
            // because we would have to read outer classes first
            T receiverT = getM().getReceiverT();
            if (receiverT == null) {
                receiverT = getM().getT();
            }
            if (receiverT == null) {
                assert getM().isDynamic();
                log.warn(getM() + ": Cannot apply type annotation '" + a + "' to missing method receiver!");
                break;
            }
            getM().setReceiverT(annotateT(receiverT, a, typePath));
            break;
        }
        case TypeReference.METHOD_RETURN:
            getM().setReturnT(annotateT(getM().getReturnT(), a, typePath));
            break;
        case TypeReference.METHOD_TYPE_PARAMETER: {
            final int typeParameterIndex = typeReference.getTypeParameterIndex();
            final T[] typeParams = getM().getTypeParams();
            if (typeParams.length <= typeParameterIndex) {
                log.warn(getM() + ": Cannot apply type annotation '" + a
                        + "' to missing method type parameter at index '" + typeParameterIndex + "'!");
                break;
            }
            final T typeParam = typeParams[typeParameterIndex];
            assert typeParam != null;
            typeParams[typeParameterIndex] = annotateT(typeParam, a, typePath);
            break;
        }
        case TypeReference.METHOD_TYPE_PARAMETER_BOUND: {
            final int typeParameterIndex = typeReference.getTypeParameterIndex();
            final int typeParameterBoundIndex = typeReference.getTypeParameterBoundIndex();
            final T t = getM().getTypeParams()[typeParameterIndex];
            if (typeParameterBoundIndex == 0) {
                // 0: annotation targets extends type
                final T superT = t.getSuperT();
                if (superT == null) {
                    log.warn(getM() + ": Cannot apply type annotation '" + a + "' to missing super type!");
                    break;
                }
                t.setSuperT(annotateT(superT, a, typePath));
                break;
            }
            // 1-based interface index
            final T[] interfaceTs = t.getInterfaceTs();
            if (interfaceTs.length < typeParameterBoundIndex) {
                log.warn(getM() + ": Cannot apply type annotation '" + a + "' to missing interface type!");
                break;
            }
            final T interfaceT = interfaceTs[typeParameterBoundIndex - 1];
            assert interfaceT != null;
            interfaceTs[typeParameterBoundIndex - 1] = annotateT(interfaceT, a, typePath);
            break;
        }
        case TypeReference.THROWS: {
            final int exceptionIndex = typeReference.getExceptionIndex();
            final T[] throwsTs = getM().getThrowsTs();
            if (throwsTs.length < exceptionIndex) {
                log.warn(getM() + ": Cannot apply type annotation '" + a + "' to missing throws type!");
                break;
            }
            final T throwsT = throwsTs[exceptionIndex];
            assert throwsT != null;
            throwsTs[exceptionIndex] = annotateT(throwsT, a, typePath);
            break;
        }
        default:
            log.warn(
                    getM() + ": Unknown type annotation ref sort '0x" + Integer.toHexString(typeReference.getSort())
                            + "' : " + typeRef + " : " + typePath + " : " + desc + " : " + visible);
        }
        return this.annotationVisitor;
    }

    @Override
    public void visitTypeInsn(final int opcode, final String type) {
        assert type != null;
        final T t = getDu().getT(type);

        switch (opcode) {
        /********
         * CAST *
         ********/
        case Opcodes.CHECKCAST:
            add(new CAST(this.ops.size(), opcode, this.line, T.REF, t));
            break;
        /**************
         * INSTANCEOF *
         **************/
        case Opcodes.INSTANCEOF:
            add(new INSTANCEOF(this.ops.size(), opcode, this.line, t));
            break;
        /*******
         * NEW *
         *******/
        case Opcodes.NEW:
            add(new NEW(this.ops.size(), opcode, this.line, t));
            break;
        /************
         * NEWARRAY *
         ************/
        case Opcodes.ANEWARRAY:
            add(new NEWARRAY(this.ops.size(), opcode, this.line, getDu().getArrayT(t), 1));
            break;
        default:
            log.warn(getM() + ": Unknown var insn opcode '" + opcode + "'!");
        }
    }

    @Override
    public void visitVarInsn(final int opcode, final int var) {
        T t = null;

        switch (opcode) {
        /********
         * LOAD *
         ********/
        case Opcodes.ALOAD:
            t = T.REF;
            // fall through
        case Opcodes.DLOAD:
            if (t == null) {
                t = T.DOUBLE;
            }
            // fall through
        case Opcodes.FLOAD:
            if (t == null) {
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.ILOAD:
            if (t == null) {
                t = T.AINT;
            }
            // fall through
        case Opcodes.LLOAD:
            if (t == null) {
                t = T.LONG;
            }
            add(new LOAD(this.ops.size(), opcode, this.line, t, var));
            break;
        /*********
         * STORE *
         *********/
        case Opcodes.ASTORE:
            t = T.AREF; // RET allowed too
            // fall through
        case Opcodes.DSTORE:
            if (t == null) {
                t = T.DOUBLE;
            }
            // fall through
        case Opcodes.FSTORE:
            if (t == null) {
                t = T.FLOAT;
            }
            // fall through
        case Opcodes.ISTORE:
            if (t == null) {
                t = T.AINT;
            }
            // fall through
        case Opcodes.LSTORE:
            if (t == null) {
                t = T.LONG;
            }
            add(new STORE(this.ops.size(), opcode, this.line, t, var));
            break;
        /*******
         * RET *
         *******/
        case Opcodes.RET: {
            add(new RET(this.ops.size(), opcode, this.line, var));
            break;
        }
        default:
            log.warn(getM() + ": Unknown var insn opcode '" + opcode + "'!");
        }
    }

}