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 }