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 }