com.google.javascript.jscomp.ControlFlowGraph Maven / Gradle / Ivy
Show all versions of closure-compiler-linter Show documentation
/*
* 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.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.jscomp.graph.LinkedDirectedGraph;
import com.google.javascript.rhino.Node;
import java.util.Comparator;
/**
* Control flow graph.
*
* @param The instruction type of the control flow graph.
*/
public class ControlFlowGraph extends
LinkedDirectedGraph {
/**
* A special node marked by the node value key null to a singleton
* "return" when control is transferred outside of the current control flow
* graph.
*/
private final DiGraphNode implicitReturn;
private final DiGraphNode entry;
/**
* Constructor.
*/
ControlFlowGraph(
N entry, boolean nodeAnnotations, boolean edgeAnnotations) {
super(nodeAnnotations, edgeAnnotations);
implicitReturn = createDirectedGraphNode(null);
this.entry = createDirectedGraphNode(entry);
}
/**
* Gets the implicit return node.
*
* @return Return node.
*/
public DiGraphNode getImplicitReturn() {
return implicitReturn;
}
/**
* Gets the entry point of the control flow graph. In general, this should be
* the beginning of the global script or beginning of a function.
*
* @return The entry point.
*/
public DiGraphNode getEntry() {
return entry;
}
/**
* Checks whether node is the implicit return.
*
* @param node Node.
* @return True if the node is the implicit return.
*/
public boolean isImplicitReturn(
DiGraphNode node) {
return node == implicitReturn;
}
/**
* Gets a comparator for the nodes. The default implementation returns
* {@code null}. See {@link ControlFlowGraph#getOptionalNodeComparator}.
* @param isForward Whether the comparator sorts the nodes in the direction of
* the flow.
* @return a comparator or null (in particular, if not overridden)
*/
public Comparator> getOptionalNodeComparator(
boolean isForward) {
return null;
}
/**
* The edge object for the control flow graph.
*/
public static enum Branch {
/** Edge is taken if the condition is true. */
ON_TRUE,
/** Edge is taken if the condition is false. */
ON_FALSE,
/** Unconditional branch. */
UNCOND,
/**
* Exception-handling code paths.
* Conflates two kind of control flow passing:
* - An exception is thrown, and falls into a catch or finally block
* - During exception handling, a finally block finishes and control
* passes to the next finally block.
* In theory, we need 2 different edge types. In practice, we
* can just treat them as "the edges we can't really optimize".
*/
ON_EX,
/** Possible folded-away template */
SYN_BLOCK;
public boolean isConditional() {
return this == ON_TRUE || this == ON_FALSE;
}
}
/**
* Abstract callback to visit a control flow graph node without going into
* subtrees of the node that are also represented by other
* control flow graph nodes.
*
* For example, traversing an IF node as root will visit the two subtrees
* pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and
* {@link ControlFlowGraph.Branch#ON_FALSE} edges.
*/
public abstract static class AbstractCfgNodeTraversalCallback implements
Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (parent == null) {
return true;
}
return !isEnteringNewCfgNode(n);
}
}
/**
* @return True if n should be represented by a new CFG node in the control
* flow graph.
*/
public static boolean isEnteringNewCfgNode(Node n) {
Node parent = n.getParent();
switch (parent.getToken()) {
case BLOCK:
case ROOT:
case SCRIPT:
case TRY:
return true;
case FUNCTION:
// A function node represents the start of a function where the name
// bleeds into the local scope and parameters are assigned
// to the formal argument names. The node includes the name of the
// function and the PARAM_LIST since we assume the whole set up process
// is atomic without change in control flow. The next change of
// control is going into the function's body, represented by the second
// child.
return n != parent.getSecondChild();
case WHILE:
case DO:
case IF:
// These control structures are represented by a node that holds the
// condition. Each of them is a branch node based on its condition.
return NodeUtil.getConditionExpression(parent) != n;
case FOR:
// The FOR(;;) node differs from other control structures in that
// it has an initialization and an increment statement. Those
// two statements have corresponding CFG nodes to represent them.
// The FOR node only represents the condition check for each iteration.
// That way the following:
// for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to
// var x = 0; while(x<10) { x++; }
return NodeUtil.getConditionExpression(parent) != n;
case FOR_IN:
// TODO(user): Investigate how we should handle the case where
// we have a very complex expression inside the FOR-IN header.
return n != parent.getFirstChild();
case SWITCH:
case CASE:
case CATCH:
case WITH:
return n != parent.getFirstChild();
default:
return false;
}
}
@Override
public String toString() {
String s = "CFG:\n";
for (GraphvizEdge e : getGraphvizEdges()) {
s += e.toString() + '\n';
}
return s;
}
}