private static boolean isLhsOfForInExpression(Node n) { Node parent = n.getParent(); if (parent.isVar()) { return isLhsOfForInExpression(parent); } return NodeUtil.isForIn(parent) && parent.getFirstChild() == n; }
private static void fuseExpressionIntoControlFlowStatement(Node before, Node control) { Preconditions.checkArgument(before.isExprResult(), "before must be expression result"); // Now we are just left with two statements. The comma tree of the first // n - 1 statements (which can be used in an expression) and the last // statement. We perform specific fusion based on the last statement's type. switch (control.getToken()) { case IF: case RETURN: case THROW: case SWITCH: case EXPR_RESULT: before.getParent().removeChild(before); fuseExpressionIntoFirstChild(before.removeFirstChild(), control); return; case FOR: before.getParent().removeChild(before); if (NodeUtil.isForIn(control)) { fuseExpressionIntoSecondChild(before.removeFirstChild(), control); } else { fuseExpressionIntoFirstChild(before.removeFirstChild(), control); } return; case LABEL: fuseExpressionIntoControlFlowStatement(before, control.getLastChild()); return; case BLOCK: fuseExpressionIntoControlFlowStatement(before, control.getFirstChild()); return; default: throw new IllegalStateException("Statement fusion missing."); } }
private boolean isFusableControlStatement(Node n) { switch (n.getToken()) { case IF: case THROW: case SWITCH: case EXPR_RESULT: return true; case RETURN: // We don't want to add a new return value. return n.hasChildren(); case FOR: if (NodeUtil.isForIn(n)) { // Avoid cases where we have for(var x = foo() in a) { .... return !mayHaveSideEffects(n.getFirstChild()); } else { // Avoid cases where we have for(var x;_;_) { .... return !n.getFirstChild().isVar(); } case LABEL: return isFusableControlStatement(n.getLastChild()); case BLOCK: return !n.isSyntheticBlock() && isFusableControlStatement(n.getFirstChild()); default: break; } return false; }
/** * Computes the destination node of n when we want to fallthough into the subtree of n. We don't * always create a CFG edge into n itself because of DOs and FORs. */ private static Node computeFallThrough(Node n) { switch (n.getType()) { case Token.DO: return computeFallThrough(n.getFirstChild()); case Token.FOR: if (NodeUtil.isForIn(n)) { return n; } return computeFallThrough(n.getFirstChild()); case Token.LABEL: return computeFallThrough(n.getLastChild()); default: return n; } }
/** Collapse VARs and EXPR_RESULT node into FOR loop initializers where possible. */ private void maybeCollapseIntoForStatements(Node n, Node parent) { // Only SCRIPT, BLOCK, and LABELs can have FORs that can be collapsed into. // LABELs are not supported here. if (parent == null || !NodeUtil.isStatementBlock(parent)) { return; } // Is the current node something that can be in a for loop initializer? if (!NodeUtil.isExpressionNode(n) && !NodeUtil.isVar(n)) { return; } // Is the next statement a valid FOR? Node nextSibling = n.getNext(); if (nextSibling != null && nextSibling.getType() == Token.FOR && !NodeUtil.isForIn(nextSibling) && nextSibling.getFirstChild().getType() == Token.EMPTY) { // Does the current node contain an in operator? If so, embedding // the expression in a for loop can cause some Javascript parsers (such // as the Playstation 3's browser based on Access's NetFront // browser) to fail to parse the code. // See bug 1778863 for details. if (NodeUtil.containsType(n, Token.IN)) { return; } // Move the current node into the FOR loop initializer. Node forNode = nextSibling; Node oldInitializer = forNode.getFirstChild(); parent.removeChild(n); Node newInitializer; if (NodeUtil.isVar(n)) { newInitializer = n; } else { // Extract the expression from EXPR_RESULT node. Preconditions.checkState(n.hasOneChild()); newInitializer = n.getFirstChild(); n.removeChild(newInitializer); } forNode.replaceChild(oldInitializer, newInitializer); compiler.reportCodeChange(); } }
private void validateFor(Node n) { validateNodeType(Token.FOR, n); if (NodeUtil.isForIn(n)) { // FOR-IN validateChildCount(n, 3); validateVarOrAssignmentTarget(n.getFirstChild()); validateExpression(n.getChildAtIndex(1)); } else { // FOR validateChildCount(n, 4); validateVarOrOptionalExpression(n.getFirstChild()); validateOptionalExpression(n.getChildAtIndex(1)); validateOptionalExpression(n.getChildAtIndex(2)); } validateBlock(n.getLastChild()); }
/** * Look at all the property assigns to all variables. These may or may not count as references. * For example, <code> * var x = {}; * x.foo = 3; // not a reference. * var y = foo(); * y.foo = 3; // is a reference. * </code> Interpreting assigments could mark a variable as referenced that wasn't referenced * before, in order to keep it alive. Because we find references by lazily traversing subtrees, * marking a variable as referenced could trigger new traversals of new subtrees, which could find * new references. * * <p>Therefore, this interpretation needs to be run to a fixed point. */ private void interpretAssigns() { boolean changes = false; do { changes = false; // We can't use traditional iterators and iterables for this list, // because our lazily-evaluated continuations will modify it while // we traverse it. for (int current = 0; current < maybeUnreferenced.size(); current++) { Var var = maybeUnreferenced.get(current); if (referenced.contains(var)) { maybeUnreferenced.remove(current); current--; } else { boolean assignedToUnknownValue = false; boolean hasPropertyAssign = false; if (var.getParentNode().getType() == Token.VAR && !NodeUtil.isForIn(var.getParentNode().getParent())) { Node value = var.getInitialValue(); assignedToUnknownValue = value != null && !NodeUtil.isLiteralValue(value, true); } else { // This was initialized to a function arg or a catch param // or a for...in variable. assignedToUnknownValue = true; } for (Assign assign : assignsByVar.get(var)) { if (assign.isPropertyAssign) { hasPropertyAssign = true; } else if (!NodeUtil.isLiteralValue(assign.assignNode.getLastChild(), true)) { assignedToUnknownValue = true; } } if (assignedToUnknownValue && hasPropertyAssign) { changes = markReferencedVar(var) || changes; maybeUnreferenced.remove(current); current--; } } } } while (changes); }
/** * 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); } }
/** * @param n The node in question. * @param cfgNode The node to add * @param conditional true if the definition is not always executed. */ private void computeMustDef(Node n, Node cfgNode, MustDef output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.WHILE: case Token.DO: case Token.IF: computeMustDef(NodeUtil.getConditionExpression(n), cfgNode, output, conditional); return; case Token.FOR: if (!NodeUtil.isForIn(n)) { computeMustDef(NodeUtil.getConditionExpression(n), cfgNode, output, conditional); } else { // for(x in y) {...} Node lhs = n.getFirstChild(); Node rhs = lhs.getNext(); if (NodeUtil.isVar(lhs)) { lhs = lhs.getLastChild(); // for(var x in y) {...} } if (NodeUtil.isName(lhs)) { addToDefIfLocal(lhs.getString(), cfgNode, rhs, output); } } return; case Token.AND: case Token.OR: computeMustDef(n.getFirstChild(), cfgNode, output, conditional); computeMustDef(n.getLastChild(), cfgNode, output, true); return; case Token.HOOK: computeMustDef(n.getFirstChild(), cfgNode, output, conditional); computeMustDef(n.getFirstChild().getNext(), cfgNode, output, true); computeMustDef(n.getLastChild(), cfgNode, output, true); return; case Token.VAR: for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (c.hasChildren()) { computeMustDef(c.getFirstChild(), cfgNode, output, conditional); addToDefIfLocal(c.getString(), conditional ? null : cfgNode, c.getFirstChild(), output); } } return; default: if (NodeUtil.isAssignmentOp(n)) { if (NodeUtil.isName(n.getFirstChild())) { Node name = n.getFirstChild(); computeMustDef(name.getNext(), cfgNode, output, conditional); addToDefIfLocal( name.getString(), conditional ? null : cfgNode, n.getLastChild(), output); return; } else if (NodeUtil.isGet(n.getFirstChild())) { // Treat all assignments to arguments as redefining the // parameters itself. Node obj = n.getFirstChild().getFirstChild(); if (NodeUtil.isName(obj) && "arguments".equals(obj.getString())) { // TODO(user): More accuracy can be introduced // ie: We know exactly what arguments[x] is if x is a constant // number. escapeParameters(output); } } } if (NodeUtil.isName(n) && "arguments".equals(n.getString())) { escapeParameters(output); } // DEC and INC actually defines the variable. if (n.getType() == Token.DEC || n.getType() == Token.INC) { Node target = n.getFirstChild(); if (NodeUtil.isName(target)) { addToDefIfLocal(target.getString(), conditional ? null : cfgNode, null, output); return; } } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { computeMustDef(c, cfgNode, output, conditional); } } }