1 /**
2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 */
10 module dextool.plugin.fuzzer.frontend.frontend;
11 
12 import std.regex : Regex;
13 import std.typecons : Nullable;
14 
15 import logger = std.experimental.logger;
16 
17 import cpptooling.type;
18 
19 import dextool.compilation_db;
20 import dextool.io : WriteStrategy;
21 import dextool.type;
22 
23 import dextool.plugin.fuzzer.type;
24 
25 import dextool.plugin.fuzzer.frontend.raw_args : RawConfiguration, XmlConfig, Symbols;
26 import dextool.plugin.fuzzer.backend.interface_ : Controller, Parameter, Product, Transform;
27 
28 private struct FileData {
29     invariant {
30         // cant have data in both.
31         assert(str_data.length == 0 || raw_data.length == 0);
32     }
33 
34     Path filename;
35     string str_data;
36     const(void)[] raw_data;
37     WriteStrategy strategy;
38 
39     const(void)[] data() {
40         if (str_data.length != 0) {
41             return cast(void[]) str_data;
42         } else {
43             return raw_data;
44         }
45     }
46 }
47 
48 class FuzzerFrontend : Controller, Parameter, Product, Transform {
49     import std.regex : regex, Regex;
50     import std.typecons : Flag;
51     import my.filter : ReFilter;
52     import dextool.compilation_db : CompileCommandFilter;
53     import dextool.type : Path;
54     import cpptooling.testdouble.header_filter : TestDoubleIncludes, LocationType;
55     import dsrcgen.cpp : CppModule, CppHModule;
56 
57     private {
58         static const hdrExt = ".hpp";
59         static const implExt = ".cpp";
60         static const xmlExt = ".xml";
61         static const rawExt = ".bin";
62 
63         CustomHeader custom_hdr;
64 
65         /// Output directory to generate data in such as code.
66         Path output_dir;
67 
68         /// Used to match symbols by their location.
69         string[] exclude;
70         string[] include;
71         ReFilter fileFilter;
72 
73         /// Data produced by the generatore intented to be written to specified file.
74         FileData[] file_data;
75 
76         CompileCommandFilter compiler_flag_filter;
77         Symbols symbols;
78     }
79 
80     static auto make(ref RawConfiguration args) {
81         // dfmt off
82         auto r = new FuzzerFrontend(Path(args.out_))
83             .argFileExclude(args.fileExclude)
84             .argFileInclude(args.fileInclude)
85             .argXmlConfig(args.xmlConfig);
86         // dfmt on
87         return r;
88     }
89 
90     this(Path output_dir) {
91         this.output_dir = output_dir;
92     }
93 
94     auto argFileExclude(string[] a) {
95         this.exclude = a;
96         fileFilter = ReFilter(include, exclude);
97         return this;
98     }
99 
100     auto argFileInclude(string[] a) {
101         this.include = a;
102         fileFilter = ReFilter(include, exclude);
103         return this;
104     }
105 
106     /// Ensure that the relevant information from the xml file is extracted.
107     auto argXmlConfig(Nullable!XmlConfig conf) {
108         import dextool.compilation_db : defaultCompilerFlagFilter;
109 
110         if (conf.isNull) {
111             compiler_flag_filter = CompileCommandFilter(defaultCompilerFlagFilter, 0);
112             return this;
113         }
114 
115         compiler_flag_filter = CompileCommandFilter(conf.get.filterClangFlags,
116                 conf.get.skipCompilerArgs);
117         symbols = conf.get.symbols;
118 
119         return this;
120     }
121 
122     ref CompileCommandFilter getCompileCommandFilter() {
123         return compiler_flag_filter;
124     }
125 
126     /// Data produced by the generatore intented to be written to specified file.
127     ref FileData[] getProducedFiles() {
128         return file_data;
129     }
130 
131     ref Symbols getSymbols() {
132         return symbols;
133     }
134 
135     // -- Controller --
136 
137     @safe bool doSymbolAtLocation(const string filename, const string symbol) {
138         return fileFilter.match(filename, (string s, string type) {
139             logger.tracef("matcher --file-%s removed %s for symbol %s. Skipping", s, symbol, type);
140         });
141     }
142 
143     bool doSymbol(string symbol) {
144         if (auto sym = symbols.lookup(FullyQualifiedNameType(symbol))) {
145             if (sym.filter == Symbol.FilterKind.exclude) {
146                 return false;
147             }
148         }
149 
150         return true;
151     }
152 
153     // -- Parameters --
154 
155     DextoolVersion getToolVersion() {
156         import dextool.utility : dextoolVersion;
157 
158         return dextoolVersion;
159     }
160 
161     CustomHeader getCustomHeader() {
162         return custom_hdr;
163     }
164 
165     // -- Products --
166 
167     void putFile(Path fname, CppHModule hdr_data) {
168         file_data ~= FileData(fname, hdr_data.render());
169     }
170 
171     void putFile(Path fname, CppModule impl_data, WriteStrategy strategy = WriteStrategy.overwrite) {
172         file_data ~= FileData(fname, impl_data.render(), null, strategy);
173     }
174 
175     void putFile(Path fname, const(ubyte)[] raw_data) {
176         file_data ~= FileData(fname, null, raw_data);
177     }
178 
179     void putFile(Path fname, string raw_data, WriteStrategy strategy = WriteStrategy.overwrite) {
180         file_data ~= FileData(fname, null, raw_data, strategy);
181     }
182 
183     // -- Transform --
184     Path createHeaderFile(string name) {
185         import std.path : buildPath;
186 
187         return Path(buildPath(output_dir, name ~ hdrExt));
188     }
189 
190     Path createImplFile(string name) {
191         import std.path : buildPath;
192 
193         return Path(buildPath(output_dir, name ~ implExt));
194     }
195 
196     Path createFuzzCase(string name, ulong id) {
197         import std.conv : to;
198         import std.path : buildPath;
199 
200         return Path(buildPath(output_dir, name ~ id.to!string ~ implExt));
201     }
202 
203     Path createFuzzyDataFile(string name) {
204         import std.path : buildPath;
205 
206         return Path(buildPath(output_dir, "test_case", name ~ rawExt));
207     }
208 
209     // try the darnest to not overwrite an existing config.
210     Path createXmlConfigFile(string name) {
211         import std.conv : to;
212         import std.path : buildPath;
213         import std.file : exists;
214 
215         string p = buildPath(output_dir, name ~ xmlExt);
216 
217         for (int i = 0; exists(p); ++i) {
218             p = buildPath(output_dir, name ~ i.to!string() ~ xmlExt);
219         }
220 
221         return Path(p);
222     }
223 }
224 
225 auto genFuzzer(FuzzerFrontend frontend, in string[] userCflags,
226         CompileCommandDB compile_db, Path[] inFiles, Regex!char strip_incl) {
227     import std.algorithm : map;
228     import std.array : array;
229 
230     import dextool.clang : reduceMissingFiles;
231     import dextool.compilation_db : limitOrAllRange, parse, prependFlags,
232         addCompiler, replaceCompiler, addSystemIncludes, fileRange;
233     import dextool.io : writeFileData;
234     import dextool.plugin.fuzzer.backend.backend : Backend;
235     import dextool.utility : prependDefaultFlags, PreferLang;
236 
237     auto backend = Backend(frontend, frontend, frontend, frontend, strip_incl);
238 
239     auto compDbRange() {
240         if (compile_db.empty) {
241             return fileRange(inFiles, Compiler("/usr/bin/c++"));
242         }
243         return compile_db.fileRange;
244     }
245 
246     auto fixedDb = compDbRange.parse(frontend.getCompileCommandFilter).addCompiler(Compiler("/usr/bin/c++"))
247         .addSystemIncludes.prependFlags(prependDefaultFlags(userCflags, PreferLang.none)).array;
248 
249     auto limitRange = limitOrAllRange(fixedDb, inFiles.map!(a => cast(string) a).array)
250         .reduceMissingFiles(fixedDb);
251 
252     if (!compile_db.empty && !limitRange.isMissingFilesEmpty) {
253         foreach (a; limitRange.missingFiles) {
254             logger.error("Unable to find any compiler flags for .", a);
255         }
256         return ExitStatusType.Errors;
257     }
258 
259     foreach (pdata; limitRange.range) {
260         if (backend.analyzeFile(pdata.cmd.absoluteFile,
261                 pdata.flags.completeFlags) == ExitStatusType.Errors) {
262             return ExitStatusType.Errors;
263         }
264     }
265 
266     backend.finalizeIncludes;
267 
268     // Analyse and generate interceptors
269     backend.process(frontend.getSymbols, frontend.getCompileCommandFilter);
270 
271     return writeFileData(frontend.getProducedFiles);
272 }