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

org.checkerframework.dataflow.cfg.ControlFlowGraph 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.cfg;

import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.UnaryTree;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.analysis.AnalysisResult;
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.SingleSuccessorBlock;
import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlockImpl;
import org.checkerframework.dataflow.cfg.block.SpecialBlock;
import org.checkerframework.dataflow.cfg.block.SpecialBlockImpl;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.ReturnNode;
import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
import org.checkerframework.dataflow.cfg.visualize.StringCFGVisualizer;
import org.plumelib.util.UniqueId;
import org.plumelib.util.UnmodifiableIdentityHashMap;

/**
 * A control flow graph (CFG for short) of a single method.
 *
 * 

The graph is represented by the successors (methods {@link SingleSuccessorBlock#getSuccessor}, * {@link ConditionalBlock#getThenSuccessor}, {@link ConditionalBlock#getElseSuccessor}, {@link * ExceptionBlock#getExceptionalSuccessors}, {@link RegularBlock#getRegularSuccessor}) and * predecessors (method {@link Block#getPredecessors}) of the entry and exit blocks. */ public class ControlFlowGraph implements UniqueId { /** The entry block of the control flow graph. */ protected final SpecialBlock entryBlock; /** The regular exit block of the control flow graph. */ protected final SpecialBlock regularExitBlock; /** The exceptional exit block of the control flow graph. */ protected final SpecialBlock exceptionalExitBlock; /** The AST this CFG corresponds to. */ public final UnderlyingAST underlyingAST; /** 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 ControlFlowGraph this) { return uid; } /** * Maps from AST {@link Tree}s to sets of {@link Node}s. * *

    *
  • Most Trees that produce a value will have at least one corresponding Node. *
  • Trees that undergo conversions, such as boxing or unboxing, can map to two distinct * Nodes. The Node for the pre-conversion value is stored in {@link #treeLookup}, while the * Node for the post-conversion value is stored in {@link #convertedTreeLookup}. *
* * Some of the mapped-to nodes (in both {@link #treeLookup} and {@link #convertedTreeLookup}) do * not appear in {@link #getAllNodes} because their blocks are not reachable in the control flow * graph. Dataflow will not compute abstract values for these nodes. */ protected final IdentityHashMap> treeLookup; /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ protected final IdentityHashMap> convertedTreeLookup; /** * 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 final IdentityHashMap postfixNodeLookup; /** * All return nodes (if any) encountered. Only includes return statements that actually return * something */ protected final List returnNodes; /** * Class declarations that have been encountered when building the control-flow graph for a * method. */ protected final List declaredClasses; /** * Lambdas encountered when building the control-flow graph for a method, variable initializer, or * initializer. */ protected final List declaredLambdas; public ControlFlowGraph( SpecialBlock entryBlock, SpecialBlockImpl regularExitBlock, SpecialBlockImpl exceptionalExitBlock, UnderlyingAST underlyingAST, IdentityHashMap> treeLookup, IdentityHashMap> convertedTreeLookup, IdentityHashMap postfixNodeLookup, List returnNodes, List declaredClasses, List declaredLambdas) { super(); this.entryBlock = entryBlock; this.underlyingAST = underlyingAST; this.treeLookup = treeLookup; this.postfixNodeLookup = postfixNodeLookup; this.convertedTreeLookup = convertedTreeLookup; this.regularExitBlock = regularExitBlock; this.exceptionalExitBlock = exceptionalExitBlock; this.returnNodes = returnNodes; this.declaredClasses = declaredClasses; this.declaredLambdas = declaredLambdas; } /** * Verify that this is a complete and well-formed CFG, i.e. that all internal invariants hold. * * @throws IllegalStateException if some internal invariant is violated */ public void checkInvariants() { // TODO: this is a big data structure with many more invariants... for (Block b : getAllBlocks()) { // Each node in the block should have this block as its parent. for (Node n : b.getNodes()) { if (!Objects.equals(n.getBlock(), b)) { throw new IllegalStateException( "Node " + n + " in block " + b + " incorrectly believes it belongs to " + n.getBlock()); } } // Each successor should have this block in its predecessors. for (Block succ : b.getSuccessors()) { if (!succ.getPredecessors().contains(b)) { throw new IllegalStateException( "Block " + b + " has successor " + succ + " but does not appear in that successor's predecessors"); } } // Each predecessor should have this block in its successors. for (Block pred : b.getPredecessors()) { if (!pred.getSuccessors().contains(b)) { throw new IllegalStateException( "Block " + b + " has predecessor " + pred + " but does not appear in that predecessor's successors"); } } } } /** * Returns the set of {@link Node}s to which the {@link Tree} {@code t} corresponds, or null for * trees that don't produce a value. * * @param t a tree * @return the set of {@link Node}s to which the {@link Tree} {@code t} corresponds, or null for * trees that don't produce a value */ public @Nullable Set getNodesCorrespondingToTree(Tree t) { if (convertedTreeLookup.containsKey(t)) { return convertedTreeLookup.get(t); } else { return treeLookup.get(t); } } /** * Returns the entry block of the control flow graph. * * @return the entry block of the control flow graph */ public SpecialBlock getEntryBlock() { return entryBlock; } public List getReturnNodes() { return returnNodes; } public SpecialBlock getRegularExitBlock() { return regularExitBlock; } public SpecialBlock getExceptionalExitBlock() { return exceptionalExitBlock; } /** * Returns the AST this CFG corresponds to. * * @return the AST this CFG corresponds to */ public UnderlyingAST getUnderlyingAST() { return underlyingAST; } /** * Returns the set of all basic blocks in this control flow graph. * * @return the set of all basic blocks in this control flow graph */ public Set getAllBlocks( @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this) { Set visited = new LinkedHashSet<>(); // worklist is always a subset of visited; any block in worklist is also in visited. Queue worklist = new ArrayDeque<>(); Block cur = entryBlock; visited.add(entryBlock); // traverse the whole control flow graph while (true) { if (cur == null) { break; } for (Block b : cur.getSuccessors()) { if (visited.add(b)) { worklist.add(b); } } cur = worklist.poll(); } return visited; } /** * Returns all nodes in this control flow graph. * * @return all nodes in this control flow graph */ public List getAllNodes( @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this) { List result = new ArrayList<>(); for (Block b : getAllBlocks()) { result.addAll(b.getNodes()); } return result; } /** * Returns the set of all basic blocks in this control flow graph, except those that are * only reachable via an exception whose type is ignored by parameter {@code * shouldIgnoreException}. * * @param shouldIgnoreException returns true if it is passed a {@code TypeMirror} that should be * ignored * @return the set of all basic blocks in this control flow graph, except those that are * only reachable via an exception whose type is ignored by {@code shouldIgnoreException} */ public Set getAllBlocks( @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this, Function shouldIgnoreException) { // This is the return value of the method. Set visited = new LinkedHashSet<>(); // `worklist` is always a subset of `visited`; any block in `worklist` is also in `visited`. Queue worklist = new ArrayDeque<>(); Block cur = entryBlock; visited.add(entryBlock); // Traverse the whole control flow graph. while (cur != null) { if (cur instanceof ExceptionBlock) { for (Map.Entry> entry : ((ExceptionBlock) cur).getExceptionalSuccessors().entrySet()) { if (!shouldIgnoreException.apply(entry.getKey())) { for (Block b : entry.getValue()) { if (visited.add(b)) { worklist.add(b); } } } } Block b = ((SingleSuccessorBlockImpl) cur).getSuccessor(); if (b != null && visited.add(b)) { worklist.add(b); } } else { for (Block b : cur.getSuccessors()) { if (visited.add(b)) { worklist.add(b); } } } cur = worklist.poll(); } return visited; } /** * Returns the list of all nodes in this control flow graph, except those that are only * reachable via an exception whose type is ignored by parameter {@code shouldIgnoreException}. * * @param shouldIgnoreException returns true if it is passed a {@code TypeMirror} that should be * ignored * @return the list of all nodes in this control flow graph, except those that are only * reachable via an exception whose type is ignored by {@code shouldIgnoreException} */ public List getAllNodes( @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this, Function shouldIgnoreException) { List result = new ArrayList<>(); getAllBlocks(shouldIgnoreException).forEach(b -> result.addAll(b.getNodes())); return result; } /** * Returns all basic blocks in this control flow graph, in reversed depth-first postorder. Blocks * may appear more than once in the sequence. * * @return the list of all basic block in this control flow graph in reversed depth-first * postorder sequence */ public List getDepthFirstOrderedBlocks() { List dfsOrderResult = new ArrayList<>(); Set visited = new HashSet<>(); // worklist can contain values that are not yet in visited. Deque worklist = new ArrayDeque<>(); worklist.add(entryBlock); while (!worklist.isEmpty()) { Block cur = worklist.getLast(); if (visited.contains(cur)) { dfsOrderResult.add(cur); worklist.removeLast(); } else { visited.add(cur); for (Block b : cur.getSuccessors()) { if (!visited.contains(b)) { worklist.add(b); } } } } Collections.reverse(dfsOrderResult); return dfsOrderResult; } /** * Returns an unmodifiable view of the tree-lookup map. Ignores convertedTreeLookup, though {@link * #getNodesCorrespondingToTree} uses that field. * * @return the unmodifiable tree-lookup map */ public UnmodifiableIdentityHashMap> getTreeLookup() { return UnmodifiableIdentityHashMap.wrap(treeLookup); } /** * Returns an unmodifiable view of the lookup-map of the binary tree for a postfix expression. * * @return the unmodifiable lookup-map of the binary tree for a postfix expression */ public UnmodifiableIdentityHashMap getPostfixNodeLookup() { return UnmodifiableIdentityHashMap.wrap(postfixNodeLookup); } /** * Get the {@link MethodTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in * the CFG, or null otherwise. * * @param t a tree that might correspond to a node in the CFG * @return the method that contains {@code t}'s Node, or null */ public @Nullable MethodTree getContainingMethod(Tree t) { if (treeLookup.containsKey(t) && underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST; return cfgMethod.getMethod(); } return null; } /** * Get the {@link ClassTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in the * CFG, or null otherwise. * * @param t a tree that might be within a class * @return the class that contains the given tree, or null */ public @Nullable ClassTree getContainingClass(Tree t) { if (treeLookup.containsKey(t) && underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST; return cfgMethod.getClassTree(); } return null; } public List getDeclaredClasses() { return declaredClasses; } public List getDeclaredLambdas() { return declaredLambdas; } @Override public String toString() { CFGVisualizer viz = new StringCFGVisualizer<>(); viz.init(Collections.singletonMap("verbose", true)); Map res = viz.visualize(this, this.getEntryBlock(), null); viz.shutdown(); if (res == null) { return "unvisualizable " + getClass().getCanonicalName(); } String stringGraph = (String) res.get("stringGraph"); return stringGraph == null ? "unvisualizable " + getClass().getCanonicalName() : stringGraph; } /** * Returns a verbose string representation of this, useful for debugging. * * @return a string representation of this */ public String toStringDebug() { String className = this.getClass().getSimpleName(); if (className.equals("ControlFlowGraph") && this.getClass() != ControlFlowGraph.class) { className = this.getClass().getCanonicalName(); } StringJoiner result = new StringJoiner(String.format("%n ")); result.add(className + " #" + getUid() + " {"); result.add("entryBlock=" + entryBlock); result.add("regularExitBlock=" + regularExitBlock); result.add("exceptionalExitBlock=" + exceptionalExitBlock); String astString = underlyingAST.toString().replaceAll("\\s", " "); if (astString.length() > 65) { astString = "\"" + astString.substring(0, 60) + "\""; } result.add("underlyingAST=" + underlyingAST); result.add("treeLookup=" + AnalysisResult.treeLookupToString(treeLookup)); result.add("convertedTreeLookup=" + AnalysisResult.treeLookupToString(convertedTreeLookup)); result.add("postfixLookup=" + postfixNodeLookup); result.add("returnNodes=" + Node.nodeCollectionToString(returnNodes)); result.add("declaredClasses=" + declaredClasses); result.add("declaredLambdas=" + declaredLambdas); result.add("}"); return result.toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy