/*
 * 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 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.core.util.shrike.ShrikeUtil;
import com.ibm.wala.core.util.warnings.Warning;
import com.ibm.wala.core.util.warnings.Warnings;
import com.ibm.wala.shrike.shrikeBT.ExceptionHandler;
import com.ibm.wala.shrike.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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;

/**
 * A control-flow graph for ssa form.
 *
 * <p>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.
 *
 * <p>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.
   */
  protected final SSAInstruction[] instructions;

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

  /** A delegate CFG, pre-built, which stores the graph structure of this CFG. */
  protected final 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 = 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.
   *
   * <p>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 static final 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();
    }

    @Override
    public Iterator<SSAPhiInstruction> iteratePhis() {
      compressPhis();
      if (stackSlotPhis == null) {
        if (localPhis == null) {
          return EmptyIterator.instance();
        } else {
          ArrayList<SSAPhiInstruction> result = new ArrayList<>();
          for (SSAPhiInstruction phi : localPhis) {
            if (phi != null) {
              result.add(phi);
            }
          }
          return result.iterator();
        }
      } else {
        // stackSlotPhis != null
        ArrayList<SSAPhiInstruction> result = new ArrayList<>();
        for (SSAPhiInstruction phi : stackSlotPhis) {
          if (phi != null) {
            result.add(phi);
          }
        }
        if (localPhis != null) {
          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 ArrayList<SSAPiInstruction> blockPiInstructions = new ArrayList<>();

    /**
     * @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<>() {
        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 static final 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<>() {
        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);
  }

  @Override
  public SSACFG.BasicBlock entry() {
    return basicBlocks[0];
  }

  @Override
  public SSACFG.BasicBlock exit() {
    return exit;
  }

  @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#iterator() */
  @Override
  public Iterator<ISSABasicBlock> iterator() {
    return Arrays.<ISSABasicBlock>asList(basicBlocks).iterator();
  }

  @Override
  public Stream<ISSABasicBlock> stream() {
    return Arrays.stream(basicBlocks);
  }

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

  /** @see com.ibm.wala.util.graph.Graph#getPredNodes(Object) */
  @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<>() {
      @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(Object) */
  @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(Object) */
  @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<>() {
      @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(Object) */
  @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(Object) */
  @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#removeAllIncidentEdges(Object) */
  @Override
  public void removeAllIncidentEdges(ISSABasicBlock node) throws UnsupportedOperationException {
    throw new UnsupportedOperationException();
  }

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

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

  @Override
  public int getProgramCounter(int index) {
    // delegate to incoming cfg.
    return delegate.getProgramCounter(index);
  }

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

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

  @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(Object) */
  @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));
  }

  @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(Object) */
  @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];
  }
}
