aboutsummaryrefslogtreecommitdiffhomepage
path: root/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java')
-rw-r--r--third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java511
1 files changed, 511 insertions, 0 deletions
diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java
new file mode 100644
index 0000000000..1eb1cb87a2
--- /dev/null
+++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java
@@ -0,0 +1,511 @@
+package org.checkerframework.dataflow.cfg;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+import com.sun.tools.javac.tree.JCTree;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Queue;
+import java.util.Set;
+import javax.lang.model.type.TypeMirror;
+import org.checkerframework.dataflow.analysis.AbstractValue;
+import org.checkerframework.dataflow.analysis.Analysis;
+import org.checkerframework.dataflow.analysis.FlowExpressions;
+import org.checkerframework.dataflow.analysis.Store;
+import org.checkerframework.dataflow.analysis.TransferFunction;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod;
+import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGStatement;
+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 org.checkerframework.javacutil.ErrorReporter;
+
+/**
+ * Generate a graph description in the DOT language of a control graph.
+ *
+ * @author Stefan Heule
+ */
+public class DOTCFGVisualizer<
+ A extends AbstractValue<A>, S extends Store<S>, T extends TransferFunction<A, S>>
+ implements CFGVisualizer<A, S, T> {
+
+ protected String outdir;
+ protected boolean verbose;
+ protected String checkerName;
+
+ protected StringBuilder sbDigraph;
+ protected StringBuilder sbStore;
+ protected StringBuilder sbBlock;
+
+ /** Mapping from class/method representation to generated dot file. */
+ protected Map<String, String> generated;
+
+ @Override
+ public void init(Map<String, Object> args) {
+ this.outdir = (String) args.get("outdir");
+ {
+ Object verb = args.get("verbose");
+ this.verbose =
+ verb == null
+ ? false
+ : verb instanceof String
+ ? Boolean.getBoolean((String) verb)
+ : (boolean) verb;
+ }
+ this.checkerName = (String) args.get("checkerName");
+
+ this.generated = new HashMap<>();
+
+ this.sbDigraph = new StringBuilder();
+
+ this.sbStore = new StringBuilder();
+
+ this.sbBlock = new StringBuilder();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public /*@Nullable*/ Map<String, Object> visualize(
+ ControlFlowGraph cfg, Block entry, /*@Nullable*/ Analysis<A, S, T> analysis) {
+
+ String dotgraph = generateDotGraph(cfg, entry, analysis);
+
+ String dotfilename = dotOutputFileName(cfg.underlyingAST);
+ // System.err.println("Output to DOT file: " + dotfilename);
+
+ try {
+ FileWriter fstream = new FileWriter(dotfilename);
+ BufferedWriter out = new BufferedWriter(fstream);
+ out.write(dotgraph);
+ out.close();
+ } catch (IOException e) {
+ ErrorReporter.errorAbort(
+ "Error creating dot file: " + dotfilename + "; ensure the path is valid", e);
+ }
+
+ Map<String, Object> res = new HashMap<>();
+ res.put("dotFileName", dotfilename);
+
+ return res;
+ }
+
+ /** Generate the dot representation as String. */
+ protected String generateDotGraph(
+ ControlFlowGraph cfg, Block entry, /*@Nullable*/ Analysis<A, S, T> analysis) {
+ this.sbDigraph.setLength(0);
+ Set<Block> visited = new HashSet<>();
+
+ // header
+ this.sbDigraph.append("digraph {\n");
+
+ Block cur = entry;
+ Queue<Block> worklist = new LinkedList<>();
+ visited.add(entry);
+ // 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();
+ addDotEdge(ccur.getId(), thenSuccessor.getId(), "then\\n" + ccur.getThenFlowRule());
+ if (!visited.contains(thenSuccessor)) {
+ visited.add(thenSuccessor);
+ worklist.add(thenSuccessor);
+ }
+ Block elseSuccessor = ccur.getElseSuccessor();
+ addDotEdge(ccur.getId(), elseSuccessor.getId(), "else\\n" + ccur.getElseFlowRule());
+ if (!visited.contains(elseSuccessor)) {
+ visited.add(elseSuccessor);
+ worklist.add(elseSuccessor);
+ }
+ } else {
+ assert cur instanceof SingleSuccessorBlock;
+ Block b = ((SingleSuccessorBlock) cur).getSuccessor();
+ if (b != null) {
+ addDotEdge(
+ cur.getId(),
+ b.getId(),
+ ((SingleSuccessorBlock) cur).getFlowRule().name());
+ 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) {
+ addDotEdge(cur.getId(), b.getId(), exception);
+ if (!visited.contains(b)) {
+ visited.add(b);
+ worklist.add(b);
+ }
+ }
+ }
+ }
+
+ cur = worklist.poll();
+ }
+
+ generateDotNodes(visited, cfg, analysis);
+
+ // footer
+ this.sbDigraph.append("}\n");
+
+ return this.sbDigraph.toString();
+ }
+
+ protected void generateDotNodes(
+ Set<Block> visited, ControlFlowGraph cfg, /*@Nullable*/ Analysis<A, S, T> analysis) {
+ IdentityHashMap<Block, List<Integer>> processOrder = getProcessOrder(cfg);
+ this.sbDigraph.append(" node [shape=rectangle];\n\n");
+ // definition of all nodes including their labels
+ for (Block v : visited) {
+ this.sbDigraph.append(" " + v.getId() + " [");
+ if (v.getType() == BlockType.CONDITIONAL_BLOCK) {
+ this.sbDigraph.append("shape=polygon sides=8 ");
+ } else if (v.getType() == BlockType.SPECIAL_BLOCK) {
+ this.sbDigraph.append("shape=oval ");
+ }
+ this.sbDigraph.append("label=\"");
+ if (verbose) {
+ this.sbDigraph.append(
+ "Process order: "
+ + processOrder.get(v).toString().replaceAll("[\\[\\]]", "")
+ + "\\n");
+ }
+ visualizeBlock(v, analysis);
+ }
+
+ this.sbDigraph.append("\n");
+ }
+
+ /** @return the file name used for DOT output. */
+ protected String dotOutputFileName(UnderlyingAST ast) {
+ StringBuilder srcloc = new StringBuilder();
+
+ StringBuilder outfile = new StringBuilder(outdir);
+ outfile.append('/');
+ if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) {
+ CFGStatement cfgs = (CFGStatement) ast;
+ String clsname = cfgs.getClassTree().getSimpleName().toString();
+ outfile.append(clsname);
+ outfile.append("-initializer-");
+ outfile.append(ast.hashCode());
+
+ srcloc.append('<');
+ srcloc.append(clsname);
+ srcloc.append("::initializer::");
+ srcloc.append(((JCTree) cfgs.getCode()).pos);
+ srcloc.append('>');
+ } else if (ast.getKind() == UnderlyingAST.Kind.METHOD) {
+ CFGMethod cfgm = (CFGMethod) ast;
+ String clsname = cfgm.getClassTree().getSimpleName().toString();
+ String methname = cfgm.getMethod().getName().toString();
+ outfile.append(clsname);
+ outfile.append('-');
+ outfile.append(methname);
+
+ srcloc.append('<');
+ srcloc.append(clsname);
+ srcloc.append("::");
+ srcloc.append(methname);
+ srcloc.append('(');
+ srcloc.append(cfgm.getMethod().getParameters());
+ srcloc.append(")::");
+ srcloc.append(((JCTree) cfgm.getMethod()).pos);
+ srcloc.append('>');
+ } else {
+ ErrorReporter.errorAbort(
+ "Unexpected AST kind: " + ast.getKind() + " value: " + ast.toString());
+ return null;
+ }
+ outfile.append('-');
+ outfile.append(checkerName);
+ outfile.append(".dot");
+
+ // make path safe for Windows
+ String out = outfile.toString().replace("<", "_").replace(">", "");
+
+ generated.put(srcloc.toString(), out);
+
+ return out;
+ }
+
+ protected 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 representation of the contests of a basic block.
+ *
+ * @param bb basic block to visualize
+ */
+ @Override
+ public void visualizeBlock(Block bb, /*@Nullable*/ Analysis<A, S, T> analysis) {
+
+ this.sbBlock.setLength(0);
+
+ // 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) {
+ this.sbBlock.append("\\n");
+ }
+ notFirst = true;
+ visualizeBlockNode(t, analysis);
+ }
+
+ // handle case where no contents are present
+ boolean centered = false;
+ if (this.sbBlock.length() == 0) {
+ centered = true;
+ if (bb.getType() == BlockType.SPECIAL_BLOCK) {
+ visualizeSpecialBlock((SpecialBlock) bb);
+ } else if (bb.getType() == BlockType.CONDITIONAL_BLOCK) {
+ this.sbDigraph.append(" \",];\n");
+ return;
+ } else {
+ this.sbDigraph.append("?? empty ?? \",];\n");
+ return;
+ }
+ }
+
+ // visualize transfer input if necessary
+ if (analysis != null) {
+ visualizeBlockTransferInput(bb, analysis);
+ }
+
+ this.sbDigraph.append(
+ (this.sbBlock.toString() + (centered ? "" : "\\n")).replace("\\n", "\\l")
+ + " \",];\n");
+ }
+
+ @Override
+ public void visualizeSpecialBlock(SpecialBlock sbb) {
+ switch (sbb.getSpecialType()) {
+ case ENTRY:
+ this.sbBlock.append("<entry>");
+ break;
+ case EXIT:
+ this.sbBlock.append("<exit>");
+ break;
+ case EXCEPTIONAL_EXIT:
+ this.sbBlock.append("<exceptional-exit>");
+ break;
+ }
+ }
+
+ @Override
+ public void visualizeBlockTransferInput(Block bb, Analysis<A, S, T> analysis) {
+ assert analysis != null
+ : "analysis should be non-null when visualizing the transfer input of a block.";
+
+ TransferInput<A, S> input = analysis.getInput(bb);
+ this.sbStore.setLength(0);
+
+ // split input representation to two lines
+ this.sbStore.append("Before:");
+ S thenStore = input.getThenStore();
+ if (!input.containsTwoStores()) {
+ S regularStore = input.getRegularStore();
+ this.sbStore.append('[');
+ visualizeStore(regularStore);
+ this.sbStore.append(']');
+ } else {
+ S elseStore = input.getElseStore();
+ this.sbStore.append("[then=");
+ visualizeStore(thenStore);
+ this.sbStore.append(", else=");
+ visualizeStore(elseStore);
+ this.sbStore.append("]");
+ }
+ // separator
+ this.sbStore.append("\\n~~~~~~~~~\\n");
+
+ // the transfer input before this block is added before the block content
+ this.sbBlock.insert(0, this.sbStore);
+
+ if (verbose) {
+ Node lastNode;
+ switch (bb.getType()) {
+ case REGULAR_BLOCK:
+ List<Node> blockContents = ((RegularBlock) bb).getContents();
+ lastNode = blockContents.get(blockContents.size() - 1);
+ break;
+ case EXCEPTION_BLOCK:
+ lastNode = ((ExceptionBlock) bb).getNode();
+ break;
+ default:
+ lastNode = null;
+ }
+ if (lastNode != null) {
+ this.sbStore.setLength(0);
+ this.sbStore.append("\\n~~~~~~~~~\\n");
+ this.sbStore.append("After:");
+ visualizeStore(analysis.getResult().getStoreAfter(lastNode));
+ this.sbBlock.append(this.sbStore);
+ }
+ }
+ }
+
+ @Override
+ public void visualizeBlockNode(Node t, /*@Nullable*/ Analysis<A, S, T> analysis) {
+ this.sbBlock.append(prepareString(t.toString()) + " [ " + prepareNodeType(t) + " ]");
+ if (analysis != null) {
+ A value = analysis.getValue(t);
+ if (value != null) {
+ this.sbBlock.append(" > " + prepareString(value.toString()));
+ }
+ }
+ }
+
+ protected String prepareNodeType(Node t) {
+ String name = t.getClass().getSimpleName();
+ return name.replace("Node", "");
+ }
+
+ protected String prepareString(String s) {
+ return s.replace("\"", "\\\"");
+ }
+
+ protected void addDotEdge(long sId, long eId, String labelContent) {
+ this.sbDigraph.append(" " + sId + " -> " + eId + " [label=\"" + labelContent + "\"];\n");
+ }
+
+ @Override
+ public void visualizeStore(S store) {
+ store.visualize(this);
+ }
+
+ @Override
+ public void visualizeStoreThisVal(A value) {
+ this.sbStore.append(" this > " + value + "\\n");
+ }
+
+ @Override
+ public void visualizeStoreLocalVar(FlowExpressions.LocalVariable localVar, A value) {
+ this.sbStore.append(" " + localVar + " > " + toStringEscapeDoubleQuotes(value) + "\\n");
+ }
+
+ @Override
+ public void visualizeStoreFieldVals(FlowExpressions.FieldAccess fieldAccess, A value) {
+ this.sbStore.append(" " + fieldAccess + " > " + toStringEscapeDoubleQuotes(value) + "\\n");
+ }
+
+ @Override
+ public void visualizeStoreArrayVal(FlowExpressions.ArrayAccess arrayValue, A value) {
+ this.sbStore.append(" " + arrayValue + " > " + toStringEscapeDoubleQuotes(value) + "\\n");
+ }
+
+ @Override
+ public void visualizeStoreMethodVals(FlowExpressions.MethodCall methodCall, A value) {
+ this.sbStore.append(
+ " " + methodCall.toString().replace("\"", "\\\"") + " > " + value + "\\n");
+ }
+
+ @Override
+ public void visualizeStoreClassVals(FlowExpressions.ClassName className, A value) {
+ this.sbStore.append(" " + className + " > " + toStringEscapeDoubleQuotes(value) + "\\n");
+ }
+
+ @Override
+ public void visualizeStoreKeyVal(String keyName, Object value) {
+ this.sbStore.append(" " + keyName + " = " + value + "\\n");
+ }
+
+ protected String escapeDoubleQuotes(final String str) {
+ return str.replace("\"", "\\\"");
+ }
+
+ protected String toStringEscapeDoubleQuotes(final Object obj) {
+ return escapeDoubleQuotes(String.valueOf(obj));
+ }
+
+ @Override
+ public void visualizeStoreHeader(String classCanonicalName) {
+ this.sbStore.append(classCanonicalName + " (\\n");
+ }
+
+ @Override
+ public void visualizeStoreFooter() {
+ this.sbStore.append(")");
+ }
+
+ /**
+ * Write a file {@code methods.txt} that contains a mapping from source code location to
+ * generated dot file.
+ */
+ @Override
+ public void shutdown() {
+ try {
+ // Open for append, in case of multiple sub-checkers.
+ FileWriter fstream = new FileWriter(outdir + "/methods.txt", true);
+ BufferedWriter out = new BufferedWriter(fstream);
+ for (Map.Entry<String, String> kv : generated.entrySet()) {
+ out.write(kv.getKey());
+ out.append('\t');
+ out.write(kv.getValue());
+ out.append('\n');
+ }
+ out.close();
+ } catch (IOException e) {
+ ErrorReporter.errorAbort(
+ "Error creating methods.txt file in: " + outdir + "; ensure the path is valid",
+ e);
+ }
+ }
+}