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

org.checkerframework.dataflow.analysis.AbstractAnalysis 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.

There is a newer version: 3.48.2
Show newest version
package org.checkerframework.dataflow.analysis;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.util.Comparator;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.Set;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.interning.qual.FindDistinct;
import org.checkerframework.checker.interning.qual.InternedDistinct;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
import org.checkerframework.dataflow.cfg.ControlFlowGraph;
import org.checkerframework.dataflow.cfg.block.Block;
import org.checkerframework.dataflow.cfg.block.SpecialBlock;
import org.checkerframework.dataflow.cfg.node.AssignmentNode;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;

/**
 * Implementation of common features for {@link BackwardAnalysisImpl} and {@link
 * ForwardAnalysisImpl}.
 *
 * @param  the abstract value type to be tracked by the analysis
 * @param  the store type used in the analysis
 * @param  the transfer function type that is used to approximated runtime behavior
 */
public abstract class AbstractAnalysis<
        V extends AbstractValue, S extends Store, T extends TransferFunction>
    implements Analysis {

  /** The direction of this analysis. */
  protected final Direction direction;

  /** Is the analysis currently running? */
  protected boolean isRunning = false;

  /** The transfer function for regular nodes. */
  // TODO: make final. Currently, the transferFunction has a reference to the analysis, so it
  //  can't be created until the Analysis is initialized.
  protected @MonotonicNonNull T transferFunction;

  /** The current control flow graph to perform the analysis on. */
  protected @MonotonicNonNull ControlFlowGraph cfg;

  /**
   * The transfer inputs of every basic block (assumed to be 'no information' if not present, inputs
   * before blocks in forward analysis, after blocks in backward analysis).
   */
  protected final IdentityHashMap> inputs = new IdentityHashMap<>();

  /** The worklist used for the fix-point iteration. */
  protected final Worklist worklist;

  /** Abstract values of nodes. */
  protected final IdentityHashMap nodeValues = new IdentityHashMap<>();

  /** Map from (effectively final) local variable elements to their abstract value. */
  protected final HashMap finalLocalValues = new HashMap<>();

  /**
   * The node that is currently handled in the analysis (if it is running). The following invariant
   * holds:
   *
   * 
   *   !isRunning ⇒ (currentNode == null)
   * 
*/ // currentNode == null when isRunning is true. // See https://github.com/typetools/checker-framework/issues/4115 protected @InternedDistinct @Nullable Node currentNode; /** * The tree that is currently being looked at. The transfer function can set this tree to make * sure that calls to {@code getValue} will not return information for this given tree. */ protected @InternedDistinct @Nullable Tree currentTree; /** The current transfer input when the analysis is running. */ protected @Nullable TransferInput currentInput; /** * Returns the tree that is currently being looked at. The transfer function can set this tree to * make sure that calls to {@code getValue} will not return information for this given tree. * * @return the tree that is currently being looked at */ public @Nullable Tree getCurrentTree() { return currentTree; } /** * Set the tree that is currently being looked at. * * @param currentTree the tree that should be currently looked at */ public void setCurrentTree(@FindDistinct Tree currentTree) { this.currentTree = currentTree; } /** * Set the node that is currently being looked at. * * @param currentNode the node that should be currently looked at */ protected void setCurrentNode(@FindDistinct @Nullable Node currentNode) { this.currentNode = currentNode; } /** * Implementation of common features for {@link BackwardAnalysisImpl} and {@link * ForwardAnalysisImpl}. * * @param direction direction of the analysis */ protected AbstractAnalysis(Direction direction) { this.direction = direction; this.worklist = new Worklist(this.direction); } /** Initialize the transfer inputs of every basic block before performing the analysis. */ @RequiresNonNull("cfg") protected abstract void initInitialInputs(); /** * Propagate the stores in {@code currentInput} to the next block in the direction of analysis, * according to the {@code flowRule}. * * @param nextBlock the target block to propagate the stores to * @param node the node of the target block * @param currentInput the current transfer input * @param flowRule the flow rule being used * @param addToWorklistAgain whether the block should be added to {@link #worklist} again */ protected abstract void propagateStoresTo( Block nextBlock, Node node, TransferInput currentInput, Store.FlowRule flowRule, boolean addToWorklistAgain); @Override public boolean isRunning() { return isRunning; } @Override public Direction getDirection() { return this.direction; } @Override @SuppressWarnings("nullness:contracts.precondition.override") // implementation field @RequiresNonNull("cfg") public AnalysisResult getResult() { if (isRunning) { throw new BugInCF( "AbstractAnalysis::getResult() shouldn't be called when the analysis is running."); } return new AnalysisResult<>( nodeValues, inputs, cfg.getTreeLookup(), cfg.getPostfixNodeLookup(), finalLocalValues); } @Override public @Nullable T getTransferFunction() { return transferFunction; } @Override public @Nullable V getValue(Node n) { if (isRunning) { // we don't have a org.checkerframework.dataflow fact about the current node yet if (currentNode == null || currentNode == n || (currentTree != null && currentTree == n.getTree())) { return null; } // check that 'n' is a subnode of 'currentNode'. Check immediate operands // first for efficiency. assert !n.isLValue() : "Did not expect an lvalue, but got " + n; if (!currentNode.getOperands().contains(n) && !currentNode.getTransitiveOperands().contains(n)) { return null; } // fall through when the current node is not 'n', and 'n' is not a subnode. } return nodeValues.get(n); } /** * Returns all current node values. * * @return {@link #nodeValues} */ public IdentityHashMap getNodeValues() { return nodeValues; } /** * Set all current node values to the given map. * * @param in the current node values */ /*package-private*/ void setNodeValues(IdentityHashMap in) { assert !isRunning; nodeValues.clear(); nodeValues.putAll(in); } @Override @SuppressWarnings("nullness:contracts.precondition.override") // implementation field @RequiresNonNull("cfg") public @Nullable S getRegularExitStore() { SpecialBlock regularExitBlock = cfg.getRegularExitBlock(); if (inputs.containsKey(regularExitBlock)) { return inputs.get(regularExitBlock).getRegularStore(); } else { return null; } } @Override @SuppressWarnings("nullness:contracts.precondition.override") // implementation field @RequiresNonNull("cfg") public @Nullable S getExceptionalExitStore() { SpecialBlock exceptionalExitBlock = cfg.getExceptionalExitBlock(); if (inputs.containsKey(exceptionalExitBlock)) { S exceptionalExitStore = inputs.get(exceptionalExitBlock).getRegularStore(); return exceptionalExitStore; } else { return null; } } /** * Get the set of {@link Node}s for a given {@link Tree}. Returns null for trees that don't * produce a value. * * @param t the given tree * @return the set of corresponding nodes to the given tree */ public @Nullable Set getNodesForTree(Tree t) { if (cfg == null) { return null; } return cfg.getNodesCorrespondingToTree(t); } @Override public @Nullable V getValue(Tree t) { // Dataflow is analyzing the tree, so no value is available. if (t == currentTree || cfg == null) { return null; } V result = getValue(getNodesForTree(t)); if (result == null) { result = getValue(cfg.getTreeLookup().get(t)); } return result; } /** * Returns the least upper bound of the values of {@code nodes}. * * @param nodes a set of nodes * @return the least upper bound of the values of {@code nodes} */ private @Nullable V getValue(@Nullable Set nodes) { if (nodes == null) { return null; } V merged = null; for (Node aNode : nodes) { if (aNode.isLValue()) { return null; } V v = getValue(aNode); if (merged == null) { merged = v; } else if (v != null) { merged = merged.leastUpperBound(v); } } return merged; } /** * Get the {@link MethodTree} of the current CFG if the argument {@link Tree} maps to a {@link * Node} in the CFG or {@code null} otherwise. * * @param t the given tree * @return the contained method tree of the given tree */ public @Nullable MethodTree getContainingMethod(Tree t) { if (cfg == null) { return null; } return cfg.getContainingMethod(t); } /** * Get the {@link ClassTree} of the current CFG if the argument {@link Tree} maps to a {@link * Node} in the CFG or {@code null} otherwise. * * @param t the given tree * @return the contained class tree of the given tree */ public @Nullable ClassTree getContainingClass(Tree t) { if (cfg == null) { return null; } return cfg.getContainingClass(t); } /** * Call the transfer function for node {@code node}, and set that node as current node first. This * method requires a {@code transferInput} that the method can modify. * * @param node the given node * @param transferInput the transfer input * @return the output of the transfer function */ protected TransferResult callTransferFunction( Node node, TransferInput transferInput) { assert transferFunction != null : "@AssumeAssertion(nullness): invariant"; if (node.isLValue()) { // TODO: should the default behavior return a regular transfer result, a conditional // transfer result (depending on store.containsTwoStores()), or is the following // correct? return new RegularTransferResult<>(null, transferInput.getRegularStore()); } transferInput.node = node; setCurrentNode(node); @SuppressWarnings("nullness") // CF bug: "INFERENCE FAILED" TransferResult transferResult = node.accept(transferFunction, transferInput); setCurrentNode(null); if (node instanceof AssignmentNode) { // store the flow-refined value effectively for final local variables AssignmentNode assignment = (AssignmentNode) node; Node lhst = assignment.getTarget(); if (lhst instanceof LocalVariableNode) { LocalVariableNode lhs = (LocalVariableNode) lhst; VariableElement elem = lhs.getElement(); if (ElementUtils.isEffectivelyFinal(elem)) { V resval = transferResult.getResultValue(); if (resval != null) { finalLocalValues.put(elem, resval); } } } } return transferResult; } /** * Initialize the analysis with a new control flow graph. * * @param cfg the control flow graph to use */ protected final void init(ControlFlowGraph cfg) { initFields(cfg); initInitialInputs(); } /** * Should exceptional control flow for a particular exception type be ignored? * *

The default implementation always returns {@code false}. Subclasses should override the * method to implement a different policy. * * @param exceptionType the exception type * @return {@code true} if exceptional control flow due to {@code exceptionType} should be * ignored, {@code false} otherwise */ protected boolean isIgnoredExceptionType(TypeMirror exceptionType) { return false; } /** * Initialize fields of this object based on a given control flow graph. Sub-class may override * this method to initialize customized fields. * * @param cfg a given control flow graph */ @EnsuresNonNull("this.cfg") protected void initFields(ControlFlowGraph cfg) { inputs.clear(); nodeValues.clear(); finalLocalValues.clear(); this.cfg = cfg; } /** * Updates the value of node {@code node} to the value of the {@code transferResult}. Returns true * if the node's value changed, or a store was updated. * * @param node the node to update * @param transferResult the transfer result being updated * @return true if the node's value changed, or a store was updated */ protected boolean updateNodeValues(Node node, TransferResult transferResult) { V newVal = transferResult.getResultValue(); boolean nodeValueChanged = false; if (newVal != null) { V oldVal = nodeValues.get(node); nodeValues.put(node, newVal); nodeValueChanged = !Objects.equals(oldVal, newVal); } return nodeValueChanged || transferResult.storeChanged(); } /** * Read the store for a particular basic block from a map of stores (or {@code null} if none * exists yet). * * @param stores a map of stores * @param b the target block * @param method return type should be a subtype of {@link Store} * @return the store for the target block */ protected static @Nullable S readFromStore(Map stores, Block b) { return stores.get(b); } /** * Add a basic block to {@link #worklist}. If {@code b} is already present, the method does * nothing. * * @param b the block to add to {@link #worklist} */ protected void addToWorklist(Block b) { // TODO: use a more efficient way to check if b is already present if (!worklist.contains(b)) { worklist.add(b); } } /** * A worklist is a priority queue of blocks in which the order is given by depth-first ordering to * place non-loop predecessors ahead of successors. */ protected static class Worklist { /** Map all blocks in the CFG to their depth-first order. */ protected final IdentityHashMap depthFirstOrder = new IdentityHashMap<>(); /** * Comparators to allow priority queue to order blocks by their depth-first order, using by * forward analysis. */ public class ForwardDFOComparator implements Comparator { @SuppressWarnings("nullness:unboxing.of.nullable") @Override public int compare(Block b1, Block b2) { return depthFirstOrder.get(b1) - depthFirstOrder.get(b2); } } /** * Comparators to allow priority queue to order blocks by their depth-first order, using by * backward analysis. */ public class BackwardDFOComparator implements Comparator { @SuppressWarnings("nullness:unboxing.of.nullable") @Override public int compare(Block b1, Block b2) { return depthFirstOrder.get(b2) - depthFirstOrder.get(b1); } } /** The backing priority queue. */ protected final PriorityQueue queue; /** * Create a Worklist. * * @param direction the direction (forward or backward) */ public Worklist(Direction direction) { if (direction == Direction.FORWARD) { queue = new PriorityQueue<>(new ForwardDFOComparator()); } else if (direction == Direction.BACKWARD) { queue = new PriorityQueue<>(new BackwardDFOComparator()); } else { throw new BugInCF("Unexpected Direction meet: " + direction.name()); } } /** * Process the control flow graph, add the blocks to {@link #depthFirstOrder}. * * @param cfg the control flow graph to process */ public void process(ControlFlowGraph cfg) { depthFirstOrder.clear(); int count = 1; for (Block b : cfg.getDepthFirstOrderedBlocks()) { depthFirstOrder.put(b, count++); } queue.clear(); } /** * See {@link PriorityQueue#isEmpty}. * * @see PriorityQueue#isEmpty * @return true if {@link #queue} is empty else false */ @Pure @EnsuresNonNullIf(result = false, expression = "poll()") @SuppressWarnings("nullness:contracts.conditional.postcondition") // forwarded public boolean isEmpty() { return queue.isEmpty(); } /** * Check if {@link #queue} contains the block which is passed as the argument. * * @param block the given block to check * @return true if {@link #queue} contains the given block */ public boolean contains(Block block) { return queue.contains(block); } /** * Add the given block to {@link #queue}. Adds unconditionally: does not check containment * first. * * @param block the block to add to {@link #queue} */ public void add(Block block) { queue.add(block); } /** * See {@link PriorityQueue#poll}. * * @see PriorityQueue#poll * @return the head of {@link #queue} */ @Pure public @Nullable Block poll() { return queue.poll(); } @Override public String toString() { return "Worklist(" + queue + ")"; } } }