/*
 * Copyright (c) 2002 - 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Adam Fuchs, Avik Chaudhuri, Steve Suh - Modified from stack to registers
 */
package com.ibm.wala.dalvik.ssa;

import com.ibm.wala.cfg.IBasicBlock;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.Language;
import com.ibm.wala.dalvik.classLoader.DexCFG;
import com.ibm.wala.dalvik.classLoader.DexCFG.BasicBlock;
import com.ibm.wala.dalvik.classLoader.DexIMethod;
import com.ibm.wala.dalvik.classLoader.Literal;
import com.ibm.wala.dalvik.dex.instructions.ArrayFill;
import com.ibm.wala.dalvik.dex.instructions.ArrayGet;
import com.ibm.wala.dalvik.dex.instructions.ArrayLength;
import com.ibm.wala.dalvik.dex.instructions.ArrayPut;
import com.ibm.wala.dalvik.dex.instructions.BinaryLiteralOperation;
import com.ibm.wala.dalvik.dex.instructions.BinaryOperation;
import com.ibm.wala.dalvik.dex.instructions.Branch;
import com.ibm.wala.dalvik.dex.instructions.CheckCast;
import com.ibm.wala.dalvik.dex.instructions.Constant;
import com.ibm.wala.dalvik.dex.instructions.GetField;
import com.ibm.wala.dalvik.dex.instructions.Goto;
import com.ibm.wala.dalvik.dex.instructions.InstanceOf;
import com.ibm.wala.dalvik.dex.instructions.Instruction;
import com.ibm.wala.dalvik.dex.instructions.Instruction.Visitor;
import com.ibm.wala.dalvik.dex.instructions.Invoke;
import com.ibm.wala.dalvik.dex.instructions.Monitor;
import com.ibm.wala.dalvik.dex.instructions.New;
import com.ibm.wala.dalvik.dex.instructions.NewArray;
import com.ibm.wala.dalvik.dex.instructions.NewArrayFilled;
import com.ibm.wala.dalvik.dex.instructions.PutField;
import com.ibm.wala.dalvik.dex.instructions.Return;
import com.ibm.wala.dalvik.dex.instructions.Switch;
import com.ibm.wala.dalvik.dex.instructions.Throw;
import com.ibm.wala.dalvik.dex.instructions.UnaryOperation;
import com.ibm.wala.shrikeBT.ArrayLengthInstruction;
import com.ibm.wala.shrikeBT.ConstantInstruction;
import com.ibm.wala.shrikeBT.GotoInstruction;
import com.ibm.wala.shrikeBT.IArrayLoadInstruction;
import com.ibm.wala.shrikeBT.IArrayStoreInstruction;
import com.ibm.wala.shrikeBT.IBinaryOpInstruction;
import com.ibm.wala.shrikeBT.IGetInstruction;
import com.ibm.wala.shrikeBT.IInvokeInstruction;
import com.ibm.wala.shrikeBT.IUnaryOpInstruction;
import com.ibm.wala.shrikeBT.MonitorInstruction;
import com.ibm.wala.shrikeBT.NewInstruction;
import com.ibm.wala.shrikeBT.ReturnInstruction;
import com.ibm.wala.shrikeBT.SwitchInstruction;
import com.ibm.wala.shrikeBT.ThrowInstruction;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.ssa.ISSABasicBlock;
import com.ibm.wala.ssa.PhiValue;
import com.ibm.wala.ssa.SSAAbstractInvokeInstruction;
import com.ibm.wala.ssa.SSACFG;
import com.ibm.wala.ssa.SSACFG.ExceptionHandlerBasicBlock;
import com.ibm.wala.ssa.SSAConditionalBranchInstruction;
import com.ibm.wala.ssa.SSAGetCaughtExceptionInstruction;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAInstructionFactory;
import com.ibm.wala.ssa.SSAInvokeInstruction;
import com.ibm.wala.ssa.SSALoadMetadataInstruction;
import com.ibm.wala.ssa.SSAPhiInstruction;
import com.ibm.wala.ssa.SSAPiInstruction;
import com.ibm.wala.ssa.SSAPiNodePolicy;
import com.ibm.wala.ssa.ShrikeIndirectionData;
import com.ibm.wala.ssa.SymbolTable;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.FieldReference;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.Pair;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.graph.dominators.Dominators;
import com.ibm.wala.util.intset.IntPair;
import java.util.Arrays;
import java.util.Iterator;

/**
 * This class constructs an SSA {@link IR} from a backing ShrikeBT instruction stream.
 *
 * <p>The basic algorithm here is an abstract interpretation over the Java bytecode to determine
 * types of stack locations and local variables. As a side effect, the flow functions of the
 * abstract interpretation emit instructions, eliminating the stack abstraction and moving to a
 * register-transfer language in SSA form.
 */
public class DexSSABuilder extends AbstractIntRegisterMachine {
  public static DexSSABuilder make(
      DexIMethod method,
      SSACFG cfg,
      DexCFG scfg,
      SSAInstruction[] instructions,
      SymbolTable symbolTable,
      boolean buildLocalMap,
      SSAPiNodePolicy piNodePolicy)
      throws IllegalArgumentException {
    if (scfg == null) {
      throw new IllegalArgumentException("scfg == null");
    }
    return new DexSSABuilder(
        method, cfg, scfg, instructions, symbolTable, buildLocalMap, piNodePolicy);
  }

  /** A wrapper around the method being analyzed. */
  private final DexIMethod method;

  /** Governing symbol table */
  private final SymbolTable symbolTable;

  /**
   * A logical mapping from &lt;bcIndex, valueNumber&gt; -&gt; local number if null, don't build it.
   */
  private final SSA2LocalMap localMap;

  /** a factory to create concrete instructions */
  private final SSAInstructionFactory insts;

  /** information about indirect use of local variables in the bytecode */
  //    private final IndirectionData bytecodeIndirections;

  private final ShrikeIndirectionData shrikeIndirections;

  private DexSSABuilder(
      DexIMethod method,
      SSACFG cfg,
      DexCFG scfg,
      SSAInstruction[] instructions,
      SymbolTable symbolTable,
      boolean buildLocalMap,
      SSAPiNodePolicy piNodePolicy) {
    super(scfg);
    localMap =
        buildLocalMap
            ? new SSA2LocalMap(
                scfg, instructions.length, cfg.getNumberOfNodes(), method.getMaxLocals())
            : null;
    init(
        new SymbolTableMeeter(cfg, scfg),
        new SymbolicPropagator(scfg, instructions, cfg, piNodePolicy));
    this.method = method;
    this.symbolTable = symbolTable;
    this.insts = method.getDeclaringClass().getClassLoader().getInstructionFactory();
    //        this.bytecodeIndirections = method.getIndirectionData();
    this.shrikeIndirections = new ShrikeIndirectionData(instructions.length);
    assert cfg != null : "Null CFG";
  }

  private class SymbolTableMeeter implements Meeter {

    final SSACFG cfg;

    final DexCFG dexCFG;

    SymbolTableMeeter(SSACFG cfg, DexCFG dexCFG) {
      this.cfg = cfg;
      //            this.instructions = instructions;
      this.dexCFG = dexCFG;
    }

