/*
 * Copyright (c) 2002 - 2006 IBM Corporation.
 * 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
 */
package com.ibm.wala.ssa;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import com.ibm.wala.cfg.AbstractCFG;
import com.ibm.wala.cfg.BytecodeCFG;
import com.ibm.wala.cfg.ControlFlowGraph;
import com.ibm.wala.cfg.IBasicBlock;
import com.ibm.wala.cfg.InducedCFG;
import com.ibm.wala.cfg.MinimalCFG;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IClassLoader;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.shrikeBT.ExceptionHandler;
import com.ibm.wala.shrikeBT.IInstruction;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.EmptyIterator;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.Iterator2Collection;
import com.ibm.wala.util.collections.Iterator2Iterable;
import com.ibm.wala.util.collections.MapIterator;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.debug.UnimplementedError;
import com.ibm.wala.util.graph.impl.NumberedNodeIterator;
import com.ibm.wala.util.intset.BitVector;
import com.ibm.wala.util.intset.IntSet;
import com.ibm.wala.util.shrike.ShrikeUtil;
import com.ibm.wala.util.warnings.Warning;
import com.ibm.wala.util.warnings.Warnings;

/**
 * A control-flow graph for ssa form.
 * 
 * This implementation is uglified in the name of performance. This implementation does not directly track the graph structure, but
 * instead delegates to a prebuilt {@link ControlFlowGraph} which stores the structure. This decision from 2004 may have been
 * premature optimization, left over from a world where {@link IR}s and related structures were long-lived. In today's system, they
 * are cached and reconstituted by {@link SSACache}. Perhaps we should just extend {@link AbstractCFG} and not worry so much about
 * space.
 * 
 * As the current implementation stands, the delegate graph stores the graph structure, and this class additionally stores
 * {@link BasicBlock}s and the {@link SSAInstruction} array.
 */
public class SSACFG implements ControlFlowGraph<SSAInstruction, ISSABasicBlock>, MinimalCFG<ISSABasicBlock> {

  private static final boolean DEBUG = false;

  /**
   * The {@link ISSABasicBlock}s which live in this graph. These {@link BasicBlock}s must have the same numbers as the corresponding
   * {@link IBasicBlock}s in the delegate {@link AbstractCFG}. This array is additionally co-indexed by these numbers.
   */
  private BasicBlock[] basicBlocks;

  /**
   * The "normal" instructions which constitute the SSA form. This does not include {@link SSAPhiInstruction}s, which dwell in
   * {@link BasicBlock}s instead.
   */
  final protected SSAInstruction[] instructions;

  /**
   * The {@link IMethod} this {@link ControlFlowGraph} represents
   */
  final protected IMethod method;

  /**
   * A delegate CFG, pre-built, which stores the graph structure of this CFG.
   */
  final protected AbstractCFG<IInstruction, IBasicBlock<IInstruction>> delegate;

  /**
   * cache a ref to the exit block for efficient access
   */
  private BasicBlock exit;

  /**
   * @throws IllegalArgumentException if method is null
   */
  @SuppressWarnings("unchecked")
  public SSACFG(IMethod method, AbstractCFG cfg, SSAInstruction[] instructions) {
    if (method == null) {
      throw new IllegalArgumentException("method is null");
    }
    this.delegate = cfg;
    if (DEBUG) {
      System.err.println(("Incoming CFG for " + method + ':'));
      System.err.println(cfg.toString());
    }

    this.method = method;
    assert method.getDeclaringClass() != null : "null declaring class for " + method;
    createBasicBlocks(cfg);
    if (cfg instanceof InducedCFG) {
      addPhisFromInducedCFG((InducedCFG) cfg);
      addPisFromInducedCFG((InducedCFG) cfg);
    }
    if (cfg instanceof BytecodeCFG) {
      recordExceptionTypes(((BytecodeCFG) cfg).getExceptionHandlers(), method.getDeclaringClass().getClassLoader());
    }
    this.instructions = instructions;

  }

  /**
   * This is ugly. Clean it up someday. {@link InducedCFG}s carry around pii instructions. add these pii instructions to the
   * SSABasicBlocks
   */
  private void addPisFromInducedCFG(InducedCFG cfg) {
    for (com.ibm.wala.cfg.InducedCFG.BasicBlock ib : cfg) {
      // we rely on the invariant that basic blocks in this cfg are numbered identically as in the source
      // InducedCFG
      BasicBlock b = getBasicBlock(ib.getNumber());
      for (SSAPiInstruction pi : ib.getPis()) {
        BasicBlock path = getBasicBlock(pi.getSuccessor());
        b.addPiForRefAndPath(pi.getVal(), path, pi);
      }
    }
  }

