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