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 }