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

org.checkerframework.dataflow.analysis.AnalysisResult Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

The newest version!
package org.checkerframework.dataflow.analysis;

import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.UnaryTree;

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.node.Node;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TreeUtils;
import org.plumelib.util.UniqueId;
import org.plumelib.util.UnmodifiableIdentityHashMap;

import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicLong;

import javax.lang.model.element.VariableElement;

/**
 * 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  type of the abstract value that is tracked
 * @param  the store type used in the analysis
 */
public class AnalysisResult, S extends Store> implements UniqueId {

    /**
     * For efficiency, certain maps stored in the result are only copied lazily, when they need to
     * be mutated. This flag tracks if the copying has occurred.
     */
    private boolean mapsCopied = false;

    /** Abstract values of nodes. */
    protected IdentityHashMap nodeValues;

    /**
     * Map from AST {@link Tree}s to sets of {@link Node}s.
     *
     * 

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 IdentityHashMap> treeLookup; /** * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the * synthetic tree that is {@code v + 1} or {@code v - 1}. */ protected IdentityHashMap postfixLookup; /** Map from (effectively final) local variable elements to their abstract value. */ protected final Map finalLocalValues; /** The stores before every method call. */ protected final IdentityHashMap> 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, IdentityHashMap>> analysisCaches; /** The unique ID for the next-created object. */ private static final AtomicLong nextUid = new AtomicLong(0); /** The unique ID of this object. */ private final transient long uid = nextUid.getAndIncrement(); @Override public long getUid(@UnknownInitialization AnalysisResult this) { return uid; } /** * Initialize with given mappings. * * @param nodeValues {@link #nodeValues} * @param stores {@link #stores} * @param treeLookup {@link #treeLookup} * @param postfixLookup {@link #postfixLookup} * @param finalLocalValues {@link #finalLocalValues} * @param analysisCaches {@link #analysisCaches} */ protected AnalysisResult( IdentityHashMap nodeValues, IdentityHashMap> stores, IdentityHashMap> treeLookup, IdentityHashMap postfixLookup, Map finalLocalValues, Map, IdentityHashMap>> analysisCaches) { this.nodeValues = UnmodifiableIdentityHashMap.wrap(nodeValues); this.treeLookup = UnmodifiableIdentityHashMap.wrap(treeLookup); this.postfixLookup = UnmodifiableIdentityHashMap.wrap(postfixLookup); // 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 postfixLookup {@link #postfixLookup} * @param finalLocalValues {@link #finalLocalValues} */ public AnalysisResult( IdentityHashMap nodeValues, IdentityHashMap> stores, IdentityHashMap> treeLookup, IdentityHashMap postfixLookup, Map finalLocalValues) { this( nodeValues, stores, treeLookup, postfixLookup, finalLocalValues, new IdentityHashMap<>()); } /** * Initialize empty result with specified cache. * * @param analysisCaches {@link #analysisCaches} */ public AnalysisResult( Map, IdentityHashMap>> 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 other) { copyMapsIfNeeded(); nodeValues.putAll(other.nodeValues); mergeTreeLookup(treeLookup, other.treeLookup); postfixLookup.putAll(other.postfixLookup); stores.putAll(other.stores); finalLocalValues.putAll(other.finalLocalValues); } /** Make copies of certain internal IdentityHashMaps, if they have not been copied already. */ private void copyMapsIfNeeded() { if (!mapsCopied) { nodeValues = new IdentityHashMap<>(nodeValues); treeLookup = new IdentityHashMap<>(treeLookup); postfixLookup = new IdentityHashMap<>(postfixLookup); mapsCopied = true; } } /** * 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> treeLookup, IdentityHashMap> otherTreeLookup) { for (Map.Entry> entry : otherTreeLookup.entrySet()) { Set 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 Map 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 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: * *

    *
  1. In a lambda expression such as {@code () -> 5} the {@code 5} is both an {@code * IntegerLiteralNode} and a {@code LambdaResultExpressionNode}. *
  2. Widening and narrowing primitive conversions can result in {@code * WideningConversionNode} and {@code NarrowingConversionNode}. *
  3. Automatic String conversion can result in a {@code StringConversionNode}. *
  4. 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. *
* * 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 getNodesForTree(Tree tree) { return treeLookup.get(tree); } /** * Returns the synthetic {@code v + 1} or {@code v - 1} corresponding to the postfix increment * or decrement tree. * * @param postfixTree a postfix increment or decrement tree * @return the synthetic {@code v + 1} or {@code v - 1} corresponding to the postfix increment * or decrement tree */ public BinaryTree getPostfixBinaryTree(UnaryTree postfixTree) { if (!postfixLookup.containsKey(postfixTree)) { throw new BugInCF(postfixTree + " is not in postfixLookup"); } return postfixLookup.get(postfixTree); } /** * 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 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 transferInput = stores.get(block); assert transferInput != null : "@AssumeAssertion(nullness): transferInput should be non-null"; Analysis analysis = transferInput.analysis; switch (analysis.getDirection()) { case FORWARD: return transferInput.getRegularStore(); case BACKWARD: List nodes = block.getNodes(); if (nodes.isEmpty()) { // This block doesn't contain any node, return the store in the transfer input. return transferInput.getRegularStore(); } else { Node firstNode = nodes.get(0); 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 transferInput = stores.get(block); assert transferInput != null : "@AssumeAssertion(nullness): transferInput should be non-null"; Analysis 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(); } else { 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 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. * *

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 is null if node is a formal parameter of a method, or is a field access thereof Block block = node.getBlock(); assert block != null : "@AssumeAssertion(nullness): null block for node " + node; TransferInput transferInput = stores.get(block); if (transferInput == null) { return null; } // Calling Analysis.runAnalysisFor() may mutate the internal nodeValues map inside an // AbstractAnalysis object, and by default the AnalysisResult constructor just wraps this // map without copying it. So here the AnalysisResult maps must be copied, to preserve // them. // TODO: Wouldn't it be safer to do at the beginning of the called method? copyMapsIfNeeded(); 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 the abstract value type to be tracked by the analysis * @param 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 , S extends Store> S runAnalysisFor( Node node, Analysis.BeforeOrAfter preOrPost, TransferInput transferInput, IdentityHashMap nodeValues, @Nullable Map, IdentityHashMap>> 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("postfixLookup = " + postfixLookup); 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 the type of values in the map * @param nodeValues a map to format * @return a printed representation of the given map */ public static String nodeValuesToString(Map nodeValues) { if (nodeValues.isEmpty()) { return "{}"; } StringJoiner result = new StringJoiner(String.format("%n ")); result.add("{"); for (Map.Entry 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> treeLookup) { if (treeLookup.isEmpty()) { return "{}"; } StringJoiner result = new StringJoiner(String.format("%n ")); result.add("{"); for (Map.Entry> entry : treeLookup.entrySet()) { Tree key = entry.getKey(); result.add( TreeUtils.toStringTruncated(key, 65) + " => " + Node.nodeCollectionToString(entry.getValue())); } result.add("}"); return result.toString(); } }