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

org.checkerframework.dataflow.cfg.ControlFlowGraph Maven / Gradle / Ivy

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 getEnclosingMethod(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 getEnclosingClass(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 - 2025 Weber Informatics LLC | Privacy Policy