
org.checkerframework.dataflow.cfg.visualize.DOTCFGVisualizer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dataflow-shaded Show documentation
Show all versions of dataflow-shaded Show documentation
dataflow-shaded is a dataflow framework based on the javac compiler.
It differs from the org.checkerframework:dataflow artifact in two ways.
First, the packages in this artifact have been renamed to org.checkerframework.shaded.*.
Second, unlike the dataflow artifact, this artifact contains the dependencies it requires.
package org.checkerframework.dataflow.cfg.visualize;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.VariableTree;
import com.sun.tools.javac.tree.JCTree;
import org.checkerframework.checker.nullness.qual.KeyFor;
import org.checkerframework.checker.nullness.qual.NonNull;
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.cfg.ControlFlowGraph;
import org.checkerframework.dataflow.cfg.UnderlyingAST;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGLambda;
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.SpecialBlock;
import org.checkerframework.dataflow.cfg.visualize.AbstractCFGVisualizer.VisualizeWhere;
import org.checkerframework.dataflow.expression.ArrayAccess;
import org.checkerframework.dataflow.expression.ClassName;
import org.checkerframework.dataflow.expression.FieldAccess;
import org.checkerframework.dataflow.expression.LocalVariable;
import org.checkerframework.dataflow.expression.MethodCall;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.UserError;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
/** Generate a graph description in the DOT language of a control graph. */
public class DOTCFGVisualizer<
V extends AbstractValue, S extends Store, T extends TransferFunction>
extends AbstractCFGVisualizer {
/** The output directory. */
@SuppressWarnings("nullness:initialization.field.uninitialized") // uses init method
protected String outDir;
/** The (optional) checker name. Used as a part of the name of the output dot file. */
protected @Nullable String checkerName;
/** Mapping from class/method representation to generated dot file. */
@SuppressWarnings("nullness:initialization.field.uninitialized") // uses init method
protected Map generated;
/** Terminator for lines that are left-justified. */
protected static final String leftJustifiedTerminator = "\\l";
@Override
@SuppressWarnings("nullness") // assume arguments are set correctly
public void init(Map args) {
super.init(args);
this.outDir = (String) args.get("outdir");
if (this.outDir == null) {
throw new BugInCF(
"outDir should never be null,"
+ " provide it in args when calling DOTCFGVisualizer.init(args).");
}
this.checkerName = (String) args.get("checkerName");
this.generated = new HashMap<>();
}
@Override
public String getSeparator() {
return leftJustifiedTerminator;
}
@Override
public Map visualize(
ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) {
String dotGraph = visualizeGraph(cfg, entry, analysis);
Map vis = new HashMap<>(2);
vis.put("dotGraph", dotGraph);
return vis;
}
@Override
public Map visualizeWithAction(
ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) {
Map vis = visualize(cfg, entry, analysis);
String dotGraph = (String) vis.get("dotGraph");
if (dotGraph == null) {
throw new BugInCF("dotGraph key missing in visualize result!");
}
String dotFileName = dotOutputFileName(cfg.underlyingAST);
try (BufferedWriter out = new BufferedWriter(new FileWriter(dotFileName))) {
out.write(dotGraph);
} catch (IOException e) {
throw new UserError("Error creating dot file (is the path valid?): " + dotFileName, e);
}
vis.put("dotFileName", dotFileName);
return vis;
}
@SuppressWarnings("keyfor:enhancedfor.type.incompatible")
@Override
public String visualizeNodes(
Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis) {
StringBuilder sbDotNodes = new StringBuilder();
IdentityHashMap> processOrder = getProcessOrder(cfg);
// Definition of all nodes including their labels.
for (@KeyFor("processOrder") Block v : blocks) {
sbDotNodes.append(" ").append(v.getUid()).append(" [");
if (v.getType() == BlockType.CONDITIONAL_BLOCK) {
sbDotNodes.append("shape=polygon sides=8 ");
} else if (v.getType() == BlockType.SPECIAL_BLOCK) {
sbDotNodes.append("shape=oval ");
} else {
sbDotNodes.append("shape=rectangle ");
}
sbDotNodes.append("label=\"");
if (verbose) {
sbDotNodes
.append(getProcessOrderSimpleString(processOrder.get(v)))
.append(getSeparator());
}
String strBlock = visualizeBlock(v, analysis);
if (strBlock.length() == 0) {
if (v.getType() == BlockType.CONDITIONAL_BLOCK) {
// The footer of the conditional block.
sbDotNodes.append("\"];");
} else {
// The footer of the block which has no content and is not a special or
// conditional block.
sbDotNodes.append("?? empty ??\"];");
}
} else {
sbDotNodes.append(strBlock).append("\"];");
}
sbDotNodes.append(System.lineSeparator());
}
return sbDotNodes.toString();
}
@Override
protected String visualizeEdge(Object sId, Object eId, String flowRule) {
return " " + format(sId) + " -> " + format(eId) + " [label=\"" + flowRule + "\"];";
}
@Override
public String visualizeBlock(Block bb, @Nullable Analysis analysis) {
return super.visualizeBlockHelper(bb, analysis, getSeparator());
}
@Override
public String visualizeSpecialBlock(SpecialBlock sbb) {
return super.visualizeSpecialBlockHelper(sbb);
}
@Override
public String visualizeConditionalBlock(ConditionalBlock cbb) {
// No extra content in DOT output.
return "";
}
@Override
public String visualizeBlockTransferInputBefore(Block bb, Analysis analysis) {
return super.visualizeBlockTransferInputHelper(
VisualizeWhere.BEFORE, bb, analysis, getSeparator());
}
@Override
public String visualizeBlockTransferInputAfter(Block bb, Analysis analysis) {
return super.visualizeBlockTransferInputHelper(
VisualizeWhere.AFTER, bb, analysis, getSeparator());
}
/**
* Create a dot file and return its name.
*
* @param ast an abstract syntax tree
* @return the file name used for DOT output
*/
protected String dotOutputFileName(UnderlyingAST ast) {
StringBuilder srcLoc = new StringBuilder();
StringBuilder outFile = new StringBuilder();
if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) {
CFGStatement cfgStatement = (CFGStatement) ast;
String clsName = cfgStatement.getSimpleClassName();
outFile.append(clsName);
outFile.append("-initializer-");
outFile.append(ast.getUid());
srcLoc.append("<");
srcLoc.append(clsName);
srcLoc.append("::initializer::");
srcLoc.append(((JCTree) cfgStatement.getCode()).pos);
srcLoc.append(">");
} else if (ast.getKind() == UnderlyingAST.Kind.METHOD) {
CFGMethod cfgMethod = (CFGMethod) ast;
String clsName = cfgMethod.getSimpleClassName();
String methodName = cfgMethod.getMethodName();
StringJoiner params = new StringJoiner(",");
for (VariableTree tree : cfgMethod.getMethod().getParameters()) {
params.add(tree.getType().toString());
}
outFile.append(clsName);
outFile.append("-");
outFile.append(methodName);
if (params.length() != 0) {
outFile.append("-");
outFile.append(params);
}
srcLoc.append("<");
srcLoc.append(clsName);
srcLoc.append("::");
srcLoc.append(methodName);
srcLoc.append("(");
srcLoc.append(params);
srcLoc.append(")::");
srcLoc.append(((JCTree) cfgMethod.getMethod()).pos);
srcLoc.append(">");
} else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) {
CFGLambda cfgLambda = (CFGLambda) ast;
String clsName = cfgLambda.getSimpleClassName();
String enclosingMethodName = cfgLambda.getEnclosingMethodName();
long uid = TreeUtils.treeUids.get(cfgLambda.getCode());
outFile.append(clsName);
outFile.append("-");
if (enclosingMethodName != null) {
outFile.append(enclosingMethodName);
outFile.append("-");
}
outFile.append(uid);
srcLoc.append("<");
srcLoc.append(clsName);
if (enclosingMethodName != null) {
srcLoc.append("::");
srcLoc.append(enclosingMethodName);
srcLoc.append("(");
@SuppressWarnings(
"nullness") // enclosingMethodName != null => getEnclosingMethod() != null
@NonNull MethodTree method = cfgLambda.getEnclosingMethod();
srcLoc.append(method.getParameters());
srcLoc.append(")");
}
srcLoc.append("::");
srcLoc.append(((JCTree) cfgLambda.getCode()).pos);
srcLoc.append(">");
} else {
throw new BugInCF("Unexpected AST kind: " + ast.getKind() + " value: " + ast);
}
if (checkerName != null && !checkerName.isEmpty()) {
outFile.append('-');
outFile.append(checkerName);
}
outFile.append(".dot");
// make path safe for Linux
if (outFile.length() > 255) {
outFile.setLength(255);
}
// make path safe for Windows
String outFileBaseName = outFile.toString().replace("<", "_").replace(">", "");
String outFileName = outDir + "/" + outFileBaseName;
generated.put(srcLoc.toString(), outFileName);
return outFileName;
}
@Override
protected String format(Object obj) {
return escapeString(obj);
}
@Override
public String visualizeStoreThisVal(V value) {
return storeEntryIndent + "this > " + escapeString(value);
}
@Override
public String visualizeStoreLocalVar(LocalVariable localVar, V value) {
return storeEntryIndent + localVar + " > " + escapeString(value);
}
@Override
public String visualizeStoreFieldVal(FieldAccess fieldAccess, V value) {
return storeEntryIndent + fieldAccess + " > " + escapeString(value);
}
@Override
public String visualizeStoreArrayVal(ArrayAccess arrayValue, V value) {
return storeEntryIndent + arrayValue + " > " + escapeString(value);
}
@Override
public String visualizeStoreMethodVals(MethodCall methodCall, V value) {
return storeEntryIndent + escapeString(methodCall) + " > " + escapeString(value);
}
@Override
public String visualizeStoreClassVals(ClassName className, V value) {
return storeEntryIndent + className + " > " + escapeString(value);
}
@Override
public String visualizeStoreKeyVal(String keyName, Object value) {
return storeEntryIndent + keyName + " = " + value;
}
/**
* Escape the input String.
*
* @param str the string to be escaped
* @return the escaped version of the string
*/
private static String escapeString(String str) {
return str.replace("\"", "\\\"").replace("\r", "\\\\r").replace("\n", "\\\\n");
}
/**
* Escape the double quotes from the string representation of the given object.
*
* @param obj an object
* @return an escaped version of the string representation of the object
*/
private static String escapeString(Object obj) {
return escapeString(String.valueOf(obj));
}
/**
* Write a file {@code methods.txt} that contains a mapping from source code location to
* generated dot file.
*/
@Override
public void shutdown() {
// Open for append, in case of multiple sub-checkers.
try (FileWriter fstream = new FileWriter(outDir + "/methods.txt", true);
BufferedWriter out = new BufferedWriter(fstream)) {
for (Map.Entry kv : generated.entrySet()) {
out.write(kv.getKey());
out.append("\t");
out.write(kv.getValue());
out.append(lineSeparator);
}
} catch (IOException e) {
throw new UserError(
"Error creating methods.txt file in: " + outDir + "; ensure the path is valid",
e);
}
}
@Override
protected String visualizeGraphHeader() {
return "digraph {" + lineSeparator;
}
@Override
protected String visualizeGraphFooter() {
return "}";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy