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

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

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

import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.graph.Annotation;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphNode;
import com.google.javascript.jscomp.graph.LatticeElement;
import com.google.javascript.jscomp.parsing.parser.util.format.SimpleFormat;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

/**
 * A framework to help writing static program analysis. A subclass of
 * this framework should specify how a single node changes the state
 * of a program. This class finds a safe estimate (a fixed-point) for
 * the whole program. The proven facts about the program will be
 * annotated with
 * {@link com.google.javascript.jscomp.graph.GraphNode#setAnnotation} to the
 * given control flow graph's nodes in form of {@link LatticeElement}
 * after calling {@link #analyze()}.
 *
 * 

As a guideline, the following is a list of behaviors that any analysis * can take: *

    *
  1. Flow Direction: Is the analysis a forward or backward analysis? *
  2. Lattice Elements: How does the analysis represent the state of the * program at any given point? *
  3. JOIN Operation: Given two incoming paths and a lattice state value, what * can the compiler conclude at the join point? *
  4. Flow Equations: How does an instruction modify the state of program in * terms of lattice values? *
  5. Initial Entry Value: What can the compiler assume at the beginning of the * program? *
  6. Initial Estimate: What can the compiler assume at each point of the * program? (What is the BOTTOM value of the lattice) By definition this lattice * JOIN {@code x} for any {@code x} must also be {@code x}. *
* To make these behaviors known to the framework, the following steps must be * taken. *
    *
  1. Flow Direction: Implement {@link #isForward()}. *
  2. Lattice Elements: Implement {@link LatticeElement}. *
  3. JOIN Operation: Implement * {@link JoinOp#apply}. *
  4. Flow Equations: Implement * {@link #flowThrough(Object, LatticeElement)}. *
  5. Initial Entry Value: Implement {@link #createEntryLattice()}. *
  6. Initial Estimate: Implement {@link #createInitialEstimateLattice()}. *
* *

Upon execution of the {@link #analyze()} method, nodes of the input * control flow graph will be annotated with a {@link FlowState} object that * represents maximum fixed point solution. Any previous annotations at the * nodes of the control flow graph will be lost. * * * @param The control flow graph's node value type. * @param Lattice element type. */ abstract class DataFlowAnalysis { private final ControlFlowGraph cfg; final JoinOp joinOp; protected final Set> orderedWorkSet; /* * Feel free to increase this to a reasonable number if you are finding that * more and more passes need more steps before finding a fixed-point. * If you just have a special case, consider calling * {@link #analyze(int)} instead. */ public static final int MAX_STEPS = 800000; /** * Constructs a data flow analysis. * *

Typical usage *

   * DataFlowAnalysis dfa = ...
   * dfa.analyze();
   * 
* * {@link #analyze()} annotates the result to the control flow graph by * means of {@link DiGraphNode#setAnnotation} without any * modification of the graph itself. Additional calls to {@link #analyze()} * recomputes the analysis which can be useful if the control flow graph * has been modified. * * @param targetCfg The control flow graph object that this object performs * on. Modification of the graph requires a separate call to * {@link #analyze()}. * * @see #analyze() */ DataFlowAnalysis(ControlFlowGraph targetCfg, JoinOp joinOp) { this.cfg = targetCfg; this.joinOp = joinOp; Comparator> nodeComparator = cfg.getOptionalNodeComparator(isForward()); if (nodeComparator != null) { this.orderedWorkSet = new TreeSet<>(nodeComparator); } else { this.orderedWorkSet = new LinkedHashSet<>(); } } /** * Returns the control flow graph that this analysis was performed on. * Modifications can be done on this graph, however, the only time that the * annotations are correct is after {@link #analyze()} is called and before * the graph has been modified. */ final ControlFlowGraph getCfg() { return cfg; } protected L join(L latticeA, L latticeB) { return joinOp.apply(ImmutableList.of(latticeA, latticeB)); } /** * Checks whether the analysis is a forward flow analysis or backward flow * analysis. * * @return {@code true} if it is a forward analysis. */ abstract boolean isForward(); /** * Computes the output state for a given node and input state. * * @param node The node. * @param input Input lattice that should be read-only. * @return Output lattice. */ abstract L flowThrough(N node, L input); /** * Finds a fixed-point solution using at most {@link #MAX_STEPS} * iterations. * * @see #analyze(int) */ final void analyze() { analyze(MAX_STEPS); } /** * Finds a fixed-point solution. The function has the side effect of replacing * the existing node annotations with the computed solutions using {@link * com.google.javascript.jscomp.graph.GraphNode#setAnnotation(Annotation)}. * *

Initially, each node's input and output flow state contains the value * given by {@link #createInitialEstimateLattice()} (with the exception of the * entry node of the graph which takes on the {@link #createEntryLattice()} * value. Each node will use the output state of its predecessor and compute a * output state according to the instruction. At that time, any nodes that * depends on the node's newly modified output value will need to recompute * their output state again. Each step will perform a computation at one node * until no extra computation will modify any existing output state anymore. * * @param maxSteps Max number of iterations before the method stops and throw * a {@link MaxIterationsExceededException}. This will prevent the * analysis from going into a infinite loop. */ final void analyze(int maxSteps) { initialize(); int step = 0; while (!orderedWorkSet.isEmpty()) { if (step > maxSteps) { throw new MaxIterationsExceededException( "Analysis did not terminate after " + maxSteps + " iterations"); } DiGraphNode curNode = orderedWorkSet.iterator().next(); orderedWorkSet.remove(curNode); joinInputs(curNode); if (flow(curNode)) { // If there is a change in the current node, we want to grab the list // of nodes that this node affects. List> nextNodes = isForward() ? cfg.getDirectedSuccNodes(curNode) : cfg.getDirectedPredNodes(curNode); for (DiGraphNode nextNode : nextNodes) { if (nextNode != cfg.getImplicitReturn()) { orderedWorkSet.add(nextNode); } } } step++; } if (isForward()) { joinInputs(getCfg().getImplicitReturn()); } } /** * Gets the state of the initial estimation at each node. * * @return Initial state. */ abstract L createInitialEstimateLattice(); /** * Gets the incoming state of the entry node. * * @return Entry state. */ abstract L createEntryLattice(); /** * Initializes the work list and the control flow graph. */ protected void initialize() { // TODO(user): Calling clear doesn't deallocate the memory in a // LinkedHashSet. Consider creating a new work set if we plan to repeatedly // call analyze. orderedWorkSet.clear(); for (DiGraphNode node : cfg.getDirectedGraphNodes()) { node.setAnnotation(new FlowState<>(createInitialEstimateLattice(), createInitialEstimateLattice())); if (node != cfg.getImplicitReturn()) { orderedWorkSet.add(node); } } } /** * Performs a single flow through a node. * * @return {@code true} if the flow state differs from the previous state. */ protected boolean flow(DiGraphNode node) { FlowState state = node.getAnnotation(); if (isForward()) { L outBefore = state.out; state.out = flowThrough(node.getValue(), state.in); return !outBefore.equals(state.out); } else { L inBefore = state.in; state.in = flowThrough(node.getValue(), state.out); return !inBefore.equals(state.in); } } /** * Computes the new flow state at a given node's entry by merging the * output (input) lattice of the node's predecessor (successor). * * @param node Node to compute new join. */ protected void joinInputs(DiGraphNode node) { FlowState state = node.getAnnotation(); if (isForward()) { if (cfg.getEntry() == node) { state.setIn(createEntryLattice()); } else { List> inNodes = cfg.getDirectedPredNodes(node); if (inNodes.size() == 1) { FlowState inNodeState = inNodes.get(0).getAnnotation(); state.setIn(inNodeState.getOut()); } else if (inNodes.size() > 1) { List values = new ArrayList<>(inNodes.size()); for (DiGraphNode currentNode : inNodes) { FlowState currentNodeState = currentNode.getAnnotation(); values.add(currentNodeState.getOut()); } state.setIn(joinOp.apply(values)); } } } else { List> inNodes = cfg.getDirectedSuccNodes(node); if (inNodes.size() == 1) { DiGraphNode inNode = inNodes.get(0); if (inNode == cfg.getImplicitReturn()) { state.setOut(createEntryLattice()); } else { FlowState inNodeState = inNode.getAnnotation(); state.setOut(inNodeState.getIn()); } } else if (inNodes.size() > 1) { List values = new ArrayList<>(inNodes.size()); for (DiGraphNode currentNode : inNodes) { FlowState currentNodeState = currentNode.getAnnotation(); values.add(currentNodeState.getIn()); } state.setOut(joinOp.apply(values)); } } } /** * The in and out states of a node. * * @param Input and output lattice element type. */ static class FlowState implements Annotation { private L in; private L out; /** * Private constructor. No other classes should create new states. * * @param inState Input. * @param outState Output. */ private FlowState(L inState, L outState) { checkNotNull(inState); checkNotNull(outState); this.in = inState; this.out = outState; } L getIn() { return in; } void setIn(L in) { checkNotNull(in); this.in = in; } L getOut() { return out; } void setOut(L out) { checkNotNull(out); this.out = out; } @Override public String toString() { return SimpleFormat.format("IN: %s OUT: %s", in, out); } @Override public boolean equals(Object o) { if (o instanceof FlowState) { FlowState that = (FlowState) o; return that.in.equals(this.in) && that.out.equals(this.out); } return false; } @Override public int hashCode() { return Objects.hash(in, out); } } /** * The exception to be thrown if the analysis has been running for a long * number of iterations. Chances are the analysis is not monotonic, a * fixed-point cannot be found and it is currently stuck in an infinite loop. */ static class MaxIterationsExceededException extends RuntimeException { private static final long serialVersionUID = 1L; MaxIterationsExceededException(String msg) { super(msg); } } abstract static class BranchedForwardDataFlowAnalysis extends DataFlowAnalysis { @Override protected void initialize() { orderedWorkSet.clear(); for (DiGraphNode node : getCfg().getDirectedGraphNodes()) { int outEdgeCount = getCfg().getOutEdges(node.getValue()).size(); List outLattices = new ArrayList<>(); for (int i = 0; i < outEdgeCount; i++) { outLattices.add(createInitialEstimateLattice()); } node.setAnnotation(new BranchedFlowState<>( createInitialEstimateLattice(), outLattices)); if (node != getCfg().getImplicitReturn()) { orderedWorkSet.add(node); } } } BranchedForwardDataFlowAnalysis(ControlFlowGraph targetCfg, JoinOp joinOp) { super(targetCfg, joinOp); } @Override final boolean isForward() { return true; } /** * The branched flow function maps a single lattice to a list of output * lattices. * *

Each outgoing edge of a node will have a corresponding output lattice * in the ordered returned by * {@link com.google.javascript.jscomp.graph.DiGraph#getOutEdges(Object)} * in the returned list. * * @return A list of output values depending on the edge's branch type. */ abstract List branchedFlowThrough(N node, L input); @Override protected final boolean flow(DiGraphNode node) { BranchedFlowState state = node.getAnnotation(); List outBefore = state.out; state.out = branchedFlowThrough(node.getValue(), state.in); checkState(outBefore.size() == state.out.size()); for (int i = 0; i < outBefore.size(); i++) { if (!outBefore.get(i).equals(state.out.get(i))) { return true; } } return false; } @Override protected void joinInputs(DiGraphNode node) { BranchedFlowState state = node.getAnnotation(); List> predNodes = getCfg().getDirectedPredNodes(node); List values = new ArrayList<>(predNodes.size()); for (DiGraphNode predNode : predNodes) { BranchedFlowState predNodeState = predNode.getAnnotation(); L in = predNodeState.out.get( getCfg().getDirectedSuccNodes(predNode).indexOf(node)); values.add(in); } if (getCfg().getEntry() == node) { state.setIn(createEntryLattice()); } else if (!values.isEmpty()) { state.setIn(joinOp.apply(values)); } } } /** * The in and out states of a node. * * @param Input and output lattice element type. */ static class BranchedFlowState implements Annotation { private L in; private List out; /** * Private constructor. No other classes should create new states. * * @param inState Input. * @param outState Output. */ private BranchedFlowState(L inState, List outState) { checkNotNull(inState); checkNotNull(outState); this.in = inState; this.out = outState; } L getIn() { return in; } void setIn(L in) { checkNotNull(in); this.in = in; } @Override public String toString() { return SimpleFormat.format("IN: %s OUT: %s", in, out); } @Override public boolean equals(Object o) { if (o instanceof BranchedFlowState) { BranchedFlowState that = (BranchedFlowState) o; return that.in.equals(this.in) && that.out.equals(this.out); } return false; } @Override public int hashCode() { return Objects.hash(in, out); } } /** * Compute set of escaped variables. When a variable is escaped in a dataflow analysis, it can be * referenced outside of the code that we are analyzing. A variable is escaped if any of the * following is true: * * 1. Exported variables as they can be needed after the script terminates. * 2. Names of named functions because in JavaScript, function foo(){} does not kill * foo in the dataflow. * * @param jsScope Must be a function scope */ static void computeEscaped( final Scope jsScope, final Set escaped, AbstractCompiler compiler, Es6SyntacticScopeCreator scopeCreator) { checkArgument(jsScope.isFunctionScope()); AbstractPostOrderCallback finder = new AbstractPostOrderCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { Node enclosingBlock = NodeUtil.getEnclosingFunction(n); if (jsScope.getRootNode() == enclosingBlock || !n.isName() || parent.isFunction()) { return; } String name = n.getString(); Var var = t.getScope().getVar(name); if (var != null) { Node enclosingScopeNode = NodeUtil.getEnclosingFunction(var.getNode()); if (enclosingScopeNode == jsScope.getRootNode()) { escaped.add(var); } } } }; Map allVarsInFn = new HashMap<>(); List orderedVars = new ArrayList<>(); NodeUtil.getAllVarsDeclaredInFunction( allVarsInFn, orderedVars, compiler, scopeCreator, jsScope); NodeTraversal t = new NodeTraversal(compiler, finder, scopeCreator); t.traverseAtScope(jsScope); // TODO (simranarora) catch variables should not be considered escaped in ES6. Getting rid of // the catch check is causing breakages however for (Var var : allVarsInFn.values()) { if (var.getParentNode().isCatch() || compiler.getCodingConvention().isExported(var.getName())) { escaped.add(var); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy