blob: 2d9692833600fb3e0ddf6d808e4cf011f91ce171 [file] [log] [blame]
package org.checkerframework.dataflow.analysis;
import com.sun.source.tree.Tree;
import com.sun.source.tree.UnaryTree;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicLong;
import javax.lang.model.element.Element;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.cfg.block.Block;
import org.checkerframework.dataflow.cfg.block.ExceptionBlock;
import org.checkerframework.dataflow.cfg.node.AssignmentNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TreeUtils;
import org.plumelib.util.UniqueId;
/**
* An {@link AnalysisResult} represents the result of a org.checkerframework.dataflow analysis by
* providing the abstract values given a node or a tree. Note that it does not keep track of custom
* results computed by some analysis.
*
* @param <V> type of the abstract value that is tracked
* @param <S> the store type used in the analysis
*/
public class AnalysisResult<V extends AbstractValue<V>, S extends Store<S>> implements UniqueId {
/** Abstract values of nodes. */
protected final IdentityHashMap<Node, V> nodeValues;
/**
* Map from AST {@link Tree}s to sets of {@link Node}s.
*
* <p>Some of those Nodes might not be keys in {@link #nodeValues}. One reason is that the Node is
* unreachable in the control flow graph, so dataflow never gave it a value.
*/
protected final IdentityHashMap<Tree, Set<Node>> treeLookup;
/** Map from AST {@link UnaryTree}s to corresponding {@link AssignmentNode}s. */
protected final IdentityHashMap<UnaryTree, AssignmentNode> unaryAssignNodeLookup;
/** Map from (effectively final) local variable elements to their abstract value. */
protected final HashMap<Element, V> finalLocalValues;
/** The stores before every method call. */
protected final IdentityHashMap<Block, TransferInput<V, S>> stores;
/**
* Caches of the analysis results for each input for the block of the node and each node.
*
* @see #runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, IdentityHashMap, Map)
*/
protected final Map<TransferInput<V, S>, IdentityHashMap<Node, TransferResult<V, S>>>
analysisCaches;
/** The unique ID for the next-created object. */
static final AtomicLong nextUid = new AtomicLong(0);
/** The unique ID of this object. */
final transient long uid = nextUid.getAndIncrement();
@Override
public long getUid(@UnknownInitialization AnalysisResult<V, S> this) {
return uid;
}
/**
* Initialize with given mappings.
*
* @param nodeValues {@link #nodeValues}
* @param stores {@link #stores}
* @param treeLookup {@link #treeLookup}
* @param unaryAssignNodeLookup {@link #unaryAssignNodeLookup}
* @param finalLocalValues {@link #finalLocalValues}
* @param analysisCaches {@link #analysisCaches}
*/
protected AnalysisResult(
IdentityHashMap<Node, V> nodeValues,
IdentityHashMap<Block, TransferInput<V, S>> stores,
IdentityHashMap<Tree, Set<Node>> treeLookup,
IdentityHashMap<UnaryTree, AssignmentNode> unaryAssignNodeLookup,
HashMap<Element, V> finalLocalValues,
Map<TransferInput<V, S>, IdentityHashMap<Node, TransferResult<V, S>>> analysisCaches) {
this.nodeValues = new IdentityHashMap<>(nodeValues);
this.treeLookup = new IdentityHashMap<>(treeLookup);
this.unaryAssignNodeLookup = new IdentityHashMap<>(unaryAssignNodeLookup);
// TODO: why are stores and finalLocalValues captured?
this.stores = stores;
this.finalLocalValues = finalLocalValues;
this.analysisCaches = analysisCaches;
}
/**
* Initialize with given mappings and empty cache.
*
* @param nodeValues {@link #nodeValues}
* @param stores {@link #stores}
* @param treeLookup {@link #treeLookup}
* @param unaryAssignNodeLookup {@link #unaryAssignNodeLookup}
* @param finalLocalValues {@link #finalLocalValues}
*/
public AnalysisResult(
IdentityHashMap<Node, V> nodeValues,
IdentityHashMap<Block, TransferInput<V, S>> stores,
IdentityHashMap<Tree, Set<Node>> treeLookup,
IdentityHashMap<UnaryTree, AssignmentNode> unaryAssignNodeLookup,
HashMap<Element, V> finalLocalValues) {
this(
nodeValues,
stores,
treeLookup,
unaryAssignNodeLookup,
finalLocalValues,
new IdentityHashMap<>());
}
/**
* Initialize empty result with specified cache.
*
* @param analysisCaches {@link #analysisCaches}
*/
public AnalysisResult(
Map<TransferInput<V, S>, IdentityHashMap<Node, TransferResult<V, S>>> analysisCaches) {
this(
new IdentityHashMap<>(),
new IdentityHashMap<>(),
new IdentityHashMap<>(),
new IdentityHashMap<>(),
new HashMap<>(),
analysisCaches);
}
/**
* Combine with another analysis result.
*
* @param other an analysis result to combine with this
*/
public void combine(AnalysisResult<V, S> other) {
nodeValues.putAll(other.nodeValues);
mergeTreeLookup(treeLookup, other.treeLookup);
unaryAssignNodeLookup.putAll(other.unaryAssignNodeLookup);
stores.putAll(other.stores);
finalLocalValues.putAll(other.finalLocalValues);
}
/**
* Merge all entries from otherTreeLookup into treeLookup. Merge sets if already present.
*
* @param treeLookup a map from abstract syntax trees to sets of nodes
* @param otherTreeLookup another treeLookup that will be merged into {@code treeLookup}
*/
private static void mergeTreeLookup(
IdentityHashMap<Tree, Set<Node>> treeLookup,
IdentityHashMap<Tree, Set<Node>> otherTreeLookup) {
for (Map.Entry<Tree, Set<Node>> entry : otherTreeLookup.entrySet()) {
Set<Node> hit = treeLookup.get(entry.getKey());
if (hit == null) {
treeLookup.put(entry.getKey(), entry.getValue());
} else {
hit.addAll(entry.getValue());
}
}
}
/**
* Returns the value of effectively final local variables.
*
* @return the value of effectively final local variables
*/
public HashMap<Element, V> getFinalLocalValues() {
return finalLocalValues;
}
/**
* Returns the abstract value for {@link Node} {@code n}, or {@code null} if no information is
* available. Note that if the analysis has not finished yet, this value might not represent the
* final value for this node.
*
* @param n a node
* @return the abstract value for {@link Node} {@code n}, or {@code null} if no information is
* available
*/
public @Nullable V getValue(Node n) {
return nodeValues.get(n);
}
/**
* Returns the abstract value for {@link Tree} {@code t}, or {@code null} if no information is
* available. Note that if the analysis has not finished yet, this value might not represent the
* final value for this node.
*
* @param t a tree
* @return the abstract value for {@link Tree} {@code t}, or {@code null} if no information is
* available
*/
public @Nullable V getValue(Tree t) {
Set<Node> nodes = treeLookup.get(t);
if (nodes == null) {
return null;
}
V merged = null;
for (Node aNode : nodes) {
V a = getValue(aNode);
if (merged == null) {
merged = a;
} else if (a != null) {
merged = merged.leastUpperBound(a);
}
}
return merged;
}
/**
* Returns the {@code Node}s corresponding to a particular {@code Tree}. Multiple {@code Node}s
* can correspond to a single {@code Tree} because of several reasons:
*
* <ol>
* <li>In a lambda expression such as {@code () -> 5} the {@code 5} is both an {@code
* IntegerLiteralNode} and a {@code LambdaResultExpressionNode}.
* <li>Widening and narrowing primitive conversions can result in {@code WideningConversionNode}
* and {@code NarrowingConversionNode}.
* <li>Automatic String conversion can result in a {@code StringConversionNode}.
* <li>Trees for {@code finally} blocks are cloned to achieve a precise CFG. Any {@code Tree}
* within a finally block can have multiple corresponding {@code Node}s attached to them.
* </ol>
*
* Callers of this method should always iterate through the returned set, possibly ignoring all
* {@code Node}s they are not interested in.
*
* @param tree a tree
* @return the set of {@link Node}s for a given {@link Tree}
*/
public @Nullable Set<Node> getNodesForTree(Tree tree) {
return treeLookup.get(tree);
}
/**
* Returns the corresponding {@link AssignmentNode} for a given {@link UnaryTree}.
*
* @param tree a unary tree
* @return the corresponding assignment node
*/
public AssignmentNode getAssignForUnaryTree(UnaryTree tree) {
if (!unaryAssignNodeLookup.containsKey(tree)) {
throw new BugInCF(tree + " is not in unaryAssignNodeLookup");
}
return unaryAssignNodeLookup.get(tree);
}
/**
* Returns the store immediately before a given {@link Tree}.
*
* @param tree a tree
* @return the store immediately before a given {@link Tree}
*/
public @Nullable S getStoreBefore(Tree tree) {
Set<Node> nodes = getNodesForTree(tree);
if (nodes == null) {
return null;
}
S merged = null;
for (Node node : nodes) {
S s = getStoreBefore(node);
if (merged == null) {
merged = s;
} else if (s != null) {
merged = merged.leastUpperBound(s);
}
}
return merged;
}
/**
* Returns the store immediately before a given {@link Node}.
*
* @param node a node
* @return the store immediately before a given {@link Node}
*/
public @Nullable S getStoreBefore(Node node) {
return runAnalysisFor(node, Analysis.BeforeOrAfter.BEFORE);
}
/**
* Returns the regular store immediately before a given {@link Block}.
*
* @param block a block
* @return the store right before the given block
*/
public S getStoreBefore(Block block) {
TransferInput<V, S> transferInput = stores.get(block);
assert transferInput != null : "@AssumeAssertion(nullness): transferInput should be non-null";
Analysis<V, S, ?> analysis = transferInput.analysis;
switch (analysis.getDirection()) {
case FORWARD:
return transferInput.getRegularStore();
case BACKWARD:
Node firstNode;
switch (block.getType()) {
case REGULAR_BLOCK:
firstNode = block.getNodes().get(0);
break;
case EXCEPTION_BLOCK:
firstNode = ((ExceptionBlock) block).getNode();
break;
default:
firstNode = null;
}
if (firstNode == null) {
// This block doesn't contains any node, return the store in the transfer input
return transferInput.getRegularStore();
}
return analysis.runAnalysisFor(
firstNode, Analysis.BeforeOrAfter.BEFORE, transferInput, nodeValues, analysisCaches);
default:
throw new BugInCF("Unknown direction: " + analysis.getDirection());
}
}
/**
* Returns the regular store immediately after a given block.
*
* @param block a block
* @return the store after the given block
*/
public S getStoreAfter(Block block) {
TransferInput<V, S> transferInput = stores.get(block);
assert transferInput != null : "@AssumeAssertion(nullness): transferInput should be non-null";
Analysis<V, S, ?> analysis = transferInput.analysis;
switch (analysis.getDirection()) {
case FORWARD:
Node lastNode = block.getLastNode();
if (lastNode == null) {
// This block doesn't contain any node, return the store in the transfer input
return transferInput.getRegularStore();
}
return analysis.runAnalysisFor(
lastNode, Analysis.BeforeOrAfter.AFTER, transferInput, nodeValues, analysisCaches);
case BACKWARD:
return transferInput.getRegularStore();
default:
throw new BugInCF("Unknown direction: " + analysis.getDirection());
}
}
/**
* Returns the store immediately after a given {@link Tree}.
*
* @param tree a tree
* @return the store immediately after a given {@link Tree}
*/
public @Nullable S getStoreAfter(Tree tree) {
Set<Node> nodes = getNodesForTree(tree);
if (nodes == null) {
return null;
}
S merged = null;
for (Node node : nodes) {
S s = getStoreAfter(node);
if (merged == null) {
merged = s;
} else if (s != null) {
merged = merged.leastUpperBound(s);
}
}
return merged;
}
/**
* Returns the store immediately after a given {@link Node}.
*
* @param node a node
* @return the store immediately after a given {@link Node}
*/
public @Nullable S getStoreAfter(Node node) {
return runAnalysisFor(node, Analysis.BeforeOrAfter.AFTER);
}
/**
* Runs the analysis again within the block of {@code node} and returns the store at the location
* of {@code node}. If {@code before} is true, then the store immediately before the {@link Node}
* {@code node} is returned. Otherwise, the store after {@code node} is returned.
*
* <p>If the given {@link Node} cannot be reached (in the control flow graph), then {@code null}
* is returned.
*
* @param node the node to analyze
* @param preOrPost which store to return: the store immediately before {@code node} or the store
* after {@code node}
* @return the store before or after {@code node} (depends on the value of {@code before}) after
* running the analysis
*/
protected @Nullable S runAnalysisFor(Node node, Analysis.BeforeOrAfter preOrPost) {
Block block = node.getBlock();
assert block != null : "@AssumeAssertion(nullness): invariant";
TransferInput<V, S> transferInput = stores.get(block);
if (transferInput == null) {
return null;
}
return runAnalysisFor(node, preOrPost, transferInput, nodeValues, analysisCaches);
}
/**
* Runs the analysis again within the block of {@code node} and returns the store at the location
* of {@code node}. If {@code before} is true, then the store immediately before the {@link Node}
* {@code node} is returned. Otherwise, the store immediately after {@code node} is returned. If
* {@code analysisCaches} is not null, this method uses a cache. {@code analysisCaches} is a map
* of a block of node to the cached analysis result. If the cache for {@code transferInput} is not
* in {@code analysisCaches}, this method creates new cache and stores it in {@code
* analysisCaches}. The cache is a map of nodes to the analysis results of the nodes.
*
* @param <V> the abstract value type to be tracked by the analysis
* @param <S> the store type used in the analysis
* @param node the node to analyze
* @param preOrPost which store to return: the store immediately before {@code node} or the store
* after {@code node}
* @param transferInput a transfer input
* @param nodeValues {@link #nodeValues}
* @param analysisCaches {@link #analysisCaches}
* @return the store before or after {@code node} (depends on the value of {@code before}) after
* running the analysis
*/
public static <V extends AbstractValue<V>, S extends Store<S>> S runAnalysisFor(
Node node,
Analysis.BeforeOrAfter preOrPost,
TransferInput<V, S> transferInput,
IdentityHashMap<Node, V> nodeValues,
Map<TransferInput<V, S>, IdentityHashMap<Node, TransferResult<V, S>>> analysisCaches) {
if (transferInput.analysis == null) {
throw new BugInCF("Analysis in transferInput cannot be null.");
}
return transferInput.analysis.runAnalysisFor(
node, preOrPost, transferInput, nodeValues, analysisCaches);
}
/**
* Returns a verbose string representation of this, useful for debugging.
*
* @return a string representation of this
*/
public String toStringDebug() {
StringJoiner result =
new StringJoiner(
String.format("%n "), String.format("AnalysisResult{%n "), String.format("%n}"));
result.add("nodeValues = " + nodeValuesToString(nodeValues));
result.add("treeLookup = " + treeLookupToString(treeLookup));
result.add("unaryAssignNodeLookup = " + unaryAssignNodeLookup);
result.add("finalLocalValues = " + finalLocalValues);
result.add("stores = " + stores);
result.add("analysisCaches = " + analysisCaches);
return result.toString();
}
/**
* Returns a verbose string representation, useful for debugging. The map has the same type as the
* {@code nodeValues} field.
*
* @param <V> the type of values in the map
* @param nodeValues a map to format
* @return a printed representation of the given map
*/
public static <V> String nodeValuesToString(Map<Node, V> nodeValues) {
if (nodeValues.isEmpty()) {
return "{}";
}
StringJoiner result = new StringJoiner(String.format("%n "));
result.add("{");
for (Map.Entry<Node, V> entry : nodeValues.entrySet()) {
Node key = entry.getKey();
result.add(String.format("%s => %s", key.toStringDebug(), entry.getValue()));
}
result.add("}");
return result.toString();
}
/**
* Returns a verbose string representation of a map, useful for debugging. The map has the same
* type as the {@code treeLookup} field.
*
* @param treeLookup a map to format
* @return a printed representation of the given map
*/
public static String treeLookupToString(Map<Tree, Set<Node>> treeLookup) {
if (treeLookup.isEmpty()) {
return "{}";
}
StringJoiner result = new StringJoiner(String.format("%n "));
result.add("{");
for (Map.Entry<Tree, Set<Node>> entry : treeLookup.entrySet()) {
Tree key = entry.getKey();
result.add(
TreeUtils.toStringTruncated(key, 65)
+ " => "
+ Node.nodeCollectionToString(entry.getValue()));
}
result.add("}");
return result.toString();
}
}