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

sootup.java.bytecode.frontend.AsmMethodSource Maven / Gradle / Ivy

There is a newer version: 1.3.0
Show newest version
package sootup.java.bytecode.frontend;
/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 2018-2020 Andreas Dann, Jan Martin Persch, Markus Schmidt and others
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import static org.objectweb.asm.tree.AbstractInsnNode.FIELD_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.FRAME;
import static org.objectweb.asm.tree.AbstractInsnNode.IINC_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.INT_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.INVOKE_DYNAMIC_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.JUMP_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.LABEL;
import static org.objectweb.asm.tree.AbstractInsnNode.LDC_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.LINE;
import static org.objectweb.asm.tree.AbstractInsnNode.LOOKUPSWITCH_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.METHOD_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.MULTIANEWARRAY_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.TABLESWITCH_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.TYPE_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.VAR_INSN;

import com.google.common.base.Suppliers;
import com.google.common.collect.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.objectweb.asm.Handle;
import org.objectweb.asm.commons.JSRInlinerAdapter;
import org.objectweb.asm.tree.*;
import sootup.core.frontend.BodySource;
import sootup.core.graph.MutableBlockStmtGraph;
import sootup.core.jimple.Jimple;
import sootup.core.jimple.basic.*;
import sootup.core.jimple.common.constant.DoubleConstant;
import sootup.core.jimple.common.constant.FloatConstant;
import sootup.core.jimple.common.constant.IntConstant;
import sootup.core.jimple.common.constant.LongConstant;
import sootup.core.jimple.common.constant.MethodHandle;
import sootup.core.jimple.common.constant.NullConstant;
import sootup.core.jimple.common.expr.AbstractBinopExpr;
import sootup.core.jimple.common.expr.AbstractConditionExpr;
import sootup.core.jimple.common.expr.AbstractInstanceInvokeExpr;
import sootup.core.jimple.common.expr.AbstractInvokeExpr;
import sootup.core.jimple.common.expr.AbstractUnopExpr;
import sootup.core.jimple.common.expr.Expr;
import sootup.core.jimple.common.expr.JAddExpr;
import sootup.core.jimple.common.expr.JCastExpr;
import sootup.core.jimple.common.expr.JDynamicInvokeExpr;
import sootup.core.jimple.common.expr.JInstanceOfExpr;
import sootup.core.jimple.common.expr.JNewArrayExpr;
import sootup.core.jimple.common.expr.JNewMultiArrayExpr;
import sootup.core.jimple.common.expr.JStaticInvokeExpr;
import sootup.core.jimple.common.ref.*;
import sootup.core.jimple.common.stmt.*;
import sootup.core.jimple.javabytecode.stmt.JSwitchStmt;
import sootup.core.model.*;
import sootup.core.signatures.FieldSignature;
import sootup.core.signatures.MethodSignature;
import sootup.core.transform.BodyInterceptor;
import sootup.core.types.ArrayType;
import sootup.core.types.ClassType;
import sootup.core.types.PrimitiveType;
import sootup.core.types.Type;
import sootup.core.types.UnknownType;
import sootup.core.types.VoidType;
import sootup.core.views.View;
import sootup.java.core.JavaIdentifierFactory;
import sootup.java.core.jimple.basic.JavaLocal;
import sootup.java.core.language.JavaJimple;
import sootup.java.core.types.JavaClassType;

/**
 * A {@link BodySource} that can read Java bytecode
 *
 * @author Andreas Dann
 */
public class AsmMethodSource extends JSRInlinerAdapter implements BodySource {

  // private static final String METAFACTORY_SIGNATURE =
  // "";
  // private static final String ALT_METAFACTORY_SIGNATURE =
  // "";

  /* -state fields- */
  private int nextLocal;
  private List locals;
  private LinkedListMultimap stmtsThatBranchToLabel;
  private Map insnToStmt;

  @Nonnull private final Map replacedStmt = new HashMap<>();

  private OperandStack operandStack;
  private Map trapHandler;

  private int currentLineNumber = -1;
  private int maxLineNumber = 0;

  @Nullable private JavaClassType declaringClass;

  private final View view;
  private final List bodyInterceptors;

  @Nonnull private final Set inlineExceptionLabels = new HashSet<>();

  @Nonnull
  private final Map>
      inlineExceptionHandlers = new HashMap<>();

  @Nonnull private final Map labelsToStmt = new HashMap<>();

  // FIXME: [ms] or JavaModuleIdentifierFactory if needed..
  private JavaIdentifierFactory javaIdentifierFactory = JavaIdentifierFactory.getInstance();
  private final Supplier lazyMethodSignature =
      Suppliers.memoize(
          () -> {
            List sigTypes = AsmUtil.toJimpleSignatureDesc(desc);
            Type retType = sigTypes.remove(sigTypes.size() - 1);

            return javaIdentifierFactory.getMethodSignature(
                declaringClass, name, retType, sigTypes);
          });

  AsmMethodSource(
      int access,
      @Nonnull String name,
      @Nonnull String desc,
      @Nonnull String signature,
      @Nonnull String[] exceptions,
      View view,
      @Nonnull List bodyInterceptors) {
    super(AsmUtil.SUPPORTED_ASM_OPCODE, null, access, name, desc, signature, exceptions);
    this.bodyInterceptors = bodyInterceptors;
    this.view = view;
  }

  @Override
  @Nonnull
  public MethodSignature getSignature() {
    return lazyMethodSignature.get();
  }

  void setDeclaringClass(@Nonnull ClassType declaringClass) {
    this.declaringClass = (JavaClassType) declaringClass;
  }

  @Override
  @Nonnull
  public Body resolveBody(@Nonnull Iterable modifierIt) {

    /* initialize */
    nextLocal = maxLocals;
    locals =
        new NonIndexOutofBoundsArrayList<>(
            maxLocals
                + Math.max((maxLocals / 2), 5)); // [ms] initial capacity is just roughly estimated.
    stmtsThatBranchToLabel = LinkedListMultimap.create();
    insnToStmt = new LinkedHashMap<>(instructions.size());
    operandStack = new OperandStack(this, instructions.size());
    trapHandler = new LinkedHashMap<>(tryCatchBlocks.size());

    /* retrieve all trap handlers */
    for (TryCatchBlockNode tc : tryCatchBlocks) {
      // reserve space/ insert labels in datastructure  - necessary?! its useless if not assigned
      // later.. -> check the meaning of that containsKey() check
      trapHandler.put(tc.handler, null);
    }

    /* build body (add stmts, locals, traps, etc.) */
    final MutableBlockStmtGraph graph = new MutableBlockStmtGraph();
    Body.BodyBuilder bodyBuilder = Body.builder(graph);
    bodyBuilder.setModifiers(AsmUtil.getModifiers(access));

    final List preambleStmts = buildPreambleLocals(bodyBuilder);

    /* convert instructions */
    try {
      convert();
    } catch (Exception e) {
      throw new RuntimeException("Failed to convert " + lazyMethodSignature.get(), e);
    }

    // collect used Locals
    Set bodyLocals =
        locals.stream()
            .filter(Objects::nonNull)
            // [ms] find out why some Local indices are not assigned(null)
            // ms -> guess because of dword values i.e. +=2 ?
            .collect(Collectors.toCollection(LinkedHashSet::new));
    bodyBuilder.setLocals(bodyLocals);

    // add converted insn as stmts into the graph
    try {
      arrangeStmts(graph, preambleStmts, bodyBuilder);
    } catch (Exception e) {
      throw new RuntimeException("Failed to convert " + lazyMethodSignature.get(), e);
    }

    // propagate position information
    if (graph.getNodes().size() > 0) {
      Position firstStmtPos = graph.getStartingStmt().getPositionInfo().getStmtPosition();
      bodyBuilder.setPosition(
          new FullPosition(
              firstStmtPos.getFirstLine(),
              firstStmtPos.getFirstCol(),
              maxLineNumber,
              Integer.MAX_VALUE));
    } else {
      bodyBuilder.setPosition(NoPositionInformation.getInstance());
    }

    /* clean up for gc */
    locals = null;
    stmtsThatBranchToLabel = null;
    insnToStmt = null;
    operandStack = null;

    bodyBuilder.setMethodSignature(lazyMethodSignature.get());

    for (BodyInterceptor bodyInterceptor : bodyInterceptors) {
      try {
        bodyInterceptor.interceptBody(bodyBuilder, view);
      } catch (Exception e) {
        throw new IllegalStateException(
            "Failed to apply " + bodyInterceptor + " to " + lazyMethodSignature.get(), e);
      }
    }
    return bodyBuilder.build();
  }

  @Override
  public Object resolveAnnotationsDefaultValue() {
    return resolveAnnotationsInDefaultValue(this.annotationDefault);
  }

  private Object resolveAnnotationsInDefaultValue(Object a) {
    if (a instanceof AnnotationNode) {
      return AsmUtil.createAnnotationUsage(Collections.singletonList((AnnotationNode) a));
    }

    if (a instanceof ArrayList) {
      List list = new ArrayList<>();
      ((ArrayList) a).forEach(e -> list.add(resolveAnnotationsInDefaultValue(e)));
      return list;
    }
    return AsmUtil.convertAnnotationValue(a);
  }

  @Nonnull
  private JavaLocal getOrCreateLocal(int idx) {
    if (idx >= maxLocals) {
      throw new IllegalArgumentException("Invalid local index: " + idx);
    }
    JavaLocal local = locals.get(idx);
    if (local == null) {
      String name = determineLocalName(idx);
      local = JavaJimple.newLocal(name, UnknownType.getInstance(), Collections.emptyList());
      locals.set(idx, local);
    }
    return local;
  }

  @Nonnull
  private String determineLocalName(int idx) {
    String name;
    if (localVariables != null) {
      name = null;
      for (LocalVariableNode lvn : localVariables) {
        if (lvn.index == idx) {
          name = lvn.name;
          break;
        }
      }
      /* normally for try-catch blocks */
      if (name == null) {
        name = "l" + idx;
      }
    } else {
      name = "l" + idx;
    }
    return name;
  }

  void setStmt(@Nonnull AbstractInsnNode insn, @Nonnull Stmt stmt) {
    Stmt overwrittenStmt = insnToStmt.put(insn, stmt);
    if (overwrittenStmt != null) {
      throw new IllegalArgumentException(
          insn.getOpcode() + " already has an associated Stmt: " + overwrittenStmt);
    }
  }

  void mergeStmts(@Nonnull AbstractInsnNode insn, @Nonnull Stmt stmt) {
    Stmt initiallyAssignedStmt = insnToStmt.put(insn, stmt);
    if (initiallyAssignedStmt != null) {
      Stmt merged = StmtContainer.getOrCreate(initiallyAssignedStmt, stmt);
      insnToStmt.put(insn, merged);
    }
  }

  @Nonnull
  Local newStackLocal() {
    int idx = nextLocal++;
    JavaLocal l =
        JavaJimple.newLocal("$stack" + idx, UnknownType.getInstance(), Collections.emptyList());
    locals.set(idx, l);
    return l;
  }

  @SuppressWarnings("unchecked")
   A getStmt(@Nonnull AbstractInsnNode insn) {
    return (A) insnToStmt.get(insn);
  }

  private void addReadOperandAssignments() {
    addReadOperandAssignments_internal(
        (opValue, operand) -> {
          if (opValue instanceof Local) {
            return true;
          }
          int op = operand.insn.getOpcode();
          return op != GETFIELD && op != GETSTATIC && (op < IALOAD || op > SALOAD);
        });
  }

  private void addReadOperandAssignments(@Nonnull Local local) {
    addReadOperandAssignments_internal(
        (opValue, operand) -> {
          if (!opValue.equivTo(local)) {
            boolean noRef = true;
            for (Value use : opValue.getUses()) {
              if (use.equivTo(local)) {
                noRef = false;
                break;
              }
            }
            return noRef;
          }
          return false;
        });
  }

  private void addReadOperandAssignments_internal(BiFunction func) {
    // determine which Operand(s) from the stack needs explicit assignments in Jimple
    for (Operand operand : operandStack.getStack()) {
      final Value opValue = operand.value;
      if (operand == Operand.DWORD_DUMMY || operand.stackLocal != null) {
        continue;
      }
      if (func.apply(opValue, operand)) {
        continue;
      }

      Local stackLocal = newStackLocal();
      operand.stackLocal = stackLocal;
      JAssignStmt asssignStmt =
          Jimple.newAssignStmt(stackLocal, opValue, new SimpleStmtPositionInfo(currentLineNumber));

      setStmt(operand.insn, asssignStmt);
      operand.updateUsages();
    }
  }

  private void convertGetFieldInsn(@Nonnull FieldInsnNode insn) {
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr;
    Type type;
    if (out == null) {
      JavaClassType declClass =
          javaIdentifierFactory.getClassType(AsmUtil.toQualifiedName(insn.owner));
      type = AsmUtil.toJimpleType(insn.desc);
      JFieldRef val;
      FieldSignature ref;
      if (insn.getOpcode() == GETSTATIC) {
        ref = javaIdentifierFactory.getFieldSignature(insn.name, declClass, type);
        val = Jimple.newStaticFieldRef(ref);
      } else {
        Operand base = operandStack.popLocal();
        ref = javaIdentifierFactory.getFieldSignature(insn.name, declClass, type);
        val = Jimple.newInstanceFieldRef((Local) base.stackOrValue(), ref);
        frame.setIn(base);
      }
      opr = new Operand(insn, val, this);
      frame.setOut(opr);
    } else {
      opr = out[0];
      type = ((JFieldRef) opr.value).getFieldSignature().getType();
      if (insn.getOpcode() == GETFIELD) {
        frame.mergeIn(currentLineNumber, operandStack.pop());
      }
    }
    operandStack.push(type, opr);
  }

  private void convertPutFieldInsn(@Nonnull FieldInsnNode insn) {
    boolean notInstance = insn.getOpcode() != PUTFIELD;
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr, rvalue;
    Type type;
    if (out == null) {
      JavaClassType declClass =
          javaIdentifierFactory.getClassType(AsmUtil.toQualifiedName(insn.owner));
      type = AsmUtil.toJimpleType(insn.desc);

      JFieldRef val;
      FieldSignature ref;
      rvalue = operandStack.popImmediate(type);
      if (notInstance) {
        ref = javaIdentifierFactory.getFieldSignature(insn.name, declClass, type);
        val = Jimple.newStaticFieldRef(ref);
        frame.setIn(rvalue);
      } else {
        Operand base = operandStack.popLocal();
        ref = javaIdentifierFactory.getFieldSignature(insn.name, declClass, type);
        val = Jimple.newInstanceFieldRef((Local) base.stackOrValue(), ref);
        frame.setIn(rvalue, base);
      }
      opr = new Operand(insn, val, this);
      frame.setOut(opr);
      JAssignStmt as =
          Jimple.newAssignStmt(
              val, rvalue.stackOrValue(), new SimpleStmtPositionInfo(currentLineNumber));
      setStmt(insn, as);
      rvalue.addUsageInStmt(as);
    } else {
      opr = out[0];
      type = ((JFieldRef) opr.value).getFieldSignature().getType();
      rvalue = operandStack.pop(type);
      if (notInstance) {
        /* PUTSTATIC only needs one operand on the stack, the rvalue */
        frame.mergeIn(currentLineNumber, rvalue);
      } else {
        /* PUTFIELD has a rvalue and a base */
        frame.mergeIn(currentLineNumber, rvalue, operandStack.pop());
      }
    }
    /*
     * in case any static field or array is read from, and the static constructor or the field this instruction writes to,
     * modifies that field, write out any previous read from field/array
     */
    addReadOperandAssignments();
  }

  private void convertFieldInsn(@Nonnull FieldInsnNode insn) {
    int op = insn.getOpcode();
    if (op == GETSTATIC || op == GETFIELD) {
      convertGetFieldInsn(insn);
    } else {
      convertPutFieldInsn(insn);
    }
  }

  private void convertIincInsn(@Nonnull IincInsnNode insn) {
    Local local = getOrCreateLocal(insn.var);
    addReadOperandAssignments(local);
    if (!insnToStmt.containsKey(insn)) {
      JAddExpr add = Jimple.newAddExpr(local, IntConstant.getInstance(insn.incr));
      setStmt(
          insn, Jimple.newAssignStmt(local, add, new SimpleStmtPositionInfo(currentLineNumber)));
    }
  }

  private void convertConstInsn(@Nonnull InsnNode insn) {
    int op = insn.getOpcode();
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr;
    if (out == null) {
      Value v;
      if (op == ACONST_NULL) {
        v = NullConstant.getInstance();
      } else if (op >= ICONST_M1 && op <= ICONST_5) {
        v = IntConstant.getInstance(op - ICONST_0);
      } else if (op == LCONST_0 || op == LCONST_1) {
        v = LongConstant.getInstance(op - LCONST_0);
      } else if (op >= FCONST_0 && op <= FCONST_2) {
        v = FloatConstant.getInstance(op - FCONST_0);
      } else if (op == DCONST_0 || op == DCONST_1) {
        v = DoubleConstant.getInstance(op - DCONST_0);
      } else {
        throw new UnsupportedOperationException("Unknown constant opcode: " + op);
      }
      opr = new Operand(insn, v, this);
      frame.setOut(opr);
    } else {
      opr = out[0];
    }
    if (op == LCONST_0 || op == LCONST_1 || op == DCONST_0 || op == DCONST_1) {
      operandStack.pushDual(opr);
    } else {
      operandStack.push(opr);
    }
  }

  private void convertArrayLoadInsn(@Nonnull InsnNode insn) {
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr;
    if (out == null) {
      Operand indx = operandStack.popImmediate();
      Operand base = operandStack.popImmediate();
      JArrayRef ar =
          JavaJimple.getInstance()
              .newArrayRef((Local) base.stackOrValue(), (Immediate) indx.stackOrValue());
      opr = new Operand(insn, ar, this);
      frame.setIn(indx, base);
      frame.setOut(opr);
    } else {
      opr = out[0];
      frame.mergeIn(currentLineNumber, operandStack.pop(), operandStack.pop());
    }
    int op = insn.getOpcode();
    if (op == DALOAD || op == LALOAD) {
      operandStack.pushDual(opr);
    } else {
      operandStack.push(opr);
    }
  }

  private void convertArrayStoreInsn(@Nonnull InsnNode insn) {
    int op = insn.getOpcode();
    boolean dword = op == LASTORE || op == DASTORE;
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    if (!insnToStmt.containsKey(insn)) {
      Operand valueOp = dword ? operandStack.popImmediateDual() : operandStack.popImmediate();
      Operand indexOp = operandStack.popImmediate();
      Operand baseOp = operandStack.popLocal();
      JArrayRef ar =
          JavaJimple.getInstance()
              .newArrayRef((Local) baseOp.stackOrValue(), (Immediate) indexOp.stackOrValue());
      JAssignStmt as =
          Jimple.newAssignStmt(
              ar, valueOp.stackOrValue(), new SimpleStmtPositionInfo(currentLineNumber));
      frame.setIn(valueOp, indexOp, baseOp);
      setStmt(insn, as);
      valueOp.addUsageInStmt(as);

    } else {
      frame.mergeIn(
          currentLineNumber,
          dword ? operandStack.popDual() : operandStack.pop(),
          operandStack.pop(),
          operandStack.pop());
    }
  }

  /*
   * Following version is more complex, using stack frames as opposed to simply swapping
   */
  /*
   * StackFrame frame = getFrame(insn); Operand[] out = frame.out(); Operand dup, dup2 = null, dupd, dupd2 = null; if (out ==
   * null) { dupd = popImmediate(); dup = new Operand(insn, dupd.stackOrValue()); if (dword) { dupd2 = peek(); if (dupd2 ==
   * DWORD_DUMMY) { pop(); dupd2 = dupd; } else { dupd2 = popImmediate(); } dup2 = new Operand(insn, dupd2.stackOrValue());
   * frame.out(dup, dup2); frame.in(dupd, dupd2); } else { frame.out(dup); frame.in(dupd); } } else { dupd = pop(); dup =
   * out[0]; if (dword) { dupd2 = pop(); if (dupd2 == DWORD_DUMMY) dupd2 = dupd; dup2 = out[1]; frame.mergeIn(dupd, dupd2); }
   * else { frame.mergeIn(dupd); } }
   */

  private void convertDupInsn(@Nonnull InsnNode insn) {
    int op = insn.getOpcode();

    // Get the top stack value which we need in either case
    Operand dupd = operandStack.popImmediate();
    Operand dupd2 = null;

    // Some instructions allow operands that take two registers
    boolean dword = op == DUP2 || op == DUP2_X1 || op == DUP2_X2;
    if (dword) {
      if (operandStack.peek() == Operand.DWORD_DUMMY) {
        operandStack.pop();
        dupd2 = dupd;
      } else {
        dupd2 = operandStack.popImmediate();
      }
    }

    if (op == DUP) {
      // val -> val, val
      operandStack.push(dupd);
      operandStack.push(dupd);
    } else if (op == DUP_X1) {
      // val2, val1 -> val1, val2, val1
      // value1, value2 must not be of type double or long
      Operand o2 = operandStack.popImmediate();
      operandStack.push(dupd);
      operandStack.push(o2);
      operandStack.push(dupd);
    } else if (op == DUP_X2) {
      // value3, value2, value1 -> value1, value3, value2, value1
      Operand o2 = operandStack.popImmediate();
      Operand o3 =
          operandStack.peek() == Operand.DWORD_DUMMY
              ? operandStack.pop()
              : operandStack.popImmediate();
      operandStack.push(dupd);
      operandStack.push(o3);
      operandStack.push(o2);
      operandStack.push(dupd);
    } else if (op == DUP2) {
      // value2, value1 -> value2, value1, value2, value1
      operandStack.push(dupd2);
      operandStack.push(dupd);
      operandStack.push(dupd2);
      operandStack.push(dupd);
    } else if (op == DUP2_X1) {
      // value3, value2, value1 -> value2, value1, value3, value2, value1
      // Attention: value2 may be
      Operand o2 = operandStack.popImmediate();
      operandStack.push(dupd2);
      operandStack.push(dupd);
      operandStack.push(o2);
      operandStack.push(dupd2);
      operandStack.push(dupd);
    } else if (op == DUP2_X2) {
      // (value4, value3), (value2, value1) -> (value2, value1), (value4, value3),
      // (value2, value1)
      Operand o2 = operandStack.popImmediate();
      Operand o2h =
          operandStack.peek() == Operand.DWORD_DUMMY
              ? operandStack.pop()
              : operandStack.popImmediate();
      operandStack.push(dupd2);
      operandStack.push(dupd);
      operandStack.push(o2h);
      operandStack.push(o2);
      operandStack.push(dupd2);
      operandStack.push(dupd);
    }
  }

  private void convertBinopInsn(@Nonnull InsnNode insn) {
    int op = insn.getOpcode();
    boolean dword =
        op == DADD
            || op == LADD
            || op == DSUB
            || op == LSUB
            || op == DMUL
            || op == LMUL
            || op == DDIV
            || op == LDIV
            || op == DREM
            || op == LREM
            || op == LSHL
            || op == LSHR
            || op == LUSHR
            || op == LAND
            || op == LOR
            || op == LXOR
            || op == LCMP
            || op == DCMPL
            || op == DCMPG;
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr;
    if (out == null) {
      Operand op2 =
          (dword && op != LSHL && op != LSHR && op != LUSHR)
              ? operandStack.popImmediateDual()
              : operandStack.popImmediate();
      Operand op1 = dword ? operandStack.popImmediateDual() : operandStack.popImmediate();
      Immediate v1 = (Immediate) op1.stackOrValue();
      Immediate v2 = (Immediate) op2.stackOrValue();
      AbstractBinopExpr binop;
      if (op >= IADD && op <= DADD) {
        binop = Jimple.newAddExpr(v1, v2);
      } else if (op >= ISUB && op <= DSUB) {
        binop = Jimple.newSubExpr(v1, v2);
      } else if (op >= IMUL && op <= DMUL) {
        binop = Jimple.newMulExpr(v1, v2);
      } else if (op >= IDIV && op <= DDIV) {
        binop = Jimple.newDivExpr(v1, v2);
      } else if (op >= IREM && op <= DREM) {
        binop = Jimple.newRemExpr(v1, v2);
      } else if (op >= ISHL && op <= LSHL) {
        binop = Jimple.newShlExpr(v1, v2);
      } else if (op >= ISHR && op <= LSHR) {
        binop = Jimple.newShrExpr(v1, v2);
      } else if (op >= IUSHR && op <= LUSHR) {
        binop = Jimple.newUshrExpr(v1, v2);
      } else if (op >= IAND && op <= LAND) {
        binop = Jimple.newAndExpr(v1, v2);
      } else if (op >= IOR && op <= LOR) {
        binop = Jimple.newOrExpr(v1, v2);
      } else if (op >= IXOR && op <= LXOR) {
        binop = Jimple.newXorExpr(v1, v2);
      } else if (op == LCMP) {
        binop = Jimple.newCmpExpr(v1, v2);
      } else if (op == FCMPL || op == DCMPL) {
        binop = Jimple.newCmplExpr(v1, v2);
      } else if (op == FCMPG || op == DCMPG) {
        binop = Jimple.newCmpgExpr(v1, v2);
      } else {
        throw new UnsupportedOperationException("Unknown binop: " + op);
      }
      opr = new Operand(insn, binop, this);
      op1.addUsageInExpr(binop);
      op2.addUsageInExpr(binop);

      frame.setIn(op2, op1);
      frame.setOut(opr);
    } else {
      opr = out[0];
      if (dword) {
        if (op != LSHL && op != LSHR && op != LUSHR) {

          frame.mergeIn(currentLineNumber, operandStack.popDual(), operandStack.popDual());
        } else {
          frame.mergeIn(currentLineNumber, operandStack.pop(), operandStack.popDual());
        }
      } else {
        frame.mergeIn(currentLineNumber, operandStack.pop(), operandStack.pop());
      }
    }
    if (dword && op < LCMP) {
      operandStack.pushDual(opr);
    } else {
      operandStack.push(opr);
    }
  }

  private void convertUnopInsn(@Nonnull InsnNode insn) {
    int op = insn.getOpcode();
    boolean dword = op == LNEG || op == DNEG;
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr;
    if (out == null) {
      Operand op1 = dword ? operandStack.popImmediateDual() : operandStack.popImmediate();
      Value v1 = op1.stackOrValue();
      AbstractUnopExpr unop;
      if (op >= INEG && op <= DNEG) {
        unop = Jimple.newNegExpr((Immediate) v1);
      } else if (op == ARRAYLENGTH) {
        unop = Jimple.newLengthExpr((Immediate) v1);
      } else {
        throw new UnsupportedOperationException("Unknown unop: " + op);
      }
      op1.addUsageInExpr(unop);
      opr = new Operand(insn, unop, this);
      frame.setIn(op1);
      frame.setOut(opr);
    } else {
      opr = out[0];
      frame.mergeIn(currentLineNumber, dword ? operandStack.popDual() : operandStack.pop());
    }
    if (dword) {
      operandStack.pushDual(opr);
    } else {
      operandStack.push(opr);
    }
  }

  private void convertPrimCastInsn(@Nonnull InsnNode insn) {
    int op = insn.getOpcode();
    boolean tod = op == I2L || op == I2D || op == F2L || op == F2D || op == D2L || op == L2D;
    boolean fromd = op == D2L || op == L2D || op == D2I || op == L2I || op == D2F || op == L2F;
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr;
    if (out == null) {
      Type totype;
      switch (op) {
        case I2L:
        case F2L:
        case D2L:
          totype = PrimitiveType.getLong();
          break;
        case L2I:
        case F2I:
        case D2I:
          totype = PrimitiveType.getInt();
          break;
        case I2F:
        case L2F:
        case D2F:
          totype = PrimitiveType.getFloat();
          break;
        case I2D:
        case L2D:
        case F2D:
          totype = PrimitiveType.getDouble();
          break;
        case I2B:
          totype = PrimitiveType.getByte();
          break;
        case I2S:
          totype = PrimitiveType.getShort();
          break;
        case I2C:
          totype = PrimitiveType.getChar();
          break;
        default:
          throw new IllegalStateException("Unknown prim cast op: " + op);
      }
      Operand val = fromd ? operandStack.popImmediateDual() : operandStack.popImmediate();
      JCastExpr cast = Jimple.newCastExpr((Immediate) val.stackOrValue(), totype);
      opr = new Operand(insn, cast, this);
      val.addUsageInExpr(cast);
      frame.setIn(val);
      frame.setOut(opr);
    } else {
      opr = out[0];
      frame.mergeIn(currentLineNumber, fromd ? operandStack.popDual() : operandStack.pop());
    }
    if (tod) {
      operandStack.pushDual(opr);
    } else {
      operandStack.push(opr);
    }
  }

  private void convertReturnInsn(@Nonnull InsnNode insn) {
    int op = insn.getOpcode();
    boolean dword = op == LRETURN || op == DRETURN;
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    if (!insnToStmt.containsKey(insn)) {
      Operand val = dword ? operandStack.popImmediateDual() : operandStack.popImmediate();
      JReturnStmt ret =
          Jimple.newReturnStmt(
              (Immediate) val.stackOrValue(), new SimpleStmtPositionInfo(currentLineNumber));
      frame.setIn(val);
      setStmt(insn, ret);
      val.addUsageInStmt(ret);
    } else {
      final Operand operand = dword ? operandStack.popDual() : operandStack.pop();
      frame.mergeIn(currentLineNumber, operand);
    }
  }

  private void convertInsn(@Nonnull InsnNode insn) {
    int op = insn.getOpcode();
    if (op == NOP) {
      /*
       * We can ignore NOP instructions, but for completeness, we handle them
       */
      if (!insnToStmt.containsKey(insn)) {
        insnToStmt.put(insn, Jimple.newNopStmt(new SimpleStmtPositionInfo(currentLineNumber)));
      }
    } else if (op >= ACONST_NULL && op <= DCONST_1) {
      convertConstInsn(insn);
    } else if (op >= IALOAD && op <= SALOAD) {
      convertArrayLoadInsn(insn);
    } else if (op >= IASTORE && op <= SASTORE) {
      convertArrayStoreInsn(insn);
    } else if (op == POP) {
      operandStack.popImmediate();
    } else if (op == POP2) {
      operandStack.popImmediate();
      if (operandStack.peek() == Operand.DWORD_DUMMY) {
        operandStack.pop();
      } else {
        operandStack.popImmediate();
      }
    } else if (op >= DUP && op <= DUP2_X2) {
      convertDupInsn(insn);
    } else if (op == SWAP) {
      Operand o1 = operandStack.popImmediate();
      Operand o2 = operandStack.popImmediate();
      operandStack.push(o1);
      operandStack.push(o2);
    } else if ((op >= IADD && op <= DREM)
        || (op >= ISHL && op <= LXOR)
        || (op >= LCMP && op <= DCMPG)) {
      convertBinopInsn(insn);
    } else if ((op >= INEG && op <= DNEG) || op == ARRAYLENGTH) {
      convertUnopInsn(insn);
    } else if (op >= I2L && op <= I2S) {
      convertPrimCastInsn(insn);
    } else if (op >= IRETURN && op <= ARETURN) {
      convertReturnInsn(insn);
    } else if (op == RETURN) {
      if (!insnToStmt.containsKey(insn)) {
        setStmt(insn, Jimple.newReturnVoidStmt(new SimpleStmtPositionInfo(currentLineNumber)));
      }
    } else if (op == ATHROW) {
      StackFrame frame = operandStack.getOrCreateStackframe(insn);
      Operand opr;
      if (!insnToStmt.containsKey(insn)) {
        opr = operandStack.popImmediate();
        JThrowStmt ts =
            Jimple.newThrowStmt(
                (Immediate) opr.stackOrValue(), new SimpleStmtPositionInfo(currentLineNumber));
        frame.setIn(opr);
        frame.setOut(opr);
        setStmt(insn, ts);
        opr.addUsageInStmt(ts);
      } else {
        opr = operandStack.pop();
        frame.mergeIn(currentLineNumber, opr);
      }
      operandStack.push(opr);
    } else if (op == MONITORENTER || op == MONITOREXIT) {
      StackFrame frame = operandStack.getOrCreateStackframe(insn);
      if (!insnToStmt.containsKey(insn)) {
        Operand opr = operandStack.popStackConst();
        AbstractOpStmt ts =
            op == MONITORENTER
                ? Jimple.newEnterMonitorStmt(
                    (Immediate) opr.stackOrValue(), new SimpleStmtPositionInfo(currentLineNumber))
                : Jimple.newExitMonitorStmt(
                    (Immediate) opr.stackOrValue(), new SimpleStmtPositionInfo(currentLineNumber));
        frame.setIn(opr);
        setStmt(insn, ts);
        opr.addUsageInStmt(ts);
      } else {
        frame.mergeIn(currentLineNumber, operandStack.pop());
      }
    } else {
      throw new UnsupportedOperationException("Unknown insn op: " + op);
    }
  }

  private void convertIntInsn(@Nonnull IntInsnNode insn) {
    int op = insn.getOpcode();
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr;
    if (out == null) {
      Value v;
      if (op == BIPUSH || op == SIPUSH) {
        v = IntConstant.getInstance(insn.operand);
      } else {
        // assert(op == NEWARRAY)
        Type type;
        switch (insn.operand) {
          case T_BOOLEAN:
            type = PrimitiveType.getBoolean();
            break;
          case T_CHAR:
            type = PrimitiveType.getChar();
            break;
          case T_FLOAT:
            type = PrimitiveType.getFloat();
            break;
          case T_DOUBLE:
            type = PrimitiveType.getDouble();
            break;
          case T_BYTE:
            type = PrimitiveType.getByte();
            break;
          case T_SHORT:
            type = PrimitiveType.getShort();
            break;
          case T_INT:
            type = PrimitiveType.getInt();
            break;
          case T_LONG:
            type = PrimitiveType.getLong();
            break;
          default:
            throw new UnsupportedOperationException("Unknown NEWARRAY type!");
        }
        Operand size = operandStack.popImmediate();
        JNewArrayExpr anew =
            JavaJimple.getInstance().newNewArrayExpr(type, (Immediate) size.stackOrValue());
        size.addUsageInExpr(anew);
        frame.setIn(size);
        v = anew;
      }
      opr = new Operand(insn, v, this);
      frame.setOut(opr);
    } else {
      opr = out[0];
      if (op == NEWARRAY) {
        frame.mergeIn(currentLineNumber, operandStack.pop());
      }
    }
    operandStack.push(opr);
  }

  private void convertJumpInsn(@Nonnull JumpInsnNode insn) {
    int op = insn.getOpcode();
    if (op == GOTO) {
      if (!insnToStmt.containsKey(insn)) {
        BranchingStmt gotoStmt = Jimple.newGotoStmt(new SimpleStmtPositionInfo(currentLineNumber));
        stmtsThatBranchToLabel.put(gotoStmt, insn.label);
        setStmt(insn, gotoStmt);
      }
      return;
    }
    /* must be ifX insn */
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    if (!insnToStmt.containsKey(insn)) {
      Operand val = operandStack.popImmediate();
      Immediate v = (Immediate) val.stackOrValue();
      AbstractConditionExpr cond;
      boolean isCmp = false;
      Operand val1 = null;

      if (op >= IF_ICMPEQ && op <= IF_ACMPNE) {
        isCmp = true;
        val1 = operandStack.popImmediate();
        Immediate v1 = (Immediate) val1.stackOrValue();
        switch (op) {
          case IF_ICMPEQ:
          case IF_ACMPEQ:
            cond = Jimple.newEqExpr(v1, v);
            break;
          case IF_ICMPNE:
          case IF_ACMPNE:
            cond = Jimple.newNeExpr(v1, v);
            break;
          case IF_ICMPLT:
            cond = Jimple.newLtExpr(v1, v);
            break;
          case IF_ICMPGE:
            cond = Jimple.newGeExpr(v1, v);
            break;
          case IF_ICMPGT:
            cond = Jimple.newGtExpr(v1, v);
            break;
          case IF_ICMPLE:
            cond = Jimple.newLeExpr(v1, v);
            break;
          default:
            throw new UnsupportedOperationException("Unknown if op: " + op);
        }
        val1.addUsageInExpr(cond);
        val.addUsageInExpr(cond);
        frame.setIn(val, val1);
      } else {
        switch (op) {
          case IFEQ:
            cond = Jimple.newEqExpr(v, IntConstant.getInstance(0));
            break;
          case IFNE:
            cond = Jimple.newNeExpr(v, IntConstant.getInstance(0));
            break;
          case IFLT:
            cond = Jimple.newLtExpr(v, IntConstant.getInstance(0));
            break;
          case IFGE:
            cond = Jimple.newGeExpr(v, IntConstant.getInstance(0));
            break;
          case IFGT:
            cond = Jimple.newGtExpr(v, IntConstant.getInstance(0));
            break;
          case IFLE:
            cond = Jimple.newLeExpr(v, IntConstant.getInstance(0));
            break;
          case IFNULL:
            cond = Jimple.newEqExpr(v, NullConstant.getInstance());
            break;
          case IFNONNULL:
            cond = Jimple.newNeExpr(v, NullConstant.getInstance());
            break;
          default:
            throw new UnsupportedOperationException("Unknown if op: " + op);
        }
        val.addUsageInExpr(cond);
        frame.setIn(val);
      }
      BranchingStmt ifStmt = Jimple.newIfStmt(cond, new SimpleStmtPositionInfo(currentLineNumber));
      stmtsThatBranchToLabel.put(ifStmt, insn.label);
      setStmt(insn, ifStmt);
      if (isCmp) {
        val1.addUsageInStmt(ifStmt);
      }

      val.addUsageInStmt(ifStmt);
    } else {
      if (op >= IF_ICMPEQ && op <= IF_ACMPNE) {
        frame.mergeIn(currentLineNumber, operandStack.pop(), operandStack.pop());
      } else {
        frame.mergeIn(currentLineNumber, operandStack.pop());
      }
    }
  }

  private void convertLdcInsn(@Nonnull LdcInsnNode insn) {
    Object val = insn.cst;
    boolean dword = val instanceof Long || val instanceof Double;
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr;
    if (out == null) {
      Value v = toSootValue(val);
      opr = new Operand(insn, v, this);
      frame.setOut(opr);
    } else {
      opr = out[0];
    }
    if (dword) {
      operandStack.pushDual(opr);
    } else {
      operandStack.push(opr);
    }
  }

  private Immediate toSootValue(@Nonnull Object val) throws UnsupportedOperationException {
    Immediate v;
    if (val instanceof Integer) {
      v = IntConstant.getInstance((Integer) val);
    } else if (val instanceof Float) {
      v = FloatConstant.getInstance((Float) val);
    } else if (val instanceof Long) {
      v = LongConstant.getInstance((Long) val);
    } else if (val instanceof Double) {
      v = DoubleConstant.getInstance((Double) val);
    } else if (val instanceof String) {
      v = JavaJimple.getInstance().newStringConstant(val.toString());
    } else if (val instanceof org.objectweb.asm.Type) {
      org.objectweb.asm.Type t = (org.objectweb.asm.Type) val;
      if (t.getSort() == org.objectweb.asm.Type.METHOD) {
        List paramTypes =
            AsmUtil.toJimpleSignatureDesc(((org.objectweb.asm.Type) val).getDescriptor());
        Type returnType = paramTypes.remove(paramTypes.size() - 1);
        v = JavaJimple.getInstance().newMethodType(paramTypes, returnType);
      } else {
        v =
            JavaJimple.getInstance()
                .newClassConstant(((org.objectweb.asm.Type) val).getDescriptor());
      }
    } else if (val instanceof Handle) {
      Handle h = (Handle) val;
      if (MethodHandle.isMethodRef(h.getTag())) {
        v =
            JavaJimple.getInstance()
                .newMethodHandle(toMethodSignature((Handle) val), ((Handle) val).getTag());
      } else {
        v =
            JavaJimple.getInstance()
                .newMethodHandle(toSootFieldRef((Handle) val), ((Handle) val).getTag());
      }
    } else {
      throw new UnsupportedOperationException("Unknown constant type: " + val.getClass());
    }
    return v;
  }

  private JFieldRef toSootFieldRef(Handle methodHandle) {
    String bsmClsName = AsmUtil.toQualifiedName(methodHandle.getOwner());
    JavaClassType bsmCls = javaIdentifierFactory.getClassType(bsmClsName);
    Type t = AsmUtil.toJimpleSignatureDesc(methodHandle.getDesc()).get(0);
    int kind = methodHandle.getTag();
    FieldSignature fieldSignature =
        javaIdentifierFactory.getFieldSignature(methodHandle.getName(), bsmCls, t);
    if (kind == MethodHandle.Kind.REF_GET_FIELD_STATIC.getValue()
        || kind == MethodHandle.Kind.REF_PUT_FIELD_STATIC.getValue()) {
      return Jimple.newStaticFieldRef(fieldSignature);
    } else {
      Operand base = operandStack.popLocal();
      return Jimple.newInstanceFieldRef((Local) base.stackOrValue(), fieldSignature);
    }
  }

  private MethodSignature toMethodSignature(Handle methodHandle) {
    String bsmClsName = AsmUtil.toQualifiedName(methodHandle.getOwner());
    JavaClassType bsmCls = javaIdentifierFactory.getClassType(bsmClsName);
    List bsmSigTypes = AsmUtil.toJimpleSignatureDesc(methodHandle.getDesc());
    Type returnType = bsmSigTypes.remove(bsmSigTypes.size() - 1);
    return JavaIdentifierFactory.getInstance()
        .getMethodSignature(bsmCls, methodHandle.getName(), returnType, bsmSigTypes);
  }

  private void convertLookupSwitchInsn(@Nonnull LookupSwitchInsnNode insn) {
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    if (insnToStmt.containsKey(insn)) {
      frame.mergeIn(currentLineNumber, operandStack.pop());
      return;
    }
    Operand key = operandStack.popImmediate();

    List keys = new ArrayList<>(insn.keys.size());
    for (Integer i : insn.keys) {
      keys.add(IntConstant.getInstance(i));
    }
    JSwitchStmt lookupSwitchStmt =
        Jimple.newLookupSwitchStmt(
            (Immediate) key.stackOrValue(), keys, new SimpleStmtPositionInfo(currentLineNumber));

    // uphold insertion order!
    stmtsThatBranchToLabel.putAll(lookupSwitchStmt, insn.labels);
    stmtsThatBranchToLabel.put(lookupSwitchStmt, insn.dflt);

    frame.setIn(key);
    setStmt(insn, lookupSwitchStmt);
    key.addUsageInStmt(lookupSwitchStmt);
  }

  private void convertMethodInsn(@Nonnull MethodInsnNode insn) {

    int op = insn.getOpcode();
    boolean isInstance = op != INVOKESTATIC;
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr;
    Type returnType;
    if (out == null) {
      String clsName = AsmUtil.toQualifiedName(insn.owner);
      if (clsName.charAt(0) == '[') {
        clsName = "java.lang.Object";
      }
      JavaClassType cls = javaIdentifierFactory.getClassType(AsmUtil.toQualifiedName(clsName));
      List sigTypes = AsmUtil.toJimpleSignatureDesc(insn.desc);
      returnType = sigTypes.remove((sigTypes.size() - 1));
      MethodSignature methodSignature =
          javaIdentifierFactory.getMethodSignature(cls, insn.name, returnType, sigTypes);
      int nrArgs = sigTypes.size();
      final Operand[] args;
      List argList = Collections.emptyList();
      if (!isInstance) {
        args = nrArgs == 0 ? null : new Operand[nrArgs];
        if (args != null) {
          argList = new ArrayList<>(nrArgs);
        }
      } else {
        args = new Operand[nrArgs + 1];
        if (nrArgs != 0) {
          argList = new ArrayList<>(nrArgs);
        }
      }
      while (nrArgs-- != 0) {
        args[nrArgs] = operandStack.popImmediate(sigTypes.get(nrArgs));
        argList.add((Immediate) args[nrArgs].stackOrValue());
      }
      if (argList.size() > 1) {
        Collections.reverse(argList);
      }
      if (isInstance) {
        args[args.length - 1] = operandStack.popLocal();
      }
      AbstractInvokeExpr invoke;
      if (!isInstance) {
        invoke = Jimple.newStaticInvokeExpr(methodSignature, argList);
      } else {
        Operand baseOperand = args[args.length - 1];
        Local base = (Local) baseOperand.stackOrValue();

        AbstractInstanceInvokeExpr iinvoke;
        switch (op) {
          case INVOKESPECIAL:
            iinvoke = Jimple.newSpecialInvokeExpr(base, methodSignature, argList);
            break;
          case INVOKEVIRTUAL:
            iinvoke = Jimple.newVirtualInvokeExpr(base, methodSignature, argList);
            break;
          case INVOKEINTERFACE:
            iinvoke = Jimple.newInterfaceInvokeExpr(base, methodSignature, argList);
            break;
          default:
            throw new UnsupportedOperationException("Unknown invoke op:" + op);
        }

        invoke = iinvoke;
        baseOperand.addUsageInExpr(invoke);
      }
      if (args != null) {
        for (int i = 0; i < sigTypes.size(); i++) {
          args[i].addUsageInExpr(invoke);
        }
        frame.setIn(args);
      }
      opr = new Operand(insn, invoke, this);
      frame.setOut(opr);
    } else {
      opr = out[0];
      AbstractInvokeExpr expr = (AbstractInvokeExpr) opr.value;
      List types = expr.getMethodSignature().getParameterTypes();
      Operand[] oprs;
      int nrArgs = types.size();
      boolean isInstanceMethod = expr instanceof AbstractInstanceInvokeExpr;
      if (!isInstanceMethod) {
        oprs = nrArgs == 0 ? null : new Operand[nrArgs];
      } else {
        oprs = new Operand[nrArgs + 1];
      }
      if (oprs != null) {
        while (nrArgs-- != 0) {
          oprs[nrArgs] = operandStack.pop(types.get(nrArgs));
        }
        if (isInstanceMethod) {
          oprs[oprs.length - 1] = operandStack.pop();
        }

        frame.mergeIn(currentLineNumber, oprs);
      }
      returnType = expr.getMethodSignature().getType();
    }
    if (AsmUtil.isDWord(returnType)) {
      operandStack.pushDual(opr);
    } else if (!(returnType == VoidType.getInstance())) {
      operandStack.push(opr);
    } else if (!insnToStmt.containsKey(insn)) {
      JInvokeStmt stmt =
          Jimple.newInvokeStmt(
              (AbstractInvokeExpr) opr.value, new SimpleStmtPositionInfo(currentLineNumber));
      setStmt(insn, stmt);
      opr.addUsageInStmt(stmt);
    }
    /*
     * assign all read ops in case the method modifies any of the fields
     */
    addReadOperandAssignments();
  }

