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.ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly); 36 this.ctrl = ctrl; 37 this.params = params; 38 this.products = products; 39 this.transf = transf; 40 this.gen_code_includes = GenericTestDoubleIncludes!Language(strip_incl); 41 } 42 43 /// Frontend signals backend that all files have been processed. 44 void finalizeIncludes() { 45 gen_code_includes.finalize(); 46 } 47 48 void putLocation(Path fname, LocationType type, Language lang) @safe { 49 gen_code_includes.put(fname, type, lang); 50 } 51 52 ExitStatusType analyzeFile(const AbsolutePath analyze_file, const string[] use_cflags) { 53 import std.typecons : NullableRef, scoped; 54 import dextool.utility : analyzeFile; 55 import cpptooling.data : MergeMode; 56 57 NullableRef!Container cont_ = &container; 58 auto visitor = new TUVisitor(cont_); 59 60 if (analyzeFile(analyze_file, use_cflags, visitor, ctx) == ExitStatusType.Errors) { 61 return ExitStatusType.Errors; 62 } 63 64 debug logger.tracef("%u", visitor.root); 65 66 // dfmt off 67 auto filtered = rawFilter(visitor.root, ctrl, products, 68 (Path a, LocationType k, Language l) @safe => this.putLocation(a, k, l), 69 (Nullable!USRType usr) @safe => container.find!LocationTag(usr.get)); 70 // dfmt on 71 72 analyze.root.merge(filtered, MergeMode.full); 73 74 gen_code_includes.process(); 75 76 return ExitStatusType.Ok; 77 } 78 79 void process(SymbolsT)(ref SymbolsT syms, ref CompileCommandFilter compiler_flag_filter) { 80 import std.algorithm : map; 81 import std.array : array; 82 83 debug { 84 logger.trace(container.toString); 85 logger.tracef("Filtered:\n%u", analyze.root); 86 } 87 88 auto impl_data = translate(analyze.root, syms, container); 89 analyze = AnalyzeData.init; 90 91 debug { 92 logger.tracef("Translated to implementation:\n%u", impl_data.root); 93 logger.tracef("Symbol data:\n%(%s:%s\n%)", impl_data.symbols); 94 } 95 96 generate(impl_data, compiler_flag_filter, gen_data, 97 gen_code_includes.includesWithPayload.array(), container); 98 99 postProcess(ctrl, transf, params, products, gen_data); 100 } 101 102 private: 103 ClangContext ctx; 104 Container container; 105 AnalyzeData analyze; 106 GeneratedData gen_data; 107 108 Controller ctrl; 109 Parameter params; 110 Product products; 111 Transform transf; 112 113 GenericTestDoubleIncludes!Language gen_code_includes; 114 } 115 116 private: 117 @safe: 118 119 import cpptooling.data : CppRoot, CFunction, USRType, CxParam, CppVariable, CppNs, Language; 120 import cpptooling.data.symbol : Container; 121 import dextool.plugin.fuzzer.type : Param, Symbol; 122 123 import dsrcgen.cpp : CppModule, CppHModule; 124 125 /** Filter the raw IR according to the users desire. 126 * 127 * Params: 128 * ctrl = removes according to directives via ctrl 129 * prod = put locations of symbols that pass filtering 130 */ 131 AnalyzeData rawFilter(PutLocT, LookupT)(AnalyzeData input, Controller ctrl, 132 Product prod, PutLocT putLoc, LookupT lookup) { 133 import std.algorithm : each, filter; 134 import std.range : tee; 135 import dextool.type : Path; 136 import cpptooling.data : StorageClass; 137 import cpptooling.generator.utility : filterAnyLocation; 138 import cpptooling.testdouble.header_filter : LocationType; 139 140 if (ctrl.doSymbolAtLocation(input.fileOfTranslationUnit, input.fileOfTranslationUnit)) { 141 putLoc(input.fileOfTranslationUnit, LocationType.Root, input.languageOfTranslationUnit); 142 } 143 144 AnalyzeData filtered; 145 146 // dfmt off 147 input.funcRange 148 .filter!(a => !a.usr.isNull) 149 // by definition static functions can't be replaced by test doubles 150 .filter!(a => a.storageClass != StorageClass.Static) 151 // ask controller if the symbol should be intercepted 152 .filter!(a => ctrl.doSymbol(a.name)) 153 // ask controller if to generate wrapper for the function based on file location 154 .filterAnyLocation!(a => ctrl.doSymbolAtLocation(a.location.file, a.value.name))(lookup) 155 // pass on location as a product to be used to calculate #include 156 .tee!(a => putLoc(Path(a.location.file), LocationType.Leaf, a.value.language.get)) 157 .each!(a => filtered.put(a.value)); 158 // dfmt on 159 160 return filtered; 161 } 162 163 /** Structurally translate the input to a fuzz wrapper. 164 * 165 * Add/transform/remove constructs from the filtered data to make it suitable 166 * to generate the fuzz wrapper. 167 */ 168 ImplData translate(SymbolsT)(CppRoot root, ref SymbolsT symbols, ref Container container) @trusted { 169 import std.algorithm : map, filter; 170 import dextool.plugin.fuzzer.backend.unique_sequence : Sequence; 171 import dextool.plugin.fuzzer.type : Symbol, FullyQualifiedNameType, Param; 172 173 Sequence!ulong test_case_seq_generator; 174 ImplData impl; 175 176 void updateSeq(ref Symbol sym) { 177 Symbol s = sym; 178 test_case_seq_generator.putOrAdjust(s.sequenceId.payload); 179 s.sequenceId.isValid = true; 180 impl.symbols[sym.fullyQualifiedName] = s; 181 impl.symbolId[sym.fullyQualifiedName] = s.sequenceId.payload; 182 } 183 184 // prepare the sequence number generator with all the used valid seq. nums. 185 foreach (sym; symbols.byKeyValue 186 .map!(a => a.value) 187 .filter!(a => a.filter == Symbol.FilterKind.keep && a.sequenceId.isValid)) { 188 updateSeq(sym); 189 } 190 191 // assign new, valid seq. nums for those symbols that had an invalid sequence. 192 foreach (sym; symbols.byKeyValue 193 .map!(a => a.value) 194 .filter!(a => a.filter == Symbol.FilterKind.keep && !a.sequenceId.isValid)) { 195 updateSeq(sym); 196 } 197 198 // prepare generation of the xml data from the excluded symbols. 199 foreach (sym; symbols.byKeyValue 200 .map!(a => a.value) 201 .filter!(a => a.filter == Symbol.FilterKind.exclude)) { 202 impl.excludedSymbols ~= IgnoreSymbol(sym.fullyQualifiedName); 203 } 204 205 // assign new numbers to those functions that are newly discovered. 206 foreach (f; root.funcRange) { 207 impl.root.put(f); 208 209 if (FullyQualifiedNameType(f.name) in impl.symbolId) { 210 // do nothing 211 } else { 212 ulong id; 213 test_case_seq_generator.putOrAdjust(id); 214 impl.symbolId[FullyQualifiedNameType(f.name)] = id; 215 } 216 } 217 218 return impl; 219 } 220 221 /** Translate the structure to code. 222 * 223 * Keep it simple. Translate should have prepared the data to make this step 224 * easy. 225 * */ 226 void generate(IncludeT)(ref ImplData impl, ref CompileCommandFilter compiler_flag_filter, 227 ref GeneratedData gen, IncludeT[] target_includes, ref const Container container) { 228 import dextool.plugin.fuzzer.backend.generate_cpp; 229 import dextool.plugin.fuzzer.backend.generate_xml; 230 231 // code 232 gen.make(Code.Kind.configTemplate); 233 generateMain(impl, gen.make(Code.Kind.main).cpp); 234 generateFuzzCases(impl, target_includes, gen); 235 236 // config 237 generateConfigPerFunction(impl, gen.templateConfig); 238 generateConfigCompilerFilter(compiler_flag_filter, gen.templateConfig); 239 240 // fuzz binary data 241 generateFuzzyInput(impl, gen); 242 } 243 244 /// Generate a initial data file for the fuzzer based on static analysis. 245 void generateFuzzyInput(ref ImplData impl, ref GeneratedData gen) { 246 import std.range : repeat; 247 import std.array : array, appender; 248 import cpptooling.data : unpackParam; 249 250 auto d = gen.make(Code.Kind.fuzzy); 251 252 auto app = appender!(ubyte[])(); 253 254 foreach (f; impl.root.funcRange) { 255 auto plen = f.paramRange.length; 256 if (f.paramRange.length == 1 && f.paramRange[0].unpackParam.isVariadic) { 257 plen -= 1; 258 } 259 260 // assume that each parameter is at least 4 bytes of fuzzy data 261 app.put(repeat(ubyte(0), 4 * plen).array()); 262 } 263 264 // must not be zero or AFL do not consider it as input 265 if (app.data.length == 0) { 266 app.put(ubyte(0)); 267 } 268 269 d.fuzzyData = app.data; 270 gen.data[Code.Kind.fuzzy] = d; 271 } 272 273 void postProcess(Controller ctrl, Transform transf, Parameter params, 274 Product prods, ref GeneratedData gen) { 275 import std.array : appender; 276 import cpptooling.generator.includes : convToIncludeGuard, makeHeader; 277 import cpptooling.type : CustomHeader; 278 import dextool.io : WriteStrategy; 279 import dextool.type : Path, DextoolVersion; 280 281 static auto outputHdr(CppModule hdr, Path fname, DextoolVersion ver, CustomHeader custom_hdr) { 282 auto o = CppHModule(convToIncludeGuard(fname)); 283 o.header.append(makeHeader(fname, ver, custom_hdr)); 284 o.content.append(hdr); 285 286 return o; 287 } 288 289 static auto output(CppModule code, Path incl_fname, Path dest, 290 DextoolVersion ver, CustomHeader custom_hdr) { 291 import std.path : baseName; 292 293 auto o = new CppModule; 294 o.suppressIndent(1); 295 o.append(makeHeader(dest, ver, custom_hdr)); 296 297 o.include(incl_fname.baseName); 298 o.sep(2); 299 300 o.append(code); 301 302 return o; 303 } 304 305 static auto outputCase(CppModule code, Path dest, DextoolVersion ver, CustomHeader custom_hdr) { 306 import std.path : baseName; 307 308 auto o = new CppModule; 309 o.suppressIndent(1); 310 o.append(makeHeader(dest, ver, custom_hdr)); 311 o.append(code); 312 313 return o; 314 } 315 316 static auto outputMain(CppModule code, Path dest, DextoolVersion ver, CustomHeader custom_hdr) { 317 import std.path : baseName; 318 319 auto o = new CppModule; 320 o.suppressIndent(1); 321 o.append(makeHeader(dest, ver, custom_hdr)); 322 o.append(code); 323 324 return o; 325 } 326 327 immutable file_main = "main"; 328 immutable file_fuzzy_data = "dextool_rawdata"; 329 immutable file_config_tmpl = "dextool_config"; 330 331 foreach (k, v; gen.data) { 332 final switch (k) with (Code) { 333 case Kind.main: 334 auto fname = transf.createImplFile(file_main); 335 prods.putFile(fname, outputMain(v.cpp, fname, 336 params.getToolVersion, params.getCustomHeader)); 337 break; 338 case Kind.fuzzy: 339 auto fname = transf.createFuzzyDataFile(file_fuzzy_data); 340 prods.putFile(fname, v.fuzzyData); 341 break; 342 case Kind.configTemplate: 343 auto fname = transf.createXmlConfigFile(file_config_tmpl); 344 auto app = appender!string(); 345 gen.templateConfig.put(app); 346 prods.putFile(fname, app.data, WriteStrategy.skip); 347 break; 348 } 349 } 350 351 foreach (fc; gen.fuzzCases) { 352 auto fname = transf.createFuzzCase(fc.filename, fc.testCaseId); 353 prods.putFile(fname, outputCase(fc.cpp, fname, params.getToolVersion, 354 params.getCustomHeader), WriteStrategy.skip); 355 } 356 }