/**
   * Connects cfgNode to the proper CATCH block if target subtree might throw an exception. If there
   * are FINALLY blocks reached before a CATCH, it will make the corresponding entry in finallyMap.
   */
  private void connectToPossibleExceptionHandler(Node cfgNode, Node target) {
    if (mayThrowException(target) && !exceptionHandler.isEmpty()) {
      Node lastJump = cfgNode;
      for (Node handler : exceptionHandler) {
        if (NodeUtil.isFunction(handler)) {
          return;
        }
        Preconditions.checkState(handler.getType() == Token.TRY);
        Node catchBlock = NodeUtil.getCatchBlock(handler);

        if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY.
          if (lastJump == cfgNode) {
            createEdge(cfgNode, Branch.ON_EX, handler.getLastChild());
          } else {
            finallyMap.put(lastJump, handler.getLastChild());
          }
        } else { // Has a catch.
          if (lastJump == cfgNode) {
            createEdge(cfgNode, Branch.ON_EX, catchBlock);
            return;
          } else {
            finallyMap.put(lastJump, catchBlock);
          }
        }
        lastJump = handler;
      }
    }
  }
  private void handleStmtList(Node node) {
    Node parent = node.getParent();
    // Special case, don't add a block of empty CATCH block to the graph.
    if (node.getType() == Token.BLOCK
        && parent != null
        && parent.getType() == Token.TRY
        && NodeUtil.getCatchBlock(parent) == node
        && !NodeUtil.hasCatchHandler(node)) {
      return;
    }

    // A block transfer control to its first child if it is not empty.
    Node child = node.getFirstChild();

    // Function declarations are skipped since control doesn't go into that
    // function (unless it is called)
    while (child != null && child.getType() == Token.FUNCTION) {
      child = child.getNext();
    }

    if (child != null) {
      createEdge(node, Branch.UNCOND, computeFallThrough(child));
    } else {
      createEdge(node, Branch.UNCOND, computeFollowNode(node));
    }

    // Synthetic blocks
    if (parent != null) {
      switch (parent.getType()) {
        case Token.DEFAULT:
        case Token.CASE:
        case Token.TRY:
          break;
        default:
          if (node.getType() == Token.BLOCK && node.isSyntheticBlock()) {
            Node next = node.getLastChild();
            if (next != null) {
              createEdge(node, Branch.SYN_BLOCK, computeFallThrough(next));
            }
          }
          break;
      }
    }
  }
  /**
   * Computes the follow() node of a given node and its parent. There is a side effect when calling
   * this function. If this function computed an edge that exists a FINALLY, it'll attempt to
   * connect the fromNode to the outer FINALLY according to the finallyMap.
   *
   * @param fromNode The original source node since {@code node} is changed during recursion.
   * @param node The node that follow() should compute.
   */
  private Node computeFollowNode(Node fromNode, Node node) {
    /*
     * This is the case where:
     *
     * 1. Parent is null implies that we are transferring control to the end of
     * the script.
     *
     * 2. Parent is a function implies that we are transferring control back to
     * the caller of the function.
     *
     * 3. If the node is a return statement, we should also transfer control
     * back to the caller of the function.
     *
     * 4. If the node is root then we have reached the end of what we have been
     * asked to traverse.
     *
     * In all cases we should transfer control to a "symbolic return" node.
     * This will make life easier for DFAs.
     */
    Node parent = node.getParent();
    if (parent == null || parent.getType() == Token.FUNCTION || node == root) {
      return null;
    }

    // If we are just before a IF/WHILE/DO/FOR:
    switch (parent.getType()) {
        // The follow() of any of the path from IF would be what follows IF.
      case Token.IF:
        return computeFollowNode(fromNode, parent);
      case Token.CASE:
      case Token.DEFAULT:
        // After the body of a CASE, the control goes to the body of the next
        // case, without having to go to the case condition.
        if (parent.getNext() != null) {
          if (parent.getNext().getType() == Token.CASE) {
            return parent.getNext().getFirstChild().getNext();
          } else if (parent.getNext().getType() == Token.DEFAULT) {
            return parent.getNext().getFirstChild();
          } else {
            Preconditions.checkState(false, "Not reachable");
          }
        } else {
          return computeFollowNode(fromNode, parent);
        }
        break;
      case Token.FOR:
        if (NodeUtil.isForIn(parent)) {
          return parent;
        } else {
          return parent.getFirstChild().getNext().getNext();
        }
      case Token.WHILE:
      case Token.DO:
        return parent;
      case Token.TRY:
        // If we are coming out of the TRY block...
        if (parent.getFirstChild() == node) {
          if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
            return computeFallThrough(parent.getLastChild());
          } else { // and have no FINALLY.
            return computeFollowNode(fromNode, parent);
          }
          // CATCH block.
        } else if (NodeUtil.getCatchBlock(parent) == node) {
          if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
            return computeFallThrough(node.getNext());
          } else {
            return computeFollowNode(fromNode, parent);
          }
          // If we are coming out of the FINALLY block...
        } else if (parent.getLastChild() == node) {
          for (Node finallyNode : finallyMap.get(parent)) {
            createEdge(fromNode, Branch.UNCOND, finallyNode);
          }
          return computeFollowNode(fromNode, parent);
        }
    }

    // Now that we are done with the special cases follow should be its
    // immediate sibling, unless its sibling is a function
    Node nextSibling = node.getNext();

    // Skip function declarations because control doesn't get pass into it.
    while (nextSibling != null && nextSibling.getType() == Token.FUNCTION) {
      nextSibling = nextSibling.getNext();
    }

    if (nextSibling != null) {
      return computeFallThrough(nextSibling);
    } else {
      // If there are no more siblings, control is transfered up the AST.
      return computeFollowNode(fromNode, parent);
    }
  }