/*******************************************************************************
 * 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 java.util.Iterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ibm.wala.dalvik.classLoader.DexCFG;
import com.ibm.wala.dalvik.classLoader.DexCFG.BasicBlock;
import com.ibm.wala.dalvik.classLoader.DexConstants;
import com.ibm.wala.dalvik.classLoader.DexIMethod;
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.BinaryOperation;
import com.ibm.wala.dalvik.dex.instructions.Branch;
import com.ibm.wala.dalvik.dex.instructions.Constant;
import com.ibm.wala.dalvik.dex.instructions.GetField;
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.PutField;
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.dataflow.graph.AbstractMeetOperator;
import com.ibm.wala.dataflow.graph.BasicFramework;
import com.ibm.wala.dataflow.graph.DataflowSolver;
import com.ibm.wala.dataflow.graph.IKilldallFramework;
import com.ibm.wala.dataflow.graph.ITransferFunctionProvider;
import com.ibm.wala.fixpoint.AbstractStatement;
import com.ibm.wala.fixpoint.AbstractVariable;
import com.ibm.wala.fixpoint.FixedPointConstants;
import com.ibm.wala.fixpoint.IVariable;
import com.ibm.wala.fixpoint.UnaryOperator;
import com.ibm.wala.shrikeBT.ArrayLengthInstruction;
import com.ibm.wala.shrikeBT.ConstantInstruction;
import com.ibm.wala.shrikeBT.IArrayLoadInstruction;
import com.ibm.wala.shrikeBT.IArrayStoreInstruction;
import com.ibm.wala.shrikeBT.IBinaryOpInstruction;
import com.ibm.wala.shrikeBT.IConditionalBranchInstruction;
import com.ibm.wala.shrikeBT.IGetInstruction;
import com.ibm.wala.shrikeBT.IInvokeInstruction;
import com.ibm.wala.shrikeBT.IPutInstruction;
import com.ibm.wala.shrikeBT.IUnaryOpInstruction;
import com.ibm.wala.shrikeBT.InstanceofInstruction;
import com.ibm.wala.shrikeBT.MonitorInstruction;
import com.ibm.wala.shrikeBT.NewInstruction;
import com.ibm.wala.shrikeBT.SwitchInstruction;
import com.ibm.wala.shrikeBT.ThrowInstruction;
import com.ibm.wala.util.CancelException;
import com.ibm.wala.util.CancelRuntimeException;
import com.ibm.wala.util.debug.UnimplementedError;

/**
 * Skeleton of functionality to propagate information through the Java bytecode stack machine using ShrikeBT.
 * <p>
 * This class computes properties the Java operand stack and of the local variables at the beginning of each basic block.
 * <p>
 * In this implementation, each dataflow variable value is an integer, and the "meeter" object provides the meets
 */
public abstract class AbstractIntRegisterMachine implements FixedPointConstants {
	private static final Logger logger = LoggerFactory.getLogger(AbstractIntRegisterMachine.class);

    private static final boolean DEBUG = false;

    public static final int TOP = -1;

    public static final int BOTTOM = -2;

    public static final int UNANALYZED = -3;

    public static final int IGNORE = -4;

    /**
     * The solver
     */
    private DataflowSolver solver;

    /**
     * The control flow graph to analyze
     */
    final private DexCFG cfg;

    /**
     * The max height of the stack.
     */
    //  final private int maxStackHeight;

    /**
     * the max number of locals in play
     */
    final protected int maxLocals;

    /**
     * Should uninitialized variables be considered TOP (optimistic) or BOTTOM (pessimistic);
     */
    final public static boolean OPTIMISTIC = true;

    protected AbstractIntRegisterMachine(final DexCFG G) {
        if (G == null) {
            throw new IllegalArgumentException("G is null");
        }
        //    maxStackHeight = G.getMaxStackHeight();
        maxLocals = G.getDexMethod().getMaxLocals();
        this.cfg = G;
    }

    protected void init(Meeter meeter, final FlowProvider flow) {
        final MeetOperator meet = new MeetOperator(meeter);
        ITransferFunctionProvider<BasicBlock, MachineState> xferFunctions = new ITransferFunctionProvider<BasicBlock, MachineState>() {
            public boolean hasNodeTransferFunctions() {
                return flow.needsNodeFlow();
            }

            public boolean hasEdgeTransferFunctions() {
                return flow.needsEdgeFlow();
            }

            public UnaryOperator<MachineState> getNodeTransferFunction(final BasicBlock node) {
                return new UnaryOperator<MachineState>() {
                    @Override
                    public byte evaluate(MachineState lhs, MachineState rhs) {

                        MachineState exit = lhs;
                        MachineState entry = rhs;

                        MachineState newExit = flow.flow(entry, node);
                        if (newExit.stateEquals(exit)) {
                            return NOT_CHANGED;
                        } else {
                            exit.copyState(newExit);
                            return CHANGED;
                        }
                    }

                    @Override
                    public String toString() {
                        return "NODE-FLOW";
                    }

                    @Override
                    public int hashCode() {
                        return 9973 * node.hashCode();
                    }

                    @Override
                    public boolean equals(Object o) {
                        return this == o;
                    }
                };
            }

            public UnaryOperator<MachineState> getEdgeTransferFunction(final BasicBlock from, final BasicBlock to) {
                return new UnaryOperator<MachineState>() {
                    @Override
                    public byte evaluate(MachineState lhs, MachineState rhs) {

                        MachineState exit = lhs;
                        MachineState entry = rhs;

                        MachineState newExit = flow.flow(entry, from, to);
                        if (newExit.stateEquals(exit)) {
                            return NOT_CHANGED;
                        } else {
                            exit.copyState(newExit);
                            return CHANGED;
                        }
                    }

                    @Override
                    public String toString() {
                        return "EDGE-FLOW";
                    }

                    @Override
                    public int hashCode() {
                        return 9973 * (from.hashCode() ^ to.hashCode());
                    }

                    @Override
                    public boolean equals(Object o) {
                        return this == o;
                    }
                };
            }

            public AbstractMeetOperator<MachineState> getMeetOperator() {
                return meet;
            }
        };

        IKilldallFramework<BasicBlock, MachineState> problem = new BasicFramework<BasicBlock, MachineState>(cfg, xferFunctions);
        solver = new DataflowSolver<BasicBlock, MachineState>(problem) {
            private MachineState entry;

            @Override
            protected MachineState makeNodeVariable(BasicBlock n, boolean IN) {
                assert n != null;
                MachineState result = new MachineState(n);
                if (IN && n.equals(cfg.entry())) {
                    entry = result;
                }
                return result;
            }

            @Override
            protected MachineState makeEdgeVariable(BasicBlock from, BasicBlock to) {
                assert from != null;
                assert to != null;
                MachineState result = new MachineState(from);

                return result;
            }

            @Override
            protected void initializeWorkList() {
                super.buildEquations(false, false);
                /*
                 * Add only the entry variable to the work list.
                 */
                 for (Iterator it = getFixedPointSystem().getStatementsThatUse(entry); it.hasNext();) {
                     AbstractStatement s = (AbstractStatement) it.next();
                     addToWorkList(s);
                 }
            }

            @Override
            protected void initializeVariables() {
                super.initializeVariables();
                AbstractIntRegisterMachine.this.initializeVariables();
            }

            @Override
            protected MachineState[] makeStmtRHS(int size) {
                return new MachineState[size];
            }
        };

    }

    public boolean solve() {
        try {
            return solver.solve(null);
        } catch (CancelException e) {
            throw new CancelRuntimeException(e);
        }
    }

    /**
     * Convenience method ... a little ugly .. perhaps delete later.
     */
    protected void initializeVariables() {
    }

    public MachineState getEntryState() {
        return (MachineState) solver.getIn(cfg.entry());
    }

    /**
     * @return the state at the entry to a given block
     */
    public MachineState getIn(BasicBlock bb) {
        return (MachineState) solver.getIn(bb);
    }

    private class MeetOperator extends AbstractMeetOperator<MachineState> {

        private final Meeter meeter;

        MeetOperator(Meeter meeter) {
            this.meeter = meeter;
        }

        @Override
        public boolean isUnaryNoOp() {
            return false;
        }

        @Override
        public byte evaluate(MachineState lhs, MachineState[] rhs) {
            BasicBlock bb = lhs.getBasicBlock();
            // TODO
//          Exception e = new Exception("evaluating a MeetOperator");
//          e.printStackTrace();
//          return NOT_CHANGED;
            if (!bb.isCatchBlock()) {
                return meet(lhs, rhs, bb, meeter) ? CHANGED : NOT_CHANGED;
            } else {
                return meetForCatchBlock(lhs, rhs, bb, meeter) ? CHANGED : NOT_CHANGED;
            }
        }

        @Override
        public int hashCode() {
            return 72223 * meeter.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof MeetOperator) {
                MeetOperator other = (MeetOperator) o;
                return meeter.equals(other.meeter);
            } else {
                return false;
            }
        }

        @Override
        public String toString() {
            return "MEETER";
        }
    }

    /**
     * A Meeter object provides the dataflow logic needed to meet the abstract machine state for a dataflow meet.
     */
    protected interface Meeter {

        /**
         * Return the integer that represents the meet of a particular stack slot at the entry to a basic block.
         *
         * @param slot The stack slot to meet
         * @param rhs The values to meet
         * @param bb The basic block at whose entry this meet occurs
         * @return The value result of the meet
         */
//      int meetStack(int slot, int[] rhs, IBasicBlock<Instruction> bb);

        /**
         * Return the integer that represents stack slot 0 after a meet at the entry to a catch block.
         *
         * @param bb The basic block at whose entry this meet occurs
         * @return The value of stack slot 0 after the meet
         */
        int meetStackAtCatchBlock(BasicBlock bb);

        /**
         * Return the integer that represents the meet of a particular local at the entry to a basic block.
         *
         * @param n The number of the local
         * @param rhs The values to meet
         * @param bb The basic block at whose entry this meet occurs
         * @return The value of local n after the meet.
         */
        int meetLocal(int n, int[] rhs, BasicBlock bb);
    }

    /**
     * Evaluate a meet of machine states.
     *
     * TODO: add some efficiency shortcuts. TODO: clean up and refactor.
     *
     * @param bb the basic block at whose entry the meet occurs
     * @return true if the lhs value changes. false otherwise.
     */
    private boolean meet(IVariable lhs, IVariable[] rhs, BasicBlock bb, Meeter meeter) {

//      boolean changed = meetStacks(lhs, rhs, bb, meeter);

        return meetLocals(lhs, rhs, bb, meeter);
//      return changed;
    }

    /**
     * Evaluate a meet of machine states at a catch block.
     *
     * TODO: add some efficiency shortcuts. TODO: clean up and refactor.
     *
     * @param bb the basic block at whose entry the meet occurs
     * @return true if the lhs value changes. false otherwise.
     */
    private boolean meetForCatchBlock(IVariable lhs, IVariable[] rhs, BasicBlock bb, Meeter meeter) {

        //TODO: do we need to check the meet of the catch block?  possibly compare the value of meet with the symboltable values of the exception registers?
        boolean changed = meetRegistersAtCatchBlock(lhs, bb, meeter);

        changed |= meetLocals(lhs, rhs, bb, meeter);

//      int meet = meeter.meetStackAtCatchBlock(bb);
//      boolean changed = meetLocals(lhs, rhs, bb, meeter);
        return changed;
    }

    /**
     * Evaluate a meet of the registers of machine states at the entry of a catch block.
     *
     * TODO: add some efficiency shortcuts. TODO: clean up and refactor.
     *
     * @param bb the basic block at whose entry the meet occurs
     * @return true if the lhs value changes. false otherwise.
     */
    private boolean meetRegistersAtCatchBlock(IVariable lhs, BasicBlock bb, Meeter meeter) {
        boolean changed = false;
        MachineState L = (MachineState) lhs;

        // evaluate the meet of the stack of height 1, which holds the exception
        // object.

        // allocate lhs registers if it's
        // not already allocated.
        if (L.locals == null) {
            L.allocateLocals();
        }

        DexIMethod dMethod = (DexIMethod)L.getBasicBlock().getMethod();
        //System.out.println("dMethod local size: " + dMethod.getExceptionReg() + " - local size: " + L.locals.length);
        assert(L.locals.length == dMethod.getExceptionReg()+1);
        for (int i = 0; i < L.locals.length; i++)
        logger.debug("local: " + L.locals[i]);

        int meet = meeter.meetStackAtCatchBlock(bb);
        if (L.locals[dMethod.getExceptionReg()] == TOP) {
            if (meet != TOP) {
                changed = true;
                L.locals[dMethod.getExceptionReg()] = meet;
            }
        } else if (meet != L.locals[dMethod.getExceptionReg()]) {
            changed = true;
            L.locals[dMethod.getExceptionReg()] = meet;
        }
        return changed;
    }

    /**
     * Evaluate a meet of the stacks of machine states at the entry of a catch block.
     *
     * TODO: add some efficiency shortcuts. TODO: clean up and refactor.
     *
     * @param bb the basic block at whose entry the meet occurs
     * @return true if the lhs value changes. false otherwise.
     */
