aboutsummaryrefslogtreecommitdiffhomepage
path: root/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/JavaSource2CFGDOT.java
blob: fa6ff55dc553e67fc219cf51851654cafdb9b9cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
package org.checkerframework.dataflow.cfg;

/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
*/

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;
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.HashMap;
import java.util.Map;
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 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;

/**
 * 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> <outputdir> [-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 outputDir, String method, String clas, boolean pdf) {
        generateDOTofCFG(inputFile, outputDir, method, clas, pdf, null);
    }

    /**
     * Generate the DOT representation of the CFG for a method.
     *
     * @param inputFile java source input file
     * @param outputDir source output directory
     * @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} 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 outputDir,
                    String method,
                    String clas,
                    boolean pdf,
                    /*@Nullable*/ Analysis<A, S, T> analysis) {
        Entry<MethodTree, CompilationUnitTree> m =
                getMethodTreeAndCompilationUnit(inputFile, method, clas);
        generateDOTofCFG(
                inputFile, outputDir, 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 outputDir,
                    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);
        }

        Map<String, Object> args = new HashMap<>();
        args.put("outdir", outputDir);
        args.put("checkerName", "");

        CFGVisualizer<A, S, T> viz = new DOTCFGVisualizer<A, S, T>();
        viz.init(args);
        Map<String, Object> res = viz.visualize(cfg, cfg.getEntryBlock(), analysis);
        viz.shutdown();

        if (pdf) {
            producePDF((String) res.get("dotFileName"));
        }
    }

    /** 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();
        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(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;
            }
        };
    }
}