blob: abd482122d8bfa5b5e4661603b1a90591a586c32 [file] [log] [blame]
package org.checkerframework.dataflow.cfg;
/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
*/
import org.checkerframework.dataflow.analysis.AbstractValue;
import org.checkerframework.dataflow.analysis.Analysis;
import org.checkerframework.dataflow.analysis.Store;
import org.checkerframework.dataflow.analysis.TransferFunction;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.cfg.block.Block;
import org.checkerframework.dataflow.cfg.block.Block.BlockType;
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.SpecialBlock;
import org.checkerframework.dataflow.cfg.node.Node;
import javax.lang.model.type.TypeMirror;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
/**
* Generate a graph description in the DOT language of a control graph.
*
* @author Stefan Heule
*
*/
public class CFGDOTVisualizer {
/**
* Output a graph description in the DOT language, representing the control
* flow graph starting at <code>entry</code>. Does not output verbose information
* or stores at the beginning of basic blocks.
*
* @see #visualize(ControlFlowGraph, Block, Analysis, boolean)
*/
public static String visualize(ControlFlowGraph cfg, Block entry) {
return visualize(cfg, entry, null, false);
}
/**
* Output a graph description in the DOT language, representing the control
* flow graph starting at <code>entry</code>.
*
* @param entry
* The entry node of the control flow graph to be represented.
* @param analysis
* An analysis containing information about the program
* represented by the CFG. The information includes {@link Store}
* s that are valid at the beginning of basic blocks reachable
* from <code>entry</code> and per-node information for value
* producing {@link Node}s. Can also be <code>null</code> to
* indicate that this information should not be output.
* @param verbose
* Add more output to the CFG description.
* @return String representation of the graph in the DOT language.
*/
public static <A extends AbstractValue<A>, S extends Store<S>, T extends TransferFunction<A, S>> String visualize(
ControlFlowGraph cfg,
Block entry,
/*@Nullable*/ Analysis<A, S, T> analysis,
boolean verbose) {
StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
Set<Block> visited = new HashSet<>();
Queue<Block> worklist = new LinkedList<>();
Block cur = entry;
visited.add(entry);
// header
sb1.append("digraph {\n");
sb1.append(" node [shape=rectangle];\n\n");
// traverse control flow graph and define all arrows
while (true) {
if (cur == null)
break;
if (cur.getType() == BlockType.CONDITIONAL_BLOCK) {
ConditionalBlock ccur = ((ConditionalBlock) cur);
Block thenSuccessor = ccur.getThenSuccessor();
sb2.append(" " + ccur.getId() + " -> "
+ thenSuccessor.getId());
sb2.append(" [label=\"then\\n" + ccur.getThenFlowRule() + "\"];\n");
if (!visited.contains(thenSuccessor)) {
visited.add(thenSuccessor);
worklist.add(thenSuccessor);
}
Block elseSuccessor = ccur.getElseSuccessor();
sb2.append(" " + ccur.getId() + " -> "
+ elseSuccessor.getId());
sb2.append(" [label=\"else\\n" + ccur.getElseFlowRule() + "\"];\n");
if (!visited.contains(elseSuccessor)) {
visited.add(elseSuccessor);
worklist.add(elseSuccessor);
}
} else {
assert cur instanceof SingleSuccessorBlock;
Block b = ((SingleSuccessorBlock) cur).getSuccessor();
if (b != null) {
sb2.append(" " + cur.getId() + " -> " + b.getId());
sb2.append(" [label=\"" + ((SingleSuccessorBlock) cur).getFlowRule() + "\"];\n");
if (!visited.contains(b)) {
visited.add(b);
worklist.add(b);
}
}
}
// exceptional edges
if (cur.getType() == BlockType.EXCEPTION_BLOCK) {
ExceptionBlock ecur = (ExceptionBlock) cur;
for (Entry<TypeMirror, Set<Block>> e : ecur
.getExceptionalSuccessors().entrySet()) {
Set<Block> blocks = e.getValue();
TypeMirror cause = e.getKey();
String exception = cause.toString();
if (exception.startsWith("java.lang.")) {
exception = exception.replace("java.lang.", "");
}
for (Block b : blocks) {
sb2.append(" " + cur.getId() + " -> " + b.getId());
sb2.append(" [label=\"" + exception + "\"];\n");
if (!visited.contains(b)) {
visited.add(b);
worklist.add(b);
}
}
}
}
cur = worklist.poll();
}
IdentityHashMap<Block, List<Integer>> processOrder = getProcessOrder(cfg);
// definition of all nodes including their labels
for (Block v : visited) {
sb1.append(" " + v.getId() + " [");
if (v.getType() == BlockType.CONDITIONAL_BLOCK) {
sb1.append("shape=polygon sides=8 ");
} else if (v.getType() == BlockType.SPECIAL_BLOCK) {
sb1.append("shape=oval ");
}
sb1.append("label=\"");
if (verbose) {
sb1.append("Process order: " + processOrder.get(v).toString().replaceAll("[\\[\\]]", "") + "\\n");
}
sb1.append(visualizeContent(v, analysis, verbose).replace("\\n", "\\l")
+ " \",];\n");
}
sb1.append("\n");
sb1.append(sb2);
// footer
sb1.append("}\n");
return sb1.toString();
}
private static IdentityHashMap<Block, List<Integer>> getProcessOrder(ControlFlowGraph cfg) {
IdentityHashMap<Block, List<Integer>> depthFirstOrder = new IdentityHashMap<>();
int count = 1;
for (Block b : cfg.getDepthFirstOrderedBlocks()) {
if (depthFirstOrder.get(b) == null) {
depthFirstOrder.put(b, new ArrayList<Integer>());
}
depthFirstOrder.get(b).add(count++);
}
return depthFirstOrder;
}
/**
* Produce a string representation of the contests of a basic block.
*
* @param bb
* Basic block to visualize.
* @return String representation.
*/
protected static <A extends AbstractValue<A>, S extends Store<S>, T extends TransferFunction<A, S>> String visualizeContent(
Block bb,
/*@Nullable*/ Analysis<A, S, T> analysis,
boolean verbose) {
StringBuilder sb = new StringBuilder();
// loop over contents
List<Node> contents = new LinkedList<>();
switch (bb.getType()) {
case REGULAR_BLOCK:
contents.addAll(((RegularBlock) bb).getContents());
break;
case EXCEPTION_BLOCK:
contents.add(((ExceptionBlock) bb).getNode());
break;
case CONDITIONAL_BLOCK:
break;
case SPECIAL_BLOCK:
break;
default:
assert false : "All types of basic blocks covered";
}
boolean notFirst = false;
for (Node t : contents) {
if (notFirst) {
sb.append("\\n");
}
notFirst = true;
sb.append(prepareString(visualizeNode(t, analysis)));
}
// handle case where no contents are present
boolean centered = false;
if (sb.length() == 0) {
centered = true;
if (bb.getType() == BlockType.SPECIAL_BLOCK) {
SpecialBlock sbb = (SpecialBlock) bb;
switch (sbb.getSpecialType()) {
case ENTRY:
sb.append("<entry>");
break;
case EXIT:
sb.append("<exit>");
break;
case EXCEPTIONAL_EXIT:
sb.append("<exceptional-exit>");
break;
}
} else if (bb.getType() == BlockType.CONDITIONAL_BLOCK) {
return "";
} else {
return "?? empty ??";
}
}
// visualize transfer input if necessary
if (analysis != null) {
TransferInput<A, S> input = analysis.getInput(bb);
StringBuilder sb2 = new StringBuilder();
// split input representation to two lines
String s = input.toDOToutput().replace("}, else={", "}\\nelse={");
sb2.append("Before:");
sb2.append(s.subSequence(1, s.length() - 1));
// separator
sb2.append("\\n~~~~~~~~~\\n");
sb2.append(sb);
sb = sb2;
if (verbose) {
Node lastNode = null;
switch (bb.getType()) {
case REGULAR_BLOCK:
List<Node> blockContents = ((RegularBlock) bb).getContents();
lastNode = contents.get(blockContents.size() - 1);
break;
case EXCEPTION_BLOCK:
lastNode = ((ExceptionBlock) bb).getNode();
break;
}
if (lastNode != null) {
sb2.append("\\n~~~~~~~~~\\n");
s = analysis.getResult().getStoreAfter(lastNode.getTree()).
toDOToutput().replace("}, else={", "}\\nelse={");
sb2.append("After:");
sb2.append(s);
}
}
}
return sb.toString() + (centered ? "" : "\\n");
}
protected static <A extends AbstractValue<A>, S extends Store<S>, T extends TransferFunction<A, S>>
String visualizeNode(Node t, /*@Nullable*/ Analysis<A, S, T> analysis) {
A value = analysis.getValue(t);
String valueInfo = "";
if (value != null) {
valueInfo = " > " + value.toString();
}
return t.toString() + " [ " + visualizeType(t) + " ]" + valueInfo;
}
protected static String visualizeType(Node t) {
String name = t.getClass().getSimpleName();
return name.replace("Node", "");
}
protected static String prepareString(String s) {
return s.replace("\"", "\\\"");
}
}