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                 (Nullable!USRType usr) @safe => container.find!LocationTag(usr.get));
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         .filter!(a => !a.usr.isNull)
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(Path(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 cpptooling.type : CustomHeader;
281     import dextool.io : WriteStrategy;
282     import dextool.type : Path, DextoolVersion;
283 
284     static auto outputHdr(CppModule hdr, Path fname, DextoolVersion ver, CustomHeader custom_hdr) {
285         auto o = CppHModule(convToIncludeGuard(fname));
286         o.header.append(makeHeader(fname, ver, custom_hdr));
287         o.content.append(hdr);
288 
289         return o;
290     }
291 
292     static auto output(CppModule code, Path incl_fname, Path dest,
293             DextoolVersion ver, CustomHeader custom_hdr) {
294         import std.path : baseName;
295 
296         auto o = new CppModule;
297         o.suppressIndent(1);
298         o.append(makeHeader(dest, ver, custom_hdr));
299 
300         o.include(incl_fname.baseName);
301         o.sep(2);
302 
303         o.append(code);
304 
305         return o;
306     }
307 
308     static auto outputCase(CppModule code, Path dest, DextoolVersion ver, 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, Path dest, DextoolVersion ver, 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 }