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.backend.backend;
11 
12 import std.typecons : Nullable;
13 
14 import logger = std.experimental.logger;
15 
16 import dextool.plugin.fuzzer.backend.analyzer;
17 import dextool.plugin.fuzzer.backend.interface_;
18 import dextool.plugin.fuzzer.backend.type;
19 
20 import dextool.compilation_db : CompileCommandFilter;
21 import dextool.hash : makeHash;
22 import dextool.type : FileName, AbsolutePath;
23 import cpptooling.data.type : LocationTag, Location;
24 
25 struct Backend {
26     import cpptooling.analyzer.clang.context : ClangContext;
27     import cpptooling.data.symbol : Container;
28     import cpptooling.testdouble.header_filter : GenericTestDoubleIncludes,
29         LocationType;
30     import dextool.type : ExitStatusType;
31     import std.regex : Regex;
32 
33     this(Controller ctrl, Parameter params, Product products, Transform transf,
34             Regex!char strip_incl) {
35         import std.typecons : Yes;
36 
37         this.analyze = AnalyzeData.make();
38         this.ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
39         this.ctrl = ctrl;
40         this.params = params;
41         this.products = products;
42         this.transf = transf;
43         this.gen_code_includes = GenericTestDoubleIncludes!Language(strip_incl);
44     }
45 
46     /// Frontend signals backend that all files have been processed.
47     void finalizeIncludes() {
48         gen_code_includes.finalize();
49     }
50 
51     void putLocation(FileName fname, LocationType type, Language lang) @safe {
52         gen_code_includes.put(fname, type, lang);
53     }
54 
55     ExitStatusType analyzeFile(const AbsolutePath analyze_file, const string[] use_cflags) {
56         import std.typecons : NullableRef, scoped;
57         import dextool.utility : analyzeFile;
58         import cpptooling.data : MergeMode;
59 
60         NullableRef!Container cont_ = &container;
61         auto visitor = new TUVisitor(cont_);
62 
63         if (analyzeFile(analyze_file, use_cflags, visitor, ctx) == ExitStatusType.Errors) {
64             return ExitStatusType.Errors;
65         }
66 
67         debug logger.tracef("%u", visitor.root);
68 
69         // dfmt off
70         auto filtered = rawFilter(visitor.root, ctrl, products,
71                 (FileName a, LocationType k, Language l) @safe => this.putLocation(a, k, l),
72                 (USRType usr) @safe => container.find!LocationTag(usr));
73         // dfmt on
74 
75         analyze.root.merge(filtered, MergeMode.full);
76 
77         gen_code_includes.process();
78 
79         return ExitStatusType.Ok;
80     }
81 
82     void process(SymbolsT)(ref SymbolsT syms, ref CompileCommandFilter compiler_flag_filter) {
83         import std.algorithm : map;
84         import std.array : array;
85 
86         assert(!analyze.isNull);
87 
88         debug {
89             logger.trace(container.toString);
90             logger.tracef("Filtered:\n%u", analyze.root);
91         }
92 
93         auto impl_data = translate(analyze.root, syms, container);
94         analyze.nullify();
95 
96         debug {
97             logger.tracef("Translated to implementation:\n%u", impl_data.root);
98             logger.tracef("Symbol data:\n%(%s:%s\n%)", impl_data.symbols);
99         }
100 
101         generate(impl_data, compiler_flag_filter, gen_data,
102                 gen_code_includes.includesWithPayload.array(), container);
103 
104         postProcess(ctrl, transf, params, products, gen_data);
105     }
106 
107 private:
108     ClangContext ctx;
109     Container container;
110     Nullable!AnalyzeData analyze;
111     GeneratedData gen_data;
112 
113     Controller ctrl;
114     Parameter params;
115     Product products;
116     Transform transf;
117 
118     GenericTestDoubleIncludes!Language gen_code_includes;
119 }
120 
121 private:
122 @safe:
123 
124 import cpptooling.data : CppRoot, CFunction, USRType, CxParam, CppVariable,
125     CppNs, Language;
126 import cpptooling.data.symbol : Container;
127 import dextool.plugin.fuzzer.type : Param, Symbol;
128 
129 import dsrcgen.cpp : CppModule, CppHModule;
130 
131 /** Filter the raw IR according to the users desire.
132  *
133  * Params:
134  *  ctrl = removes according to directives via ctrl
135  *  prod = put locations of symbols that pass filtering
136  */
137 AnalyzeData rawFilter(PutLocT, LookupT)(AnalyzeData input, Controller ctrl,
138         Product prod, PutLocT putLoc, LookupT lookup) {
139     import std.algorithm : each, filter;
140     import std.range : tee;
141     import dextool.type : FileName;
142     import cpptooling.data : StorageClass;
143     import cpptooling.generator.utility : filterAnyLocation;
144     import cpptooling.testdouble.header_filter : LocationType;
145 
146     if (ctrl.doSymbolAtLocation(input.fileOfTranslationUnit, input.fileOfTranslationUnit)) {
147         putLoc(input.fileOfTranslationUnit, LocationType.Root, input.languageOfTranslationUnit);
148     }
149 
150     auto filtered = AnalyzeData.make;
151 
152     // dfmt off
153     input.funcRange
154         // by definition static functions can't be replaced by test doubles
155         .filter!(a => a.storageClass != StorageClass.Static)
156         // ask controller if the symbol should be intercepted
157         .filter!(a => ctrl.doSymbol(a.name))
158         // ask controller if to generate wrapper for the function based on file location
159         .filterAnyLocation!(a => ctrl.doSymbolAtLocation(a.location.file, a.value.name))(lookup)
160         // pass on location as a product to be used to calculate #include
161         .tee!(a => putLoc(FileName(a.location.file), LocationType.Leaf, a.value.language))
162         .each!(a => filtered.put(a.value));
163     // dfmt on
164 
165     return filtered;
166 }
167 
168 /** Structurally translate the input to a fuzz wrapper.
169  *
170  * Add/transform/remove constructs from the filtered data to make it suitable
171  * to generate the fuzz wrapper.
172  */
173 ImplData translate(SymbolsT)(CppRoot root, ref SymbolsT symbols, ref Container container) @trusted {
174     import std.algorithm : map, filter;
175     import dextool.plugin.fuzzer.backend.unique_sequence : Sequence;
176     import dextool.plugin.fuzzer.type : Symbol, FullyQualifiedNameType, Param;
177 
178     Sequence!ulong test_case_seq_generator;
179     auto impl = ImplData.make();
180 
181     void updateSeq(ref Symbol sym) {
182         Symbol s = sym;
183         test_case_seq_generator.putOrAdjust(s.sequenceId.payload);
184         s.sequenceId.isValid = true;
185         impl.symbols[sym.fullyQualifiedName] = s;
186         impl.symbolId[sym.fullyQualifiedName] = s.sequenceId.payload;
187     }
188 
189     // prepare the sequence number generator with all the used valid seq. nums.
190     foreach (sym; symbols.byKeyValue.map!(a => a.value)
191             .filter!(a => a.filter == Symbol.FilterKind.keep && a.sequenceId.isValid)) {
192         updateSeq(sym);
193     }
194 
195     // assign new, valid seq. nums for those symbols that had an invalid sequence.
196     foreach (sym; symbols.byKeyValue.map!(a => a.value)
197             .filter!(a => a.filter == Symbol.FilterKind.keep && !a.sequenceId.isValid)) {
198         updateSeq(sym);
199     }
200 
201     // prepare generation of the xml data from the excluded symbols.
202     foreach (sym; symbols.byKeyValue.map!(a => a.value)
203             .filter!(a => a.filter == Symbol.FilterKind.exclude)) {
204         impl.excludedSymbols ~= IgnoreSymbol(sym.fullyQualifiedName);
205     }
206 
207     // assign new numbers to those functions that are newly discovered.
208     foreach (f; root.funcRange) {
209         impl.root.put(f);
210 
211         if (FullyQualifiedNameType(f.name) in impl.symbolId) {
212             // do nothing
213         } else {
214             ulong id;
215             test_case_seq_generator.putOrAdjust(id);
216             impl.symbolId[FullyQualifiedNameType(f.name)] = id;
217         }
218     }
219 
220     return impl;
221 }
222 
223 /** Translate the structure to code.
224  *
225  * Keep it simple. Translate should have prepared the data to make this step
226  * easy.
227  * */
228 void generate(IncludeT)(ref ImplData impl, ref CompileCommandFilter compiler_flag_filter,
229         ref GeneratedData gen, IncludeT[] target_includes, ref const Container container) {
230     import dextool.plugin.fuzzer.backend.generate_cpp;
231     import dextool.plugin.fuzzer.backend.generate_xml;
232 
233     // code
234     gen.make(Code.Kind.configTemplate);
235     generateMain(impl, gen.make(Code.Kind.main).cpp);
236     generateFuzzCases(impl, target_includes, gen);
237 
238     // config
239     generateConfigPerFunction(impl, gen.templateConfig);
240     generateConfigCompilerFilter(compiler_flag_filter, gen.templateConfig);
241 
242     // fuzz binary data
243     generateFuzzyInput(impl, gen);
244 }
245 
246 /// Generate a initial data file for the fuzzer based on static analysis.
247 void generateFuzzyInput(ref ImplData impl, ref GeneratedData gen) {
248     import std.range : repeat;
249     import std.array : array, appender;
250     import cpptooling.data : unpackParam;
251 
252     auto d = gen.make(Code.Kind.fuzzy);
253 
254     auto app = appender!(ubyte[])();
255 
256     foreach (f; impl.root.funcRange) {
257         auto plen = f.paramRange.length;
258         if (f.paramRange.length == 1 && f.paramRange[0].unpackParam.isVariadic) {
259             plen -= 1;
260         }
261 
262         // assume that each parameter is at least 4 bytes of fuzzy data
263         app.put(repeat(ubyte(0), 4 * plen).array());
264     }
265 
266     // must not be zero or AFL do not consider it as input
267     if (app.data.length == 0) {
268         app.put(ubyte(0));
269     }
270 
271     d.fuzzyData = app.data;
272     gen.data[Code.Kind.fuzzy] = d;
273 }
274 
275 void postProcess(Controller ctrl, Transform transf, Parameter params,
276         Product prods, ref GeneratedData gen) {
277     import std.array : appender;
278     import cpptooling.generator.includes : convToIncludeGuard, makeHeader;
279     import dextool.type : FileName, DextoolVersion, CustomHeader, WriteStrategy;
280 
281     static auto outputHdr(CppModule hdr, FileName fname, DextoolVersion ver,
282             CustomHeader custom_hdr) {
283         auto o = CppHModule(convToIncludeGuard(fname));
284         o.header.append(makeHeader(fname, ver, custom_hdr));
285         o.content.append(hdr);
286 
287         return o;
288     }
289 
290     static auto output(CppModule code, FileName incl_fname, FileName dest,
291             DextoolVersion ver, CustomHeader custom_hdr) {
292         import std.path : baseName;
293 
294         auto o = new CppModule;
295         o.suppressIndent(1);
296         o.append(makeHeader(dest, ver, custom_hdr));
297 
298         o.include(incl_fname.baseName);
299         o.sep(2);
300 
301         o.append(code);
302 
303         return o;
304     }
305 
306     static auto outputCase(CppModule code, FileName dest, DextoolVersion ver,
307             CustomHeader custom_hdr) {
308         import std.path : baseName;
309 
310         auto o = new CppModule;
311         o.suppressIndent(1);
312         o.append(makeHeader(dest, ver, custom_hdr));
313         o.append(code);
314 
315         return o;
316     }
317 
318     static auto outputMain(CppModule code, FileName dest, DextoolVersion ver,
319             CustomHeader custom_hdr) {
320         import std.path : baseName;
321 
322         auto o = new CppModule;
323         o.suppressIndent(1);
324         o.append(makeHeader(dest, ver, custom_hdr));
325         o.append(code);
326 
327         return o;
328     }
329 
330     immutable file_main = "main";
331     immutable file_fuzzy_data = "dextool_rawdata";
332     immutable file_config_tmpl = "dextool_config";
333 
334     foreach (k, v; gen.data) {
335         final switch (k) with (Code) {
336         case Kind.main:
337             auto fname = transf.createImplFile(file_main);
338             prods.putFile(fname, outputMain(v.cpp, fname,
339                     params.getToolVersion, params.getCustomHeader));
340             break;
341         case Kind.fuzzy:
342             auto fname = transf.createFuzzyDataFile(file_fuzzy_data);
343             prods.putFile(fname, v.fuzzyData);
344             break;
345         case Kind.configTemplate:
346             auto fname = transf.createXmlConfigFile(file_config_tmpl);
347             auto app = appender!string();
348             gen.templateConfig.put(app);
349             prods.putFile(fname, app.data, WriteStrategy.skip);
350             break;
351         }
352     }
353 
354     foreach (fc; gen.fuzzCases) {
355         auto fname = transf.createFuzzCase(fc.filename, fc.testCaseId);
356         prods.putFile(fname, outputCase(fc.cpp, fname, params.getToolVersion,
357                 params.getCustomHeader), WriteStrategy.skip);
358     }
359 }