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

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

There is a newer version: 3.42.0-eisop4
Show newest version
package org.checkerframework.dataflow.cfg.visualize;

import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Options;

import org.checkerframework.checker.mustcall.qual.MustCall;
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.CFGProcessor;
import org.checkerframework.dataflow.cfg.CFGProcessor.CFGProcessResult;
import org.checkerframework.dataflow.cfg.ControlFlowGraph;
import org.plumelib.util.ArrayMap;

import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Collections;
import java.util.Map;

import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;

/**
 * Launcher to generate the DOT or String representation of the control flow graph of a given method
 * in a given class.
 *
 * 

Usage: Directly run it as the main class to generate the DOT representation of the control * flow graph of a given method in a given class. See {@link * org.checkerframework.dataflow.cfg.playground.ConstantPropagationPlayground} for another way to * use it. */ public final class CFGVisualizeLauncher { /** Class cannot be instantiated. */ private CFGVisualizeLauncher() { throw new AssertionError("Class CFGVisualizeLauncher cannot be instantiated."); } /** * The main entry point of CFGVisualizeLauncher. * * @param args command-line arguments */ public static void main(String[] args) { CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); performAnalysis(config, null); } /** * Generate a visualization of the CFG of a method, with an optional analysis. * * @param the abstract value type of the analysis * @param the store type of the analysis * @param the transfer function type of the analysis * @param config CFGVisualizeOptions that includes input file, output directory, method name, * and class name * @param analysis analysis to perform before the visualization (or {@code null} if no analysis * is to be performed) */ public static , S extends Store, T extends TransferFunction> void performAnalysis(CFGVisualizeOptions config, @Nullable Analysis analysis) { if (!config.isString()) { if (analysis == null) { generateDOTofCFGWithoutAnalysis( config.getInputFile(), config.getOutputDirectory(), config.getMethodName(), config.getClassName(), config.isPDF(), config.isVerbose()); } else { generateDOTofCFG( config.getInputFile(), config.getOutputDirectory(), config.getMethodName(), config.getClassName(), config.isPDF(), config.isVerbose(), analysis); } } else { if (analysis == null) { String stringGraph = generateStringOfCFGWithoutAnalysis( config.getInputFile(), config.getMethodName(), config.getClassName(), config.isVerbose()); System.out.println(stringGraph); } else { Map res = generateStringOfCFG( config.getInputFile(), config.getMethodName(), config.getClassName(), config.isVerbose(), analysis); if (res != null) { String stringGraph = (String) res.get("stringGraph"); if (stringGraph == null) { System.err.println( "Unexpected output from generating string control flow graph, shouldn't be" + " null. Result map: " + res); return; } System.out.println(stringGraph); } else { System.err.println( "Unexpected output from generating string control flow graph, shouldn't be" + " null."); } } } } /** * Generate the DOT representation of the CFG for a method, only. Does no dataflow analysis. * * @param inputFile a Java source file, used as input * @param outputDir output directory * @param method name of the method to generate the CFG for * @param clas name of the class which includes the method to generate the CFG for * @param pdf also generate a PDF * @param verbose show verbose information in CFG */ private static void generateDOTofCFGWithoutAnalysis( String inputFile, String outputDir, String method, String clas, boolean pdf, boolean verbose) { generateDOTofCFG(inputFile, outputDir, method, clas, pdf, verbose, null); } /** * Generate the String representation of the CFG for a method, only. Does no dataflow analysis. * * @param inputFile a Java source file, used as input * @param method name of the method to generate the CFG for * @param clas name of the class which includes the method to generate the CFG for * @param verbose show verbose information in CFG * @return the String representation of the CFG */ private static String generateStringOfCFGWithoutAnalysis( String inputFile, String method, String clas, boolean verbose) { Map res = generateStringOfCFG(inputFile, method, clas, verbose, null); if (res != null) { String stringGraph = (String) res.get("stringGraph"); if (stringGraph == null) { return "Unexpected output from generating string control flow graph, shouldn't be" + " null."; } return stringGraph; } else { return "Unexpected output from generating string control flow graph, shouldn't be" + " null."; } } /** * Generate the DOT representation of the CFG for a method. * * @param the abstract value type to be tracked by the analysis * @param the store type used in the analysis * @param the transfer function type that is used to approximated runtime behavior * @param inputFile a Java source file, used as input * @param outputDir source output directory * @param method name of the method to generate the CFG for * @param clas name of the class which includes the method to generate the CFG for * @param pdf also generate a PDF * @param verbose show verbose information in CFG * @param analysis analysis to perform before the visualization (or {@code null} if no analysis * is to be performed) */ private static < V extends AbstractValue, S extends Store, T extends TransferFunction> void generateDOTofCFG( String inputFile, String outputDir, String method, String clas, boolean pdf, boolean verbose, @Nullable Analysis analysis) { ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method); if (analysis != null) { analysis.performAnalysis(cfg); } Map args = new ArrayMap<>(2); args.put("outdir", outputDir); args.put("verbose", verbose); CFGVisualizer viz = new DOTCFGVisualizer<>(); viz.init(args); Map res = viz.visualizeWithAction(cfg, cfg.getEntryBlock(), analysis); viz.shutdown(); if (pdf && res != null) { assert res.get("dotFileName") != null : "@AssumeAssertion(nullness): specification"; producePDF((String) res.get("dotFileName")); } } /** * Generate the control flow graph of a method in a class. * * @param file a Java source file, used as input * @param clas name of the class which includes the method to generate the CFG for * @param method name of the method to generate the CFG for * @return control flow graph of the specified method */ public static ControlFlowGraph generateMethodCFG(String file, String clas, String method) { CFGProcessor cfgProcessor = new CFGProcessor(clas, method); Context context = new Context(); Options.instance(context).put("compilePolicy", "ATTR_ONLY"); JavaCompiler javac = new JavaCompiler(context); JavaFileObject l; try (@SuppressWarnings( "mustcall:type.argument") // Context isn't annotated for the Must Call // Checker. JavacFileManager fileManager = (JavacFileManager) context.get(JavaFileManager.class)) { l = fileManager.getJavaFileObjectsFromStrings(List.of(file)).iterator().next(); } catch (IOException e) { throw new Error(e); } PrintStream err = System.err; try { // Redirect syserr to nothing (and prevent the compiler from issuing // warnings about our exception). @SuppressWarnings({ "builder:required.method.not.called", "mustcall:assignment" }) // Won't be needed in JDK 11+ with use of "OutputStream.nullOutputStream()". @MustCall() OutputStream nullOS = // In JDK 11+, this can be just "OutputStream.nullOutputStream()". new OutputStream() { @Override public void write(int b) throws IOException {} }; System.setErr(new PrintStream(nullOS)); javac.compile(List.of(l), List.of(clas), List.of(cfgProcessor), List.nil()); } catch (Throwable e) { // ok } finally { System.setErr(err); } CFGProcessResult res = cfgProcessor.getCFGProcessResult(); if (res == null) { printError( "internal error in type processor! method typeProcessOver() doesn't get" + " called."); System.exit(1); } if (!res.isSuccess()) { printError(res.getErrMsg()); System.exit(1); } return res.getCFG(); } /** * Write generated String representation of the CFG for a method to a file. * * @param inputFile a Java source file, used as input * @param method name of the method to generate the CFG for * @param clas name of the class which includes the method to generate the CFG for * @param outputFile source output file * @param analysis instance of forward or backward analysis from specific dataflow test case */ @SuppressWarnings("CatchAndPrintStackTrace") // we want to use e.printStackTrace here. public static void writeStringOfCFG( String inputFile, String method, String clas, String outputFile, Analysis analysis) { Map res = generateStringOfCFG(inputFile, method, clas, true, analysis); try (FileWriter out = new FileWriter(outputFile)) { if (res != null && res.get("stringGraph") != null) { out.write(res.get("stringGraph").toString()); } out.write("\n"); } catch (IOException e) { e.printStackTrace(); } } /** * Invoke "dot" command to generate a PDF. * * @param file name of the dot file */ private static void producePDF(String file) { try { String command = "dot -Tpdf \"" + file + "\" -o \"" + file + ".pdf\""; Process child = Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", command}); child.waitFor(); } catch (InterruptedException | IOException e) { e.printStackTrace(); System.exit(1); } } /** * Generate the String representation of the CFG for a method. * * @param the abstract value type to be tracked by the analysis * @param the store type used in the analysis * @param the transfer function type that is used to approximated runtime behavior * @param inputFile a Java source file, used as input * @param method name of the method to generate the CFG for * @param clas name of the class which includes the method to generate the CFG for * @param verbose show verbose information in CFG * @param analysis analysis to perform before the visualization (or {@code null} if no analysis * is to be performed) * @return a map which includes a key "stringGraph" and the String representation of CFG as the * value */ private static < V extends AbstractValue, S extends Store, T extends TransferFunction> @Nullable Map generateStringOfCFG( String inputFile, String method, String clas, boolean verbose, @Nullable Analysis analysis) { ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method); if (analysis != null) { analysis.performAnalysis(cfg); } Map args = Collections.singletonMap("verbose", verbose); CFGVisualizer viz = new StringCFGVisualizer<>(); viz.init(args); Map res = viz.visualize(cfg, cfg.getEntryBlock(), analysis); viz.shutdown(); return res; } /** * Print error message. * * @param string error message */ private static void printError(@Nullable String string) { System.err.println("ERROR: " + string); } }