  private void convertInvokeDynamicInsn(@Nonnull InvokeDynamicInsnNode insn) {
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr;
    Type returnType;
    if (out == null) {
      // convert info on bootstrap method
      MethodSignature bsmMethodRef = toMethodSignature(insn.bsm);
      List bsmMethodArgs = new ArrayList<>(insn.bsmArgs.length);
      for (Object bsmArg : insn.bsmArgs) {
        bsmMethodArgs.add(toSootValue(bsmArg));
      }

      // create ref to actual method
      JavaClassType bclass =
          javaIdentifierFactory.getClassType(JDynamicInvokeExpr.INVOKEDYNAMIC_DUMMY_CLASS_NAME);

      // Generate parameters & returnType & parameterTypes
      List types = AsmUtil.toJimpleSignatureDesc(insn.desc);
      int nrArgs = types.size() - 1;
      List parameterTypes = new ArrayList<>(nrArgs);
      List methodArgs = new ArrayList<>(nrArgs);

      Operand[] args = new Operand[nrArgs];
      // Beware: Call stack is FIFO, Jimple is linear

      for (int i = nrArgs - 1; i >= 0; i--) {
        parameterTypes.add(types.get(i));
        args[i] = operandStack.popImmediate(types.get(i));
        methodArgs.add((Immediate) args[i].stackOrValue());
      }
      if (methodArgs.size() > 1) {
        Collections.reverse(methodArgs); // Call stack is FIFO, Jimple is linear
        Collections.reverse(parameterTypes);
      }
      returnType = types.get(types.size() - 1);

      // we always model invokeDynamic method refs as static method references
      // of methods on the type SootClass.INVOKEDYNAMIC_DUMMY_CLASS_NAME
      MethodSignature methodSig =
          javaIdentifierFactory.getMethodSignature(bclass, insn.name, returnType, parameterTypes);

      JDynamicInvokeExpr indy =
          Jimple.newDynamicInvokeExpr(
              bsmMethodRef, bsmMethodArgs, methodSig, insn.bsm.getTag(), methodArgs);
      for (int i = 0; i < types.size() - 1; i++) {
        args[i].addUsageInExpr(indy);
      }

      frame.setIn(args);
      opr = new Operand(insn, indy, this);
      frame.setOut(opr);
    } else {
      opr = out[0];
      AbstractInvokeExpr expr = (AbstractInvokeExpr) opr.value;
      List types = expr.getMethodSignature().getParameterTypes();
      Operand[] oprs;
      int nrArgs = types.size() - 1;
      final boolean isStaticInvokeExpr = expr instanceof JStaticInvokeExpr;
      if (isStaticInvokeExpr) {
        oprs = (nrArgs <= 0) ? null : new Operand[nrArgs];
      } else {
        oprs = (nrArgs < 0) ? null : new Operand[nrArgs + 1];
      }
      if (oprs != null) {
        while (nrArgs-- > 0) {
          oprs[nrArgs] = operandStack.pop(types.get(nrArgs));
        }
        if (!isStaticInvokeExpr) {
          oprs[oprs.length - 1] = operandStack.pop();
        }
        frame.mergeIn(currentLineNumber, oprs);
      }
      returnType = expr.getType();
    }
    if (AsmUtil.isDWord(returnType)) {
      operandStack.pushDual(opr);
    } else if (!(returnType instanceof VoidType)) {
      operandStack.push(opr);
    } else if (!insnToStmt.containsKey(insn)) {
      JInvokeStmt stmt =
          Jimple.newInvokeStmt(
              (AbstractInvokeExpr) opr.value, new SimpleStmtPositionInfo(currentLineNumber));
      setStmt(insn, stmt);
      opr.addUsageInStmt(stmt);
    }
    /*
     * assign all read ops in case the method modifies any of the fields
     */
    addReadOperandAssignments();
  }

  // private @Nonnull MethodRef toSootMethodRef(@Nonnull Handle methodHandle) {
  // String bsmClsName = AsmUtil.toQualifiedName(methodHandle.getOwner());
  // JavaClassType bsmCls = view.getIdentifierFactory().getClassSignature(bsmClsName);
  // List bsmSigTypes = AsmUtil.toJimpleSignatureDesc(methodHandle.getDesc(), view);
  // Type returnType = bsmSigTypes.remove(bsmSigTypes.size() - 1);
  // MethodSignature methodSignature =
  // view.getIdentifierFactory().getMethodSignature(methodHandle.getName(), bsmCls,
  // returnType, bsmSigTypes);
  // boolean isStatic = methodHandle.getTag() == MethodHandle.Kind.REF_INVOKE_STATIC.getValue();
  // return Jimple.createSymbolicMethodRef(methodSignature, isStatic);
  // }
  //
  // private JFieldRef toSootFieldRef(Handle methodHandle) {
  // String bsmClsName = AsmUtil.toQualifiedName(methodHandle.getOwner());
  // JavaClassType bsmCls = view.getIdentifierFactory().getClassSignature(bsmClsName);
  //
  // Type t = AsmUtil.toJimpleSignatureDesc(methodHandle.getDesc(), view).get(0);
  // int kind = methodHandle.getTag();
  // boolean isStatic = kind == MethodHandle.Kind.REF_GET_FIELD_STATIC.getValue()
  // || kind == MethodHandle.Kind.REF_PUT_FIELD_STATIC.getValue();
  //
  // FieldSignature fieldSignature =
  // view.getIdentifierFactory().getFieldSignature(methodHandle.getName(), bsmCls, t);
  // return Jimple.createSymbolicFieldRef(fieldSignature, isStatic);
  // }

  private void convertMultiANewArrayInsn(@Nonnull MultiANewArrayInsnNode insn) {
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr;
    if (out == null) {
      ArrayType t = (ArrayType) AsmUtil.toJimpleType(insn.desc);
      int dims = insn.dims;
      Operand[] sizes = new Operand[dims];
      Immediate[] sizeVals = new Immediate[dims];
      while (dims-- != 0) {
        sizes[dims] = operandStack.popImmediate();
        sizeVals[dims] = (Immediate) sizes[dims].stackOrValue();
      }
      JNewMultiArrayExpr nm = Jimple.newNewMultiArrayExpr(t, Arrays.asList(sizeVals));
      for (int i = 0; i < dims; i++) {
        sizes[i].addUsageInExpr(nm);
      }
      frame.setIn(sizes);
      opr = new Operand(insn, nm, this);
      frame.setOut(opr);
    } else {
      opr = out[0];
      int dims = insn.dims;
      Operand[] sizes = new Operand[dims];
      while (dims-- != 0) {
        sizes[dims] = operandStack.pop();
      }
      frame.mergeIn(currentLineNumber, sizes);
    }
    operandStack.push(opr);
  }

  private void convertTableSwitchInsn(@Nonnull TableSwitchInsnNode insn) {
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    if (insnToStmt.containsKey(insn)) {
      frame.mergeIn(currentLineNumber, operandStack.pop());
      return;
    }
    Operand key = operandStack.popImmediate();
    JSwitchStmt tableSwitchStmt =
        Jimple.newTableSwitchStmt(
            (Immediate) key.stackOrValue(),
            insn.min,
            insn.max,
            new SimpleStmtPositionInfo(currentLineNumber));

    // uphold insertion order!
    stmtsThatBranchToLabel.putAll(tableSwitchStmt, insn.labels);
    stmtsThatBranchToLabel.put(tableSwitchStmt, insn.dflt);

    frame.setIn(key);
    setStmt(insn, tableSwitchStmt);
    key.addUsageInStmt(tableSwitchStmt);
  }

  private void convertTypeInsn(@Nonnull TypeInsnNode insn) {
    int op = insn.getOpcode();
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr;
    if (out == null) {
      Expr val;
      if (op == NEW) {
        val = Jimple.newNewExpr(AsmUtil.toJimpleClassType(insn.desc));
      } else {
        Operand op1 = operandStack.popImmediate();
        Value v1 = op1.stackOrValue();
        switch (op) {
          case ANEWARRAY:
            {
              JNewArrayExpr expr =
                  JavaJimple.getInstance()
                      .newNewArrayExpr(AsmUtil.arrayTypetoJimpleType(insn.desc), (Immediate) v1);
              val = expr;
              op1.addUsageInExpr(expr);
              break;
            }
          case CHECKCAST:
            {
              JCastExpr expr =
                  Jimple.newCastExpr((Immediate) v1, AsmUtil.toJimpleClassType(insn.desc));
              val = expr;
              op1.addUsageInExpr(expr);
              break;
            }
          case INSTANCEOF:
            {
              JInstanceOfExpr expr =
                  Jimple.newInstanceOfExpr((Immediate) v1, AsmUtil.toJimpleClassType(insn.desc));
              val = expr;
              op1.addUsageInExpr(expr);
              break;
            }
          default:
            throw new UnsupportedOperationException("Unknown type op: " + op);
        }
        op1.addUsageInExpr(val);
        frame.setIn(op1);
      }
      opr = new Operand(insn, val, this);
      frame.setOut(opr);
    } else {
      opr = out[0];
      if (op != NEW) {
        frame.mergeIn(currentLineNumber, operandStack.pop());
      }
    }
    operandStack.push(opr);
  }

  private void convertVarLoadInsn(@Nonnull VarInsnNode insn) {
    int op = insn.getOpcode();
    boolean dword = op == LLOAD || op == DLOAD;
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand[] out = frame.getOut();
    Operand opr;
    if (out == null) {
      opr = new Operand(insn, getOrCreateLocal(insn.var), this);
      frame.setOut(opr);
    } else {
      opr = out[0];
    }
    if (dword) {
      operandStack.pushDual(opr);
    } else {
      operandStack.push(opr);
    }
  }

  private void convertVarStoreInsn(@Nonnull VarInsnNode insn) {
    int op = insn.getOpcode();
    boolean dword = op == LSTORE || op == DSTORE;
    StackFrame frame = operandStack.getOrCreateStackframe(insn);
    Operand opr = dword ? operandStack.popDual() : operandStack.pop();
    Local local = getOrCreateLocal(insn.var);
    if (!insnToStmt.containsKey(insn)) {
      AbstractDefinitionStmt as =
          Jimple.newAssignStmt(
              local, opr.stackOrValue(), new SimpleStmtPositionInfo(currentLineNumber));
      frame.setIn(opr);
      setStmt(insn, as);
      opr.addUsageInStmt(as);
    } else {
      frame.mergeIn(currentLineNumber, opr);
    }
    addReadOperandAssignments(local);
  }

  private void convertVarInsn(@Nonnull VarInsnNode insn) {
    int op = insn.getOpcode();
    if (op >= ILOAD && op <= ALOAD) {
      convertVarLoadInsn(insn);
    } else if (op >= ISTORE && op <= ASTORE) {
      convertVarStoreInsn(insn);
    } else if (op == RET) {
      /* we handle it, even though it should be removed */
      if (!insnToStmt.containsKey(insn)) {
        setStmt(
            insn,
            Jimple.newRetStmt(
                getOrCreateLocal(insn.var), new SimpleStmtPositionInfo(currentLineNumber)));
      }
    } else {
      throw new UnsupportedOperationException("Unknown var op: " + op);
    }
  }