    //      public int meetStack(int slot, int[] rhs, IBasicBlock<Instruction> bb) {
    //
    //          assert bb != null : "null basic block";
    //
    //          if (bb.isExitBlock()) {
    //              return TOP;
    //          }
    //
    //          if (allTheSame(rhs)) {
    //              for (int i = 0; i < rhs.length; i++) {
    //                  if (rhs[i] != TOP) {
    //                      return rhs[i];
    //                  }
    //              }
    //              // didn't find anything but TOP
    //              return TOP;
    //          } else {
    //              SSACFG.BasicBlock newBB = cfg.getNode(dexCFG.getNumber(bb));
    //              // if we already have a phi for this stack location
    //              SSAPhiInstruction phi = newBB.getPhiForStackSlot(slot);
    //              int result;
    //              if (phi == null) {
    //                  // no phi already exists. create one.
    //                  result = symbolTable.newPhi(rhs);
    //                  PhiValue v = symbolTable.getPhiValue(result);
    //                  phi = v.getPhiInstruction();
    //                  newBB.addPhiForStackSlot(slot, phi);
    //              } else {
    //                  // already created a phi. update it to account for the
    //                  // new merge.
    //                  result = phi.getDef();
    //                  phi.setValues(rhs.clone());
    //              }
    //              return result;
    //          }
    //      }

    @Override
    public int meetLocal(int n, int[] rhs, DexCFG.BasicBlock bb) {
      if (allTheSame(rhs)) {
        for (int rh : rhs) {
          if (rh != TOP) {
            return rh;
          }
        }
        // didn't find anything but TOP
        return TOP;
      } else {
        SSACFG.BasicBlock newBB = cfg.getNode(dexCFG.getNumber(bb));
        if (bb.isExitBlock()) {
          // no phis in exit block please
          return TOP;
        }
        // if we already have a phi for this local
        SSAPhiInstruction phi = newBB.getPhiForLocal(n);
        int result;
        if (phi == null) {
          // no phi already exists. create one.
          result = symbolTable.newPhi(rhs);
          PhiValue v = symbolTable.getPhiValue(result);
          phi = v.getPhiInstruction();
          newBB.addPhiForLocal(n, phi);
        } else {
          // already created a phi. update it to account for the
          // new merge.
          result = phi.getDef();
          phi.setValues(rhs.clone());
        }
        return result;
      }
    }

    /**
     * Are all rhs values all the same? Note, we consider TOP (-1) to be same as everything else.
     *
     * @return boolean
     */
    private boolean allTheSame(int[] rhs) {
      int x = -1;
      // set x := the first non-TOP value
      int i = 0;
      for (i = 0; i < rhs.length; i++) {
        if (rhs[i] != TOP) {
          x = rhs[i];
          break;
        }
      }
      // check the remaining values
      for (i++; i < rhs.length; i++) {
        if (rhs[i] != x && rhs[i] != TOP) return false;
      }
      return true;
    }
  }

  @Override
  protected void initializeVariables() {
    MachineState entryState = getEntryState();
    int parameterNumber = 0;
    // added -2 to account for the return and exception register
    //      int local = method.getMaxLocals() - method.getNumberOfParameters() - 1 - 2;

    // can't use just getNumberOfParameters because it does not account for Long/Wide variables
    // which take up 2 registers
    // as the parameter
    int local = method.getMaxLocals() - method.getNumberOfParameterRegisters() - 1 - 2;

    //      MyLogger.log(LogLevel.DEBUG, "DexSSABuilder - initializeVariables() - local: " + local);

    // initialize the "this" parameter if it needs to be set.
    // the "this" parameter will be symbol number 1 in a virtual method.
    //      if (method.getMaxLocals() - method.getNumberOfParameters() - 2 > 0)
    //          entryState.setLocal(local, 1);

    //      System.out.println("visiting initalizeVartiables()");
    //      if (method.isStatic())
    //          System.out.println("Static");
    //      if (method.isClinit())
    //          System.out.println("Clinit");
    //      System.out.println("GetNumberOfParameter: " + method.getNumberOfParameterRegisters());
    //      System.out.println("Total Registers: " + (method.getMaxLocals()-2));
    //      System.out.println("local: " + local);

    //      if (local >= 0) {
    //          System.out.println("Max Registers: " + (int)(method.getMaxLocals() - 2));
    //          System.out.println("Parameters: " + method.getNumberOfParameters());
    //          System.out.println("Setting Entry State, local:"+ local + " with 1");
    //          entryState.setLocal(local, 1);
    //      }

    //      System.out.println("Max Registers: " + (int)(method.getMaxLocals() - 2));
    //      System.out.println("Parameters: " + method.getNumberOfParameters());

    entryState.allocateLocals();
    for (int i = 0; i < method.getNumberOfParameters(); i++) {
      local++;
      TypeReference t = method.getParameterType(i);
      if (t != null) {
        int symbol = symbolTable.getParameter(parameterNumber++);
        entryState.setLocal(local, symbol);
        if (t.equals(TypeReference.Double) || t.equals(TypeReference.Long)) {
          local++;
          entryState.setLocal(local, symbol);
        }
      }
    }
    //      MyLogger.log(LogLevel.DEBUG, "DexSSABuilder - initializeVariables() - local: " + local);
    // This useless value ensures that the state cannot be empty, even
    // for a static method with no arguments in blocks with an empty stack
    // and no locals being used. This ensures that propagation of the
    // state thru the CFGSystem will always show changes the first time
    // it reaches a block, and thus no piece of the CFG will be skipped.
    //
    // (note that this bizarre state really happened, in java_cup)
    //
    // SJF: I don't understand how this is supposed to work. It
    // causes a bug right now in normal cases, so I'm commenting it out
    // for now. If there's a problem, let's add a regression test
    // to catch it.
    //
    // TODO: if there's no stack do we need this?
    // entryState.push(symbolTable.newSymbol());
  }

  /**
   * This class defines the type abstractions for this analysis and the flow function for each
   * instruction in the ShrikeBT IR.
   */
  private class SymbolicPropagator extends BasicRegisterFlowProvider {

    final SSAInstruction[] instructions;

    final DexCFG dexCFG;

    final SSACFG cfg;

    final ClassLoaderReference loader;

    /** creators[i] holds the instruction that defs value number i */
    private SSAInstruction[] creators;

    //        final SSA2LocalMap localMap;

    final SSAPiNodePolicy piNodePolicy;

    public SymbolicPropagator(
        DexCFG dexCFG, SSAInstruction[] instructions, SSACFG cfg, SSAPiNodePolicy piNodePolicy) {
      super(dexCFG);
      this.piNodePolicy = piNodePolicy;
      this.cfg = cfg;
      this.creators = new SSAInstruction[0];
      this.dexCFG = dexCFG;
      this.instructions = instructions;
      this.loader = dexCFG.getMethod().getDeclaringClass().getClassLoader().getReference();
      //            this.localMap = localMap;
      init(this.new NodeVisitor(cfg), this.new EdgeVisitor());
    }

    @Override
    public boolean needsEdgeFlow() {
      return piNodePolicy != null;
    }

    private void emitInstruction(SSAInstruction s) {
      instructions[getCurrentInstructionIndex()] = s;
      for (int i = 0; i < s.getNumberOfDefs(); i++) {
        if (creators.length < (s.getDef(i) + 1)) {
          creators = Arrays.copyOf(creators, 2 * s.getDef(i));
        }

        assert s.getDef(i) != -1 : "invalid def " + i + " for " + s;

        creators[s.getDef(i)] = s;
      }
    }

    private SSAInstruction getCurrentInstruction() {
      return instructions[getCurrentInstructionIndex()];
    }

    /**
     * If we've already created the current instruction, return the value number def'ed by the
     * current instruction. Else, create a new symbol.
     */
    private int reuseOrCreateDef() {
      if (getCurrentInstruction() == null || !getCurrentInstruction().hasDef()) {
        return symbolTable.newSymbol();
      } else {
        return getCurrentInstruction().getDef();
      }
    }

    /**
     * If we've already created the current instruction, return the value number representing the
     * exception the instruction may throw. Else, create a new symbol
     */
    private int reuseOrCreateException() {

      if (getCurrentInstruction() != null) {
        assert getCurrentInstruction() instanceof SSAInvokeInstruction;
      }
      if (getCurrentInstruction() == null) {
        return symbolTable.newSymbol();
      } else {
        SSAInvokeInstruction s = (SSAInvokeInstruction) getCurrentInstruction();
        return s.getException();
      }
    }

    /** Update the machine state to account for an instruction */
    class NodeVisitor extends BasicRegisterMachineVisitor {
      private final SSACFG cfg;

      public NodeVisitor(SSACFG cfg) {
        this.cfg = cfg;
      }

      // TODO: make sure all visit functions are overridden

