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 }