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

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

There is a newer version: 9.0.8
Show newest version
/*
 * Copyright 2009 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.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.CodingConvention.SubclassRelationship;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.ArrayList;
import java.util.List;

/**
 * Callback that gathers subexpressions that may have side effects
 * and appends copies of those subexpressions to the replacements
 * list.  In the case of branching subexpressions, it simplifies the
 * subexpression before adding it to the replacement list.
 *
 */
class GatherSideEffectSubexpressionsCallback implements Callback {

  /**
   * Used by GatherSideEffectSubexpressionsCallback to notify client
   * code about side effect expressions that should be kept.
   */
  interface SideEffectAccumulator {

    /**
     * Returns true if the "mixin" and "inherits" function calls
     * should be treated as if they had side effects.
     */
    boolean classDefiningCallsHaveSideEffects();

    /**
     * Adds subtree to the list of nodes that have side effects.
     *
     * @param original - root of the tree.
     */
    void keepSubTree(Node original);

    /**
     * Simplifies a subtree whose root node is an AND or OR expression
     * and adds the resulting subtree to the list of nodes that have
     * side effects.
     *
     * @param original - root of the and/or expression.
     */
    void keepSimplifiedShortCircuitExpression(Node original);

    /**
     * Simplifies a subtree whose root node is a HOOK expression
     * and adds the resulting subtree to the list of nodes that have
     * side effects.
     *
     * @param hook - root of the hook expression.
     * @param thenHasSideEffects - then branch has side effects
     * @param elseHasSideEffects - else branch has side effects
     */
    void keepSimplifiedHookExpression(Node hook,
                                      boolean thenHasSideEffects,
                                      boolean elseHasSideEffects);
  }

  /**
   * Populates the provided replacement list by appending copies of
   * subtrees that have side effects.
   *
   * It is OK if this class tears up the original tree, because
   * we're going to throw the tree out anyway.
   */
  static final class GetReplacementSideEffectSubexpressions
      implements SideEffectAccumulator {
    private final AbstractCompiler compiler;
    private final List replacements;

    /**
     * Creates the accumulator.
     *
     * @param compiler - the AbstractCompiler
     * @param replacements - list to accumulate into
     */
    GetReplacementSideEffectSubexpressions(AbstractCompiler compiler,
        List replacements) {
      this.compiler = compiler;
      this.replacements = replacements;
    }

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

    @Override
    public void keepSubTree(Node original) {
      if (original.getParent() != null) {
        original.detach();
      }
      replacements.add(original);
    }

    @Override
    public void keepSimplifiedShortCircuitExpression(Node original) {
      Preconditions.checkArgument(
          (original.isAnd()) || (original.isOr()),
          "Expected: AND or OR, Got: %s",
          original.getToken());
      Node left = original.getFirstChild();
      Node right = left.getNext();
      Node simplifiedRight = simplifyShortCircuitBranch(right);
      original.detachChildren();
      original.addChildToBack(left);
      original.addChildToBack(simplifiedRight);
      keepSubTree(original);
    }

    @Override
    public void keepSimplifiedHookExpression(Node hook,
                                             boolean thenHasSideEffects,
                                             boolean elseHasSideEffects) {
      Preconditions.checkArgument(hook.isHook(), "Expected: HOOK, Got: %s", hook.getToken());
      Node condition = hook.getFirstChild();
      Node thenBranch = condition.getNext();
      Node elseBranch = thenBranch.getNext();
      if (thenHasSideEffects && elseHasSideEffects) {
        hook.detachChildren();
        hook.addChildToBack(condition);
        hook.addChildToBack(simplifyShortCircuitBranch(thenBranch));
        hook.addChildToBack(simplifyShortCircuitBranch(elseBranch));
        keepSubTree(hook);
      } else if (thenHasSideEffects || elseHasSideEffects) {
        Token type = thenHasSideEffects ? Token.AND : Token.OR;
        Node body = thenHasSideEffects ? thenBranch : elseBranch;
        Node simplified = new Node(
            type, condition.detach(),
            simplifyShortCircuitBranch(body))
            .useSourceInfoIfMissingFrom(hook);
        keepSubTree(simplified);
      } else {
        throw new IllegalArgumentException(
            "keepSimplifiedHookExpression must keep at least 1 branch");
      }
    }

    private Node simplifyShortCircuitBranch(Node node) {
      List parts = new ArrayList<>();
      NodeTraversal.traverseEs6(
          compiler, node,
          new GatherSideEffectSubexpressionsCallback(
              compiler,
              new GetReplacementSideEffectSubexpressions(compiler, parts)));

      Node ret = null;
      for (Node part : parts) {
        if (ret != null) {
          ret = IR.comma(ret, part).srcref(node);
        } else {
          ret = part;
        }
      }

      if (ret == null) {
        throw new IllegalArgumentException(
            "expected at least one side effect subexpression in short "
            + "circuit branch.");
      }

      return ret;
    }
  }

  private static final ImmutableSet FORBIDDEN_TYPES =
      ImmutableSet.of(Token.BLOCK, Token.SCRIPT, Token.VAR, Token.EXPR_RESULT, Token.RETURN);
  private final AbstractCompiler compiler;
  private final SideEffectAccumulator accumulator;

  /**
   * @param compiler - AbstractCompiler object
   * @param accumulator - object that will accumulate roots of
   *                      subtrees that have side effects.
   */
  GatherSideEffectSubexpressionsCallback(AbstractCompiler compiler,
                                         SideEffectAccumulator accumulator) {
    this.compiler = compiler;
    this.accumulator = accumulator;
  }

  /**
   * Determines if a call defines a class inheritance or mixing
   * relation, according to the current coding convention.
   */
  private boolean isClassDefiningCall(Node callNode) {
    SubclassRelationship classes =
        compiler.getCodingConvention().getClassesDefinedByCall(callNode);
    return classes != null;
  }

  /**
   * Computes the list of subtrees whose root nodes have side effects.
   *
   * 

If the current subtree's root has side effects this method should * call accumulator.keepSubTree and return 'false' to add the * subtree to the result list and avoid avoid traversing the nodes children. * *

Branching nodes whose then or else branch contain side effects * must be simplified by doing a recursive traversal; this method * should call the appropriate accumulator 'keepSimplified' method * and return 'false' to stop the regular traversal. */ @Override public boolean shouldTraverse( NodeTraversal traversal, Node node, Node parent) { if (FORBIDDEN_TYPES.contains(node.getToken()) || NodeUtil.isControlStructure(node)) { throw new IllegalArgumentException(node.getToken() + " nodes are not supported."); } // Do not recurse into nested functions. if (node.isFunction()) { return false; } // simplify and maybe keep hook expression. if (node.isHook()) { return processHook(node); } // simplify and maybe keep AND/OR expression. if ((node.isAnd()) || (node.isOr())) { return processShortCircuitExpression(node); } if (!NodeUtil.nodeTypeMayHaveSideEffects(node, compiler)) { return true; } else { // Node type suggests that the expression has side effects. if (node.isCall()) { return processFunctionCall(node); } else if (node.isNew()) { return processConstructorCall(node); } else { accumulator.keepSubTree(node); return false; } } } /** * Processes an AND or OR expression. * * @return true to continue traversal, false otherwise */ boolean processShortCircuitExpression(Node node) { Preconditions.checkArgument( (node.isAnd()) || (node.isOr()), "Expected: AND or OR, Got: %s", node.getToken()); // keep whole expression if RHS of the branching expression // contains a call. Node left = node.getFirstChild(); Node right = left.getNext(); if (NodeUtil.mayHaveSideEffects(right, compiler)) { accumulator.keepSimplifiedShortCircuitExpression(node); return false; } else { return true; } } /** * Processes a HOOK expression. * * @return true to continue traversal, false otherwise */ boolean processHook(Node node) { Preconditions.checkArgument(node.isHook(), "Expected: HOOK, Got: %s", node.getToken()); Node condition = node.getFirstChild(); Node ifBranch = condition.getNext(); Node elseBranch = ifBranch.getNext(); boolean thenHasSideEffects = NodeUtil.mayHaveSideEffects( ifBranch, compiler); boolean elseHasSideEffects = NodeUtil.mayHaveSideEffects( elseBranch, compiler); if (thenHasSideEffects || elseHasSideEffects) { accumulator.keepSimplifiedHookExpression( node, thenHasSideEffects, elseHasSideEffects); return false; } else { return true; } } /** * Processes a CALL expression. * * @return true to continue traversal, false otherwise */ boolean processFunctionCall(Node node) { Preconditions.checkArgument(node.isCall(), "Expected: CALL, Got: %s", node.getToken()); // Calls to functions that are known to be "pure" have no side // effects. Node functionName = node.getFirstChild(); if (functionName.isName() || functionName.isGetProp()) { if (!accumulator.classDefiningCallsHaveSideEffects() && isClassDefiningCall(node)) { return true; } } if (!NodeUtil.functionCallHasSideEffects(node)) { return true; } accumulator.keepSubTree(node); return false; } /** * Processes a NEW expression. * * @return true to continue traversal, false otherwise */ boolean processConstructorCall(Node node) { Preconditions.checkArgument(node.isNew(), "Expected: NEW, Got: %s", node.getToken()); // Calls to constructors that are known to be "pure" have no // side effects. if (!NodeUtil.constructorCallHasSideEffects(node)) { return true; } accumulator.keepSubTree(node); return false; } @Override public void visit(NodeTraversal traversal, Node node, Node parent) {} }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy