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

dataflow.src.org.checkerframework.dataflow.analysis.Analysis 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.42.0
Show newest version
package org.checkerframework.dataflow.analysis;

/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
*/

import com.sun.source.tree.LambdaExpressionTree;
import org.checkerframework.dataflow.cfg.ControlFlowGraph;
import org.checkerframework.dataflow.cfg.UnderlyingAST;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGLambda;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod;
import org.checkerframework.dataflow.cfg.UnderlyingAST.Kind;
import org.checkerframework.dataflow.cfg.block.Block;
import org.checkerframework.dataflow.cfg.block.ConditionalBlock;
import org.checkerframework.dataflow.cfg.block.ExceptionBlock;
import org.checkerframework.dataflow.cfg.block.RegularBlock;
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.cfg.node.ReturnNode;

import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.Pair;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.Set;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;

/**
 * An implementation of an iterative algorithm to solve a org.checkerframework.dataflow problem,
 * given a control flow graph and a transfer function.
 *
 * @author Stefan Heule
 *
 * @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 class Analysis, S extends Store, T extends TransferFunction> {

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

    /** The transfer function for regular nodes. */
    protected T transferFunction;

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

    /** The associated processing environment */
    protected final ProcessingEnvironment env;

    /** Instance of the types utility. */
    protected final Types types;

    /**
     * Then stores before every basic block (assumed to be 'no information' if
     * not present).
     */
    protected IdentityHashMap thenStores;

    /**
     * Else stores before every basic block (assumed to be 'no information' if
     * not present).
     */
    protected IdentityHashMap elseStores;

    /**
     * The transfer inputs before every basic block (assumed to be 'no information' if
     * not present).
     */
    protected IdentityHashMap> inputs;

    /**
     * The stores after every return statement.
     */
    protected IdentityHashMap> storesAtReturnStatements;

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

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

    /** Map from (effectively final) local variable elements to their abstract value. */
    public HashMap finalLocalValues;

    /**
     * The node that is currently handled in the analysis (if it is running).
     * The following invariant holds:
     *
     * 
     *   !isRunning ==> (currentNode == null)
     * 
*/ protected 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 Tree currentTree; /** * The current transfer input when the analysis is running. */ protected TransferInput currentInput; public Tree getCurrentTree() { return currentTree; } public void setCurrentTree(Tree currentTree) { this.currentTree = currentTree; } /** * Construct an object that can perform a org.checkerframework.dataflow analysis over a control * flow graph. The transfer function is set later using * {@code setTransferFunction}. */ public Analysis(ProcessingEnvironment env) { this.env = env; types = env.getTypeUtils(); } /** * Construct an object that can perform a org.checkerframework.dataflow analysis over a control * flow graph, given a transfer function. */ public Analysis(ProcessingEnvironment env, T transfer) { this(env); this.transferFunction = transfer; } public void setTransferFunction(T transfer) { this.transferFunction = transfer; } public T getTransferFunction() { return transferFunction; } public Types getTypes() { return types; } public ProcessingEnvironment getEnv() { return env; } /** * Perform the actual analysis. Should only be called once after the object * has been created. */ public void performAnalysis(ControlFlowGraph cfg) { assert isRunning == false; isRunning = true; init(cfg); while (!worklist.isEmpty()) { Block b = worklist.poll(); switch (b.getType()) { case REGULAR_BLOCK: { RegularBlock rb = (RegularBlock) b; // apply transfer function to contents TransferInput inputBefore = getInputBefore(rb); currentInput = inputBefore.copy(); TransferResult transferResult = null; Node lastNode = null; boolean addToWorklistAgain = false; for (Node n : rb.getContents()) { transferResult = callTransferFunction(n, currentInput); addToWorklistAgain |= updateNodeValues(n, transferResult); currentInput = new TransferInput<>(n, this, transferResult); lastNode = n; } // loop will run at least one, making transferResult non-null // propagate store to successors Block succ = rb.getSuccessor(); assert succ != null : "regular basic block without non-exceptional successor unexpected"; propagateStoresTo(succ, lastNode, currentInput, rb.getFlowRule(), addToWorklistAgain); break; } case EXCEPTION_BLOCK: { ExceptionBlock eb = (ExceptionBlock) b; // apply transfer function to content TransferInput inputBefore = getInputBefore(eb); currentInput = inputBefore.copy(); Node node = eb.getNode(); TransferResult transferResult = callTransferFunction( node, currentInput); boolean addToWorklistAgain = updateNodeValues(node, transferResult); // propagate store to successor Block succ = eb.getSuccessor(); if (succ != null) { currentInput = new TransferInput<>(node, this, transferResult); // TODO? Variable wasn't used. // Store.FlowRule storeFlow = eb.getFlowRule(); propagateStoresTo(succ, node, currentInput, eb.getFlowRule(), addToWorklistAgain); } // propagate store to exceptional successors for (Entry> e : eb.getExceptionalSuccessors() .entrySet()) { TypeMirror cause = e.getKey(); S exceptionalStore = transferResult .getExceptionalStore(cause); if (exceptionalStore != null) { for (Block exceptionSucc : e.getValue()) { addStoreBefore(exceptionSucc, node, exceptionalStore, Store.Kind.BOTH, addToWorklistAgain); } } else { for (Block exceptionSucc : e.getValue()) { addStoreBefore(exceptionSucc, node, inputBefore.copy().getRegularStore(), Store.Kind.BOTH, addToWorklistAgain); } } } break; } case CONDITIONAL_BLOCK: { ConditionalBlock cb = (ConditionalBlock) b; // get store before TransferInput inputBefore = getInputBefore(cb); TransferInput input = inputBefore.copy(); // propagate store to successor Block thenSucc = cb.getThenSuccessor(); Block elseSucc = cb.getElseSuccessor(); propagateStoresTo(thenSucc, null, input, cb.getThenFlowRule(), false); propagateStoresTo(elseSucc, null, input, cb.getElseFlowRule(), false); break; } case SPECIAL_BLOCK: { // special basic blocks are empty and cannot throw exceptions, // thus there is no need to perform any analysis. SpecialBlock sb = (SpecialBlock) b; Block succ = sb.getSuccessor(); if (succ != null) { propagateStoresTo(succ, null, getInputBefore(b), sb.getFlowRule(), false); } break; } default: assert false; break; } } assert isRunning == true; isRunning = false; } /** * Propagate the stores in currentInput to the successor block, succ, according to the * flowRule. */ protected void propagateStoresTo(Block succ, Node node, TransferInput currentInput, Store.FlowRule flowRule, boolean addToWorklistAgain) { switch (flowRule) { case EACH_TO_EACH: if (currentInput.containsTwoStores()) { addStoreBefore(succ, node, currentInput.getThenStore(), Store.Kind.THEN, addToWorklistAgain); addStoreBefore(succ, node, currentInput.getElseStore(), Store.Kind.ELSE, addToWorklistAgain); } else { addStoreBefore(succ, node, currentInput.getRegularStore(), Store.Kind.BOTH, addToWorklistAgain); } break; case THEN_TO_BOTH: addStoreBefore(succ, node, currentInput.getThenStore(), Store.Kind.BOTH, addToWorklistAgain); break; case ELSE_TO_BOTH: addStoreBefore(succ, node, currentInput.getElseStore(), Store.Kind.BOTH, addToWorklistAgain); break; case THEN_TO_THEN: addStoreBefore(succ, node, currentInput.getThenStore(), Store.Kind.THEN, addToWorklistAgain); break; case ELSE_TO_ELSE: addStoreBefore(succ, node, currentInput.getElseStore(), Store.Kind.ELSE, addToWorklistAgain); break; } } /** * 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. */ protected boolean updateNodeValues(Node node, TransferResult transferResult) { A newVal = transferResult.getResultValue(); boolean nodeValueChanged = false; if (newVal != null) { A oldVal = nodeValues.get(node); nodeValues.put(node, newVal); nodeValueChanged = !Objects.equals(oldVal, newVal); } return nodeValueChanged || transferResult.storeChanged(); } /** * Call the transfer function for node {@code node}, and set that node as * current node first. */ protected TransferResult callTransferFunction(Node node, TransferInput store) { if (node.isLValue()) { // TODO: should the default behavior be to return either a regular // transfer result or a conditional transfer result (depending on // store.hasTwoStores()), or is the following correct? return new RegularTransferResult(null, store.getRegularStore()); } store.node = node; currentNode = node; TransferResult transferResult = node.accept(transferFunction, store); currentNode = null; if (node instanceof ReturnNode) { // save a copy of the store to later check if some property held at // a given return statement storesAtReturnStatements.put((ReturnNode) node, transferResult); } if (node instanceof AssignmentNode) { // store the flow-refined value for effectively final local variables AssignmentNode assignment = (AssignmentNode) node; Node lhst = assignment.getTarget(); if (lhst instanceof LocalVariableNode) { LocalVariableNode lhs = (LocalVariableNode) lhst; Element elem = lhs.getElement(); if (ElementUtils.isEffectivelyFinal(elem)) { finalLocalValues.put(elem, transferResult.getResultValue()); } } } return transferResult; } /** Initialize the analysis with a new control flow graph. */ protected void init(ControlFlowGraph cfg) { this.cfg = cfg; thenStores = new IdentityHashMap<>(); elseStores = new IdentityHashMap<>(); inputs = new IdentityHashMap<>(); storesAtReturnStatements = new IdentityHashMap<>(); worklist = new Worklist(cfg); nodeValues = new IdentityHashMap<>(); finalLocalValues = new HashMap<>(); worklist.add(cfg.getEntryBlock()); List parameters = null; UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); if (underlyingAST.getKind() == Kind.METHOD) { MethodTree tree = ((CFGMethod) underlyingAST).getMethod(); parameters = new ArrayList<>(); for (VariableTree p : tree.getParameters()) { LocalVariableNode var = new LocalVariableNode(p); parameters.add(var); // TODO: document that LocalVariableNode has no block that it // belongs to } } else if (underlyingAST.getKind() == Kind.LAMBDA) { LambdaExpressionTree lambda = ((CFGLambda) underlyingAST).getLambdaTree(); parameters = new ArrayList<>(); for (VariableTree p : lambda.getParameters()) { LocalVariableNode var = new LocalVariableNode(p); parameters.add(var); // TODO: document that LocalVariableNode has no block that it // belongs to } } else { // nothing to do } S initialStore = transferFunction.initialStore(underlyingAST, parameters); Block entry = cfg.getEntryBlock(); thenStores.put(entry, initialStore); elseStores.put(entry, initialStore); inputs.put(entry, new TransferInput<>(null, this, initialStore)); } /** * Add a basic block to the worklist. If b is already present, * the method does nothing. */ 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); } } /** * Add a store before the basic block b by merging with the * existing stores for that location. */ protected void addStoreBefore(Block b, Node node, S s, Store.Kind kind, boolean addBlockToWorklist) { S thenStore = getStoreBefore(b, Store.Kind.THEN); S elseStore = getStoreBefore(b, Store.Kind.ELSE); switch (kind) { case THEN: { // Update the then store S newThenStore = (thenStore != null) ? thenStore.leastUpperBound(s) : s; if (!newThenStore.equals(thenStore)) { thenStores.put(b, newThenStore); if (elseStore != null) { inputs.put(b, new TransferInput<>(node, this, newThenStore, elseStore)); addBlockToWorklist = true; } } break; } case ELSE: { // Update the else store S newElseStore = (elseStore != null) ? elseStore.leastUpperBound(s) : s; if (!newElseStore.equals(elseStore)) { elseStores.put(b, newElseStore); if (thenStore != null) { inputs.put(b, new TransferInput<>(node, this, thenStore, newElseStore)); addBlockToWorklist = true; } } break; } case BOTH: if (thenStore == elseStore) { // Currently there is only one regular store S newStore = (thenStore != null) ? thenStore.leastUpperBound(s) : s; if (!newStore.equals(thenStore)) { thenStores.put(b, newStore); elseStores.put(b, newStore); inputs.put(b, new TransferInput<>(node, this, newStore)); addBlockToWorklist = true; } } else { boolean storeChanged = false; S newThenStore = (thenStore != null) ? thenStore.leastUpperBound(s) : s; if (!newThenStore.equals(thenStore)) { thenStores.put(b, newThenStore); storeChanged = true; } S newElseStore = (elseStore != null) ? elseStore.leastUpperBound(s) : s; if (!newElseStore.equals(elseStore)) { elseStores.put(b, newElseStore); storeChanged = true; } if (storeChanged) { inputs.put(b, new TransferInput<>(node, this, newThenStore, newElseStore)); addBlockToWorklist = true; } } } if (addBlockToWorklist) { addToWorklist(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 IdentityHashMap depthFirstOrder; /** Comparator to allow priority queue to order blocks by their depth-first order. */ public class DFOComparator implements Comparator { @Override public int compare(Block b1, Block b2) { return depthFirstOrder.get(b1) - depthFirstOrder.get(b2); } } /** The backing priority queue. */ protected PriorityQueue queue; public Worklist(ControlFlowGraph cfg) { depthFirstOrder = new IdentityHashMap<>(); int count = 1; for (Block b : cfg.getDepthFirstOrderedBlocks()) { depthFirstOrder.put(b, count++); } queue = new PriorityQueue(11, new DFOComparator()); } public boolean isEmpty() { return queue.isEmpty(); } public boolean contains(Block block) { return queue.contains(block); } public void add(Block block) { queue.add(block); } public Block poll() { return queue.poll(); } @Override public String toString() { return "Worklist(" + queue + ")"; } } /** * Read the {@link TransferInput} for a particular basic block (or {@code null} if * none exists yet). */ public /*@Nullable*/ TransferInput getInput(Block b) { return getInputBefore(b); } /** * @return the transfer input corresponding to the location right before the basic * block b. */ protected /*@Nullable*/ TransferInput getInputBefore(Block b) { return inputs.get(b); } /** * @return the store corresponding to the location right before the basic * block b. */ protected /*@Nullable*/ S getStoreBefore(Block b, Store.Kind kind) { switch (kind) { case THEN: return readFromStore(thenStores, b); case ELSE: return readFromStore(elseStores, b); default: assert false; return null; } } /** * Read the {@link Store} for a particular basic block from a map of stores * (or {@code null} if none exists yet). */ protected static /*@Nullable*/ S readFromStore(Map stores, Block b) { return stores.get(b); } /** Is the analysis currently running? */ public boolean isRunning() { return isRunning; } /** * @return 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. */ public /*@Nullable*/ A getValue(Node n) { if (isRunning) { // we do not yet have a org.checkerframework.dataflow fact about the current node if (currentNode == n || (currentTree != null && currentTree == n.getTree())) { return null; } // check that 'n' is a subnode of 'node'. Check immediate operands // first for efficiency. assert currentNode != null; assert !n.isLValue() : "Did not expect an lvalue, but got " + n; if (!(currentNode != n && (currentNode.getOperands().contains(n) || currentNode .getTransitiveOperands().contains(n)))) { return null; } return nodeValues.get(n); } return nodeValues.get(n); } /** * @return 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. */ public /*@Nullable*/ A getValue(Tree t) { // we do not yet have a org.checkerframework.dataflow fact about the current node if (t == currentTree) { return null; } Node nodeCorrespondingToTree = getNodeForTree(t); if (nodeCorrespondingToTree == null || nodeCorrespondingToTree.isLValue()) { return null; } return getValue(nodeCorrespondingToTree); } /** * Get the {@link Node} for a given {@link Tree}. */ public Node getNodeForTree(Tree t) { return cfg.getNodeCorrespondingToTree(t); } /** * Get the {@link MethodTree} of the current CFG if the argument {@link Tree} maps * to a {@link Node} in the CFG or null otherwise. */ public /*@Nullable*/ MethodTree getContainingMethod(Tree t) { 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 null otherwise. */ public /*@Nullable*/ ClassTree getContainingClass(Tree t) { return cfg.getContainingClass(t); } public List>> getReturnStatementStores() { List>> result = new ArrayList<>(); for (ReturnNode returnNode : cfg.getReturnNodes()) { TransferResult store = storesAtReturnStatements .get(returnNode); result.add(Pair.of(returnNode, store)); } return result; } public AnalysisResult getResult() { assert !isRunning; IdentityHashMap treeLookup = cfg.getTreeLookup(); return new AnalysisResult<>(nodeValues, inputs, treeLookup, finalLocalValues); } /** * @return the regular exit store, or {@code null}, if there is no such * store (because the method cannot exit through the regular exit * block). */ public /*@Nullable*/ S getRegularExitStore() { SpecialBlock regularExitBlock = cfg.getRegularExitBlock(); if (inputs.containsKey(regularExitBlock)) { S regularExitStore = inputs.get(regularExitBlock).getRegularStore(); return regularExitStore; } else { return null; } } public S getExceptionalExitStore() { S exceptionalExitStore = inputs.get(cfg.getExceptionalExitBlock()) .getRegularStore(); return exceptionalExitStore; } }