      /**
       * @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitArrayLength(ArrayLengthInstruction)
       */
      @Override
      public void visitArrayLength(ArrayLength instruction) {
        int arrayRef = workingState.getLocal(instruction.source);
        int dest = instruction.destination;
        int length = reuseOrCreateDef();
        setLocal(dest, length);

        emitInstruction(
            insts.ArrayLengthInstruction(getCurrentInstructionIndex(), length, arrayRef));
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitArrayLoad(IArrayLoadInstruction) */
      @Override
      public void visitArrayGet(ArrayGet instruction) {
        int index = workingState.getLocal(instruction.offset);
        int arrayRef = workingState.getLocal(instruction.array);
        int dest = instruction.destination;
        //              int index = workingState.pop();
        //              int arrayRef = workingState.pop();
        int result = reuseOrCreateDef();
        setLocal(dest, result);
        //              workingState.push(result);
        TypeReference t = instruction.getType();
        //              if (instruction.isAddressOf()) {
        //                  emitInstruction(insts.AddressOfInstruction(result, arrayRef, index, t));
        //              } else {
        emitInstruction(
            insts.ArrayLoadInstruction(getCurrentInstructionIndex(), result, arrayRef, index, t));
        //              }
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitArrayStore(IArrayStoreInstruction) */
      @Override
      public void visitArrayPut(ArrayPut instruction) {

        int value = workingState.getLocal(instruction.source);
        int index = workingState.getLocal(instruction.offset);
        int arrayRef = workingState.getLocal(instruction.array);
        TypeReference t = instruction.getType();
        // System.out.println(t.getName().toString());
        //              int value = workingState.pop();
        //              int index = workingState.pop();
        //              int arrayRef = workingState.pop();
        //              TypeReference t = ShrikeUtil.makeTypeReference(loader,
        // instruction.getType());
        emitInstruction(
            insts.ArrayStoreInstruction(getCurrentInstructionIndex(), arrayRef, index, value, t));
      }

      @Override
      public void visitArrayFill(ArrayFill instruction) {
        Iterator<Number> iae = instruction.getTable().getArrayElements().iterator();
        int i = 0;
        while (iae.hasNext()) {
          Number ae = iae.next();
          int index = symbolTable.getConstant(i);
          int arrayRef = workingState.getLocal(instruction.array);
          TypeReference t = instruction.getType();
          //                  System.out.println(t.getName().toString());
          int value;
          //
          //                      System.out.println("Index: " + ae.bufferIndex + ", Width: " +
          // ae.elementWidth + ", Value: " +  byte_buffer.getSomethingDependingonType );

          // okay to call the getConstant(String) for a char?
          if (t.equals(TypeReference.Char)) value = symbolTable.getConstant((char) ae.intValue());
          else if (t.equals(TypeReference.Byte)) value = symbolTable.getConstant(ae.byteValue());
          else if (t.equals(TypeReference.Short)) value = symbolTable.getConstant(ae.shortValue());
          else if (t.equals(TypeReference.Int)) value = symbolTable.getConstant(ae.intValue());
          else if (t.equals(TypeReference.Long)) value = symbolTable.getConstant(ae.longValue());
          else if (t.equals(TypeReference.Float)) value = symbolTable.getConstant(ae.floatValue());
          else if (t.equals(TypeReference.Double))
            value = symbolTable.getConstant(ae.doubleValue());
          else if (t.equals(TypeReference.Boolean))
            value = symbolTable.getConstant(ae.intValue() != 0);
          else {

            value = 0;
          }
          emitInstruction(
              insts.ArrayStoreInstruction(getCurrentInstructionIndex(), arrayRef, index, value, t));

          //                  System.out.println("Index: " + t.bufferIndex + ", Value: " +
          // t.buffer[t.bufferIndex]);

          i++;
        }
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitBinaryOp(IBinaryOpInstruction) */
      @Override
      public void visitBinaryOperation(BinaryOperation instruction) {
        int val2 = workingState.getLocal(instruction.oper2);
        int val1 = workingState.getLocal(instruction.oper1);
        int dest = instruction.destination;
        //              int val2 = workingState.pop();
        //              int val1 = workingState.pop();
        int result = reuseOrCreateDef();
        setLocal(dest, result);
        //              workingState.push(result);
        //              boolean isFloat = instruction.getType().equals(TYPE_double) ||
        // instruction.getType().equals(TYPE_float);
        emitInstruction(
            insts.BinaryOpInstruction(
                getCurrentInstructionIndex(),
                instruction.getOperator(),
                false,
                instruction.isUnsigned(),
                result,
                val1,
                val2,
                !instruction.isFloat()));
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitBinaryOp(IBinaryOpInstruction) */
      @Override
      public void visitBinaryLiteral(BinaryLiteralOperation instruction) {
        // int val2 = workingState.getLocal(instruction.oper2);

        Literal lit = instruction.oper2;

        int val2;
        if (lit instanceof Literal.IntLiteral)
          val2 = symbolTable.getConstant(((Literal.IntLiteral) lit).value);
        else if (lit instanceof Literal.LongLiteral)
          val2 = symbolTable.getConstant(((Literal.LongLiteral) lit).value);
        else if (lit instanceof Literal.DoubleLiteral)
          val2 = symbolTable.getConstant(((Literal.DoubleLiteral) lit).value);
        else val2 = symbolTable.getConstant(((Literal.FloatLiteral) lit).value);

        int val1 = workingState.getLocal(instruction.oper1);
        int dest = instruction.destination;
        //              int val2 = workingState.pop();
        //              int val1 = workingState.pop();
        int result = reuseOrCreateDef();
        setLocal(dest, result);
        //              workingState.push(result);
        //              boolean isFloat = instruction.getType().equals(TYPE_double) ||
        // instruction.getType().equals(TYPE_float);
        try {
          if (instruction.isSub())
            emitInstruction(
                insts.BinaryOpInstruction(
                    getCurrentInstructionIndex(),
                    instruction.getOperator(),
                    false,
                    instruction.isUnsigned(),
                    result,
                    val2,
                    val1,
                    !instruction.isFloat()));
          else
            emitInstruction(
                insts.BinaryOpInstruction(
                    getCurrentInstructionIndex(),
                    instruction.getOperator(),
                    false,
                    instruction.isUnsigned(),
                    result,
                    val1,
                    val2,
                    !instruction.isFloat()));
        } catch (AssertionError e) {
          System.err.println("When visiting Instuction " + instruction);
          throw e;
        }
      }

      protected void setLocal(int dest, int result) {
        assert result <= symbolTable.getMaxValueNumber();
        workingState.setLocal(dest, result);
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitCheckCast */
      @Override
      public void visitCheckCast(CheckCast instruction) {
        int val = workingState.getLocal(instruction.object);
        //              int val = workingState.pop();
        // dex does not use this result, but we need it for the SSA CheckCastInstruction
        int result = reuseOrCreateDef();
        workingState.setLocal(instruction.object, result);
        //              workingState.push(result);
        //              TypeReference t = instruction.getType();
        emitInstruction(
            insts.CheckCastInstruction(
                getCurrentInstructionIndex(), result, val, instruction.type, instruction.isPEI()));
      }

      @Override
      public void visitBranch(Branch instruction) {
        if (instruction instanceof Branch.BinaryBranch) {
          Branch.BinaryBranch bbranch = (Branch.BinaryBranch) instruction;
          int val2 = workingState.getLocal(bbranch.oper2);
          int val1 = workingState.getLocal(bbranch.oper1);
          //                  int val2 = workingState.pop();
          //                  int val1 = workingState.pop();

          TypeReference t = TypeReference.Int;
          emitInstruction(
              insts.ConditionalBranchInstruction(
                  getCurrentInstructionIndex(), instruction.getOperator(), t, val1, val2, -1));
        } else if (instruction instanceof Branch.UnaryBranch) {
          Branch.UnaryBranch ubranch = (Branch.UnaryBranch) instruction;
          int val2 = symbolTable.getConstant(0);
          int val1 = workingState.getLocal(ubranch.oper1);
          TypeReference t = TypeReference.Int;
          emitInstruction(
              insts.ConditionalBranchInstruction(
                  getCurrentInstructionIndex(), instruction.getOperator(), t, val1, val2, -1));
        } else {
          throw new IllegalArgumentException("instruction is of an unknown subtype of Branch");
        }
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitConstant(ConstantInstruction) */
      @Override
      public void visitConstant(Constant instruction) {
        int dest = instruction.destination;
        int symbol = 0;
        if (instruction instanceof Constant.ClassConstant) {
          Constant.ClassConstant constInst = (Constant.ClassConstant) instruction;

          // TODO: change to a symbol that represents the given IClass
          //                  symbol =
          // symbolTable.getConstant(((Constant.ClassConstant)instruction).value);
          symbol = reuseOrCreateDef();

          TypeReference typeRef = constInst.value;
          SSALoadMetadataInstruction s =
              insts.LoadMetadataInstruction(
                  getCurrentInstructionIndex(), symbol, TypeReference.JavaLangClass, typeRef);
          emitInstruction(s);
        } else if (instruction instanceof Constant.IntConstant) {
          symbol = symbolTable.getConstant(((Constant.IntConstant) instruction).value);
        } else if (instruction instanceof Constant.LongConstant) {
          symbol = symbolTable.getConstant(((Constant.LongConstant) instruction).value);
        } else if (instruction instanceof Constant.StringConstant) {
          symbol = symbolTable.getConstant(((Constant.StringConstant) instruction).value);
        } else {
          Assertions.UNREACHABLE("unexpected constant instruction " + instruction);
        }
        setLocal(dest, symbol);
        //              Language l =
        // cfg.getMethod().getDeclaringClass().getClassLoader().getLanguage();
        //              TypeReference type = l.getConstantType(instruction.getValue());
        //              int symbol = 0;
        //              if (l.isNullType(type)) {
        //                  symbol = symbolTable.getNullConstant();
        //              } else if (l.isIntType(type)) {
        //                  Integer value = (Integer) instruction.getValue();
        //                  symbol = symbolTable.getConstant(value.intValue());
        //              } else if (l.isLongType(type)) {
        //                  Long value = (Long) instruction.getValue();
        //                  symbol = symbolTable.getConstant(value.longValue());
        //              } else if (l.isFloatType(type)) {
        //                  Float value = (Float) instruction.getValue();
        //                  symbol = symbolTable.getConstant(value.floatValue());
        //              } else if (l.isDoubleType(type)) {
        //                  Double value = (Double) instruction.getValue();
        //                  symbol = symbolTable.getConstant(value.doubleValue());
        //              } else if (l.isStringType(type)) {
        //                  String value = (String) instruction.getValue();
        //                  symbol = symbolTable.getConstant(value);
        //              } else if (l.isMetadataType(type)) {
        //                  Object rval = l.getMetadataToken(instruction.getValue());
        //                  symbol = reuseOrCreateDef();
        //                  emitInstruction(insts.LoadMetadataInstruction(symbol, type, rval));
        //              } else {
        //                  Assertions.UNREACHABLE("unexpected " + type);
        //              }
        //              workingState.push(symbol);
      }

      // TODO: is this just a unary operation?
      //          @Override
      //          public void visitConversion(IConversionInstruction instruction) {
      //
      //              int val = workingState.pop();
      //              int result = reuseOrCreateDef();
      //              workingState.push(result);
      //
      //              TypeReference fromType = ShrikeUtil.makeTypeReference(loader,
      // instruction.getFromType());
      //              TypeReference toType = ShrikeUtil.makeTypeReference(loader,
      // instruction.getToType());
      //
      //              emitInstruction(insts.ConversionInstruction(result, val, fromType, toType,
      // instruction.throwsExceptionOnOverflow()));
      //          }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitGet(IGetInstruction) */
      @Override
      public void visitGetField(GetField instruction) {
        int dest = instruction.destination;
        int result = reuseOrCreateDef();
        FieldReference f =
            FieldReference.findOrCreate(
                loader, instruction.clazzName, instruction.fieldName, instruction.fieldType);
        // TODO: what is isAddressOf()?
        // shouldn't matter, java doesn't allow isAddressOf()
        if (instruction instanceof GetField.GetInstanceField) {
          int instance = workingState.getLocal(((GetField.GetInstanceField) instruction).instance);
          emitInstruction(insts.GetInstruction(getCurrentInstructionIndex(), result, instance, f));
        } else if (instruction instanceof GetField.GetStaticField) {
          emitInstruction(insts.GetInstruction(getCurrentInstructionIndex(), result, f));
        } else {
          throw new IllegalArgumentException("unknown subclass of GetField: " + instruction);
        }
        //              if (instruction.isAddressOf()) {
        //                  int ref = instruction.isStatic()? -1: workingState.pop();
        //                  emitInstruction(insts.AddressOfInstruction(result, ref, f,
        // f.getFieldType()));
        //              } else if (instruction.isStatic()) {
        //                  emitInstruction(insts.GetInstruction(result, f));
        //              } else {
        //                  int ref = workingState.pop();
        //                  emitInstruction(insts.GetInstruction(result, ref, f));
        //              }
        setLocal(dest, result);
        //              workingState.push(result);
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitGoto(GotoInstruction) */
      @Override
      public void visitGoto(Goto instruction) {
        emitInstruction(
            insts.GotoInstruction(getCurrentInstructionIndex(), instruction.destination));
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitInstanceof */
      @Override
      public void visitInstanceof(InstanceOf instruction) {
        int ref = workingState.getLocal(instruction.source);
        int dest = instruction.destination;
        //              int ref = workingState.pop();
        int result = reuseOrCreateDef();
        setLocal(dest, result);
        //              workingState.push(result);
        //              TypeReference t = ShrikeUtil.makeTypeReference(loader,
        // instruction.getType());
        emitInstruction(
            insts.InstanceofInstruction(
                getCurrentInstructionIndex(), result, ref, instruction.type));
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitInvoke(IInvokeInstruction) */
      @Override
      public void visitInvoke(Invoke instruction) {
        // TODO: can other methods do indirect reads from a dex method?
        //
        // doIndirectReads(bytecodeIndirections.indirectlyReadLocals(getCurrentInstructionIndex()));
        //              int n = instruction.getPoppedCount();
        //              int n = instruction.args.length;
        //              int[] params = new int[n];
        //              for (int i = n - 1; i >= 0; i--) {
        //                  params[i] = workingState.pop();
        //              }

        Language lang = dexCFG.getMethod().getDeclaringClass().getClassLoader().getLanguage();
        // TODO: check that the signature needed by findOrCreate can use the descriptor
        MethodReference m =
            MethodReference.findOrCreate(
                lang,
                loader,
                instruction.clazzName,
                instruction.methodName,
                instruction.descriptor);

        // ((DexIClass)dexCFG.getDexMethod().getDeclaringClass()).getSuperclass().get
        //              MethodReference m =
        // ((DexIClass)dexCFG.getDexMethod().getDeclaringClass()).loader.lookupClass(TypeName.findOrCreate(instruction.clazzName)).
        //              this.myClass.loader.lookupClass(TypeName.findOrCreate(cname)),
        //              ((Instruction21c)inst).getRegisterA()));

        IInvokeInstruction.IDispatch code = instruction.getInvocationCode();
        CallSiteReference site = CallSiteReference.make(getCurrentProgramCounter(), m, code);
        int exc = reuseOrCreateException();
        setLocal(dexCFG.getDexMethod().getExceptionReg(), exc);

        int n = instruction.args.length;
        for (int i = 0; i < m.getNumberOfParameters(); i++)
          if (m.getParameterType(i) == TypeReference.Double
              || m.getParameterType(i) == TypeReference.Long) n--;
        int[] params = new int[n];

        int arg_i = 0;
        // there is no "this" parameter when calling this invoke call
        if (n == m.getNumberOfParameters()) {
          for (int i = 0; i < n; i++) {
            params[i] = workingState.getLocal(instruction.args[arg_i]);

            if (m.getParameterType(i) == TypeReference.Double
                || m.getParameterType(i) == TypeReference.Long) arg_i++;
            arg_i++;
          }
        }
        // there is a "this" parameter in this invoke call
        else if (n == m.getNumberOfParameters() + 1) {
          params[0] = workingState.getLocal(instruction.args[0]);

          arg_i = 1;
          for (int i = 0; i < (n - 1); i++) {
            params[i + 1] = workingState.getLocal(instruction.args[arg_i]);
            if (m.getParameterType(i) == TypeReference.Double
                || m.getParameterType(i) == TypeReference.Long) arg_i++;
            arg_i++;
          }
        }
        // this should not happen
        else throw new UnsupportedOperationException("visitInvoke DexSSABuilder, error");

        if (m.getReturnType().equals(TypeReference.Void)) {
          SSAInstruction inst =
              insts.InvokeInstruction(getCurrentInstructionIndex(), params, exc, site, null);
          // System.out.println("Emitting(1) InvokeInstruction: "+inst);
          emitInstruction(inst);
        } else {
          int result = reuseOrCreateDef();
          assert result != -1;
          // TODO: check that this return register is correct
          // I think it be registerCount() or registerCount()+1
          //                  int dest = dexCFG.getDexMethod().regBank.getReturnReg().regID;
          int dest = dexCFG.getDexMethod().getReturnReg();

          setLocal(dest, result);
          SSAInstruction inst =
              insts.InvokeInstruction(
                  getCurrentInstructionIndex(), result, params, exc, site, null);
          // System.out.println("Emitting(2) InvokeInstruction: "+inst);
          emitInstruction(inst);
        }
        //              if (instruction.getPushedWordSize() > 0) {
        //                  int result = reuseOrCreateDef();
        //                  workingState.push(result);
        //                  emitInstruction(insts.InvokeInstruction(result, params, exc, site));
        //              } else {
        //                  emitInstruction(insts.InvokeInstruction(params, exc, site));
        //              }
        // TODO: can other methods do indirect writes to a dex method?
        //
        // doIndirectWrites(bytecodeIndirections.indirectlyWrittenLocals(getCurrentInstructionIndex()), -1);
      }

      // Dex doesn't have local load or store
      //          @Override
      //          public void visitLocalLoad(ILoadInstruction instruction) {
      //              if (instruction.isAddressOf()) {
      //                  int result = reuseOrCreateDef();
      //
      //                  int t = workingState.getLocal(instruction.getVarIndex());
      //                  if (t == -1) {
      //                      doIndirectWrites(new int[]{instruction.getVarIndex()}, -1);
      //                      t = workingState.getLocal(instruction.getVarIndex());
      //                  }
      //
      //                  TypeReference type = ShrikeUtil.makeTypeReference(loader,
      // instruction.getType());
      //                  emitInstruction(insts.AddressOfInstruction(result, t, type));
      //                  workingState.push(result);
      //              } else {
      //                  super.visitLocalLoad(instruction);
      //              }
      //          }
      //
      //          /*
      //           * @see
      // com.ibm.wala.shrikeBT.IInstruction.Visitor#visitLocalStore(com.ibm.wala.shrikeBT.StoreInstruction)
      //           */
      //          @Override
      //          public void visitLocalStore(IStoreInstruction instruction) {
      //              if (localMap != null) {
      //                  localMap.startRange(getCurrentInstructionIndex(),
      // instruction.getVarIndex(), workingState.peek());
      //              }
      //              super.visitLocalStore(instruction);
      //          }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitMonitor(MonitorInstruction) */
      @Override
      public void visitMonitor(Monitor instruction) {
        int ref = workingState.getLocal(instruction.object);
        //              int ref = workingState.pop();
        emitInstruction(
            insts.MonitorInstruction(getCurrentInstructionIndex(), ref, instruction.enter));
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitNew(NewInstruction) */
      @Override
      public void visitNew(New instruction) {
        int dest = instruction.destination;
        int result = reuseOrCreateDef();
        //              TypeReference t = ShrikeUtil.makeTypeReference(loader,
        // instruction.getType());
        //              NewSiteReference ref = NewSiteReference.make(getCurrentProgramCounter(), t);

        //              if (t.isArrayType()) {
        //                  int[] sizes = new int[instruction.getArrayBoundsCount()];
        //                  for (int i = 0; i < instruction.getArrayBoundsCount(); i++) {
        //                      sizes[instruction.getArrayBoundsCount() - 1 - i] =
        // workingState.pop();
        //                  }
        //                  emitInstruction(insts.NewInstruction(result, ref, sizes));
        //              } else {
        emitInstruction(
            insts.NewInstruction(getCurrentInstructionIndex(), result, instruction.newSiteRef));
        //                  popN(instruction);
        //              }
        setLocal(dest, result);
        //              workingState.push(result);
      }

      @Override
      public void visitNewArray(NewArray instruction) {
        int dest = instruction.destination;
        int result = reuseOrCreateDef();
        int[] sizes = new int[instruction.sizes.length];
        for (int i = 0; i < instruction.sizes.length; i++) {
          sizes[i] = workingState.getLocal(instruction.sizes[i]);
        }
        emitInstruction(
            insts.NewInstruction(
                getCurrentInstructionIndex(), result, instruction.newSiteRef, sizes));
        setLocal(dest, result);
      }

      @Override
      public void visitNewArrayFilled(NewArrayFilled instruction) {
        int dest = instruction.destination;
        int result = reuseOrCreateDef();
        int[] sizes = new int[instruction.sizes.length];
        for (int i = 0; i < instruction.sizes.length; i++) {
          sizes[i] = symbolTable.getConstant(instruction.sizes[i]);
        }
        emitInstruction(
            insts.NewInstruction(
                getCurrentInstructionIndex(), result, instruction.newSiteRef, sizes));
        setLocal(dest, result);

        /*
         * we need to emit these instructions somehow, but for now this clobbers the emitInstruction mechanism
         *
        for (int i = 0; i < instruction.args.length; i++)
        {
            int value = workingState.getLocal(instruction.args[i]);
            int index = symbolTable.getConstant(i);
            int arrayRef = result;
            TypeReference t = instruction.myType;
            emitInstruction(insts.ArrayStoreInstruction(getCurrentInstructionIndex(), arrayRef, index, value, t));
        }
        */
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitGet(IGetInstruction) */
      @Override
      public void visitPutField(PutField instruction) {
        int value = workingState.getLocal(instruction.source);
        FieldReference f =
            FieldReference.findOrCreate(
                loader, instruction.clazzName, instruction.fieldName, instruction.fieldType);
        //              int value = workingState.pop();
        if (instruction instanceof PutField.PutStaticField) {
          //              if (instruction.isStatic()) {
          emitInstruction(insts.PutInstruction(getCurrentInstructionIndex(), value, f));
        } else if (instruction instanceof PutField.PutInstanceField) {
          //              } else {
          int ref = workingState.getLocal(((PutField.PutInstanceField) instruction).instance);
          //                  int ref = workingState.pop();
          emitInstruction(insts.PutInstruction(getCurrentInstructionIndex(), ref, value, f));
        } else {
          throw new IllegalArgumentException("Unknown subclass of PutField: " + instruction);
        }
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitReturn(ReturnInstruction) */
      @Override
      public void visitReturn(Return instruction) {
        if (instruction instanceof Return.ReturnDouble) {
          // TODO: figure out how to return a double
          Return.ReturnDouble retD = (Return.ReturnDouble) instruction;
          int result = workingState.getLocal(retD.source1);
          //                  boolean isPrimitive = symbolTable.isLongConstant(result) ||
          // symbolTable.isDoubleConstant(result);
          boolean isPrimitive = true;
          emitInstruction(
              insts.ReturnInstruction(getCurrentInstructionIndex(), result, isPrimitive));
          //                  throw new UnsupportedOperationException("can't yet support returning
          // doubles");
        } else if (instruction instanceof Return.ReturnSingle) {
          Return.ReturnSingle retS = (Return.ReturnSingle) instruction;
          int result = workingState.getLocal(retS.source);
          // TODO: figure out if this is primitive or not
          // boolean isPrimitive = false;
          boolean isPrimitive = retS.isPrimitive();
          emitInstruction(
              insts.ReturnInstruction(getCurrentInstructionIndex(), result, isPrimitive));
        } else if (instruction instanceof Return.ReturnVoid) {
          emitInstruction(insts.ReturnInstruction(getCurrentInstructionIndex()));
        }
        //              if (instruction.getPoppedCount() == 1) {
        //                  int result = workingState.pop();
        //                  TypeReference t = ShrikeUtil.makeTypeReference(loader,
        // instruction.getType());
        //                  emitInstruction(insts.ReturnInstruction(result, t.isPrimitiveType()));
        //              } else {
        //                  emitInstruction(insts.ReturnInstruction());
        //              }
      }

      // TODO: this is just a binary operation
      //          @Override
      //          public void visitShift(IShiftInstruction instruction) {
      //              int val2 = workingState.pop();
      //              int val1 = workingState.pop();
      //              int result = reuseOrCreateDef();
      //              workingState.push(result);
      //              emitInstruction(insts.BinaryOpInstruction(instruction.getOperator(), false,
      // instruction.isUnsigned(), result, val1, val2,
      //                      true));
      //          }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitSwitch(SwitchInstruction) */
      @Override
      public void visitSwitch(Switch instruction) {
        int val = workingState.getLocal(instruction.regA);
        //              int val = workingState.pop();
        // TODO: figure out if the switch offset should refer to a pc offset or an instruction id or
        // what
        emitInstruction(
            insts.SwitchInstruction(
                getCurrentInstructionIndex(),
                val,
                instruction.getDefaultLabel(),
                instruction.getCasesAndLabels()));
      }

      private Dominators<ISSABasicBlock> dom = null;

      @SuppressWarnings("unused")
      private int findRethrowException() {
        int index = getCurrentInstructionIndex();
        SSACFG.BasicBlock bb = cfg.getBlockForInstruction(index);
        if (bb.isCatchBlock()) {
          SSACFG.ExceptionHandlerBasicBlock newBB = (SSACFG.ExceptionHandlerBasicBlock) bb;
          SSAGetCaughtExceptionInstruction s = newBB.getCatchInstruction();
          return s.getDef();
        } else {
          // TODO: should we really use dominators here? maybe it would be cleaner to propagate
          // the notion of 'current exception to rethrow' using the abstract interpreter.
          if (dom == null) {
            dom = Dominators.make(cfg, cfg.entry());
          }

          ISSABasicBlock x = bb;
          while (x != null) {
            if (x.isCatchBlock()) {
              SSACFG.ExceptionHandlerBasicBlock newBB = (SSACFG.ExceptionHandlerBasicBlock) x;
              SSAGetCaughtExceptionInstruction s = newBB.getCatchInstruction();
              return s.getDef();
            } else {
              x = dom.getIdom(x);
            }
          }

          // assert false;
          return -1;
        }
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitThrow(ThrowInstruction) */
      @Override
      public void visitThrow(Throw instruction) {
        int throwable = workingState.getLocal(instruction.throwable);
        assert symbolTable.getMaxValueNumber() >= throwable;
        emitInstruction(insts.ThrowInstruction(getCurrentInstructionIndex(), throwable));
        //              if (instruction.isRethrow()) {
        //                  workingState.clearStack();
        //                  emitInstruction(insts.ThrowInstruction(findRethrowException()));
        //              } else {
        //                  int exception = workingState.pop();
        //                  workingState.clearStack();
        //                  workingState.push(exception);
        //                  emitInstruction(insts.ThrowInstruction(exception));
        //              }
      }

      /** @see com.ibm.wala.shrikeBT.IInstruction.Visitor#visitUnaryOp(IUnaryOpInstruction) */
      @Override
      public void visitUnaryOperation(UnaryOperation instruction) {

        if (instruction.op == UnaryOperation.OpID.MOVE_EXCEPTION) {

          int idx = getCurrentInstructionIndex();
          int bbidx = dexCFG.getBlockForInstruction(idx).getNumber();
          ExceptionHandlerBasicBlock newBB = (ExceptionHandlerBasicBlock) cfg.getBasicBlock(bbidx);

          SSAGetCaughtExceptionInstruction s = newBB.getCatchInstruction();
          int exceptionValue;
          if (s == null) {
            exceptionValue = symbolTable.newSymbol();
            s =
                insts.GetCaughtExceptionInstruction(
                    newBB.getLastInstructionIndex(), bbidx, exceptionValue);
            newBB.setCatchInstruction(s);
          } else {
            exceptionValue = s.getException();
          }

          setLocal(instruction.destination, exceptionValue);
          return;
        }

        // System.out.println("Instruction: " + getCurrentInstructionIndex());
        int val = workingState.getLocal(instruction.source);
        //              int val = workingState.pop();
        //              workingState.push(result);
        if (instruction.isConversion()) {
          TypeReference fromType, toType;
          boolean overflows = false;
          // TODO: figure out if any of these can overflow
          switch (instruction.op) {
            case DOUBLETOLONG:
              fromType = TypeReference.Double;
              toType = TypeReference.Long;
              break;
            case DOUBLETOFLOAT:
              fromType = TypeReference.Double;
              toType = TypeReference.Float;
              break;
            case INTTOBYTE:
              fromType = TypeReference.Int;
              toType = TypeReference.Byte;
              break;
            case INTTOCHAR:
              fromType = TypeReference.Int;
              toType = TypeReference.Char;
              break;
            case INTTOSHORT:
              fromType = TypeReference.Int;
              toType = TypeReference.Short;
              break;
            case DOUBLETOINT:
              fromType = TypeReference.Double;
              toType = TypeReference.Int;
              break;
            case FLOATTODOUBLE:
              fromType = TypeReference.Float;
              toType = TypeReference.Double;
              break;
            case FLOATTOLONG:
              fromType = TypeReference.Float;
              toType = TypeReference.Long;
              break;
            case FLOATTOINT:
              fromType = TypeReference.Float;
              toType = TypeReference.Int;
              break;
            case LONGTODOUBLE:
              fromType = TypeReference.Long;
              toType = TypeReference.Double;
              break;
            case LONGTOFLOAT:
              fromType = TypeReference.Long;
              toType = TypeReference.Float;
              break;
            case LONGTOINT:
              fromType = TypeReference.Long;
              toType = TypeReference.Int;
              break;
            case INTTODOUBLE:
              fromType = TypeReference.Int;
              toType = TypeReference.Double;
              break;
            case INTTOFLOAT:
              fromType = TypeReference.Int;
              toType = TypeReference.Float;
              break;
            case INTTOLONG:
              fromType = TypeReference.Int;
              toType = TypeReference.Long;
              break;
            default:
              throw new IllegalArgumentException(
                  "unknown conversion type "
                      + instruction.op
                      + " in unary instruction: "
                      + instruction);
          }
          int dest = instruction.destination;
          int result = reuseOrCreateDef();
          setLocal(dest, result);
          emitInstruction(
              insts.ConversionInstruction(
                  getCurrentInstructionIndex(), result, val, fromType, toType, overflows));
        } else {
          if (instruction.op == UnaryOperation.OpID.MOVE) {
            setLocal(instruction.destination, workingState.getLocal(instruction.source));
          } else if (instruction.op == UnaryOperation.OpID.MOVE_WIDE) {
            setLocal(instruction.destination, workingState.getLocal(instruction.source));
            if (instruction.source == dexCFG.getDexMethod().getReturnReg())
              setLocal(instruction.destination + 1, workingState.getLocal(instruction.source));
            else
              setLocal(instruction.destination + 1, workingState.getLocal(instruction.source + 1));
          } else {
            int dest = instruction.destination;
            int result = reuseOrCreateDef();
            setLocal(dest, result);
            emitInstruction(
                insts.UnaryOpInstruction(
                    getCurrentInstructionIndex(), instruction.getOperator(), result, val));
          }
        }
      }

      // dex doesn't have indirect reads
      //          private void doIndirectReads(int[] locals) {
      //              for(int i = 0; i < locals.length; i++) {
      //                  ssaIndirections.setUse(getCurrentInstructionIndex(), new
      // ShrikeLocalName(locals[i]), workingState.getLocal(locals[i]));
      //              }
      //          }

      // dex doesn't have load or store, even with indirection
      //          @Override
      //          public void visitLoadIndirect(ILoadIndirectInstruction instruction) {
      //              int addressVal = workingState.pop();
      //              int result = reuseOrCreateDef();
      //
      // doIndirectReads(bytecodeIndirections.indirectlyReadLocals(getCurrentInstructionIndex()));
      //              TypeReference t = ShrikeUtil.makeTypeReference(loader,
      // instruction.getPushedType(null));
      //              emitInstruction(insts.LoadIndirectInstruction(result, t, addressVal));
      //              workingState.push(result);
      //          }
      //
      //          private void doIndirectWrites(int[] locals, int rval) {
      //              for(int i = 0; i < locals.length; i++) {
      //                  ShrikeLocalName name = new ShrikeLocalName(locals[i]);
      //                  int idx = getCurrentInstructionIndex();
      //                  if (ssaIndirections.getDef(idx, name) == -1) {
      //                      ssaIndirections.setDef(idx, name, rval==-1? symbolTable.newSymbol():
      // rval);
      //                  }
      //                  workingState.setLocal(locals[i], ssaIndirections.getDef(idx, name));
      //              }
      //          }
      //
      //          @Override
      //          public void visitStoreIndirect(IStoreIndirectInstruction instruction) {
      //              int val = workingState.pop();
      //              int addressVal = workingState.pop();
      //
      // doIndirectWrites(bytecodeIndirections.indirectlyWrittenLocals(getCurrentInstructionIndex()), val);
      //              TypeReference t = ShrikeUtil.makeTypeReference(loader, instruction.getType());
      //              emitInstruction(insts.StoreIndirectInstruction(addressVal, val, t));
      //          }

    }

    private void reuseOrCreatePi(SSAInstruction piCause, int ref) {
      int n = getCurrentInstructionIndex();
      SSACFG.BasicBlock bb = cfg.getBlockForInstruction(n);

      BasicBlock path = getCurrentSuccessor();
      int outNum = dexCFG.getNumber(path);

      SSAPiInstruction pi = bb.getPiForRefAndPath(ref, path);
      if (pi == null) {
        pi =
            insts.PiInstruction(
                getCurrentInstructionIndex(),
                symbolTable.newSymbol(),
                ref,
                bb.getNumber(),
                outNum,
                piCause);
        bb.addPiForRefAndPath(ref, path, pi);
      }

      workingState.replaceValue(ref, pi.getDef());
    }

    // private void maybeInsertPi(int val) {
    // if ((addPiForFieldSelect) && (creators.length > val) && (creators[val] instanceof
    // SSAGetInstruction)
    // && !((SSAGetInstruction) creators[val]).isStatic()) {
    // reuseOrCreatePi(creators[val], val);
    // } else if ((addPiForDispatchSelect)
    // && (creators.length > val)
    // && (creators[val] instanceof SSAInvokeInstruction)
    // && (((SSAInvokeInstruction) creators[val]).getInvocationCode() ==
    // IInvokeInstruction.Dispatch.VIRTUAL ||
    // ((SSAInvokeInstruction) creators[val])
    // .getInvocationCode() == IInvokeInstruction.Dispatch.INTERFACE)) {
    // reuseOrCreatePi(creators[val], val);
    // }
    // }

    private void maybeInsertPi(SSAAbstractInvokeInstruction call) {
      if (piNodePolicy != null) {
        Pair<Integer, SSAInstruction> pi = piNodePolicy.getPi(call, symbolTable);
        if (pi != null) {
          reuseOrCreatePi(pi.snd, pi.fst);
        }
      }
    }

    private void maybeInsertPi(SSAConditionalBranchInstruction cond) {
      if (piNodePolicy != null) {
        Pair<Integer, SSAInstruction> pi =
            piNodePolicy.getPi(cond, getDef(cond.getUse(0)), getDef(cond.getUse(1)), symbolTable);
        if (pi != null) {
          reuseOrCreatePi(pi.snd, pi.fst);
        }
      }
    }

    private SSAInstruction getDef(int vn) {
      if (vn < creators.length) {
        return creators[vn];
      } else {
        return null;
      }
    }

    class EdgeVisitor extends Visitor {

      @Override
      public void visitInvoke(Invoke instruction) {
        maybeInsertPi((SSAAbstractInvokeInstruction) getCurrentInstruction());
      }

      @Override
      public void visitBranch(Branch instruction) {
        maybeInsertPi((SSAConditionalBranchInstruction) getCurrentInstruction());
      }
    }

    @Override
    public Instruction[] getInstructions() {
      return dexCFG.getDexMethod().getDexInstructions();
    }
  }

  /** Build the IR */
  public void build() {
    try {
      solve();
      if (localMap != null) {
        localMap.finishLocalMap(this);
      }
    } catch (AssertionError e) {
      System.err.println("When handling method " + method.getReference());
      e.printStackTrace();
      // throw e;
    }
  }

  public SSA2LocalMap getLocalMap() {
    return localMap;
  }

  public ShrikeIndirectionData getIndirectionData() {
    return shrikeIndirections;
  }

  /**
   * A logical mapping from &lt;pc, valueNumber&gt; -&gt; local number Note: make sure this class
   * remains static: this persists as part of the IR!!
   */
  private static class SSA2LocalMap implements com.ibm.wala.ssa.IR.SSA2LocalMap {

    private final DexCFG dexCFG;

    /**
     * Mapping Integer -&gt; IntPair where p maps to (vn,L) iff we've started a range at pc p where
     * value number vn corresponds to local L
     */
    private final IntPair[] localStoreMap;

    /**
     * For each basic block i and local j, block2LocalState[i][j] gives the contents of local j at
     * the start of block i
     */
    private final int[][] block2LocalState;

    /** maximum number of locals used at any program point */
    private final int maxLocals;

    /**
     * @param nInstructions number of instructions in the bytecode for this method
     * @param nBlocks number of basic blocks in the CFG
     */
    SSA2LocalMap(DexCFG dexCfg, int nInstructions, int nBlocks, int maxLocals) {
      dexCFG = dexCfg;
      localStoreMap = new IntPair[nInstructions];
      block2LocalState = new int[nBlocks][];
      this.maxLocals = maxLocals;
    }

    /**
     * Record the beginning of a new range, starting at the given program counter, in which a
     * particular value number corresponds to a particular local number
     */
    @SuppressWarnings("unused")
    void startRange(int pc, int localNumber, int valueNumber) {
      int max = ((DexIMethod) dexCFG.getMethod()).getMaxLocals();
      if (localNumber >= max) {
        assert false : "invalid local " + localNumber + '>' + max;
      }

      localStoreMap[pc] = new IntPair(valueNumber, localNumber);
    }

    /** Finish populating the map of local variable information */
    private void finishLocalMap(DexSSABuilder builder) {
      for (BasicBlock bb : dexCFG) {
        MachineState S = builder.getIn(bb);
        int number = bb.getNumber();
        block2LocalState[number] = S.getLocals();
      }
    }

    /**
     * @param index - index into IR instruction array
     * @param vn - value number
     */
    @Override
    public String[] getLocalNames(int index, int vn) {
      try {
        if (!dexCFG.getMethod().hasLocalVariableTable()) {
          return null;
        } else {
          int[] localNumbers = findLocalsForValueNumber(index, vn);
          if (localNumbers == null) {
            return null;
          } else {
            DexIMethod m = dexCFG.getDexMethod();
            String[] result = new String[localNumbers.length];
            for (int i = 0; i < localNumbers.length; i++) {
              result[i] = m.getLocalVariableName(m.getBytecodeIndex(index), localNumbers[i]);
            }
            return result;
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
        Assertions.UNREACHABLE();
        return null;
      }
    }

    /**
     * @param pc a program counter (index into ShrikeBT instruction array)
     * @param vn a value number
     * @return if we know that immediately after the given program counter, v_vn corresponds to some
     *     set of locals, then return an array of the local numbers. else return null.
     */
    private int[] findLocalsForValueNumber(int pc, int vn) {

      IBasicBlock<Instruction> bb = dexCFG.getBlockForInstruction(pc);
      int firstInstruction = bb.getFirstInstructionIndex();
      // walk forward from the first instruction to reconstruct the
      // state of the locals at this pc
      int[] locals = block2LocalState[bb.getNumber()];
      if (locals == null) {
        locals = allocateNewLocalsArray();
      }
      for (int i = firstInstruction; i <= pc; i++) {
        if (localStoreMap[i] != null) {
          IntPair p = localStoreMap[i];
          locals[p.getY()] = p.getX();
        }
      }
      return extractIndices(locals, vn);
    }

    public int[] allocateNewLocalsArray() {
      int[] result = new int[maxLocals];
      for (int i = 0; i < maxLocals; i++) {
        result[i] = OPTIMISTIC ? TOP : BOTTOM;
      }
      return result;
    }

    /** @return the indices i s.t. x[i] == y, or null if none found. */
    private static int[] extractIndices(int[] x, int y) {
      int count = 0;
      for (int element : x) {
        if (element == y) {
          count++;
        }
      }
      if (count == 0) {
        return null;
      } else {
        int[] result = new int[count];
        int j = 0;
        for (int i = 0; i < x.length; i++) {
          if (x[i] == y) {
            result[j++] = i;
          }
        }
        return result;
      }
    }
  }
}
