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.types;
24 
25 import dextool.plugin.fuzzer.type;
26 
27 import dextool.plugin.fuzzer.frontend.raw_args : RawConfiguration, XmlConfig, Symbols;
28 import dextool.plugin.fuzzer.backend.interface_ : Controller, Parameter, Product, Transform;
29 
30 private struct FileData {
31     invariant {
32         // cant have data in both.
33         assert(str_data.length == 0 || raw_data.length == 0);
34     }
35 
36     Path filename;
37     string str_data;
38     const(void)[] raw_data;
39     WriteStrategy strategy;
40 
41     const(void)[] data() {
42         if (str_data.length != 0) {
43             return cast(void[]) str_data;
44         } else {
45             return raw_data;
46         }
47     }
48 }
49 
50 class FuzzerFrontend : Controller, Parameter, Product, Transform {
51     import std.regex : regex, Regex;
52     import std.typecons : Flag;
53     import dextool.compilation_db : CompileCommandFilter;
54     import dextool.type : Path;
55     import cpptooling.testdouble.header_filter : TestDoubleIncludes, LocationType;
56     import dsrcgen.cpp : CppModule, CppHModule;
57 
58     private {
59         static const hdrExt = ".hpp";
60         static const implExt = ".cpp";
61         static const xmlExt = ".xml";
62         static const rawExt = ".bin";
63 
64         CustomHeader custom_hdr;
65 
66         /// Output directory to generate data in such as code.
67         Path output_dir;
68 
69         /// Used to match symbols by their location.
70         Regex!char[] exclude;
71         Regex!char[] restrict;
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             .argFileRestrict(args.fileRestrict)
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         import std.array : array;
96         import std.algorithm : map;
97 
98         this.exclude = a.map!(a => regex(a)).array();
99         return this;
100     }
101 
102     auto argFileRestrict(string[] a) {
103         import std.array : array;
104         import std.algorithm : map;
105 
106         this.restrict = a.map!(a => regex(a)).array();
107         return this;
108     }
109 
110     /// Ensure that the relevant information from the xml file is extracted.
111     auto argXmlConfig(Nullable!XmlConfig conf) {
112         import dextool.compilation_db : defaultCompilerFlagFilter;
113 
114         if (conf.isNull) {
115             compiler_flag_filter = CompileCommandFilter(defaultCompilerFlagFilter, 1);
116             return this;
117         }
118 
119         compiler_flag_filter = CompileCommandFilter(conf.get.filterClangFlags,
120                 conf.get.skipCompilerArgs);
121         symbols = conf.get.symbols;
122 
123         return this;
124     }
125 
126     ref CompileCommandFilter getCompileCommandFilter() {
127         return compiler_flag_filter;
128     }
129 
130     /// Data produced by the generatore intented to be written to specified file.
131     ref FileData[] getProducedFiles() {
132         return file_data;
133     }
134 
135     ref Symbols getSymbols() {
136         return symbols;
137     }
138 
139     // -- Controller --
140 
141     @safe bool doSymbolAtLocation(const string filename, const string symbol) {
142         import dextool.plugin.regex_matchers : matchAny;
143 
144         // if there are no filter registered then it automatically passes.
145 
146         bool restrict_pass = restrict.length == 0 || matchAny(filename, restrict);
147         debug logger.tracef(!restrict_pass,
148                 "--file-restrict skipping: %s in %s", symbol, filename);
149 
150         bool exclude_pass = exclude.length == 0 || !matchAny(filename, exclude);
151         debug logger.tracef(!exclude_pass, "--file-exclude skipping: %s in %s", symbol, filename);
152 
153         return restrict_pass && exclude_pass;
154     }
155 
156     bool doSymbol(string symbol) {
157         if (auto sym = symbols.lookup(FullyQualifiedNameType(symbol))) {
158             if (sym.filter == Symbol.FilterKind.exclude) {
159                 return false;
160             }
161         }
162 
163         return true;
164     }
165 
166     // -- Parameters --
167 
168     DextoolVersion getToolVersion() {
169         import dextool.utility : dextoolVersion;
170 
171         return dextoolVersion;
172     }
173 
174     CustomHeader getCustomHeader() {
175         return custom_hdr;
176     }
177 
178     // -- Products --
179 
180     void putFile(Path fname, CppHModule hdr_data) {
181         file_data ~= FileData(fname, hdr_data.render());
182     }
183 
184     void putFile(Path fname, CppModule impl_data, WriteStrategy strategy = WriteStrategy.overwrite) {
185         file_data ~= FileData(fname, impl_data.render(), null, strategy);
186     }
187 
188     void putFile(Path fname, const(ubyte)[] raw_data) {
189         file_data ~= FileData(fname, null, raw_data);
190     }
191 
192     void putFile(Path fname, string raw_data, WriteStrategy strategy = WriteStrategy.overwrite) {
193         file_data ~= FileData(fname, null, raw_data, strategy);
194     }
195 
196     // -- Transform --
197     Path createHeaderFile(string name) {
198         import std.path : buildPath;
199 
200         return Path(buildPath(output_dir, name ~ hdrExt));
201     }
202 
203     Path createImplFile(string name) {
204         import std.path : buildPath;
205 
206         return Path(buildPath(output_dir, name ~ implExt));
207     }
208 
209     Path createFuzzCase(string name, ulong id) {
210         import std.conv : to;
211         import std.path : buildPath;
212 
213         return Path(buildPath(output_dir, name ~ id.to!string ~ implExt));
214     }
215 
216     Path createFuzzyDataFile(string name) {
217         import std.path : buildPath;
218 
219         return Path(buildPath(output_dir, "test_case", name ~ rawExt));
220     }
221 
222     // try the darnest to not overwrite an existing config.
223     Path 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 Path(p);
235     }
236 }
237 
238 auto genFuzzer(FuzzerFrontend frontend, in string[] in_cflags,
239         CompileCommandDB compile_db, Path[] 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(Path(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 }