Package com.google.javascript.jscomp

Source Code of com.google.javascript.jscomp.CheckPropertyOrder$PropertyOrders

/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.javascript.jscomp;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.DataFlowAnalysis.FlowState;
import com.google.javascript.jscomp.JoinOp.BinaryJoinOp;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.graph.Annotation;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.ObjectType;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.Stack;

/**
* Checks that all paths through a constructor add properties in the same order.
*
* Background: one of the key elements of the design of V8 that improves
* performance is that it attempts to discover the classes of the objects
* created at run time.  Rather than implementing an object as a hash table,
* most objects have a reference to a "hidden class" that stores the list of
* properties so that the object itself can maintain only a list of property
* values.  If many objects share the same hidden class, then this reduces
* memory usage.  Furthermore, at a get-prop site, if only one hidden class ever
* occurs for the receiver, then it can speed up the dispatch process, compiling
* it into only 3 instructions.  (For more information, see this
* <a href="http://www.youtube.com/watch?v=hWhMKalEicY">video</a>.)
*
* The key point relating to this pass is that a hidden class is not a set of
* properties but rather an ordered list.  This is necesary so that iterating
* over an object produces its keys in insertion order, as expected.  As a
* result, if two different paths through the constructor generate different
* ordered lists of properties, then the objects created via those paths will
* have different hidden classes, defeating the optimizations described above.
* This pass attempts to find and warn the user about such code.
*
*/
class CheckPropertyOrder extends AbstractPostOrderCallback
    implements CompilerPass {
  static final DiagnosticType UNASSIGNED_PROPERTY = DiagnosticType.error(
      "UNASSIGNED_PROPERTY",
      "not all control paths assign property {1} in function {0}");
  static final DiagnosticType UNEQUAL_PROPERTIES = DiagnosticType.error(
      "UNEQUAL_PROPERTIES",
      "different control paths produce different (ordered) property lists:"
      + " {0} vs. {1}");

  private final AbstractCompiler compiler;
  private final CheckLevel level;
  private final boolean onlyOneError;
  private int errorCount;

  CheckPropertyOrder(AbstractCompiler compiler, CheckLevel level) {
    this(compiler, level, false);
  }

  CheckPropertyOrder(
      AbstractCompiler compiler, CheckLevel level, boolean onlyOneError) {
    this.compiler = compiler;
    this.level = level;
    this.onlyOneError = onlyOneError;
  }

  @Override
  public void process(Node externs, Node root) {
    NodeTraversal.traverse(compiler, root, this);
  }

  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    // Look for both top-level functions and assignments of functions to
    // qualified names.
    Node func = null;
    String funcName = null;
    if (NodeUtil.isFunction(n) && isConstructor(n)) {
      func = n;
      funcName = n.getFirstChild().getString();
    } else if (NodeUtil.isAssign(n)
               && NodeUtil.isFunction(n.getFirstChild().getNext())
               && isConstructor(n)) {
      func = n.getFirstChild().getNext();
      funcName = n.getFirstChild().getQualifiedName();
    }

    if (func != null) {
      FunctionType funcType = JSType.toMaybeFunctionType(func.getJSType());
      checkConstructor(
          func, (funcType != null) ? funcType.getInstanceType() : null,
          t.getSourceName(), funcName);
    }
  }

  /** Determines whether the given node is jsdoc-ed as a constructor. */
  private static boolean isConstructor(Node n) {
    return (n.getJSDocInfo() != null) && n.getJSDocInfo().isConstructor();
  }

  @SuppressWarnings("unchecked")
  private void checkConstructor(Node func, ObjectType objType,
                                String sourceName, String funcName) {
    Preconditions.checkArgument(NodeUtil.isFunction(func));

    ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, false);
    cfa.process(null, func.getFirstChild().getNext().getNext());
    ControlFlowGraph<Node> cfg = cfa.getCfg();

    new PropertyOrdersFlowAnalysis(cfa.getCfg()).analyze();

    Annotation ann = cfa.getCfg().getImplicitReturn().getAnnotation();
    List<String>[] orders =
        ((FlowState<PropertyOrders>) ann).getIn().getOrders();
    if (orders.length == 0) {
      throw new AssertionError(
          "no paths through constructor " + funcName + "?");
    }
    if (orders.length > 1) {
      report(sourceName, func, UNEQUAL_PROPERTIES,
             reverse(orders[0]).toString(), reverse(orders[1]).toString());
    }
    if (objType != null) {
      for (String propName : objType.getOwnPropertyNames()) {
        if (!orders[0].contains(propName)) {
          report(sourceName, func, UNASSIGNED_PROPERTY, funcName, propName);
        }
      }
    }
  }

  /** Reports the given error. Returns whether to continue. */
  private void report(
      String srcName, Node node, DiagnosticType type, String... args) {
    if (!onlyOneError || (++errorCount <= 1)) {
      compiler.report(JSError.make(srcName, node, level, type, args));
    }
  }

  /** Returns the given List in the reverse order. */
  private static <T> List<T> reverse(List<T> seq) {
    if (seq.isEmpty()) {
      return seq;
    }
    List<T> rev = Lists.newArrayList(seq);
    Collections.reverse(seq);
    return rev;
  }

  private static class OrdersJoinOp extends BinaryJoinOp<PropertyOrders> {
    @Override
    public PropertyOrders apply(PropertyOrders a, PropertyOrders b) {
      return new PropertyOrders(
          Sets.newHashSet(Sets.union(a.orders, b.orders)));
    }
  }

  /**
   * Stores all possible (ordered) lists of properties that may have been added
   * to <code>this</code> at some point in the code.
   *
   * The bottom element of the lattice is an empty set, meaning no
   * possibilities.  At the first statement in the constructor, we start with a
   * set containing only one possibility: an empty list of properties.  (Note
   * that we do not model the properties of the superclass, if any.)  Each
   * assignment, say, <code>this.a = ...</code> adds <code>a</code> to the end
   * of all lists in the set.  Joining two results in the union of the
   * possibilities of those results.
   *
   * For example, if the constructor body contained just the code <code><pre>
   *   if (x) {
   *     this.a = 1;
   *     this.b = 2;
   *   } else {
   *     this.b = 2;
   *     this.a = 1;
   *   }
   * </pre></code>, then at the end of the constructor, the possibilities would
   * be {['a', 'b'], ['b', 'a']} because there are two possible orderings,
   * based on which path is taken in the <code>if</code>.
   */
  private static class PropertyOrders implements LatticeElement {
    /** The bottom element of the lattice: an empty set of lists. */
    public static final PropertyOrders EMPTY =
        new PropertyOrders(Sets.<List<String>>newHashSet());

    /** A set of possible ordered lists of properties. */
    private final Set<List<String>> orders;

    private PropertyOrders(Set<List<String>> orders) {
      this.orders = orders;
    }

    @Override
    public boolean equals(Object other) {
      if (!(other instanceof PropertyOrders)) {
        return false;
      }
      return orders.equals(((PropertyOrders) other).orders);
    }

    /**
     * Returns a new set of possible orders with the given string added at the
     * end of each possibility.
     */
    public PropertyOrders copyAndAdd(String propName) {
      Set<List<String>> orders = Sets.newHashSet();
      for (List<String> order : this.orders) {
        if (!order.contains(propName)) {
          List<String> nOrder = Lists.newArrayList(order);
          nOrder.add(propName);
          order = nOrder;
        }
        orders.add(order);
      }
      return new PropertyOrders(orders);
    }

    /**
     * Returns all possible orders over the first N properties that are
     * definitely assigned over all paths.
     */
    @SuppressWarnings("unchecked")
    public List<String>[] getOrders() {
      int minSize = Integer.MAX_VALUE;
      for (List<String> seq : this.orders) {
        minSize = Math.min(minSize, seq.size());
      }
      Set<List<String>> orders = Sets.newHashSet();
      for (List<String> seq : this.orders) {
        //orders.add(seq.subList(seq.size() - minSize, seq.size()));
        orders.add(seq.subList(0, minSize));
      }
      return orders.toArray(
          (List<String>[]) new List<?>[orders.size()]);
    }

    @Override
    public String toString() {
      return "{" + Joiner.on(", ").join(orders) + "}";
    }
  }

  /** Implements a data flow analysis over PropertyOrders. */
  private static class PropertyOrdersFlowAnalysis
      extends DataFlowAnalysis<Node, PropertyOrders> {
    public PropertyOrdersFlowAnalysis(ControlFlowGraph<Node> cfg) {
      super(cfg, new OrdersJoinOp());
    }

    @Override
    public boolean isForward() { return true; }

    @Override
    public PropertyOrders createInitialEstimateLattice() {
      // An empty orders means no possibilities at all.
      return PropertyOrders.EMPTY;
    }

    @Override
    public PropertyOrders createEntryLattice() {
      // Initially, we have only one possibility:  no properties.
      Set<List<String>> orders = Sets.newHashSet();
      orders.add(new Stack<String>());
      return new PropertyOrders(orders);
    }

    /** Computes the orders after executing the given node. */
    @Override
    public PropertyOrders flowThrough(Node node, PropertyOrders input) {
      switch (node.getType()) {
        case Token.BLOCK:
        case Token.LABEL:
        case Token.FUNCTION:
          return input;

        case Token.IF:
        case Token.WHILE:
        case Token.DO:
          return flowThrough(NodeUtil.getConditionExpression(node), input);

        case Token.SWITCH:
        case Token.WITH:
          return flowThrough(node.getFirstChild(), input);

        case Token.FOR:
          if (node.getChildCount() == 4) {
            // Note that the post-loop expression is not considered here.  That
            // is handled in the branched version above.
            Node pre = node.getFirstChild(), cond = pre.getNext();
            return flowThrough(cond, flowThrough(pre, input));
          } else {
            Node lhs = node.getFirstChild(), rhs = lhs.getNext();
            return flowThrough(rhs, flowThrough(lhs, input));
          }

        case Token.HOOK:
          Node cond = node.getFirstChild();
          input = flowThrough(cond, input);
          Node ifTrue = cond.getNext(), ifFalse = ifTrue.getNext();
          return join(flowThrough(ifTrue, input), flowThrough(ifFalse, input));

        case Token.AND:
        case Token.OR:
          Node left = node.getFirstChild(), right = left.getNext();
          input = flowThrough(left, input);
          return join(input, flowThrough(right, input));

        case Token.ASSIGN:
          // If the left hand side is "this.x", then add x to the lists.
          Node lhs = node.getFirstChild(), rhs = lhs.getNext();
          if (lhs.getType() == Token.GETPROP) {
            Node llhs = lhs.getFirstChild(), lrhs = llhs.getNext();
            if ((llhs.getType() == Token.THIS)
                && (lrhs.getType() == Token.STRING)
                && (lrhs.getNext() == null)) {
              return flowThrough(rhs, input.copyAndAdd(lrhs.getString()));
            }
          }
          return flowThrough(rhs, flowThrough(lhs, input));

        default:
          for (node = node.getFirstChild();
               node != null;
               node = node.getNext()) {
            input = flowThrough(node, input);
          }
          return input;
      }
    }
  }
}
TOP

Related Classes of com.google.javascript.jscomp.CheckPropertyOrder$PropertyOrders

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.