  private void convertLabel(@Nonnull LabelNode ln) {
    // only do it for Labels which are referring to a traphandler
    if (!trapHandler.containsKey(ln)) {
      return;
    }

    // We create a nop statement as a placeholder so that we can jump
    // somewhere from the real exception handler in case this is inline
    // code
    if (inlineExceptionLabels.contains(ln)) {
      if (!insnToStmt.containsKey(ln)) {
        JNopStmt nop = Jimple.newNopStmt(new SimpleStmtPositionInfo(currentLineNumber));
        setStmt(ln, nop);
      }
      return;
    }

    StackFrame frame = operandStack.getOrCreateStackframe(ln);
    Operand[] out = frame.getOut();
    Operand opr;
    if (out == null) {
      JCaughtExceptionRef ref = JavaJimple.getInstance().newCaughtExceptionRef();
      Local stack = newStackLocal();
      AbstractDefinitionStmt as =
          Jimple.newIdentityStmt(stack, ref, new SimpleStmtPositionInfo(currentLineNumber));
      opr = new Operand(ln, ref, this);
      opr.stackLocal = stack;
      frame.setOut(opr);
      setStmt(ln, as);
      opr.addUsageInStmt(as);
    } else {
      opr = out[0];
    }
    operandStack.push(opr);
  }

  private void convertLine(@Nonnull LineNumberNode ln) {
    currentLineNumber = ln.line;
    if (currentLineNumber > maxLineNumber) {
      maxLineNumber = currentLineNumber;
    }
  }

  /* Conversion */
  private void addEdges(
      @Nonnull Table edges,
      @Nonnull ArrayDeque conversionWorklist,
      @Nonnull AbstractInsnNode branchingInsn, /*  branching instruction node */
      @Nonnull
          AbstractInsnNode
              tgt, /* "default" targets i.e. LabelNode or fallsthrough "target" of if  */
      @Nonnull List tgts /* other branch target(s) */) {
    Operand[] stackss = operandStack.getStack().toArray(new Operand[0]);
    /* iterate over possible following/successing instructions which is: combined(tgt, tgts) */
    int i = 0;
    int lastIdx = tgts.size();
    outer_loop:
    do {
      BranchedInsnInfo edge = edges.get(branchingInsn, tgt);
      if (edge == null) {
        // [ms] check why this edge could be already there
        edge = new BranchedInsnInfo(tgt, operandStack.getStack());
        edge.addToPrevStack(stackss);
        edges.put(branchingInsn, tgt, edge);
        conversionWorklist.add(edge);
        continue;
      }
      for (List stackTemp : edge.getOperandStacks()) {
        if (stackTemp.size() == stackss.length) {
          int j = 0;
          while (j < stackss.length && stackTemp.get(j).equivTo(stackss[j])) {
            j++;
          }
          if (j == stackss.length) {
            continue outer_loop;
          }
        }
      }
      final LinkedList prevStacks = edge.getPrevStacks();
      for (Operand[] ps : prevStacks) {
        if (Arrays.equals(ps, stackss)) {
          continue outer_loop;
        }
      }
      edge.addOperandStack(operandStack.getStack());
      edge.addToPrevStack(stackss);
      conversionWorklist.add(edge);
    } while (i < lastIdx && (tgt = tgts.get(i++)) != null);
  }

  private void convert() {
    ArrayDeque worklist = new ArrayDeque<>();

    indexInlineExceptionHandlers();

    // If this label is reachable through an exception and through normal
    // code, we have to split the exceptional case (with the exception on
    // the stack) from the normal fall-through case without anything on the
    // stack.
    for (LabelNode handlerNode : trapHandler.keySet()) {
      if (inlineExceptionLabels.contains(handlerNode)) {
        // Catch the exception
        JCaughtExceptionRef ref = JavaJimple.getInstance().newCaughtExceptionRef();
        Local local = newStackLocal();
        AbstractDefinitionStmt as =
            Jimple.newIdentityStmt(local, ref, new SimpleStmtPositionInfo(currentLineNumber));

        Operand opr = new Operand(handlerNode, ref, this);
        opr.stackLocal = local;

        worklist.add(new BranchedInsnInfo(handlerNode, Collections.singletonList(opr)));

        // Save the statements
        inlineExceptionHandlers.put(handlerNode, as);
      } else {
        worklist.add(new BranchedInsnInfo(handlerNode, new ArrayList<>()));
      }
    }
    worklist.add(new BranchedInsnInfo(instructions.getFirst(), Collections.emptyList()));
    Table edges = HashBasedTable.create(1, 1);

    do {
      BranchedInsnInfo edge = worklist.pollLast();
      AbstractInsnNode insn = edge.getInsn();
      operandStack.setOperandStack(
          new ArrayList<>(edge.getOperandStacks().get(edge.getOperandStacks().size() - 1)));
      do {
        int type = insn.getType();
        if (type == FIELD_INSN) {
          convertFieldInsn((FieldInsnNode) insn);
        } else if (type == IINC_INSN) {
          convertIincInsn((IincInsnNode) insn);
        } else if (type == INSN) {
          convertInsn((InsnNode) insn);
          int op = insn.getOpcode();
          if ((op >= IRETURN && op <= RETURN) || op == ATHROW) {
            break;
          }
        } else if (type == INT_INSN) {
          convertIntInsn((IntInsnNode) insn);
        } else if (type == LDC_INSN) {
          convertLdcInsn((LdcInsnNode) insn);
        } else if (type == JUMP_INSN) {
          JumpInsnNode jmp = (JumpInsnNode) insn;
          convertJumpInsn(jmp);
          int op = jmp.getOpcode();
          if (op == JSR) {
            throw new UnsupportedOperationException("JSR!");
          }
          if (op != GOTO) {
            /* ifX opcode, i.e. two successors */
            AbstractInsnNode next = insn.getNext();
            addEdges(edges, worklist, insn, next, Collections.singletonList(jmp.label));
          } else {
            addEdges(edges, worklist, insn, jmp.label, Collections.emptyList());
          }
          break;
        } else if (type == LOOKUPSWITCH_INSN) {
          LookupSwitchInsnNode swtch = (LookupSwitchInsnNode) insn;
          convertLookupSwitchInsn(swtch);
          LabelNode dflt = swtch.dflt;
          addEdges(edges, worklist, insn, dflt, swtch.labels);
          break;
        } else if (type == METHOD_INSN) {
          convertMethodInsn((MethodInsnNode) insn);
        } else if (type == INVOKE_DYNAMIC_INSN) {
          convertInvokeDynamicInsn((InvokeDynamicInsnNode) insn);
        } else if (type == MULTIANEWARRAY_INSN) {
          convertMultiANewArrayInsn((MultiANewArrayInsnNode) insn);
        } else if (type == TABLESWITCH_INSN) {
          TableSwitchInsnNode swtch = (TableSwitchInsnNode) insn;
          convertTableSwitchInsn(swtch);
          LabelNode dflt = swtch.dflt;
          addEdges(edges, worklist, insn, dflt, swtch.labels);
          break;
        } else if (type == TYPE_INSN) {
          convertTypeInsn((TypeInsnNode) insn);
        } else if (type == VAR_INSN) {
          if (insn.getOpcode() == RET) {
            throw new UnsupportedOperationException("RET!");
          }
          convertVarInsn((VarInsnNode) insn);
        } else if (type == LABEL) {
          convertLabel((LabelNode) insn);
        } else if (type == LINE) {
          convertLine((LineNumberNode) insn);
        } else
        //noinspection StatementWithEmptyBody
        if (type == FRAME) {
          // we can ignore it
        } else {
          throw new RuntimeException("Unknown instruction type: " + type);
        }
      } while ((insn = insn.getNext()) != null);
    } while (!worklist.isEmpty());
  }

  // inline exceptionhandler := exceptionhandler thats reachable through unexceptional "normal" flow
  // and exceptional flow
  private void indexInlineExceptionHandlers() {
    final Set handlerLabelNodes = trapHandler.keySet();

    if (handlerLabelNodes.isEmpty()) {
      // my job is done here
      return;
    }

    for (AbstractInsnNode node : instructions) {
      if (node instanceof JumpInsnNode) {
        final LabelNode handlerLabel = ((JumpInsnNode) node).label;
        if (handlerLabelNodes.contains(handlerLabel)) {
          inlineExceptionLabels.add(handlerLabel);
        }
      } else if (node instanceof LookupSwitchInsnNode) {

        final LookupSwitchInsnNode lookupSwitchInsnNode = (LookupSwitchInsnNode) node;
        if (handlerLabelNodes.contains(lookupSwitchInsnNode.dflt)) {
          inlineExceptionLabels.add(lookupSwitchInsnNode.dflt);
          continue;
        }
        for (LabelNode l : lookupSwitchInsnNode.labels) {
          if (handlerLabelNodes.contains(l)) {
            inlineExceptionLabels.add(l);
            break;
          }
        }
      } else if (node instanceof TableSwitchInsnNode) {

        final TableSwitchInsnNode tableSwitchInsnNode = (TableSwitchInsnNode) node;
        if (handlerLabelNodes.contains(tableSwitchInsnNode.dflt)) {
          inlineExceptionLabels.add(tableSwitchInsnNode.dflt);
          continue;
        }
        for (LabelNode l : tableSwitchInsnNode.labels) {
          if (handlerLabelNodes.contains(l)) {
            inlineExceptionLabels.add(l);
            break;
          }
        }
      }
    }
  }

  public static LineNumberNode findLineInfo(
      @Nonnull InsnList insnList, @Nonnull AbstractInsnNode insnNode) {
    int idx = insnList.indexOf(insnNode);
    if (idx < 0) {
      return null;
    }

    // Get index of labels and insnNode within method
    ListIterator insnIt = insnList.iterator(idx);
    while (insnIt.hasPrevious()) {
      AbstractInsnNode node = insnIt.previous();

      if (node instanceof LineNumberNode) {
        return (LineNumberNode) node;
      }
    }
    return null;
  }

  @Nonnull
  private StmtPositionInfo getFirstLineOfMethod() {
    for (AbstractInsnNode node : instructions) {
      if (node instanceof LineNumberNode) {
        return new SimpleStmtPositionInfo(((LineNumberNode) node).line);
      }
    }
    return StmtPositionInfo.createNoStmtPositionInfo();
  }

