package org.checkerframework.dataflow.cfg; /*>>> 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.javacutil.BasicTypeProcessor; import org.checkerframework.javacutil.TreeUtils; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.Map.Entry; import javax.lang.model.element.ExecutableElement; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.xml.ws.Holder; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.MethodTree; import com.sun.source.util.TreePathScanner; 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; /** * Class to generate the DOT representation of the control flow graph of a given * method. * * @author Stefan Heule */ public class JavaSource2CFGDOT { /** Main method. */ public static void main(String[] args) { if (args.length < 2) { printUsage(); System.exit(1); } String input = args[0]; String output = args[1]; File file = new File(input); if (!file.canRead()) { printError("Cannot read input file: " + file.getAbsolutePath()); printUsage(); System.exit(1); } String method = "test"; String clas = "Test"; boolean pdf = false; boolean error = false; for (int i = 2; i < args.length; i++) { if (args[i].equals("-pdf")) { pdf = true; } else if (args[i].equals("-method")) { if (i >= args.length - 1) { printError("Did not find <name> after -method."); continue; } i++; method = args[i]; } else if (args[i].equals("-class")) { if (i >= args.length - 1) { printError("Did not find <name> after -class."); continue; } i++; clas = args[i]; } else { printError("Unknown command line argument: " + args[i]); error = true; } } if (error) { System.exit(1); } generateDOTofCFG(input, output, method, clas, pdf); } /** Print an error message. */ protected static void printError(String string) { System.err.println("ERROR: " + string); } /** Print usage information. */ protected static void printUsage() { System.out .println("Generate the control flow graph of a Java method, represented as a DOT graph."); System.out .println("Parameters: <inputfile> <outputfile> [-method <name>] [-class <name>] [-pdf]"); System.out .println(" -pdf: Also generate the PDF by invoking 'dot'."); 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')."); } /** Just like method above but without analysis. */ public static void generateDOTofCFG(String inputFile, String outputFile, String method, String clas, boolean pdf) { generateDOTofCFG(inputFile, outputFile, method, clas, pdf, null); } /** * Generate the DOT representation of the CFG for a method. * * @param inputFile * Java source input file. * @param outputFile * Source output file (without file extension) * @param method * Method name to generate the CFG for. * @param pdf * Also generate a PDF? * @param analysis * Analysis to perform befor the visualization (or * <code>null</code> if no analysis is to be performed). */ public static <A extends AbstractValue<A>, S extends Store<S>, T extends TransferFunction<A, S>> void generateDOTofCFG( String inputFile, String outputFile, String method, String clas, boolean pdf, /*@Nullable*/ Analysis<A, S, T> analysis) { Entry<MethodTree, CompilationUnitTree> m = getMethodTreeAndCompilationUnit(inputFile, method, clas); generateDOTofCFG(inputFile, outputFile, method, clas, pdf, analysis, m.getKey(), m.getValue()); } public static <A extends AbstractValue<A>, S extends Store<S>, T extends TransferFunction<A, S>> void generateDOTofCFG( String inputFile, String outputFile, String method, String clas, boolean pdf, /*@Nullable*/ Analysis<A, S, T> analysis, MethodTree m, CompilationUnitTree r) { String fileName = (new File(inputFile)).getName(); System.out.println("Working on " + fileName + "..."); if (m == null) { printError("Method not found."); System.exit(1); } ControlFlowGraph cfg = CFGBuilder.build(r, null, m, null); if (analysis != null) { analysis.performAnalysis(cfg); } String s = CFGDOTVisualizer.visualize(cfg, cfg.getEntryBlock(), analysis, false); try { FileWriter fstream = new FileWriter(outputFile + ".txt"); BufferedWriter out = new BufferedWriter(fstream); out.write(s); System.out.println("Finished " + fileName + "."); out.close(); } catch (IOException e) { e.printStackTrace(); System.exit(1); } if (pdf) { producePDF(outputFile); } } /** * Invoke DOT to generate a PDF. */ protected static void producePDF(String file) { try { String command = "dot -Tpdf \"" + file + ".txt\" -o \"" + file + ".pdf\""; Process child = Runtime.getRuntime().exec(command); child.waitFor(); } catch (InterruptedException | IOException e) { e.printStackTrace(); System.exit(1); } } /** * @return The AST of a specific method in a specific class in a specific * file (or null if no such method exists). */ public static /*@Nullable*/ MethodTree getMethodTree(String file, final String method, String clas) { return getMethodTreeAndCompilationUnit(file, method, clas).getKey(); } /** * @return The AST of a specific method in a specific class as well as the * {@link CompilationUnitTree} in a specific file (or null they do * not exist). */ public static Entry</*@Nullable*/ MethodTree, /*@Nullable*/ CompilationUnitTree> getMethodTreeAndCompilationUnit( String file, final String method, String clas) { final Holder<MethodTree> m = new Holder<>(); final Holder<CompilationUnitTree> c = new Holder<>(); BasicTypeProcessor typeProcessor = new BasicTypeProcessor() { @Override protected TreePathScanner<?, ?> createTreePathScanner( CompilationUnitTree root) { c.value = root; return new TreePathScanner<Void, Void>() { @Override public Void visitMethod(MethodTree node, Void p) { ExecutableElement el = TreeUtils .elementFromDeclaration(node); if (el.getSimpleName().contentEquals(method)) { m.value = node; // stop execution by throwing an exception. this // makes sure that compilation does not proceed, and // thus the AST is not modified by further phases of // the compilation (and we save the work to do the // compilation). throw new RuntimeException(); } return null; } }; } }; Context context = new Context(); JavaCompiler javac = new JavaCompiler(context); javac.attrParseOnly = true; 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(typeProcessor)); } catch (Throwable e) { // ok } finally { System.setErr(err); } return new Entry<MethodTree, CompilationUnitTree>() { @Override public CompilationUnitTree setValue(CompilationUnitTree value) { return null; } @Override public CompilationUnitTree getValue() { return c.value; } @Override public MethodTree getKey() { return m.value; } }; } }