All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.ExploitAssigns Maven / Gradle / Ivy

There is a newer version: 9.0.8
Show newest version
/*
 * Copyright 2006 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 static com.google.common.base.Preconditions.checkArgument;

import com.google.javascript.rhino.Node;

/**
 * Tries to chain assignments together.
 *
 */
class ExploitAssigns extends AbstractPeepholeOptimization {

  @Override
  Node optimizeSubtree(Node subtree) {
    for (Node child = subtree.getFirstChild(); child != null;) {
      Node next = child.getNext();
      if (NodeUtil.isExprAssign(child)) {
        collapseAssign(child.getFirstChild(), child, subtree);
      }
      child = next;
    }
    return subtree;
  }

  /** Try to collapse the given assign into subsequent expressions. */
  private void collapseAssign(Node assign, Node expr, Node exprParent) {
    Node leftValue = assign.getFirstChild();
    Node rightValue = leftValue.getNext();

    if (leftValue.isDestructuringPattern()) {
      // We don't collapse expressions containing these because they can have side effects:
      //   - Reassigning RHS names. (e.g. `() => { ({c: a} = a); return a; }`).
      //   - Calling a getter defined on the RHS.
      //   - Evaluating a default value.
      // TODO(b/123102446): Check for these issues and optimize when they aren't present.
      return;
    } else if (isCollapsibleValue(leftValue, true)
        && collapseAssignEqualTo(expr, exprParent, leftValue)) {
      // Condition has side-effects.
    } else if (isCollapsibleValue(rightValue, false)
        && collapseAssignEqualTo(expr, exprParent, rightValue)) {
      // Condition has side-effects.
    } else if (rightValue.isAssign()) {
      // Recursively deal with nested assigns.
      collapseAssign(rightValue, expr, exprParent);
    }
  }

  /**
   * Determines whether we know enough about the given value to be able to collapse it into
   * subsequent expressions.
   *
   * 

For example, we can collapse booleans and variable names: * *

   * 
   * x = 3; y = x; // y = x = 3;
   * a = true; b = true; // b = a = true;
   * 
   * 
* *

But we won't try to collapse complex expressions. * * @param value The value node. * @param isLValue Whether it's on the left-hand side of an expr. */ private static boolean isCollapsibleValue(Node value, boolean isLValue) { switch (value.getToken()) { case GETPROP: case OPTCHAIN_GETPROP: // Do not collapse GETPROPs on arbitrary objects, because // they may be implemented setter functions, and oftentimes // setter functions fail on native objects. This is OK for "THIS" // objects, because we assume that they are non-native. return !isLValue || value.getFirstChild().isThis(); case NAME: return true; default: return NodeUtil.isImmutableValue(value); } } /** * Collapse the given assign expression into the expression directly following it, if possible. * * @param expr The expression that may be moved. * @param exprParent The parent of {@code expr}. * @param value The value of this expression, expressed as a node. Each expression may have * multiple values, so this function may be called multiple times for the same expression. For * example, * a = true; * is equal to the name "a" and the boolean "true". * @return Whether the expression was collapsed successfully. */ private boolean collapseAssignEqualTo(Node expr, Node exprParent, Node value) { Node assign = expr.getFirstChild(); Node parent = exprParent; Node next = expr.getNext(); while (next != null) { switch (next.getToken()) { case AND: case OR: case HOOK: case IF: case RETURN: case EXPR_RESULT: case COALESCE: // Dive down the left side parent = next; next = next.getFirstChild(); break; case CONST: case LET: case VAR: if (next.getFirstChild().hasChildren()) { parent = next.getFirstChild(); next = parent.getFirstChild(); break; } return false; // `OPTCHAIN_GETPROP` cannot be matched with a value seen before on the RHS of an assign // as it is not a qualified name. Consequently `b = a.x; a = a.x` optimizes to `a = b = // a.x` but `b = a?.x; a = a?.x` does not. case OPTCHAIN_GETPROP: return false; case GETPROP: case NAME: if (next.isQualifiedName()) { if (value.isQualifiedName() && next.matchesQualifiedName(value)) { // If the previous expression evaluates to value of a // qualified name, and that qualified name is used again // shortly, then we can exploit the assign here. // Verify the assignment doesn't change its own value. if (!isSafeReplacement(next, assign)) { return false; } expr.detach(); assign.detach(); next.replaceWith(assign); reportChangeToEnclosingScope(parent); return true; } } return false; case ASSIGN: // Assigns are really tricky. In lots of cases, we want to inline // into the right side of the assign. But the left side of the // assign is evaluated first, and it may have convoluted logic: // a = null; // (a = b).c = null; // We don't want to exploit the first assign. Similarly: // a.b = null; // a.b.c = null; // We don't want to exploit the first assign either. // // To protect against this, we simply only inline when the left side // is guaranteed to evaluate to the same L-value no matter what. Node leftSide = next.getFirstChild(); if (leftSide.isName() || (leftSide.isGetProp() && leftSide.getFirstChild().isThis())) { // Dive down the right side of the assign. parent = next; next = leftSide.getNext(); break; } else { return false; } default: if (NodeUtil.isImmutableValue(next) && next.isEquivalentTo(value)) { // If the r-value of the expr assign is an immutable value, // and the value is used again shortly, then we can exploit // the assign here. expr.detach(); assign.detach(); next.replaceWith(assign); reportChangeToEnclosingScope(parent); return true; } // Return without inlining a thing return false; } } return false; } /** * Checks name referenced in node to determine if it might have * changed. * @return Whether the replacement can be made. */ private boolean isSafeReplacement(Node node, Node replacement) { // No checks are needed for simple names. if (node.isName()) { return true; } checkArgument(node.isGetProp()); while (node.isGetProp()) { node = node.getFirstChild(); } return !(node.isName() && isNameAssignedTo(node.getString(), replacement)); } /** * @return Whether name is assigned in the expression rooted at node. */ private static boolean isNameAssignedTo(String name, Node node) { for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { if (isNameAssignedTo(name, c)) { return true; } } if (node.isName()) { Node parent = node.getParent(); if (parent.isAssign() && parent.getFirstChild() == node) { if (name.equals(node.getString())) { return true; } } } return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy