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