  /**
   * This is ugly. Clean it up someday. {@link InducedCFG}s carry around phi instructions. add these phi instructions to the
   * SSABasicBlocks
   */
  private void addPhisFromInducedCFG(InducedCFG cfg) {
    for (com.ibm.wala.cfg.InducedCFG.BasicBlock ib : cfg) {
      // we rely on the invariant that basic blocks in this cfg are numbered identically as in the source
      // InducedCFG
      BasicBlock b = getBasicBlock(ib.getNumber());
      // this is really ugly. we pretend that each successively phi in the basic block defs a
      // particular 'local'. TODO: fix the API in some way so that this is unnecessary.
      // {What does Julian do for, say, Javascript?}
      int local = 0;
      for (SSAPhiInstruction phi : ib.getPhis()) {
        b.addPhiForLocal(local++, phi);
      }
    }

  }

  @Override
  public int hashCode() {
    return -3 * delegate.hashCode();
  }

  @Override
  public boolean equals(Object o) {
    return (o instanceof SSACFG) && delegate.equals(((SSACFG) o).delegate);
  }

  private void recordExceptionTypes(Set<ExceptionHandler> set, IClassLoader loader) {
    for (ExceptionHandler handler : set) {
      TypeReference t = null;
      if (handler.getCatchClass() == null) {
        // by convention, in ShrikeCT this means catch everything
        t = TypeReference.JavaLangThrowable;
      } else if (handler.getCatchClassLoader() instanceof ClassLoaderReference) {
        t = ShrikeUtil.makeTypeReference((ClassLoaderReference)handler.getCatchClassLoader(), handler.getCatchClass());
      } else {
        TypeReference exceptionType = ShrikeUtil.makeTypeReference(loader.getReference(), handler.getCatchClass());
        IClass klass = null;
        klass = loader.lookupClass(exceptionType.getName());
        if (klass == null) {
          Warnings.add(ExceptionLoadFailure.create(exceptionType, method));
          t = exceptionType;
        } else {
          t = klass.getReference();
        }
      }
      int instructionIndex = handler.getHandler();
      IBasicBlock<?> b = getBlockForInstruction(instructionIndex);
      assert b instanceof ExceptionHandlerBasicBlock : "not exception handler " + b + " index " + instructionIndex;
      ExceptionHandlerBasicBlock bb = (ExceptionHandlerBasicBlock) getBlockForInstruction(instructionIndex);
      bb.addCaughtExceptionType(t);
    }
  }

  private void createBasicBlocks(AbstractCFG<?, ?> G) {
    basicBlocks = new BasicBlock[G.getNumberOfNodes()];
    for (int i = 0; i <= G.getMaxNumber(); i++) {
      if (G.getCatchBlocks().get(i)) {
        basicBlocks[i] = new ExceptionHandlerBasicBlock(i);
      } else {
        basicBlocks[i] = new BasicBlock(i);
      }
    }
    exit = basicBlocks[delegate.getNumber(delegate.exit())];
  }

  /**
   * Get the basic block an instruction belongs to. Note: the instruction2Block array is filled in lazily. During initialization,
   * the mapping is set up only for the first instruction of each basic block.
   */
  @Override
  public SSACFG.BasicBlock getBlockForInstruction(int instructionIndex) {
    IBasicBlock<IInstruction> N = delegate.getBlockForInstruction(instructionIndex);
    int number = delegate.getNumber(N);
    return basicBlocks[number];
  }

  /**
   * NB: Use iterators such as IR.iterateAllInstructions() instead of this method. This will probably be deprecated someday.
   * 
   * Return the instructions. Note that the CFG is created from the Shrike CFG prior to creating the SSA instructions.
   * 
   * @return an array containing the SSA instructions.
   */
  @Override
  public SSAInstruction[] getInstructions() {
    return instructions;
  }

  private final Map<RefPathKey, SSAPiInstruction> piInstructions = HashMapFactory.make(2);

  private static class RefPathKey {
    private final int n;

    private final Object src;

    private final Object path;

    RefPathKey(int n, Object src, Object path) {
      this.n = n;
      this.src = src;
      this.path = path;
    }

    @Override
    public int hashCode() {
      return n * path.hashCode();
    }

    @Override
    public boolean equals(Object x) {
      return (x instanceof RefPathKey) && n == ((RefPathKey) x).n && src == ((RefPathKey) x).src && path == ((RefPathKey) x).path;
    }
  }

  /**
   * A Basic Block in an SSA IR
   */
  public class BasicBlock implements ISSABasicBlock {

    /**
     * state needed for the numbered graph.
     */
    private final int number;

    /**
     * List of PhiInstructions associated with the entry of this block.
     */
    private SSAPhiInstruction stackSlotPhis[];

    private SSAPhiInstruction localPhis[];

    private final static int initialCapacity = 10;

    public BasicBlock(int number) {
      this.number = number;
    }

    @Override
    public int getNumber() {
      return number;
    }

    /**
     * Method getFirstInstructionIndex.
     */
    @Override
    public int getFirstInstructionIndex() {
      IBasicBlock<?> B = delegate.getNode(number);
      return B.getFirstInstructionIndex();
    }

    /**
     * Is this block marked as a catch block?
     */
    @Override
    public boolean isCatchBlock() {
      return delegate.getCatchBlocks().get(getNumber());
    }

    @Override
    public int getLastInstructionIndex() {
      IBasicBlock<?> B = delegate.getNode(number);
      return B.getLastInstructionIndex();
    }

    /*
     * @see com.ibm.wala.ssa.ISSABasicBlock#iteratePhis()
     */
    @Override
    public Iterator<SSAPhiInstruction> iteratePhis() {
      compressPhis();
      if (stackSlotPhis == null) {
        if (localPhis == null) {
          return EmptyIterator.instance();
        } else {
          LinkedList<SSAPhiInstruction> result = new LinkedList<>();
          for (SSAPhiInstruction phi : localPhis) {
            if (phi != null) {
              result.add(phi);
            }
          }
          return result.iterator();
        }
      } else {
        // stackSlotPhis != null
        LinkedList<SSAPhiInstruction> result = new LinkedList<>();
        for (SSAPhiInstruction phi : stackSlotPhis) {
          if (phi != null) {
            result.add(phi);
          }
        }
        if (localPhis == null) {
          return result.iterator();
        } else {
          for (SSAPhiInstruction phi : localPhis) {
            if (phi != null) {
              result.add(phi);
            }
          }
          return result.iterator();
        }
      }
    }

    /**
     * This method is used during SSA construction.
     */
    public SSAPhiInstruction getPhiForStackSlot(int slot) {
      if (stackSlotPhis == null) {
        return null;
      } else {
        if (slot >= stackSlotPhis.length) {
          return null;
        } else {
          return stackSlotPhis[slot];
        }
      }
    }

    /**
     * This method is used during SSA construction.
     */
    public SSAPhiInstruction getPhiForLocal(int n) {
      if (localPhis == null) {
        return null;
      } else {
        if (n >= localPhis.length) {
          return null;
        } else {
          return localPhis[n];
        }
      }
    }

    public void addPhiForStackSlot(int slot, SSAPhiInstruction phi) {
      if (stackSlotPhis == null) {
        stackSlotPhis = new SSAPhiInstruction[initialCapacity];
      }
      if (slot >= stackSlotPhis.length) {
        stackSlotPhis = Arrays.copyOf(stackSlotPhis, slot * 2);
      }
      stackSlotPhis[slot] = phi;
    }

    public void addPhiForLocal(int n, SSAPhiInstruction phi) {
      if (localPhis == null) {
        localPhis = new SSAPhiInstruction[initialCapacity];
      }
      if (n >= localPhis.length) {
        localPhis = Arrays.copyOf(localPhis, n * 2);
      }
      localPhis[n] = phi;
    }

    /**
     * Remove any phis in the set.
     */
    public void removePhis(Set<SSAPhiInstruction> toRemove) {
      int nRemoved = 0;
      if (stackSlotPhis != null) {
        for (int i = 0; i < stackSlotPhis.length; i++) {
          if (toRemove.contains(stackSlotPhis[i])) {
            stackSlotPhis[i] = null;
            nRemoved++;
          }
        }
      }
      if (nRemoved > 0) {
        int newLength = stackSlotPhis.length - nRemoved;
        if (newLength == 0) {
          stackSlotPhis = null;
        } else {
          SSAPhiInstruction[] old = stackSlotPhis;
          stackSlotPhis = new SSAPhiInstruction[newLength];
          int j = 0;
          for (SSAPhiInstruction element : old) {
            if (element != null) {
              stackSlotPhis[j++] = element;
            }
          }
        }
      }
      nRemoved = 0;
      if (localPhis != null) {
        for (int i = 0; i < localPhis.length; i++) {
          if (toRemove.contains(localPhis[i])) {
            localPhis[i] = null;
            nRemoved++;
          }
        }
      }
      if (nRemoved > 0) {
        int newLength = localPhis.length - nRemoved;
        if (newLength == 0) {
          localPhis = null;
        } else {
          SSAPhiInstruction[] old = localPhis;
          localPhis = new SSAPhiInstruction[newLength];
          int j = 0;
          for (SSAPhiInstruction element : old) {
            if (element != null) {
              localPhis[j++] = element;
            }
          }
        }
      }
    }

    public SSAPiInstruction getPiForRefAndPath(int n, Object path) {
      return piInstructions.get(new RefPathKey(n, this, path));
    }

    private final LinkedList<SSAPiInstruction> blockPiInstructions = new LinkedList<>();

    /**
     * 
     * @param n can be the val in the pi instruction
     * @param path can be the successor block in the pi instruction
     */
    public void addPiForRefAndPath(int n, Object path, SSAPiInstruction pi) {
      piInstructions.put(new RefPathKey(n, this, path), pi);
      blockPiInstructions.add(pi);
    }

    @Override
    public Iterator<SSAPiInstruction> iteratePis() {
      return blockPiInstructions.iterator();
    }

    public Iterator<SSAInstruction> iterateNormalInstructions() {
      int lookup = getFirstInstructionIndex();
      final int end = getLastInstructionIndex();
      // skip to first non-null instruction
      while (lookup <= end && instructions[lookup] == null) {
        lookup++;
      }
      final int dummy = lookup;
      return new Iterator<SSAInstruction>() {
        private int start = dummy;

        @Override
        public boolean hasNext() {
          return (start <= end);
        }

        @Override
        public SSAInstruction next() {
          SSAInstruction i = instructions[start];
          start++;
          while (start <= end && instructions[start] == null) {
            start++;
          }
          return i;
        }

        @Override
        public void remove() {
          throw new UnsupportedOperationException();
        }
      };
    }

    /**
     * TODO: make this more efficient if needed
     */
    public List<SSAInstruction> getAllInstructions() {
      compressPhis();

      ArrayList<SSAInstruction> result = new ArrayList<>();
      for (SSAInstruction inst : Iterator2Iterable.make(iteratePhis())) {
        result.add(inst);
      }

      for (int i = getFirstInstructionIndex(); i <= getLastInstructionIndex(); i++) {
        SSAInstruction s = instructions[i];
        if (s != null) {
          result.add(s);
        }
      }

      for (SSAInstruction inst : Iterator2Iterable.make(iteratePis())) {
        result.add(inst);
      }
      return result;
    }

    /**
     * rewrite the phi arrays so they have no null entries.
     */
    private void compressPhis() {
      if (stackSlotPhis != null && stackSlotPhis[stackSlotPhis.length - 1] == null) {
        int size = countNonNull(stackSlotPhis);
        if (size == 0) {
          stackSlotPhis = null;
        } else {
          SSAPhiInstruction[] old = stackSlotPhis;
          stackSlotPhis = new SSAPhiInstruction[size];
          int j = 0;
          for (SSAPhiInstruction element : old) {
            if (element != null) {
              stackSlotPhis[j++] = element;
            }
          }
        }
      }
      if (localPhis != null && localPhis[localPhis.length - 1] == null) {
        int size = countNonNull(localPhis);
        if (size == 0) {
          localPhis = null;
        } else {
          SSAPhiInstruction[] old = localPhis;
          localPhis = new SSAPhiInstruction[size];
          int j = 0;
          for (SSAPhiInstruction element : old) {
            if (element != null) {
              localPhis[j++] = element;
            }
          }
        }
      }
    }

    private int countNonNull(SSAPhiInstruction[] a) {
      int result = 0;
      for (SSAPhiInstruction element : a) {
        if (element != null) {
          result++;
        }
      }
      return result;
    }

    @Override
    public Iterator<SSAInstruction> iterator() {
      return getAllInstructions().iterator();
    }

    /**
     * @return true iff this basic block has at least one phi
     */
    public boolean hasPhi() {
      return stackSlotPhis != null || localPhis != null;
    }

    /*
     * @see com.ibm.wala.util.graph.INodeWithNumber#getGraphNodeId()
     */
    @Override
    public int getGraphNodeId() {
      return number;
    }

    /*
     * @see com.ibm.wala.util.graph.INodeWithNumber#setGraphNodeId(int)
     */
    @Override
    public void setGraphNodeId(int number) {
      // TODO Auto-generated method stub
    }

    @Override
    public String toString() {
      return "BB[SSA:" + getFirstInstructionIndex() + ".." + getLastInstructionIndex() + ']' + getNumber() + " - "
          + method.getSignature();
    }

    private SSACFG getGraph() {
      return SSACFG.this;
    }

    @Override
    public boolean equals(Object arg0) {
      if (arg0 instanceof BasicBlock) {
        BasicBlock b = (BasicBlock) arg0;
        if (getNumber() == b.getNumber()) {
          if (getMethod().equals(b.getMethod())) {
            return getGraph().equals(b.getGraph());
          } else {
            return false;
          }
        } else {
          return false;
        }
      } else {
        return false;
      }
    }

    @Override
    public IMethod getMethod() {
      return method;
    }

    @Override
    public int hashCode() {
      return delegate.getNode(getNumber()).hashCode() * 6271;
    }

    /*
     * @see com.ibm.wala.cfg.IBasicBlock#isExitBlock()
     */
    @Override
    public boolean isExitBlock() {
      return this == SSACFG.this.exit();
    }

    /*
     * @see com.ibm.wala.cfg.IBasicBlock#isEntryBlock()
     */
    @Override
    public boolean isEntryBlock() {
      return this == SSACFG.this.entry();
    }

    @Override
    public SSAInstruction getLastInstruction() {
      return instructions[getLastInstructionIndex()];
    }

    /**
     * The {@link ExceptionHandlerBasicBlock} subclass will override this.
     *
     */
    @Override
    public Iterator<TypeReference> getCaughtExceptionTypes() {
      return EmptyIterator.instance();
    }
  }

  public class ExceptionHandlerBasicBlock extends BasicBlock {

    /**
     * The type of the exception caught by this block.
     */
    private TypeReference[] exceptionTypes;

    private final static int initialCapacity = 3;

    private int nExceptionTypes = 0;

    /**
     * Instruction that defines the exception value this block catches
     */
    private SSAGetCaughtExceptionInstruction catchInstruction;

    public ExceptionHandlerBasicBlock(int number) {
      super(number);
    }

    public SSAGetCaughtExceptionInstruction getCatchInstruction() {
      return catchInstruction;
    }

    public void setCatchInstruction(SSAGetCaughtExceptionInstruction catchInstruction) {
      this.catchInstruction = catchInstruction;
    }

    @Override
    public Iterator<TypeReference> getCaughtExceptionTypes() {
      return new Iterator<TypeReference>() {
        int next = 0;

        @Override
        public boolean hasNext() {
          return next < nExceptionTypes;
        }

        @Override
        public TypeReference next() {
          return exceptionTypes[next++];
        }

        @Override
        public void remove() {
          Assertions.UNREACHABLE();
        }
      };
    }

    @Override
    public String toString() {
      return "BB(Handler)[SSA]" + getNumber() + " - " + method.getSignature();
    }

    public void addCaughtExceptionType(TypeReference exceptionType) {
      if (exceptionTypes == null) {
        exceptionTypes = new TypeReference[initialCapacity];
      }
      nExceptionTypes++;
      if (nExceptionTypes > exceptionTypes.length) {
        exceptionTypes = Arrays.copyOf(exceptionTypes, nExceptionTypes * 2);
      }
      exceptionTypes[nExceptionTypes - 1] = exceptionType;
    }

    @Override
    public List<SSAInstruction> getAllInstructions() {
      List<SSAInstruction> result = super.getAllInstructions();
      if (catchInstruction != null) {
        // it's disturbing that catchInstruction can be null here. must be some
        // strange corner case
        // involving dead code. oh well .. keep on chugging until we have hard
        // evidence that this is a bug
        result.add(0, catchInstruction);
      }
      return result;
    }

  }

  @Override
  public String toString() {
    StringBuilder s = new StringBuilder();
    for (int i = 0; i <= getNumber(exit()); i++) {
      BasicBlock bb = getNode(i);
      s.append("BB").append(i).append('[').append(bb.getFirstInstructionIndex()).append("..").append(bb.getLastInstructionIndex())
          .append("]\n");

      Iterator<ISSABasicBlock> succNodes = getSuccNodes(bb);
      while (succNodes.hasNext()) {
        s.append("    -> BB").append(((BasicBlock) succNodes.next()).getNumber()).append('\n');
      }
    }
    return s.toString();
  }

