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