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

org.checkerframework.dataflow.cfg.visualize.DOTCFGVisualizer Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 3.42.0-eisop5
Show newest version
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