  @Override
  public BitVector getCatchBlocks() {
    return delegate.getCatchBlocks();
  }

  /**
   * is the given i a catch block?
   * 
   * @return true if catch block, false otherwise
   */
  public boolean isCatchBlock(int i) {
    return delegate.isCatchBlock(i);
  }

  /*
   * @see com.ibm.wala.cfg.ControlFlowGraph#entry()
   */
  @Override
  public SSACFG.BasicBlock entry() {
    return basicBlocks[0];
  }

  /*
   * @see com.ibm.wala.cfg.ControlFlowGraph#exit()
   */
  @Override
  public SSACFG.BasicBlock exit() {
    return exit;
  }

  /*
   * @see com.ibm.wala.util.graph.NumberedGraph#getNumber(com.ibm.wala.util.graph.Node)
   */
  @Override
  public int getNumber(ISSABasicBlock b) throws IllegalArgumentException {
    if (b == null) {
      throw new IllegalArgumentException("N == null");
    }
    return b.getNumber();
  }

  /*
   * @see com.ibm.wala.util.graph.NumberedGraph#getNode(int)
   */
  @Override
  public BasicBlock getNode(int number) {
    return basicBlocks[number];
  }

  /*
   * @see com.ibm.wala.util.graph.NumberedGraph#getMaxNumber()
   */
  @Override
  public int getMaxNumber() {
    return basicBlocks.length - 1;
  }

  /*
   * @see com.ibm.wala.util.graph.Graph#iterateNodes()
   */
  @Override
  public Iterator<ISSABasicBlock> iterator() {
    return Arrays.<ISSABasicBlock>asList(basicBlocks).iterator();
  }

  /*
   * @see com.ibm.wala.util.graph.Graph#getNumberOfNodes()
   */
  @Override
  public int getNumberOfNodes() {
    return delegate.getNumberOfNodes();
  }

  /*
   * @see com.ibm.wala.util.graph.Graph#getPredNodes(com.ibm.wala.util.graph.Node)
   */
  @Override
  public Iterator<ISSABasicBlock> getPredNodes(ISSABasicBlock b) throws IllegalArgumentException {
    if (b == null) {
      throw new IllegalArgumentException("b == null");
    }
    IBasicBlock<IInstruction> n = delegate.getNode(b.getNumber());
    final Iterator<IBasicBlock<IInstruction>> i = delegate.getPredNodes(n);
    return new Iterator<ISSABasicBlock>() {
      @Override
      public boolean hasNext() {
        return i.hasNext();
      }

      @Override
      public BasicBlock next() {
        IBasicBlock<?> n = i.next();
        int number = n.getNumber();
        return basicBlocks[number];
      }

      @Override
      public void remove() {
        Assertions.UNREACHABLE();
      }
    };
  }

  /*
   * @see com.ibm.wala.util.graph.Graph#getPredNodeCount(com.ibm.wala.util.graph.Node)
   */
  @Override
  public int getPredNodeCount(ISSABasicBlock b) throws IllegalArgumentException {
    if (b == null) {
      throw new IllegalArgumentException("b == null");
    }
    IBasicBlock<IInstruction> n = delegate.getNode(b.getNumber());
    return delegate.getPredNodeCount(n);
  }

  /*
   * @see com.ibm.wala.util.graph.Graph#getSuccNodes(com.ibm.wala.util.graph.Node)
   */
  @Override
  public Iterator<ISSABasicBlock> getSuccNodes(ISSABasicBlock b) throws IllegalArgumentException {
    if (b == null) {
      throw new IllegalArgumentException("b == null");
    }
    IBasicBlock<IInstruction> n = delegate.getNode(b.getNumber());
    final Iterator<IBasicBlock<IInstruction>> i = delegate.getSuccNodes(n);
    return new Iterator<ISSABasicBlock>() {
      @Override
      public boolean hasNext() {
        return i.hasNext();
      }

      @Override
      public ISSABasicBlock next() {
        IBasicBlock<?> n = i.next();
        int number = n.getNumber();
        return basicBlocks[number];
      }

      @Override
      public void remove() {
        Assertions.UNREACHABLE();
      }
    };
  }

  /*
   * @see com.ibm.wala.util.graph.Graph#getSuccNodeCount(com.ibm.wala.util.graph.Node)
   */
  @Override
  public int getSuccNodeCount(ISSABasicBlock b) throws IllegalArgumentException {
    if (b == null) {
      throw new IllegalArgumentException("b == null");
    }
    IBasicBlock<IInstruction> n = delegate.getNode(b.getNumber());
    return delegate.getSuccNodeCount(n);
  }

  /*
   * @see com.ibm.wala.util.graph.NumberedGraph#addNode(com.ibm.wala.util.graph.Node)
   */
  @Override
  public void addNode(ISSABasicBlock n) throws UnsupportedOperationException {
    throw new UnsupportedOperationException();
  }

  @Override
  public void addEdge(ISSABasicBlock src, ISSABasicBlock dst) throws UnsupportedOperationException {
    throw new UnsupportedOperationException();
  }

  @Override
  public void removeEdge(ISSABasicBlock src, ISSABasicBlock dst) throws UnsupportedOperationException {
    throw new UnsupportedOperationException();
  }

  /*
   * @see com.ibm.wala.util.graph.EdgeManager#removeEdges(com.ibm.wala.util.graph.Node)
   */
  @Override
  public void removeAllIncidentEdges(ISSABasicBlock node) throws UnsupportedOperationException {
    throw new UnsupportedOperationException();
  }

  /*
   * @see com.ibm.wala.util.graph.Graph#removeNode(com.ibm.wala.util.graph.Node)
   */
  @Override
  public void removeNodeAndEdges(ISSABasicBlock N) throws UnsupportedOperationException {
    throw new UnsupportedOperationException();
  }

  /*
   * @see com.ibm.wala.util.graph.NodeManager#remove(com.ibm.wala.util.graph.Node)
   */
  @Override
  public void removeNode(ISSABasicBlock n) throws UnsupportedOperationException {
    throw new UnsupportedOperationException();
  }

  /*
   * @see com.ibm.wala.cfg.ControlFlowGraph#getProgramCounter(int)
   */
  @Override
  public int getProgramCounter(int index) {
    // delegate to incoming cfg.
    return delegate.getProgramCounter(index);
  }

  /*
   * @see com.ibm.wala.util.graph.Graph#containsNode(com.ibm.wala.util.graph.Node)
   */
  @Override
  public boolean containsNode(ISSABasicBlock N) {
    if (N instanceof BasicBlock) {
      return basicBlocks[getNumber(N)] == N;
    } else {
      return false;
    }
  }

  /*
   * @see com.ibm.wala.cfg.ControlFlowGraph#getMethod()
   */
  @Override
  public IMethod getMethod() {
    return method;
  }

  /*
   * @see com.ibm.wala.cfg.ControlFlowGraph#getExceptionalSuccessors(com.ibm.wala.cfg.IBasicBlock)
   */
  @Override
  public List<ISSABasicBlock> getExceptionalSuccessors(final ISSABasicBlock b) {
    if (b == null) {
      throw new IllegalArgumentException("b is null");
    }
    final IBasicBlock<IInstruction> n = delegate.getNode(b.getNumber());
    final Collection<IBasicBlock<IInstruction>> ss = delegate.getExceptionalSuccessors(n);
    final List<ISSABasicBlock> c = new ArrayList<>(getSuccNodeCount(b));
    for (final IBasicBlock<IInstruction> s : ss) {
      c.add(basicBlocks[delegate.getNumber(s)]);
    }
    return c;
  }

  /*
   * @see com.ibm.wala.cfg.ControlFlowGraph#getExceptionalSuccessors(com.ibm.wala.cfg.IBasicBlock)
   */
  @Override
  public Collection<ISSABasicBlock> getExceptionalPredecessors(ISSABasicBlock b) {
    if (b == null) {
      throw new IllegalArgumentException("b is null");
    }
    IBasicBlock<IInstruction> n = delegate.getNode(b.getNumber());
    Function<IBasicBlock<IInstruction>, ISSABasicBlock> f = object -> basicBlocks[delegate.getNumber(object)];
    return Iterator2Collection.toSet(new MapIterator<>(delegate
        .getExceptionalPredecessors(n).iterator(), f));
  }

  private IBasicBlock<IInstruction> getUnderlyingBlock(SSACFG.BasicBlock block) {
    return delegate.getNode(getNumber(block));
  }

  /**
   * has exceptional edge src -&gt; dest
   * 
   * @throws IllegalArgumentException if dest is null
   */
  public boolean hasExceptionalEdge(BasicBlock src, BasicBlock dest) {
    if (dest == null) {
      throw new IllegalArgumentException("dest is null");
    }
    if (dest.isExitBlock()) {
      int srcNum = getNumber(src);
      return delegate.getExceptionalToExit().get(srcNum);
    }
    return delegate.hasExceptionalEdge(getUnderlyingBlock(src), getUnderlyingBlock(dest));
  }

  /**
   * has normal edge src -&gt; dest
   * 
   * @throws IllegalArgumentException if dest is null
   */
  public boolean hasNormalEdge(BasicBlock src, BasicBlock dest) {
    if (dest == null) {
      throw new IllegalArgumentException("dest is null");
    }
    if (dest.isExitBlock()) {
      int srcNum = getNumber(src);
      return delegate.getNormalToExit().get(srcNum);
    }
    return delegate.hasNormalEdge(getUnderlyingBlock(src), getUnderlyingBlock(dest));
  }

  /*
   * @see com.ibm.wala.cfg.ControlFlowGraph#getNormalSuccessors(com.ibm.wala.cfg.IBasicBlock)
   */
  @Override
  public Collection<ISSABasicBlock> getNormalSuccessors(ISSABasicBlock b) {
    if (b == null) {
      throw new IllegalArgumentException("b is null");
    }
    IBasicBlock<IInstruction> n = delegate.getNode(b.getNumber());
    final Collection<IBasicBlock<IInstruction>> ss = delegate.getNormalSuccessors(n);
    Collection<ISSABasicBlock> c = new ArrayList<>(getSuccNodeCount(b));
    for (IBasicBlock<IInstruction> s : ss) {
      c.add(basicBlocks[delegate.getNumber(s)]);
    }
    return c;
  }

  /*
   * @see com.ibm.wala.cfg.ControlFlowGraph#getNormalSuccessors(com.ibm.wala.cfg.IBasicBlock)
   */
  @Override
  public Collection<ISSABasicBlock> getNormalPredecessors(ISSABasicBlock b) {
    if (b == null) {
      throw new IllegalArgumentException("b is null");
    }
    IBasicBlock<IInstruction> n = delegate.getNode(b.getNumber());
    final Collection<IBasicBlock<IInstruction>> ss = delegate.getNormalPredecessors(n);
    Collection<ISSABasicBlock> c = new ArrayList<>(getPredNodeCount(b));
    for (IBasicBlock<IInstruction> s : ss) {
      c.add(basicBlocks[delegate.getNumber(s)]);
    }
    return c;
  }

  /*
   * @see com.ibm.wala.util.graph.NumberedNodeManager#iterateNodes(com.ibm.wala.util.intset.IntSet)
   */
  @Override
  public Iterator<ISSABasicBlock> iterateNodes(IntSet s) {
    return new NumberedNodeIterator<>(s, this);
  }

  @Override
  public void removeIncomingEdges(ISSABasicBlock node) throws UnsupportedOperationException {
    throw new UnsupportedOperationException();
  }

  @Override
  public void removeOutgoingEdges(ISSABasicBlock node) throws UnsupportedOperationException {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean hasEdge(ISSABasicBlock src, ISSABasicBlock dst) throws UnimplementedError {
    return getSuccNodeNumbers(src).contains(getNumber(dst));
  }

  @Override
  public IntSet getSuccNodeNumbers(ISSABasicBlock b) throws IllegalArgumentException {
    if (b == null) {
      throw new IllegalArgumentException("b == null");
    }
    IBasicBlock<IInstruction> n = delegate.getNode(b.getNumber());
    return delegate.getSuccNodeNumbers(n);
  }

  @Override
  public IntSet getPredNodeNumbers(ISSABasicBlock node) throws UnimplementedError {
    Assertions.UNREACHABLE();
    return null;
  }

  /**
   * A warning for when we fail to resolve the type for a checkcast
   */
  private static class ExceptionLoadFailure extends Warning {

    final TypeReference type;

    final IMethod method;

    ExceptionLoadFailure(TypeReference type, IMethod method) {
      super(Warning.MODERATE);
      this.type = type;
      this.method = method;
    }

    @Override
    public String getMsg() {
      return getClass().toString() + " : " + type + ' ' + method;
    }

    public static ExceptionLoadFailure create(TypeReference type, IMethod method) {
      return new ExceptionLoadFailure(type, method);
    }
  }

  /**
   * @return the basic block with a particular number
   */
  public BasicBlock getBasicBlock(int bb) {
    return basicBlocks[bb];
  }

}