//  private boolean meetStacksAtCatchBlock(IVariable lhs, IBasicBlock<Instruction> bb, Meeter meeter) {
//      boolean changed = false;
//      MachineState L = (MachineState) lhs;
//
//      // evaluate the meet of the stack of height 1, which holds the exception
//      // object.
//
//      // allocate lhs.stack if it's
//      // not already allocated.
//      if (L.stack == null) {
//          L.allocateStack();
//          L.stackHeight = 1;
//      }
//
//      int meet = meeter.meetStackAtCatchBlock(bb);
//      if (L.stack[0] == TOP) {
//          if (meet != TOP) {
//              changed = true;
//              L.stack[0] = meet;
//          }
//      } else if (meet != L.stack[0]) {
//          changed = true;
//          L.stack[0] = meet;
//      }
//      return changed;
//  }

    /**
     * Evaluate a meet of the stacks of machine states at the entry of a basic block.
     *
     * TODO: add some efficiency shortcuts. TODO: clean up and refactor.
     *
     * @param bb the basic block at whose entry the meet occurs
     * @return true if the lhs value changes. false otherwise.
     */
//  private boolean meetStacks(IVariable lhs, IVariable[] rhs, IBasicBlock<Instruction> bb, Meeter meeter) {
//      boolean changed = false;
//      MachineState L = (MachineState) lhs;
//
//      // evaluate the element-wise meet over the stacks
//
//      // first ... how high are the stacks?
//      int height = computeMeetStackHeight(rhs);
//
//      // if there's any stack height to meet, allocate lhs.stack if it's
//      // not already allocated.
//      if (height > -1 && L.stack == null) {
//          L.allocateStack();
//          L.stackHeight = height;
//          changed = true;
//      }
//
//      // now do the element-wise meet.
//      for (int i = 0; i < height; i++) {
//          int[] R = new int[rhs.length];
//          for (int j = 0; j < R.length; j++) {
//              MachineState m = (MachineState) rhs[j];
//              if (m.stack == null) {
//                  R[j] = TOP;
//              } else {
//                  R[j] = m.stack[i];
//                  if (R[j] == 0) {
//                      R[j] = TOP;
//                  }
//              }
//          }
//          int meet = meeter.meetStack(i, R, bb);
//          if (L.stack[i] == TOP) {
//              if (meet != TOP) {
//                  changed = true;
//                  L.stack[i] = meet;
//              }
//          } else if (meet != L.stack[i]) {
//              changed = true;
//              L.stack[i] = meet;
//          }
//      }
//      return changed;
//  }

    /**
     * Evaluate a meet of locals of machine states at the entry to a basic block.
     *
     * TODO: add some efficiency shortcuts. TODO: clean up and refactor.
     *
     * @param bb the basic block at whose entry the meet occurs
     * @return true if the lhs value changes. false otherwise.
     */
    private boolean meetLocals(IVariable lhs, IVariable[] rhs, BasicBlock bb, Meeter meeter) {

        boolean changed = false;
        MachineState L = (MachineState) lhs;
        // need we allocate lhs.locals?
        int nLocals = computeMeetNLocals(rhs);
        if (nLocals > -1 && L.locals == null) {
            L.allocateLocals();
        }

        // evaluate the element-wise meet over the locals.
        for (int i = 0; i < nLocals; i++) {
            int[] R = new int[rhs.length];
            for (int j = 0; j < rhs.length; j++) {
                R[j] = ((MachineState) rhs[j]).getLocal(i);
            }
            int meet = meeter.meetLocal(i, R, bb);
            if (L.locals[i] == TOP) {
                if (meet != TOP) {
                    changed = true;
                    L.locals[i] = meet;
                }
            } else if (meet != L.locals[i]) {
                changed = true;
                L.locals[i] = meet;
            }
        }
        return changed;
    }

    /**
     * @return the number of locals to meet. Return -1 if there is no local meet necessary.
     * @param operands The operands for this operator. operands[0] is the left-hand side.
     */
    private static int computeMeetNLocals(IVariable[] operands) {
        MachineState lhs = (MachineState) operands[0];
        int nLocals = -1;
        if (lhs.locals != null) {
            nLocals = lhs.locals.length;
        } else {
            for (int i = 1; i < operands.length; i++) {
                MachineState rhs = (MachineState) operands[i];
                if (rhs.locals != null) {
                    nLocals = rhs.locals.length;
                    break;
                }
            }
        }
        return nLocals;
    }

    /**
     * @return the height of stacks that are being meeted. Return -1 if there is no stack meet necessary.
     * @param operands The operands for this operator. operands[0] is the left-hand side.
     */
    private static int computeMeetStackHeight(IVariable[] operands) {
        MachineState lhs = (MachineState) operands[0];
        int height = -1;
        if (lhs.stack != null) {
            height = lhs.stackHeight;
        } else {
            for (int i = 1; i < operands.length; i++) {
                MachineState rhs = (MachineState) operands[i];
                if (rhs.stack != null) {
                    height = rhs.stackHeight;
                    break;
                }
            }
        }
        return height;
    }

    /**
     * Representation of the state of the JVM stack machine at some program point.
     */
    public class MachineState extends AbstractVariable<MachineState> {
        private int[] stack;

        private int[] locals;

        // NOTE: stackHeight == -1 is a special code meaning "this variable is TOP"
        private int stackHeight;

        private final BasicBlock bb;

        /**
         * I'm not using clone because I don't want to necessarily inherit the AbstractVariable state from the superclass
         */
        public MachineState duplicate() {
            MachineState result = new MachineState(bb);
            result.copyState(this);
            return result;
        }

        public MachineState(BasicBlock bb) {
            setTOP();
            this.bb = bb;
        }

        public BasicBlock getBasicBlock() {
            return bb;
        }

        void setTOP() {
            stackHeight = -1;
        }

        boolean isTOP() {
            return stackHeight == -1;
        }

//      public void push(int i) {
//          if (stack == null)
//              allocateStack();
//          stack[stackHeight++] = i;
//      }

//      public int pop() {
//          if (stackHeight <= 0) {
//              assert stackHeight > 0 : "can't pop stack of height " + stackHeight;
//          }
//          stackHeight -= 1;
//          return stack[stackHeight];
//      }

//      public int peek() {
//          return stack[stackHeight - 1];
//      }

//      public void swap() {
//          int temp = stack[stackHeight - 1];
//          stack[stackHeight - 1] = stack[stackHeight - 2];
//          stack[stackHeight - 2] = temp;
//      }

//      private void allocateStack() {
//          stack = new int[maxStackHeight + 1];
//          stackHeight = 0;
//      }

        private void allocateLocals() {
            locals = allocateNewLocalsArray();
        }

//      public void clearStack() {
//          stackHeight = 0;
//      }

        /**
         * set the value of local i to symbol j
         *
         * @param i
         * @param j
         */
        public void setLocal(int i, int j) {
            if (locals == null) {
                if (OPTIMISTIC && (j == TOP)) {
                    return;
                } else {
                    allocateLocals();
                }
            }
            locals[i] = j;
        }

        /**
         * @param i
         * @return the number of the symbol corresponding to local i
         */
        public int getLocal(int i) {
            if (locals == null) {
                if (OPTIMISTIC) {
                    return TOP;
                } else {
                    return BOTTOM;
                }
            } else {
                return locals[i];
            }
        }

        public void replaceValue(int from, int to) {
            if (stack != null)
                for (int i = 0; i < stackHeight; i++)
                    if (stack[i] == from)
                        stack[i] = to;

            if (locals != null)
                for (int i = 0; i < maxLocals; i++)
                    if (locals[i] == from)
                        locals[i] = to;
        }

        public boolean hasValue(int val) {
            if (stack != null)
                for (int i = 0; i < stackHeight; i++)
                    if (stack[i] == val)
                        return true;

            if (locals != null)
                for (int i = 0; i < maxLocals; i++)
                    if (locals[i] == val)
                        return true;

            return false;
        }

        @Override
        public String toString() {
            // TODO
            return "Some machine state...";
//          if (isTOP()) {
//              return "<TOP>@" + System.identityHashCode(this);
//          }
//          StringBuffer result = new StringBuffer("<");
//          result.append("S");
//          if (stackHeight == 0) {
//              result.append("[empty]");
//          } else {
//              result.append(array2StringBuffer(stack, stackHeight));
//          }
//          result.append("L");
//          result.append(array2StringBuffer(locals, maxLocals));
//          result.append(">");
//          return result.toString();
        }

//      private StringBuffer array2StringBuffer(int[] array, int n) {
//          StringBuffer result = new StringBuffer("[");
//          if (array == null) {
//              result.append(OPTIMISTIC ? "TOP" : "BOTTOM");
//          } else {
//              for (int i = 0; i < n - 1; i++) {
//                  result.append(array[i]).append(",");
//              }
//              result.append(array[n - 1]);
//          }
//          result.append("]");
//          return result;
//      }

        public void copyState(MachineState other) {
            if (other.stack == null) {
                stack = null;
            } else {
                stack = new int[other.stack.length];
                System.arraycopy(other.stack, 0, stack, 0, other.stack.length);
            }
            if (other.locals == null) {
                locals = null;
            } else {
                locals = new int[other.locals.length];
                System.arraycopy(other.locals, 0, locals, 0, other.locals.length);
            }
            stackHeight = other.stackHeight;
        }

        boolean stateEquals(MachineState exit) {
            if (stackHeight != exit.stackHeight)
                return false;
            if (locals == null) {
                if (exit.locals != null)
                    return false;
            } else {
                if (exit.locals == null)
                    return false;
                else if (locals.length != exit.locals.length)
                    return false;
            }

            for (int i = 0; i < stackHeight; i++) {
                if (stack[i] != exit.stack[i])
                    return false;
            }
            if (locals != null) {
                for (int i = 0; i < locals.length; i++) {
                    if (locals[i] == TOP) {
                        if (exit.locals[i] != TOP)
                            return false;
                    }
                    if (locals[i] != exit.locals[i])
                        return false;
                }
            }
            return true;
        }

        /**
         * Returns the stackHeight.
         *
         * @return int
         */
         public int getStackHeight() {
             return stackHeight;
         }

         /**
          * Use with care.
          */
         public int[] getLocals() {
             return locals;
         }

    }

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

    /**
     * Interface which defines a flow function for a basic block
     */
    public interface FlowProvider {

        public boolean needsNodeFlow();

        public boolean needsEdgeFlow();

        /**
         * Compute the MachineState at the exit of a basic block, given a MachineState at the block's entry.
         */
        public MachineState flow(MachineState entry, BasicBlock basicBlock);

        /**
         * Compute the MachineState at the end of an edge, given a MachineState at the edges's entry.
         */
        public MachineState flow(MachineState entry, BasicBlock from, BasicBlock to);
    }

    /**
     * This gives some basic facilities for shoving things around on the stack. Client analyses should subclass this as needed.
     */
    protected static abstract class BasicRegisterFlowProvider implements FlowProvider, DexConstants {
        private final DexCFG cfg;

        protected MachineState workingState;

        private BasicRegisterMachineVisitor visitor;

        private Visitor edgeVisitor;

        private int currentInstructionIndex = 0;

        private BasicBlock currentBlock;

        private BasicBlock currentSuccessorBlock;

        /**
         * Only subclasses can instantiate
         */
        protected BasicRegisterFlowProvider(DexCFG cfg) {
            this.cfg = cfg;
        }

        /**
         * Initialize the visitors used to perform the flow functions
         */
        protected void init(BasicRegisterMachineVisitor v, Visitor ev) {
            this.visitor = v;
            this.edgeVisitor = ev;
        }

        public boolean needsNodeFlow() {
            return true;
        }

        public boolean needsEdgeFlow() {
            return false;
        }

        public MachineState flow(MachineState entry, BasicBlock basicBlock) {
            workingState = entry.duplicate();
            currentBlock = basicBlock;
            currentSuccessorBlock = null;
            Instruction[] instructions = getInstructions();
            if (DEBUG) {
                logger.debug(("Entry to BB" + cfg.getNumber(basicBlock) + " " + workingState));
            }
            for (int i = basicBlock.getFirstInstructionIndex(); i <= basicBlock.getLastInstructionIndex(); i++) {
                currentInstructionIndex = i;
                instructions[i].visit(visitor);

                if (DEBUG) {
                    logger.debug(("After " + instructions[i] + " " + workingState));
                }
            }
            return workingState;
        }

        public MachineState flow(MachineState entry, BasicBlock from, BasicBlock to) {
            workingState = entry.duplicate();
            currentBlock = from;
            currentSuccessorBlock = to;
            Instruction[] instructions = getInstructions();
            if (DEBUG) {
                logger.debug(("Entry to BB" + cfg.getNumber(from) + " " + workingState));
            }
            for (int i = from.getFirstInstructionIndex(); i <= from.getLastInstructionIndex(); i++) {
                currentInstructionIndex = i;
                instructions[i].visit(edgeVisitor);
                if (DEBUG) {
                    logger.debug(("After " + instructions[i] + " " + workingState));
                }
            }
            return workingState;
        }

        protected int getCurrentInstructionIndex() {
            return currentInstructionIndex;
        }

        protected int getCurrentProgramCounter() {
            return cfg.getProgramCounter(currentInstructionIndex);
        }

        protected BasicBlock getCurrentBlock() {
            return currentBlock;
        }

        protected BasicBlock getCurrentSuccessor() {
            return currentSuccessorBlock;
        }

        public abstract Instruction[] getInstructions();

        /**
         * Update the machine state to account for an instruction
         */
        protected class BasicRegisterMachineVisitor extends Visitor {

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitArrayLength(ArrayLengthInstruction)
             */
            @Override
            public void visitArrayLength(ArrayLength instruction) {

                // TODO
                //        workingState.pop();
                //        workingState.push(UNANALYZED);
                throw new UnimplementedError();
            }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitArrayLoad(IArrayLoadInstruction)
             */
            @Override
            public void visitArrayGet(ArrayGet instruction) {

                // TODO
                //        workingState.pop();
                //        workingState.pop();
                //        workingState.push(UNANALYZED);
                throw new UnimplementedError();
            }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitArrayStore(IArrayStoreInstruction)
             */
            @Override
            public void visitArrayPut(ArrayPut instruction) {
                // TODO
                //        workingState.pop();
                //        workingState.pop();
                //        workingState.pop();
                throw new UnimplementedError();
            }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitBinaryOp(IBinaryOpInstruction)
             */
            @Override
            public void visitBinaryOperation(BinaryOperation instruction) {
                // TODO
//              workingState.pop();
                throw new UnimplementedError();
            }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitComparison(IComparisonInstruction)
             */
//          @Override
//          public void visitComparison(IComparisonInstruction instruction) {
//              workingState.pop();
//              workingState.pop();
//              workingState.push(UNANALYZED);
//          }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitConditionalBranch(IConditionalBranchInstruction)
             */
            @Override
            public void visitBranch(Branch instruction) {
                // TODO
//              workingState.pop();
//              workingState.pop();
                throw new UnimplementedError();
            }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitConstant(ConstantInstruction)
             */
            @Override
            public void visitConstant(Constant instruction) {
                // TODO
//              workingState.push(UNANALYZED);
                throw new UnimplementedError();
            }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitConversion(IConversionInstruction)
             */
//          @Override
//          public void visitConversion(IConversionInstruction instruction) {
//              workingState.pop();
//              workingState.push(UNANALYZED);
//          }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitDup(DupInstruction)
             */
//          @Override
//          public void visitDup(DupInstruction instruction) {
//
//              int size = instruction.getSize();
//              int delta = instruction.getDelta();
//              assert size == 1 || size == 2;
//              assert delta == 0 || delta == 1 || delta == 2;
//              int toPop = size + delta;
//              int v1 = workingState.pop();
//              int v2 = (toPop > 1) ? workingState.pop() : IGNORE;
//              int v3 = (toPop > 2) ? workingState.pop() : IGNORE;
//              int v4 = (toPop > 3) ? workingState.pop() : IGNORE;
//
//              if (size > 1) {
//                  workingState.push(v2);
//              }
//              workingState.push(v1);
//              if (v4 != IGNORE) {
//                  workingState.push(v4);
//              }
//              if (v3 != IGNORE) {
//                  workingState.push(v3);
//              }
//              if (v2 != IGNORE) {
//                  workingState.push(v2);
//              }
//              workingState.push(v1);
//
//          }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitGet(IGetInstruction)
             */
            @Override
            public void visitGetField(GetField instruction) {
                // TODO
//              popN(instruction);
//              workingState.push(UNANALYZED);
                throw new UnimplementedError();
            }

//          protected void popN(IInstruction instruction) {
//              for (int i = 0; i < instruction.getPoppedCount(); i++) {
//                  workingState.pop();
//              }
//          }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitInstanceof(InstanceofInstruction)
             */
            @Override
            public void visitInstanceof(InstanceOf instruction) {
                // TODO
//              workingState.pop();
//              workingState.push(UNANALYZED);
                throw new UnimplementedError();
            }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitInvoke(IInvokeInstruction)
             */
            @Override
            public void visitInvoke(Invoke instruction) {
                // TODO
                throw new UnimplementedError();
//              popN(instruction);
//              ClassLoaderReference loader = cfg.getMethod().getDeclaringClass().getClassLoader().getReference();
//              TypeReference returnType = ShrikeUtil.makeTypeReference(loader, Util.getReturnType(instruction.getMethodSignature()));
//              if (!returnType.equals(TypeReference.Void)) {
//                  workingState.push(UNANALYZED);
//              }
            }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitMonitor(MonitorInstruction)
             */
            @Override
            public void visitMonitor(Monitor instruction) {
                // TODO
//              workingState.pop();
                throw new UnimplementedError();
            }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitLocalLoad(ILoadInstruction)
             */
//          @Override
//          public void visitLocalLoad(ILoadInstruction instruction) {
//              int t = workingState.getLocal(instruction.getVarIndex());
//              workingState.push(t);
//          }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitLocalStore(IStoreInstruction)
             */
//          @Override
//          public void visitLocalStore(IStoreInstruction instruction) {
//              int index = instruction.getVarIndex();
//              workingState.setLocal(index, workingState.pop());
//          }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitNew(NewInstruction)
             */
            @Override
            public void visitNew(New instruction) {
                // TODO
//              popN(instruction);
//              workingState.push(UNANALYZED);
                throw new UnimplementedError();
            }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitPop(PopInstruction)
             */
//          @Override
//          public void visitPop(PopInstruction instruction) {
//              if (instruction.getPoppedCount() > 0) {
//                  workingState.pop();
//              }
//          }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitPut(IPutInstruction)
             */
            @Override
            public void visitPutField(PutField instruction) {
                // TODO
//              popN(instruction);
                throw new UnimplementedError();
            }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitShift(IShiftInstruction)
             */
//          @Override
//          public void visitShift(IShiftInstruction instruction) {
//              workingState.pop();
//          }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitSwap(SwapInstruction)
             */
//          @Override
//          public void visitSwap(SwapInstruction instruction) {
//              workingState.swap();
//          }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitSwitch(SwitchInstruction)
             */
            @Override
            public void visitSwitch(Switch instruction) {
                // TODO
//              workingState.pop();
                throw new UnimplementedError();
            }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitThrow(ThrowInstruction)
             */
            @Override
            public void visitThrow(Throw instruction) {
                // TODO
//              int exceptionType = workingState.pop();
//              workingState.clearStack();
//              workingState.push(exceptionType);
                throw new UnimplementedError();
            }

            /**
             * @see com.ibm.wala.shrikeBT.Instruction.Visitor#visitUnaryOp(IUnaryOpInstruction)
             */
            @Override
            public void visitUnaryOperation(UnaryOperation instruction) {
                // TODO
                // treated as a no-op in basic scheme
                throw new UnimplementedError();
            }
        }
    }
}
