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.analyze = AnalyzeData.make();
36         this.ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
37         this.ctrl = ctrl;
38         this.params = params;
39         this.products = products;
40         this.transf = transf;
41         this.gen_code_includes = GenericTestDoubleIncludes!Language(strip_incl);
42     }
43 
44     /// Frontend signals backend that all files have been processed.
45     void finalizeIncludes() {
46         gen_code_includes.finalize();
47     }
48 
49     void putLocation(Path fname, LocationType type, Language lang) @safe {
50         gen_code_includes.put(fname, type, lang);
51     }
52 
53     ExitStatusType analyzeFile(const AbsolutePath analyze_file, const string[] use_cflags) {
54         import std.typecons : NullableRef, scoped;
55         import dextool.utility : analyzeFile;
56         import cpptooling.data : MergeMode;
57 
58         NullableRef!Container cont_ = &container;
59         auto visitor = new TUVisitor(cont_);
60 
61         if (analyzeFile(analyze_file, use_cflags, visitor, ctx) == ExitStatusType.Errors) {
62             return ExitStatusType.Errors;
63         }
64 
65         debug logger.tracef("%u", visitor.root);
66 
67         // dfmt off
68         auto filtered = rawFilter(visitor.root, ctrl, products,
69                 (Path a, LocationType k, Language l) @safe => this.putLocation(a, k, l),
70                 (USRType usr) @safe => container.find!LocationTag(usr));
71         // dfmt on
72 
73         analyze.get.root.merge(filtered, MergeMode.full);
74 
75         gen_code_includes.process();
76 
77         return ExitStatusType.Ok;
78     }
79 
80     void process(SymbolsT)(ref SymbolsT syms, ref CompileCommandFilter compiler_flag_filter) {
81         import std.algorithm : map;
82         import std.array : array;
83 
84         assert(!analyze.isNull);
85 
86         debug {
87             logger.trace(container.toString);
88             logger.tracef("Filtered:\n%u", analyze.get.root);
89         }
90 
91         auto impl_data = translate(analyze.get.root, syms, container);
92         analyze.nullify();
93 
94         debug {
95             logger.tracef("Translated to implementation:\n%u", impl_data.root);
96             logger.tracef("Symbol data:\n%(%s:%s\n%)", impl_data.symbols);
97         }
98 
99         generate(impl_data, compiler_flag_filter, gen_data,
100                 gen_code_includes.includesWithPayload.array(), container);
101 
102         postProcess(ctrl, transf, params, products, gen_data);
103     }
104 
105 private:
106     ClangContext ctx;
107     Container container;
108     Nullable!AnalyzeData analyze;
109     GeneratedData gen_data;
110 
111     Controller ctrl;
112     Parameter params;
113     Product products;
114     Transform transf;
115 
116     GenericTestDoubleIncludes!Language gen_code_includes;
117 }
118 
119 private:
120 @safe:
121 
122 import cpptooling.data : CppRoot, CFunction, USRType, CxParam, CppVariable, CppNs, Language;
123 import cpptooling.data.symbol : Container;
124 import dextool.plugin.fuzzer.type : Param, Symbol;
125 
126 import dsrcgen.cpp : CppModule, CppHModule;
127 
128 /** Filter the raw IR according to the users desire.
129  *
130  * Params:
131  *  ctrl = removes according to directives via ctrl
132  *  prod = put locations of symbols that pass filtering
133  */
134 AnalyzeData rawFilter(PutLocT, LookupT)(AnalyzeData input, Controller ctrl,
135         Product prod, PutLocT putLoc, LookupT lookup) {
136     import std.algorithm : each, filter;
137     import std.range : tee;
138     import dextool.type : Path;
139     import cpptooling.data : StorageClass;
140     import cpptooling.generator.utility : filterAnyLocation;
141     import cpptooling.testdouble.header_filter : LocationType;
142 
143     if (ctrl.doSymbolAtLocation(input.fileOfTranslationUnit, input.fileOfTranslationUnit)) {
144         putLoc(input.fileOfTranslationUnit, LocationType.Root, input.languageOfTranslationUnit);
145     }
146 
147     auto filtered = AnalyzeData.make;
148 
149     // dfmt off
150     input.funcRange
151         // by definition static functions can't be replaced by test doubles
152         .filter!(a => a.storageClass != StorageClass.Static)
153         // ask controller if the symbol should be intercepted
154         .filter!(a => ctrl.doSymbol(a.name))
155         // ask controller if to generate wrapper for the function based on file location
156         .filterAnyLocation!(a => ctrl.doSymbolAtLocation(a.location.file, a.value.name))(lookup)
157         // pass on location as a product to be used to calculate #include
158         .tee!(a => putLoc(Path(a.location.file), LocationType.Leaf, a.value.language.get))
159         .each!(a => filtered.put(a.value));
160     // dfmt on
161 
162     return filtered;
163 }
164 
165 /** Structurally translate the input to a fuzz wrapper.
166  *
167  * Add/transform/remove constructs from the filtered data to make it suitable
168  * to generate the fuzz wrapper.
169  */
170 ImplData translate(SymbolsT)(CppRoot root, ref SymbolsT symbols, ref Container container) @trusted {
171     import std.algorithm : map, filter;
172     import dextool.plugin.fuzzer.backend.unique_sequence : Sequence;
173     import dextool.plugin.fuzzer.type : Symbol, FullyQualifiedNameType, Param;
174 
175     Sequence!ulong test_case_seq_generator;
176     auto impl = ImplData.make();
177 
178     void updateSeq(ref Symbol sym) {
179         Symbol s = sym;
180         test_case_seq_generator.putOrAdjust(s.sequenceId.payload);
181         s.sequenceId.isValid = true;
182         impl.symbols[sym.fullyQualifiedName] = s;
183         impl.symbolId[sym.fullyQualifiedName] = s.sequenceId.payload;
184     }
185 
186     // prepare the sequence number generator with all the used valid seq. nums.
187     foreach (sym; symbols.byKeyValue
188             .map!(a => a.value)
189             .filter!(a => a.filter == Symbol.FilterKind.keep && a.sequenceId.isValid)) {
190         updateSeq(sym);
191     }
192 
193     // assign new, valid seq. nums for those symbols that had an invalid sequence.
194     foreach (sym; symbols.byKeyValue
195             .map!(a => a.value)
196             .filter!(a => a.filter == Symbol.FilterKind.keep && !a.sequenceId.isValid)) {
197         updateSeq(sym);
198     }
199 
200     // prepare generation of the xml data from the excluded symbols.
201     foreach (sym; symbols.byKeyValue
202             .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 cpptooling.type : CustomHeader;
280     import dextool.io : WriteStrategy;
281     import dextool.type : Path, DextoolVersion;
282 
283     static auto outputHdr(CppModule hdr, Path fname, DextoolVersion ver, 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, Path incl_fname, Path 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, Path dest, DextoolVersion ver, 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, Path dest, DextoolVersion ver, CustomHeader custom_hdr) {
319         import std.path : baseName;
320 
321         auto o = new CppModule;
322         o.suppressIndent(1);
323         o.append(makeHeader(dest, ver, custom_hdr));
324         o.append(code);
325 
326         return o;
327     }
328 
329     immutable file_main = "main";
330     immutable file_fuzzy_data = "dextool_rawdata";
331     immutable file_config_tmpl = "dextool_config";
332 
333     foreach (k, v; gen.data) {
334         final switch (k) with (Code) {
335         case Kind.main:
336             auto fname = transf.createImplFile(file_main);
337             prods.putFile(fname, outputMain(v.cpp, fname,
338                     params.getToolVersion, params.getCustomHeader));
339             break;
340         case Kind.fuzzy:
341             auto fname = transf.createFuzzyDataFile(file_fuzzy_data);
342             prods.putFile(fname, v.fuzzyData);
343             break;
344         case Kind.configTemplate:
345             auto fname = transf.createXmlConfigFile(file_config_tmpl);
346             auto app = appender!string();
347             gen.templateConfig.put(app);
348             prods.putFile(fname, app.data, WriteStrategy.skip);
349             break;
350         }
351     }
352 
353     foreach (fc; gen.fuzzCases) {
354         auto fname = transf.createFuzzCase(fc.filename, fc.testCaseId);
355         prods.putFile(fname, outputCase(fc.cpp, fname, params.getToolVersion,
356                 params.getCustomHeader), WriteStrategy.skip);
357     }
358 }