  @Nonnull
  private List buildPreambleLocals(Body.BodyBuilder bodyBuilder) {

    List preambleBlock = new ArrayList<>();
    MethodSignature methodSignature = lazyMethodSignature.get();
    final StmtPositionInfo methodPosInfo = getFirstLineOfMethod();

    int localIdx = 0;
    // create this Local if necessary ( i.e. not static )
    if (!bodyBuilder.getModifiers().contains(Modifier.STATIC)) {
      JavaLocal thisLocal = JavaJimple.newLocal(determineLocalName(localIdx), declaringClass);
      locals.set(localIdx++, thisLocal);
      final JIdentityStmt stmt =
          Jimple.newIdentityStmt(thisLocal, Jimple.newThisRef(declaringClass), methodPosInfo);
      preambleBlock.add(stmt);
    }
    // add parameter Locals
    for (int i = 0; i < methodSignature.getParameterTypes().size(); i++) {
      Type parameterType = methodSignature.getParameterTypes().get(i);
      // [BH] parameterlocals do not exist yet -> create with annotation
      JavaLocal local =
          JavaJimple.newLocal(
              determineLocalName(localIdx),
              parameterType,
              AsmUtil.createAnnotationUsage(
                  invisibleParameterAnnotations == null ? null : invisibleParameterAnnotations[i]));
      locals.set(localIdx, local);

      final JIdentityStmt stmt =
          Jimple.newIdentityStmt(local, Jimple.newParameterRef(parameterType, i), methodPosInfo);
      preambleBlock.add(stmt);

      // see https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.6.1
      if (AsmUtil.isDWord(parameterType)) {
        localIdx += 2;
      } else {
        localIdx++;
      }
    }

    return preambleBlock;
  }

  private List buildTraps() {
    List traps = new ArrayList<>();
    for (TryCatchBlockNode trycatch : tryCatchBlocks) {
      Stmt handler = trapHandler.get(trycatch.handler);
      if (handler == null) {
        throw new IllegalStateException(
            "Label for the TrapHandler "
                + trycatch.handler
                + " has no associated Stmt to jump to.");
      }

      // FIXME: [ms] create java.lang.Throwable for modules i.e. with ModuleSignature if modules are
      // used..
      final String exceptionName =
          (trycatch.type != null) ? AsmUtil.toQualifiedName(trycatch.type) : "java.lang.Throwable";
      JavaClassType exceptionType = javaIdentifierFactory.getClassType(exceptionName);

      Trap trap =
          Jimple.newTrap(
              exceptionType,
              labelsToStmt.get(trycatch.start),
              labelsToStmt.get(trycatch.end),
              handler);
      traps.add(trap);
    }
    return traps;
  }

  /** all Instructions are converted. Now they can be arranged into the StmtGraph. */
  private void arrangeStmts(
      MutableBlockStmtGraph graph, List stmtList, Body.BodyBuilder builder) {

    AbstractInsnNode insn = instructions.getFirst();
    ArrayDeque danglingLabel = new ArrayDeque<>();

    Map currentTraps = new HashMap<>();

    // (n, n+1) := (from, to)
    // List connectBlocks = new ArrayList<>();
    // every LabelNode denotes a border of a Block

    do {

      // assign Stmt associated with the current instruction. see
      // https://asm.ow2.io/javadoc/org/objectweb/asm/Label.html
      // there can be multiple labels assigned to the following stmt! (and other AbstractNodes in
      // between!)
      final boolean isLabelNode = insn instanceof LabelNode;
      if (isLabelNode) {
        // Save the label to assign it then to the next real Stmt
        danglingLabel.add((LabelNode) insn);
      }

      Stmt stmt = insnToStmt.get(insn);
      if (stmt == null) {
        continue;
      }

      if (!danglingLabel.isEmpty()) {
        // there is (at least) a LabelNode ->
        // associate collected labels from danglingLabel with the following stmt
        Stmt targetStmt =
            stmt instanceof StmtContainer ? ((StmtContainer) stmt).getFirstStmt() : stmt;
        danglingLabel.forEach(l -> labelsToStmt.put(l, targetStmt));
        if (isLabelNode) {
          // If the targetStmt is an exception handler, register the starting Stmt for it
          JIdentityStmt identityRef = findIdentityRefInStmtContainer(stmt);
          if (identityRef != null && identityRef.getRightOp() instanceof JCaughtExceptionRef) {
            danglingLabel.forEach(label -> trapHandler.put(label, identityRef));
          }
        }
        danglingLabel.clear();
      }

      emitStmt(stmt, stmtList);

    } while ((insn = insn.getNext()) != null);

    Map> branchingMap = new HashMap<>();
    for (Map.Entry> entry :
        stmtsThatBranchToLabel.asMap().entrySet()) {
      final BranchingStmt fromStmt = entry.getKey();
      List targets = new ArrayList<>();
      for (LabelNode labelNode : entry.getValue()) {
        final Stmt targetStmt = labelsToStmt.get(labelNode);
        if (targetStmt == null) {
          throw new IllegalStateException(
              "targetStmt not found for fromStmt"
                  + fromStmt
                  + " "
                  + entry.getValue()
                  + " in method "
                  + lazyMethodSignature.get());
        }
        targets.add(targetStmt);
      }
      branchingMap.put(fromStmt, targets);
    }

    final List traps = buildTraps();
    // TODO: performance: [ms] we already know Blocks borders from the label information -> use
    // addBlocks+collect trap data and connect blocks afterwards via branching information +
    // collected fallsthroughBlock information
    graph.initializeWith(stmtList, branchingMap, traps);

    // Emit the inline exception handler blocks i.e. those that are reachable without exceptional
    // flow
    // FIXME:[ms] the following code seems odd.. we need a testcase to test inlineexceptionhandling!
    for (Entry> entry :
        inlineExceptionHandlers.entrySet()) {

      AbstractDefinitionStmt handlerStmt = entry.getValue();
      emitStmt(handlerStmt, stmtList);
      trapHandler.put(entry.getKey(), handlerStmt);
      // TODO: update handlerStmts positioninfo!

      // jump back to the original implementation
      JGotoStmt gotoStmt = Jimple.newGotoStmt(handlerStmt.getPositionInfo());
      stmtList.add(gotoStmt);

      // add stmtList to graph
      graph.addBlock(stmtList, currentTraps);
      stmtList.clear();

      // connect tail of stmtList with its target
      Stmt targetStmt = insnToStmt.get(entry.getKey());
      graph.putEdge(gotoStmt, targetStmt);
    }
  }

  private void emitStmt(@Nonnull Stmt handlerStmt, @Nonnull List block) {
    if (handlerStmt instanceof StmtContainer) {
      block.addAll(((StmtContainer) handlerStmt).getStmts());
    } else {
      block.add(handlerStmt);
    }
  }

  @Nullable
  private JIdentityStmt findIdentityRefInStmtContainer(@Nonnull Stmt stmt) {
    if (stmt instanceof JIdentityStmt) {
      return (JIdentityStmt) stmt;
    } else if (stmt instanceof StmtContainer) {
      for (Stmt stmtEntry : ((StmtContainer) stmt).getStmts()) {
        if (stmtEntry instanceof JIdentityStmt) {
          return (JIdentityStmt) stmtEntry;
        }
      }
    }
    return null;
  }

  /**
   * Returns the latest version of a statement that is used in this method source, or null if the
   * statement is not used
   *
   * @param oldStmt the Stmt which we want to check if there is a newer Stmt replacing it
   * @return the most recent version of a Stmt or itself if there is no newer version. Otherwise
   *     returns null.
   */
  @Nonnull
  Stmt getLatestVersionOfStmt(@Nonnull Stmt oldStmt) {
    while (true) {
      final Stmt replacedVersion = replacedStmt.get(oldStmt);
      if (replacedVersion != null) {
        oldStmt = replacedVersion;
      } else {
        return oldStmt;
      }
    }
  }

  void replaceStmt(@Nonnull Stmt oldStmt, Stmt newStmt) {
    AbstractInsnNode key = null;

    // TODO: [ms] bit expensive and called a lot? -> find better solution!
    for (Entry entry : insnToStmt.entrySet()) {
      if (Objects.equals(oldStmt, entry.getValue())) {
        key = entry.getKey();
      }
    }

    if (key == null) {
      // throw new IllegalStateException("Could not replace value in insn map because oldStmt " +
      // oldStmt + " it is absent");
      return;
    }

    if (newStmt == null) {
      insnToStmt.remove(key);
      return;
    }

    insnToStmt.put(key, newStmt);
    replacedStmt.put(oldStmt, newStmt);

    if (oldStmt instanceof BranchingStmt) {
      List branchLabels = stmtsThatBranchToLabel.get((BranchingStmt) oldStmt);
      if (branchLabels != null) {
        branchLabels.forEach(bl -> stmtsThatBranchToLabel.put((BranchingStmt) newStmt, bl));
        stmtsThatBranchToLabel.removeAll(oldStmt);
      }
    }
  }

  /**
   * * returns all stmts that use this expr
   *
   * @param expr which is used to filter associated Stmts
   */
  public Stream getStmtsThatUse(@Nonnull Expr expr) {
    Stream currentUses =
        insnToStmt.values().stream()
            .flatMap(
                stmt ->
                    stmt instanceof StmtContainer
                        ? ((StmtContainer) stmt).getStmts().stream()
                        : Stream.of(stmt))
            .filter(stmt -> stmt.getUses().contains(expr));

    Stream oldMappedUses =
        replacedStmt.entrySet().stream()
            .filter(stmt -> stmt.getKey().getUses().contains(expr))
            .map(stmt -> getLatestVersionOfStmt(stmt.getValue()));

    return Stream.concat(currentUses, oldMappedUses);
  }
}