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.44.0
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 java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
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;

/** 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 @Nullable Map visualize(
      ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) {

    String dotGraph = visualizeGraph(cfg, entry, analysis);
    String dotFileName = dotOutputFileName(cfg.underlyingAST);

    try {
      FileWriter fStream = new FileWriter(dotFileName);
      BufferedWriter out = new BufferedWriter(fStream);
      out.write(dotGraph);
      out.close();
    } catch (IOException e) {
      throw new UserError("Error creating dot file (is the path valid?): " + dotFileName, e);
    }

    return Collections.singletonMap("dotFileName", dotFileName);
  }

  @SuppressWarnings("keyfor:enhancedfor")
  @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(outDir);

    outFile.append("/");

    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 Windows
    String outFileName = outFile.toString().replace("<", "_").replace(">", "");

    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(final 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(final 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() {
    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 kv : generated.entrySet()) {
        out.write(kv.getKey());
        out.append("\t");
        out.write(kv.getValue());
        out.append(lineSeparator);
      }
      out.close();
    } 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