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 dextool.compilation_db;
18 import dextool.type;
19 
20 import dextool.plugin.types;
21 
22 import dextool.plugin.fuzzer.type;
23 
24 import dextool.plugin.fuzzer.frontend.raw_args : RawConfiguration, XmlConfig,
25     Symbols;
26 import dextool.plugin.fuzzer.backend.interface_ : Controller, Parameter,
27     Product, Transform;
28 
29 private struct FileData {
30     import dextool.type : FileName, WriteStrategy;
31 
32     invariant {
33         // cant have data in both.
34         assert(str_data.length == 0 || raw_data.length == 0);
35     }
36 
37     FileName filename;
38     string str_data;
39     const(void)[] raw_data;
40     WriteStrategy strategy;
41 
42     const(void)[] data() {
43         if (str_data.length != 0) {
44             return cast(void[]) str_data;
45         } else {
46             return raw_data;
47         }
48     }
49 }
50 
51 class FuzzerFrontend : Controller, Parameter, Product, Transform {
52     import std.regex : regex, Regex;
53     import std.typecons : Flag;
54     import dextool.compilation_db : CompileCommandFilter;
55     import dextool.type : FileName;
56     import cpptooling.testdouble.header_filter : TestDoubleIncludes,
57         LocationType;
58     import dsrcgen.cpp : CppModule, CppHModule;
59 
60     private {
61         static const hdrExt = ".hpp";
62         static const implExt = ".cpp";
63         static const xmlExt = ".xml";
64         static const rawExt = ".bin";
65 
66         CustomHeader custom_hdr;
67 
68         /// Output directory to generate data in such as code.
69         DirName output_dir;
70 
71         /// Used to match symbols by their location.
72         Regex!char[] exclude;
73         Regex!char[] restrict;
74 
75         /// Data produced by the generatore intented to be written to specified file.
76         FileData[] file_data;
77 
78         CompileCommandFilter compiler_flag_filter;
79         Symbols symbols;
80     }
81 
82     static auto make(ref RawConfiguration args) {
83         // dfmt off
84         auto r = new FuzzerFrontend(DirName(args.out_))
85             .argFileExclude(args.fileExclude)
86             .argFileRestrict(args.fileRestrict)
87             .argXmlConfig(args.xmlConfig);
88         // dfmt on
89         return r;
90     }
91 
92     this(DirName output_dir) {
93         this.output_dir = output_dir;
94     }
95 
96     auto argFileExclude(string[] a) {
97         import std.array : array;
98         import std.algorithm : map;
99 
100         this.exclude = a.map!(a => regex(a)).array();
101         return this;
102     }
103 
104     auto argFileRestrict(string[] a) {
105         import std.array : array;
106         import std.algorithm : map;
107 
108         this.restrict = a.map!(a => regex(a)).array();
109         return this;
110     }
111 
112     /// Ensure that the relevant information from the xml file is extracted.
113     auto argXmlConfig(Nullable!XmlConfig conf) {
114         import dextool.compilation_db : defaultCompilerFlagFilter;
115 
116         if (conf.isNull) {
117             compiler_flag_filter = CompileCommandFilter(defaultCompilerFlagFilter, 1);
118             return this;
119         }
120 
121         compiler_flag_filter = CompileCommandFilter(conf.filterClangFlags, conf.skipCompilerArgs);
122         symbols = conf.symbols;
123 
124         return this;
125     }
126 
127     ref CompileCommandFilter getCompileCommandFilter() {
128         return compiler_flag_filter;
129     }
130 
131     /// Data produced by the generatore intented to be written to specified file.
132     ref FileData[] getProducedFiles() {
133         return file_data;
134     }
135 
136     ref Symbols getSymbols() {
137         return symbols;
138     }
139 
140     // -- Controller --
141 
142     @safe bool doSymbolAtLocation(const string filename, const string symbol) {
143         import dextool.plugin.regex_matchers : matchAny;
144 
145         // if there are no filter registered then it automatically passes.
146 
147         bool restrict_pass = restrict.length == 0 || matchAny(filename, restrict);
148         debug logger.tracef(!restrict_pass,
149                 "--file-restrict skipping: %s in %s", symbol, filename);
150 
151         bool exclude_pass = exclude.length == 0 || !matchAny(filename, exclude);
152         debug logger.tracef(!exclude_pass, "--file-exclude skipping: %s in %s", symbol, filename);
153 
154         return restrict_pass && exclude_pass;
155     }
156 
157     bool doSymbol(string symbol) {
158         if (auto sym = symbols.lookup(FullyQualifiedNameType(symbol))) {
159             if (sym.filter == Symbol.FilterKind.exclude) {
160                 return false;
161             }
162         }
163 
164         return true;
165     }
166 
167     // -- Parameters --
168 
169     DextoolVersion getToolVersion() {
170         import dextool.utility : dextoolVersion;
171 
172         return dextoolVersion;
173     }
174 
175     CustomHeader getCustomHeader() {
176         return custom_hdr;
177     }
178 
179     // -- Products --
180 
181     void putFile(FileName fname, CppHModule hdr_data) {
182         file_data ~= FileData(fname, hdr_data.render());
183     }
184 
185     void putFile(FileName fname, CppModule impl_data,
186             WriteStrategy strategy = WriteStrategy.overwrite) {
187         file_data ~= FileData(fname, impl_data.render(), null, strategy);
188     }
189 
190     void putFile(FileName fname, const(ubyte)[] raw_data) {
191         file_data ~= FileData(fname, null, raw_data);
192     }
193 
194     void putFile(FileName fname, string raw_data, WriteStrategy strategy = WriteStrategy.overwrite) {
195         file_data ~= FileData(fname, null, raw_data, strategy);
196     }
197 
198     // -- Transform --
199     FileName createHeaderFile(string name) {
200         import std.path : buildPath;
201 
202         return FileName(buildPath(output_dir, name ~ hdrExt));
203     }
204 
205     FileName createImplFile(string name) {
206         import std.path : buildPath;
207 
208         return FileName(buildPath(output_dir, name ~ implExt));
209     }
210 
211     FileName createFuzzCase(string name, ulong id) {
212         import std.conv : to;
213         import std.path : buildPath;
214 
215         return FileName(buildPath(output_dir, name ~ id.to!string ~ implExt));
216     }
217 
218     FileName createFuzzyDataFile(string name) {
219         import std.path : buildPath;
220 
221         return FileName(buildPath(output_dir, "test_case", name ~ rawExt));
222     }
223 
224     // try the darnest to not overwrite an existing config.
225     FileName createXmlConfigFile(string name) {
226         import std.conv : to;
227         import std.path : buildPath;
228         import std.file : exists;
229 
230         string p = buildPath(output_dir, name ~ xmlExt);
231 
232         for (int i = 0; exists(p); ++i) {
233             p = buildPath(output_dir, name ~ i.to!string() ~ xmlExt);
234         }
235 
236         return FileName(p);
237     }
238 }
239 
240 auto genFuzzer(FuzzerFrontend frontend, in string[] in_cflags,
241         CompileCommandDB compile_db, InFiles in_files, Regex!char strip_incl) {
242     import dextool.io : writeFileData;
243     import dextool.plugin.fuzzer.backend.backend : Backend;
244     import dextool.utility : prependDefaultFlags, PreferLang;
245 
246     const auto user_cflags = prependDefaultFlags(in_cflags, PreferLang.none);
247     const auto total_files = in_files.length;
248     auto backend = Backend(frontend, frontend, frontend, frontend, strip_incl);
249 
250     foreach (idx, in_file; in_files) {
251         logger.infof("File %d/%d ", idx + 1, total_files);
252         string[] use_cflags;
253         AbsolutePath analyze_file;
254 
255         if (compile_db.length > 0) {
256             auto db_search_result = compile_db.appendOrError(user_cflags,
257                     in_file, frontend.getCompileCommandFilter);
258             if (db_search_result.isNull) {
259                 return ExitStatusType.Errors;
260             }
261             use_cflags = db_search_result.get.cflags;
262             analyze_file = db_search_result.get.absoluteFile;
263         } else {
264             use_cflags = user_cflags.dup;
265             analyze_file = AbsolutePath(FileName(in_file));
266         }
267 
268         if (backend.analyzeFile(analyze_file, use_cflags) == ExitStatusType.Errors) {
269             return ExitStatusType.Errors;
270         }
271     }
272 
273     backend.finalizeIncludes;
274 
275     // Analyse and generate interceptors
276     backend.process(frontend.getSymbols, frontend.getCompileCommandFilter);
277 
278     return writeFileData(frontend.getProducedFiles);
279 }