diff options
Diffstat (limited to 'third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/Analysis.java')
-rw-r--r-- | third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/Analysis.java | 718 |
1 files changed, 718 insertions, 0 deletions
diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/Analysis.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/Analysis.java new file mode 100644 index 0000000000..2d0e7595de --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/Analysis.java @@ -0,0 +1,718 @@ +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 <A> + * The abstract value type to be tracked by the analysis. + * @param <S> + * The store type used in the analysis. + * @param <T> + * The transfer function type that is used to approximated runtime + * behavior. + */ +public class Analysis<A extends AbstractValue<A>, S extends Store<S>, T extends TransferFunction<A, S>> { + + /** 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<Block, S> thenStores; + + /** + * Else stores before every basic block (assumed to be 'no information' if + * not present). + */ + protected IdentityHashMap<Block, S> elseStores; + + /** + * The transfer inputs before every basic block (assumed to be 'no information' if + * not present). + */ + protected IdentityHashMap<Block, TransferInput<A, S>> inputs; + + /** + * The stores after every return statement. + */ + protected IdentityHashMap<ReturnNode, TransferResult<A, S>> storesAtReturnStatements; + + /** The worklist used for the fix-point iteration. */ + protected Worklist worklist; + + /** Abstract values of nodes. */ + protected IdentityHashMap<Node, A> nodeValues; + + /** Map from (effectively final) local variable elements to their abstract value. */ + public HashMap<Element, A> finalLocalValues; + + /** + * The node that is currently handled in the analysis (if it is running). + * The following invariant holds: + * + * <pre> + * !isRunning ⇒ (currentNode == null) + * </pre> + */ + 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<A, S> 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<A, S> inputBefore = getInputBefore(rb); + currentInput = inputBefore.copy(); + TransferResult<A, S> 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<A, S> inputBefore = getInputBefore(eb); + currentInput = inputBefore.copy(); + Node node = eb.getNode(); + TransferResult<A, S> 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<TypeMirror, Set<Block>> 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<A, S> inputBefore = getInputBefore(cb); + TransferInput<A, S> 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<A, S> 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<A, S> 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<A, S> callTransferFunction(Node node, + TransferInput<A, S> 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<A, S>(null, + store.getRegularStore()); + } + store.node = node; + currentNode = node; + TransferResult<A, S> 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<LocalVariableNode> 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 {@code 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 {@code 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<Block, Integer> depthFirstOrder; + + /** Comparator to allow priority queue to order blocks by their depth-first + order. */ + public class DFOComparator implements Comparator<Block> { + @Override + public int compare(Block b1, Block b2) { + return depthFirstOrder.get(b1) - depthFirstOrder.get(b2); + } + } + + /** The backing priority queue. */ + protected PriorityQueue<Block> queue; + + + public Worklist(ControlFlowGraph cfg) { + depthFirstOrder = new IdentityHashMap<>(); + int count = 1; + for (Block b : cfg.getDepthFirstOrderedBlocks()) { + depthFirstOrder.put(b, count++); + } + + queue = new PriorityQueue<Block>(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<A, S> getInput(Block b) { + return getInputBefore(b); + } + + /** + * @return the transfer input corresponding to the location right before the basic + * block {@code b}. + */ + protected /*@Nullable*/ TransferInput<A, S> getInputBefore(Block b) { + return inputs.get(b); + } + + /** + * @return the store corresponding to the location right before the basic + * block {@code 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 <S> /*@Nullable*/ S readFromStore(Map<Block, S> 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<Pair<ReturnNode, TransferResult<A, S>>> getReturnStatementStores() { + List<Pair<ReturnNode, TransferResult<A, S>>> result = new ArrayList<>(); + for (ReturnNode returnNode : cfg.getReturnNodes()) { + TransferResult<A, S> store = storesAtReturnStatements + .get(returnNode); + result.add(Pair.of(returnNode, store)); + } + return result; + } + + public AnalysisResult<A, S> getResult() { + assert !isRunning; + IdentityHashMap<Tree, Node> 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; + } +} |