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();
    }
  }
예제 #6
0
 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);
        }
    }
  }