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.*;

import com.google.common.base.Suppliers;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Table;
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.*;
import sootup.core.jimple.common.expr.*;
import sootup.core.jimple.common.ref.*;
import sootup.core.jimple.common.stmt.*;
import sootup.core.jimple.javabytecode.stmt.JSwitchStmt;
import sootup.core.model.Body;
import sootup.core.model.FullPosition;
import sootup.core.model.MethodModifier;
import sootup.core.model.Position;
import sootup.core.signatures.FieldSignature;
import sootup.core.signatures.MethodSignature;
import sootup.core.transform.BodyInterceptor;
import sootup.core.types.*;
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;

  /** Labels at which a trap handler range (try block) begins */
  private final Map startTrapHandler = new HashMap<>();

  /** Labels at which a trap handler range (try block) ends */
  private final Map endTrapHandler = new HashMap<>();

  /** Keeps track of all trap handlers that are active at the current instruction */
  Set activeTrapHandlers = new HashSet<>();

  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<>();

  private final JavaIdentifierFactory identifierFactory;
  private final Supplier lazyMethodSignature;

  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;

    identifierFactory = (JavaIdentifierFactory) view.getIdentifierFactory();
    lazyMethodSignature =
        Suppliers.memoize(
            () -> {
              List sigTypes = AsmUtil.toJimpleSignatureDesc(desc);
              Type retType = sigTypes.remove(sigTypes.size() - 1);

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

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

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

  StmtPositionInfo getStmtPositionInfo() {
    return currentLineNumber > 0
        ? new SimpleStmtPositionInfo(currentLineNumber)
        : StmtPositionInfo.getNoStmtPositionInfo();
  }

  @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);
      startTrapHandler.put(tc.start, tc);
      endTrapHandler.put(tc.end, tc);
    }

    /* build body (add stmts, locals, traps, etc.) */
    final MutableBlockStmtGraph graph = new MutableBlockStmtGraph();
    Body.BodyBuilder bodyBuilder = Body.builder(graph);
    bodyBuilder.setModifiers(AsmUtil.getMethodModifiers(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
    final Stmt startingStmt = graph.getStartingStmt();
    if (!graph.getNodes().isEmpty() && startingStmt != null) {
      Position firstStmtPos = startingStmt.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) {
    insnToStmt.put(insn, stmt);
  }

  @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;
      }

      operand.emitStatement();
    }
  }

  private void convertGetFieldInsn(@Nonnull FieldInsnNode insn) {
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    Type type = AsmUtil.toJimpleType(insn.desc);
    JavaClassType declClass = identifierFactory.getClassType(AsmUtil.toQualifiedName(insn.owner));
    FieldSignature ref;
    JFieldRef val;
    if (insn.getOpcode() == GETSTATIC) {
      ref = identifierFactory.getFieldSignature(insn.name, declClass, type);
      val = Jimple.newStaticFieldRef(ref);
    } else {
      Operand base = operandStack.pop();
      merging.mergeInputs(base);
      ref = identifierFactory.getFieldSignature(insn.name, declClass, type);
      val = Jimple.newInstanceFieldRef(base.toLocal(), ref);
    }

    Operand opr = new Operand(insn, val, this);
    merging.mergeOutput(opr);
    operandStack.push(type, opr);
  }

  private void convertPutFieldInsn(@Nonnull FieldInsnNode insn) {
    boolean notInstance = insn.getOpcode() != PUTFIELD;
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    JavaClassType declClass = identifierFactory.getClassType(AsmUtil.toQualifiedName(insn.owner));
    Type type = AsmUtil.toJimpleType(insn.desc);

    Operand rvalue = operandStack.pop(type);
    JFieldRef val;
    FieldSignature ref;
    if (notInstance) {
      merging.mergeInputs(rvalue);
      ref = identifierFactory.getFieldSignature(insn.name, declClass, type);
      val = Jimple.newStaticFieldRef(ref);
    } else {
      Operand base = operandStack.pop();
      merging.mergeInputs(rvalue, base);
      ref = identifierFactory.getFieldSignature(insn.name, declClass, type);
      val = Jimple.newInstanceFieldRef(base.toLocal(), ref);
    }
    JAssignStmt as = Jimple.newAssignStmt(val, rvalue.toImmediate(), getStmtPositionInfo());
    setStmt(insn, as);
    /*
     * 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);
    JAddExpr add = Jimple.newAddExpr(local, IntConstant.getInstance(insn.incr));
    setStmt(insn, Jimple.newAssignStmt(local, add, getStmtPositionInfo()));
  }

  private void convertConstInsn(@Nonnull InsnNode insn) {
    int op = insn.getOpcode();
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    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);
    }
    Operand opr = new Operand(insn, v, this);
    merging.mergeOutput(opr);
    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) {
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    Operand indx = operandStack.pop();
    Operand base = operandStack.pop();
    merging.mergeInputs(indx, base);
    JArrayRef ar = JavaJimple.getInstance().newArrayRef(base.toLocal(), indx.toImmediate());
    Operand opr = new Operand(insn, ar, this);
    merging.mergeOutput(opr);
    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;
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    Operand valueOp = dword ? operandStack.popDual() : operandStack.pop();
    Operand indexOp = operandStack.pop();
    Operand baseOp = operandStack.pop();
    merging.mergeInputs(valueOp, indexOp, baseOp);
    JArrayRef ar = JavaJimple.getInstance().newArrayRef(baseOp.toLocal(), indexOp.toImmediate());
    JAssignStmt as = Jimple.newAssignStmt(ar, valueOp.toImmediate(), getStmtPositionInfo());
    setStmt(insn, as);
  }

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

    // Get the top stack value which we need in either case
    Operand dupd = operandStack.pop();
    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.pop();
      }
    }

    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.pop();
      operandStack.push(dupd);
      operandStack.push(o2);
      operandStack.push(dupd);
    } else if (op == DUP_X2) {
      // value3, value2, value1 -> value1, value3, value2, value1
      Operand o2 = operandStack.pop();
      // pops either the `Operand.DWORD_DUMMY` or the third value
      Operand o3 = operandStack.pop();
      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.pop();
      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.pop();
      Operand o2h = operandStack.pop();
      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;
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    Operand op2 =
        (dword && op != LSHL && op != LSHR && op != LUSHR)
            ? operandStack.popDual()
            : operandStack.pop();
    Operand op1 = dword ? operandStack.popDual() : operandStack.pop();
    merging.mergeInputs(op2, op1);
    Immediate v1 = op1.toImmediate();
    Immediate v2 = op2.toImmediate();
    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);
    }

    Operand opr = new Operand(insn, binop, this);
    merging.mergeOutput(opr);

    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;
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    Operand op1 = dword ? operandStack.popDual() : operandStack.pop();
    merging.mergeInputs(op1);
    AbstractUnopExpr unop;
    if (op >= INEG && op <= DNEG) {
      unop = Jimple.newNegExpr(op1.toImmediate());
    } else if (op == ARRAYLENGTH) {
      unop = Jimple.newLengthExpr(op1.toImmediate());
    } else {
      throw new UnsupportedOperationException("Unknown unop: " + op);
    }
    Operand opr = new Operand(insn, unop, this);
    merging.mergeOutput(opr);
    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;
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    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.popDual() : operandStack.pop();
    merging.mergeInputs(val);
    JCastExpr cast = Jimple.newCastExpr(val.toImmediate(), totype);
    Operand opr = new Operand(insn, cast, this);
    merging.mergeOutput(opr);
    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;
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    Operand val = dword ? operandStack.popDual() : operandStack.pop();
    merging.mergeInputs(val);
    JReturnStmt ret = Jimple.newReturnStmt(val.toImmediate(), getStmtPositionInfo());
    setStmt(insn, ret);
  }

  private void convertInsn(@Nonnull InsnNode insn) {
    int op = insn.getOpcode();
    if (op == NOP) {
      /*
       * We can ignore NOP instructions, but for completeness, we handle them
       */
      setStmt(insn, Jimple.newNopStmt(getStmtPositionInfo()));
    } 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.pop().emitStatement();
    } else if (op == POP2) {
      operandStack.pop().emitStatement();
      // pops the `Operand.DWORD_DUMMY` or the second value
      operandStack.pop().emitStatement();
    } else if (op >= DUP && op <= DUP2_X2) {
      convertDupInsn(insn);
    } else if (op == SWAP) {
      Operand o1 = operandStack.pop();
      Operand o2 = operandStack.pop();
      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) {
      setStmt(insn, Jimple.newReturnVoidStmt(getStmtPositionInfo()));
    } else if (op == ATHROW) {
      OperandMerging merging = operandStack.getOrCreateMerging(insn);
      Operand opr = operandStack.pop();
      merging.mergeInputs(opr);
      JThrowStmt ts = Jimple.newThrowStmt(opr.toImmediate(), getStmtPositionInfo());
      setStmt(insn, ts);
      merging.mergeOutput(opr);
      operandStack.push(opr);
    } else if (op == MONITORENTER || op == MONITOREXIT) {
      OperandMerging merging = operandStack.getOrCreateMerging(insn);
      Operand opr = operandStack.popStackConst();
      merging.mergeInputs(opr);
      Stmt ts =
          op == MONITORENTER
              ? Jimple.newEnterMonitorStmt(opr.toImmediate(), getStmtPositionInfo())
              : Jimple.newExitMonitorStmt(opr.toImmediate(), getStmtPositionInfo());
      setStmt(insn, ts);
    } else {
      throw new UnsupportedOperationException("Unknown insn op: " + op);
    }
  }

  private void convertIntInsn(@Nonnull IntInsnNode insn) {
    int op = insn.getOpcode();
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    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.pop();
      merging.mergeInputs(size);
      v = JavaJimple.getInstance().newNewArrayExpr(type, size.toImmediate());
    }
    Operand opr = new Operand(insn, v, this);
    merging.mergeOutput(opr);
    operandStack.push(opr);
  }

  private void convertJumpInsn(@Nonnull JumpInsnNode insn) {
    int op = insn.getOpcode();
    if (op == GOTO) {
      BranchingStmt gotoStmt = Jimple.newGotoStmt(getStmtPositionInfo());
      stmtsThatBranchToLabel.put(gotoStmt, insn.label);
      setStmt(insn, gotoStmt);
      return;
    }
    /* must be ifX insn */
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    Operand val = operandStack.pop();
    Immediate v = val.toImmediate();
    AbstractConditionExpr cond;

    if (op >= IF_ICMPEQ && op <= IF_ACMPNE) {
      Operand val1 = operandStack.pop();
      merging.mergeInputs(val, val1);
      Immediate v1 = val1.toImmediate();
      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);
      }
    } else {
      merging.mergeInputs(val);
      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);
      }
    }
    BranchingStmt ifStmt = Jimple.newIfStmt(cond, getStmtPositionInfo());
    stmtsThatBranchToLabel.put(ifStmt, insn.label);
    setStmt(insn, ifStmt);
  }

  private void convertLdcInsn(@Nonnull LdcInsnNode insn) {
    Object val = insn.cst;
    boolean dword = val instanceof Long || val instanceof Double;
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    Value v = toSootValue(val);
    Operand opr = new Operand(insn, v, this);
    merging.mergeOutput(opr);
    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(toSootFieldSignature((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 = identifierFactory.getClassType(bsmClsName);
    Type t = AsmUtil.toJimpleSignatureDesc(methodHandle.getDesc()).get(0);
    int kind = methodHandle.getTag();
    FieldSignature fieldSignature =
        identifierFactory.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.pop();
      return Jimple.newInstanceFieldRef(base.toLocal(), fieldSignature);
    }
  }

  private FieldSignature toSootFieldSignature(Handle methodHandle) {
    String bsmClsName = AsmUtil.toQualifiedName(methodHandle.getOwner());
    JavaClassType bsmCls = identifierFactory.getClassType(bsmClsName);
    Type t = AsmUtil.toJimpleSignatureDesc(methodHandle.getDesc()).get(0);
    return identifierFactory.getFieldSignature(methodHandle.getName(), bsmCls, t);
  }

  private MethodSignature toMethodSignature(Handle methodHandle) {
    String bsmClsName = AsmUtil.toQualifiedName(methodHandle.getOwner());
    JavaClassType bsmCls = identifierFactory.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) {
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    if (insnToStmt.containsKey(insn)) {
      merging.mergeInputs(operandStack.pop());
      return;
    }
    Operand key = operandStack.pop();
    merging.mergeInputs(key);

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

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

    setStmt(insn, lookupSwitchStmt);
  }

  private void convertMethodInsn(@Nonnull MethodInsnNode insn) {
    int op = insn.getOpcode();
    boolean isInstance = op != INVOKESTATIC;
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    String clsName = AsmUtil.toQualifiedName(insn.owner);
    if (clsName.charAt(0) == '[') {
      clsName = "java.lang.Object";
    }
    JavaClassType cls = identifierFactory.getClassType(AsmUtil.toQualifiedName(clsName));
    List sigTypes = AsmUtil.toJimpleSignatureDesc(insn.desc);
    Type returnType = sigTypes.remove((sigTypes.size() - 1));
    MethodSignature methodSignature =
        identifierFactory.getMethodSignature(cls, insn.name, returnType, sigTypes);
    int nrArgs = sigTypes.size();
    final Operand[] operands;
    Immediate[] argList = new Immediate[nrArgs];
    final List args;
    if (!isInstance) {
      operands = nrArgs == 0 ? null : new Operand[nrArgs];
    } else {
      operands = new Operand[nrArgs + 1];
    }
    while (nrArgs-- != 0) {
      operands[nrArgs] = operandStack.pop(sigTypes.get(nrArgs));
    }
    if (isInstance) {
      operands[operands.length - 1] = operandStack.pop();
    }
    if (operands != null) {
      merging.mergeInputs(operands);
    }
    nrArgs = sigTypes.size();
    while (nrArgs-- != 0) {
      argList[nrArgs] = operands[nrArgs].toImmediate();
    }
    args = Arrays.asList(argList);
    AbstractInvokeExpr invoke;
    if (!isInstance) {
      invoke = Jimple.newStaticInvokeExpr(methodSignature, args);
    } else {
      Operand baseOperand = operands[operands.length - 1];
      Local base = baseOperand.toLocal();

      switch (op) {
        case INVOKESPECIAL:
          invoke = Jimple.newSpecialInvokeExpr(base, methodSignature, args);
          break;
        case INVOKEVIRTUAL:
          invoke = Jimple.newVirtualInvokeExpr(base, methodSignature, args);
          break;
        case INVOKEINTERFACE:
          invoke = Jimple.newInterfaceInvokeExpr(base, methodSignature, args);
          break;
        default:
          throw new UnsupportedOperationException("Unknown invoke op:" + op);
      }
    }
    Operand opr = new Operand(insn, invoke, this);
    merging.mergeOutput(opr);

    if (AsmUtil.isDWord(returnType)) {
      operandStack.pushDual(opr);
    } else if (returnType != VoidType.getInstance()) {
      operandStack.push(opr);
    } else {
      JInvokeStmt stmt =
          Jimple.newInvokeStmt((AbstractInvokeExpr) opr.value, getStmtPositionInfo());
      setStmt(insn, stmt);
    }
    /*
     * assign all read ops in case the method modifies any of the fields
     */
    addReadOperandAssignments();
  }

  private void convertInvokeDynamicInsn(@Nonnull InvokeDynamicInsnNode insn) {
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    // 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 =
        identifierFactory.getClassType(JDynamicInvokeExpr.INVOKEDYNAMIC_DUMMY_CLASS_NAME);

    // Generate parameters & returnType & parameterTypes
    List types = AsmUtil.toJimpleSignatureDesc(insn.desc);
    int nrArgs = types.size() - 1;
    Type[] parameterTypes = new Type[nrArgs];
    Immediate[] methodArgs = new Immediate[nrArgs];

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

    for (int i = nrArgs - 1; i >= 0; i--) {
      parameterTypes[i] = types.get(i);

      args[i] = operandStack.pop(types.get(i));
    }
    merging.mergeInputs(args);
    for (int i = nrArgs - 1; i >= 0; i--) {
      methodArgs[i] = args[i].toImmediate();
    }
    Type 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 =
        identifierFactory.getMethodSignature(
            bclass, insn.name, returnType, Arrays.asList(parameterTypes));

    JDynamicInvokeExpr indy =
        Jimple.newDynamicInvokeExpr(
            bsmMethodRef, bsmMethodArgs, methodSig, insn.bsm.getTag(), Arrays.asList(methodArgs));

    Operand opr = new Operand(insn, indy, this);
    merging.mergeOutput(opr);
    if (AsmUtil.isDWord(returnType)) {
      operandStack.pushDual(opr);
    } else if (!(returnType instanceof VoidType)) {
      operandStack.push(opr);
    } else {
      JInvokeStmt stmt =
          Jimple.newInvokeStmt((AbstractInvokeExpr) opr.value, getStmtPositionInfo());
      setStmt(insn, stmt);
    }
    /*
     * assign all read ops in case the method modifies any of the fields
     */
    addReadOperandAssignments();
  }

  private void convertMultiANewArrayInsn(@Nonnull MultiANewArrayInsnNode insn) {
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    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.pop();
    }
    merging.mergeInputs(sizes);
    dims = insn.dims;
    while (dims-- != 0) {
      sizeVals[dims] = sizes[dims].toImmediate();
    }
    JNewMultiArrayExpr nm = Jimple.newNewMultiArrayExpr(t, Arrays.asList(sizeVals));
    Operand opr = new Operand(insn, nm, this);
    merging.mergeOutput(opr);
    operandStack.push(opr);
  }

  private void convertTableSwitchInsn(@Nonnull TableSwitchInsnNode insn) {
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    if (insnToStmt.containsKey(insn)) {
      merging.mergeInputs(operandStack.pop());
      return;
    }
    Operand key = operandStack.pop();
    merging.mergeInputs(key);
    JSwitchStmt tableSwitchStmt =
        Jimple.newTableSwitchStmt(key.toImmediate(), insn.min, insn.max, getStmtPositionInfo());

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

    setStmt(insn, tableSwitchStmt);
  }

  private void convertTypeInsn(@Nonnull TypeInsnNode insn) {
    int op = insn.getOpcode();
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    Expr val;
    if (op == NEW) {
      val = Jimple.newNewExpr(AsmUtil.toJimpleClassType(insn.desc));
    } else {
      Operand op1 = operandStack.pop();
      merging.mergeInputs(op1);
      switch (op) {
        case ANEWARRAY:
          {
            val =
                JavaJimple.getInstance()
                    .newNewArrayExpr(AsmUtil.arrayTypetoJimpleType(insn.desc), op1.toImmediate());
            break;
          }
        case CHECKCAST:
          {
            val = Jimple.newCastExpr(op1.toImmediate(), AsmUtil.arrayTypetoJimpleType(insn.desc));
            break;
          }
        case INSTANCEOF:
          {
            val = Jimple.newInstanceOfExpr(op1.toImmediate(), AsmUtil.toJimpleClassType(insn.desc));
            break;
          }
        default:
          throw new UnsupportedOperationException("Unknown type op: " + op);
      }
    }
    Operand opr = new Operand(insn, val, this);
    merging.mergeOutput(opr);
    operandStack.push(opr);
  }

  private void convertVarLoadInsn(@Nonnull VarInsnNode insn) {
    int op = insn.getOpcode();
    boolean dword = op == LLOAD || op == DLOAD;
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    Operand opr = new Operand(insn, getOrCreateLocal(insn.var), this);
    merging.mergeOutput(opr);
    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;
    OperandMerging merging = operandStack.getOrCreateMerging(insn);
    Operand opr = dword ? operandStack.popDual() : operandStack.pop();
    merging.mergeInputs(opr);
    Local local = getOrCreateLocal(insn.var);
    AbstractDefinitionStmt as;
    if (opr.stackLocal == null || opr.stackLocal == local) {
      // Can skip creating a new stack local for the operand
      // and store the value in the local directly.
      as = Jimple.newAssignStmt(local, opr.value, getStmtPositionInfo());
      opr.stackLocal = local;
      setStmt(opr.insn, as);
    } else {
      as = Jimple.newAssignStmt(local, opr.toImmediate(), getStmtPositionInfo());
      setStmt(insn, as);
    }
    // The `local` has just been assigned a new value,
    // but an operand with `value == local` might still be on the stack.
    // That operand should use the old value,
    // so the following call adds a `$stackLocalX = $local` statement
    // to persist the old value when necessary.
    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 */
      setStmt(insn, Jimple.newRetStmt(getOrCreateLocal(insn.var), getStmtPositionInfo()));
    } else {
      throw new UnsupportedOperationException("Unknown var op: " + op);
    }
  }

  private void convertLabel(@Nonnull LabelNode ln) {
    if (startTrapHandler.containsKey(ln)) {
      activeTrapHandlers.add(startTrapHandler.get(ln));
    }

    if (endTrapHandler.containsKey(ln)) {
      activeTrapHandlers.remove(endTrapHandler.get(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(getStmtPositionInfo());
        setStmt(ln, nop);
      }
      return;
    }

    OperandMerging merging = operandStack.getOrCreateMerging(ln);
    JCaughtExceptionRef ref = JavaJimple.getInstance().newCaughtExceptionRef();
    Operand opr = new Operand(ln, ref, this);
    merging.mergeOutput(opr);
    if (opr.stackLocal == null) {
      opr.stackLocal = newStackLocal();
    }
    JIdentityStmt as = Jimple.newIdentityStmt(opr.stackLocal, ref, getStmtPositionInfo());
    setStmt(ln, as);
    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(), currentLineNumber, activeTrapHandlers);
        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();
        JIdentityStmt as = Jimple.newIdentityStmt(local, ref, getStmtPositionInfo());

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

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

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

    do {
      BranchedInsnInfo edge = worklist.pollLast();
      AbstractInsnNode insn = edge.getInsn();
      currentLineNumber = edge.getLineNumber();
      operandStack.setOperandStack(
          new ArrayList<>(edge.getOperandStacks().get(edge.getOperandStacks().size() - 1)));
      activeTrapHandlers = edge.getActiveTrapHandlers();
      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.getNoStmtPositionInfo();
  }

  @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(MethodModifier.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 = identifierFactory.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;
        danglingLabel.forEach(l -> labelsToStmt.put(l, targetStmt));
        if (isLabelNode) {
          // If the targetStmt is an exception handler, register the starting Stmt for it
          JIdentityStmt identityRef = stmt instanceof JIdentityStmt ? (JIdentityStmt) stmt : null;
          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()) {

      JIdentityStmt 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, 0, targetStmt);
    }
  }

  private void emitStmt(@Nonnull Stmt handlerStmt, @Nonnull List block) {
    block.add(handlerStmt);
  }

  /**
   * 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) {
    if (oldStmt == newStmt) {
      return;
    }

    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) {
      final BranchingStmt branchingStmt = (BranchingStmt) oldStmt;
      List branchLabels = stmtsThatBranchToLabel.get(branchingStmt);
      branchLabels.forEach(bl -> stmtsThatBranchToLabel.put((BranchingStmt) newStmt, bl));
      stmtsThatBranchToLabel.removeAll(branchingStmt);
    }
  }

  /**
   * * returns all stmts that use this expr
   *
   * @param value which is used to filter associated Stmts
   */
  public Stream getStmtsThatUse(@Nonnull Value value) {
    Stream currentUses =
        insnToStmt.values().stream().filter(stmt -> stmt.getUses().contains(value));

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

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