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

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.42.0-eisop4
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 - 2024 Weber Informatics LLC | Privacy Policy