blob: 721c57bd3c8388053a937d1f3b6553a3a09f0df0 [file] [log] [blame]
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 java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
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;
/**
* Launcher to generate the DOT or String representation of the control flow graph of a given method
* in a given class.
*
* <p>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 class CFGVisualizeLauncher {
/**
* The main entry point of CFGVisualizeLauncher.
*
* @param args the passed arguments, see {@link #printUsage()} for the usage
*/
public static void main(String[] args) {
CFGVisualizeLauncher cfgVisualizeLauncher = new CFGVisualizeLauncher();
if (args.length == 0) {
cfgVisualizeLauncher.printUsage();
System.exit(1);
}
String input = args[0];
File file = new File(input);
if (!file.canRead()) {
cfgVisualizeLauncher.printError("Cannot read input file: " + file.getAbsolutePath());
cfgVisualizeLauncher.printUsage();
System.exit(1);
}
String method = "test";
String clas = "Test";
String output = ".";
boolean pdf = false;
boolean error = false;
boolean verbose = false;
boolean string = false;
for (int i = 1; i < args.length; i++) {
switch (args[i]) {
case "--outputdir":
if (i >= args.length - 1) {
cfgVisualizeLauncher.printError("Did not find <outputdir> after --outputdir.");
continue;
}
i++;
output = args[i];
break;
case "--pdf":
pdf = true;
break;
case "--method":
if (i >= args.length - 1) {
cfgVisualizeLauncher.printError("Did not find <name> after --method.");
continue;
}
i++;
method = args[i];
break;
case "--class":
if (i >= args.length - 1) {
cfgVisualizeLauncher.printError("Did not find <name> after --class.");
continue;
}
i++;
clas = args[i];
break;
case "--verbose":
verbose = true;
break;
case "--string":
string = true;
break;
default:
cfgVisualizeLauncher.printError("Unknown command line argument: " + args[i]);
error = true;
break;
}
}
if (error) {
System.exit(1);
}
if (!string) {
cfgVisualizeLauncher.generateDOTofCFGWithoutAnalysis(
input, output, method, clas, pdf, verbose);
} else {
String stringGraph =
cfgVisualizeLauncher.generateStringOfCFGWithoutAnalysis(input, method, clas, verbose);
System.out.println(stringGraph);
}
}
/**
* Generate the DOT representation of the CFG for a method, only. Does no dataflow analysis.
*
* @param inputFile java source input file
* @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
*/
protected 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 java source input file
* @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
*/
protected String generateStringOfCFGWithoutAnalysis(
String inputFile, String method, String clas, boolean verbose) {
@Nullable Map<String, Object> 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 <V> the abstract value type to be tracked by the analysis
* @param <S> the store type used in the analysis
* @param <T> the transfer function type that is used to approximated runtime behavior
* @param inputFile java source input file
* @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)
*/
public <V extends AbstractValue<V>, S extends Store<S>, T extends TransferFunction<V, S>>
void generateDOTofCFG(
String inputFile,
String outputDir,
String method,
String clas,
boolean pdf,
boolean verbose,
@Nullable Analysis<V, S, T> analysis) {
ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method);
if (analysis != null) {
analysis.performAnalysis(cfg);
}
Map<String, Object> args = new HashMap<>(2);
args.put("outdir", outputDir);
args.put("verbose", verbose);
CFGVisualizer<V, S, T> viz = new DOTCFGVisualizer<>();
viz.init(args);
Map<String, Object> res = viz.visualize(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 java source input file
* @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
*/
protected ControlFlowGraph generateMethodCFG(String file, String clas, final String method) {
CFGProcessor cfgProcessor = new CFGProcessor(clas, method);
Context context = new Context();
Options.instance(context).put("compilePolicy", "ATTR_ONLY");
JavaCompiler javac = new JavaCompiler(context);
JavacFileManager fileManager = (JavacFileManager) context.get(JavaFileManager.class);
JavaFileObject l = fileManager.getJavaFileObjectsFromStrings(List.of(file)).iterator().next();
PrintStream err = System.err;
try {
// redirect syserr to nothing (and prevent the compiler from issuing
// warnings about our exception.
System.setErr(
new PrintStream(
new OutputStream() {
@Override
public void write(int b) throws IOException {}
}));
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();
}
/**
* Invoke "dot" command to generate a PDF.
*
* @param file name of the dot file
*/
protected 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 <V> the abstract value type to be tracked by the analysis
* @param <S> the store type used in the analysis
* @param <T> the transfer function type that is used to approximated runtime behavior
* @param inputFile java source input file
* @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
*/
public <V extends AbstractValue<V>, S extends Store<S>, T extends TransferFunction<V, S>>
@Nullable Map<String, Object> generateStringOfCFG(
String inputFile,
String method,
String clas,
boolean verbose,
@Nullable Analysis<V, S, T> analysis) {
ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method);
if (analysis != null) {
analysis.performAnalysis(cfg);
}
Map<String, Object> args = Collections.singletonMap("verbose", verbose);
CFGVisualizer<V, S, T> viz = new StringCFGVisualizer<>();
viz.init(args);
Map<String, Object> res = viz.visualize(cfg, cfg.getEntryBlock(), analysis);
viz.shutdown();
return res;
}
/** Print usage information. */
protected void printUsage() {
System.out.println(
"Generate the control flow graph of a Java method, represented as a DOT or String graph.");
System.out.println(
"Parameters: <inputfile> [--outputdir <outputdir>] [--method <name>] [--class <name>]"
+ " [--pdf] [--verbose] [--string]");
System.out.println(
" --outputdir: The output directory for the generated files (defaults to '.').");
System.out.println(" --method: The method to generate the CFG for (defaults to 'test').");
System.out.println(
" --class: The class in which to find the method (defaults to 'Test').");
System.out.println(" --pdf: Also generate the PDF by invoking 'dot'.");
System.out.println(" --verbose: Show the verbose output (defaults to 'false').");
System.out.println(
" --string: Print the string representation of the control flow graph (defaults to"
+ " 'false').");
}
/**
* Print error message.
*
* @param string error message
*/
protected void printError(@Nullable String string) {
System.err.println("ERROR: " + string);
}
}