| 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("\"", "\\\""); |
| } |
| } |