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