MarkNoSideEffectCalls.java
/*
* 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 static com.google.common.base.Preconditions.checkNotNull;
import com.google.javascript.jscomp.DefinitionsRemover.Definition;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Set the NoSideEffects property for function and constructor calls
* that refer to functions that are known to have no side effects.
* Current implementation relies on @nosideeffects annotations at
* function definition sites; eventually we should traverse function
* bodies to determine if they have side effects.
*
*/
class MarkNoSideEffectCalls implements CompilerPass {
private final AbstractCompiler compiler;
// Left hand side expression associated with a function node that
// has a @nosideeffects annotation.
private final Set<Node> noSideEffectFunctionNames;
MarkNoSideEffectCalls(AbstractCompiler compiler) {
this.compiler = compiler;
this.noSideEffectFunctionNames = new HashSet<>();
}
@Override
public void process(Node externs, Node root) {
NameBasedDefinitionProvider defFinder = new NameBasedDefinitionProvider(compiler, false);
defFinder.process(externs, root);
// Gather the list of function nodes that have @nosideeffects annotations.
// For use by SetNoSideEffectCallProperty.
NodeTraversal.traverseEs6(
compiler, externs, new GatherNoSideEffectFunctions());
NodeTraversal.traverseEs6(
compiler, root, new GatherNoSideEffectFunctions());
NodeTraversal.traverseEs6(compiler, root, new SetNoSideEffectCallProperty(defFinder));
}
/**
* Determines if the type of the value of the RHS expression can
* be a function node.
*/
private static boolean definitionTypeContainsFunctionType(Definition def) {
Node rhs = def.getRValue();
if (rhs == null) {
return true;
}
switch (rhs.getToken()) {
case ASSIGN:
case AND:
case CALL:
case GETPROP:
case GETELEM:
case FUNCTION:
case HOOK:
case NAME:
case NEW:
case OR:
return true;
default:
return false;
}
}
/**
* Get the value of the @nosideeffects annotation stored in the
* doc info.
*/
private static boolean hasNoSideEffectsAnnotation(Node node) {
JSDocInfo docInfo = node.getJSDocInfo();
return docInfo != null && docInfo.isNoSideEffects();
}
/**
* Gather function nodes that have @nosideeffects annotations.
*/
private class GatherNoSideEffectFunctions extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
if (node.isGetProp()) {
if (parent.isExprResult() &&
hasNoSideEffectsAnnotation(node)) {
noSideEffectFunctionNames.add(node);
}
} else if (node.isFunction()) {
// The annotation may attached to the function node, the
// variable declaration or assignment expression.
boolean hasAnnotation = hasNoSideEffectsAnnotation(node);
List<Node> nameNodes = new ArrayList<>();
nameNodes.add(node.getFirstChild());
if (parent.isName()) {
Node gramp = parent.getParent();
if (gramp.isVar() &&
gramp.hasOneChild() &&
hasNoSideEffectsAnnotation(gramp)) {
hasAnnotation = true;
}
nameNodes.add(parent);
} else if (parent.isAssign()) {
if (hasNoSideEffectsAnnotation(parent)) {
hasAnnotation = true;
}
nameNodes.add(parent.getFirstChild());
}
if (hasAnnotation) {
noSideEffectFunctionNames.addAll(nameNodes);
}
}
}
}
/**
* Set the no side effects property for CALL and NEW nodes that
* refer to function names that are known to have no side effects.
*/
private class SetNoSideEffectCallProperty extends AbstractPostOrderCallback {
private final NameBasedDefinitionProvider defFinder;
SetNoSideEffectCallProperty(NameBasedDefinitionProvider defFinder) {
this.defFinder = defFinder;
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
if (!NodeUtil.isCallOrNew(node)) {
return;
}
Node nameNode = node.getFirstChild();
// This is the result of an anonymous function execution. function() {}();
if (!nameNode.isName() && !nameNode.isGetProp()) {
return;
}
Collection<Definition> definitions = defFinder.getDefinitionsReferencedAt(nameNode);
if (definitions == null) {
return;
}
boolean maybeFunction = false;
for (Definition def : definitions) {
Node lValue = def.getLValue();
checkNotNull(lValue);
if (definitionTypeContainsFunctionType(def)) {
maybeFunction = true;
if (!noSideEffectFunctionNames.contains(lValue)) {
return;
}
}
}
if (maybeFunction) {
if (node.getSideEffectFlags() != Node.NO_SIDE_EFFECTS) {
node.setSideEffectFlags(Node.NO_SIDE_EFFECTS);
compiler.reportChangeToEnclosingScope(node);
}
}
}
}
}