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.frontend.frontend; 11 12 import std.regex : Regex; 13 import std.typecons : Nullable; 14 15 import logger = std.experimental.logger; 16 17 import dextool.compilation_db; 18 import dextool.type; 19 20 import dextool.plugin.types; 21 22 import dextool.plugin.fuzzer.type; 23 24 import dextool.plugin.fuzzer.frontend.raw_args : RawConfiguration, XmlConfig, 25 Symbols; 26 import dextool.plugin.fuzzer.backend.interface_ : Controller, Parameter, 27 Product, Transform; 28 29 private struct FileData { 30 import dextool.type : FileName, WriteStrategy; 31 32 invariant { 33 // cant have data in both. 34 assert(str_data.length == 0 || raw_data.length == 0); 35 } 36 37 FileName filename; 38 string str_data; 39 const(void)[] raw_data; 40 WriteStrategy strategy; 41 42 const(void)[] data() { 43 if (str_data.length != 0) { 44 return cast(void[]) str_data; 45 } else { 46 return raw_data; 47 } 48 } 49 } 50 51 class FuzzerFrontend : Controller, Parameter, Product, Transform { 52 import std.regex : regex, Regex; 53 import std.typecons : Flag; 54 import dextool.compilation_db : CompileCommandFilter; 55 import dextool.type : FileName; 56 import cpptooling.testdouble.header_filter : TestDoubleIncludes, 57 LocationType; 58 import dsrcgen.cpp : CppModule, CppHModule; 59 60 private { 61 static const hdrExt = ".hpp"; 62 static const implExt = ".cpp"; 63 static const xmlExt = ".xml"; 64 static const rawExt = ".bin"; 65 66 CustomHeader custom_hdr; 67 68 /// Output directory to generate data in such as code. 69 DirName output_dir; 70 71 /// Used to match symbols by their location. 72 Regex!char[] exclude; 73 Regex!char[] restrict; 74 75 /// Data produced by the generatore intented to be written to specified file. 76 FileData[] file_data; 77 78 CompileCommandFilter compiler_flag_filter; 79 Symbols symbols; 80 } 81 82 static auto make(ref RawConfiguration args) { 83 // dfmt off 84 auto r = new FuzzerFrontend(DirName(args.out_)) 85 .argFileExclude(args.fileExclude) 86 .argFileRestrict(args.fileRestrict) 87 .argXmlConfig(args.xmlConfig); 88 // dfmt on 89 return r; 90 } 91 92 this(DirName output_dir) { 93 this.output_dir = output_dir; 94 } 95 96 auto argFileExclude(string[] a) { 97 import std.array : array; 98 import std.algorithm : map; 99 100 this.exclude = a.map!(a => regex(a)).array(); 101 return this; 102 } 103 104 auto argFileRestrict(string[] a) { 105 import std.array : array; 106 import std.algorithm : map; 107 108 this.restrict = a.map!(a => regex(a)).array(); 109 return this; 110 } 111 112 /// Ensure that the relevant information from the xml file is extracted. 113 auto argXmlConfig(Nullable!XmlConfig conf) { 114 import dextool.compilation_db : defaultCompilerFlagFilter; 115 116 if (conf.isNull) { 117 compiler_flag_filter = CompileCommandFilter(defaultCompilerFlagFilter, 1); 118 return this; 119 } 120 121 compiler_flag_filter = CompileCommandFilter(conf.filterClangFlags, conf.skipCompilerArgs); 122 symbols = conf.symbols; 123 124 return this; 125 } 126 127 ref CompileCommandFilter getCompileCommandFilter() { 128 return compiler_flag_filter; 129 } 130 131 /// Data produced by the generatore intented to be written to specified file. 132 ref FileData[] getProducedFiles() { 133 return file_data; 134 } 135 136 ref Symbols getSymbols() { 137 return symbols; 138 } 139 140 // -- Controller -- 141 142 @safe bool doSymbolAtLocation(const string filename, const string symbol) { 143 import dextool.plugin.regex_matchers : matchAny; 144 145 // if there are no filter registered then it automatically passes. 146 147 bool restrict_pass = restrict.length == 0 || matchAny(filename, restrict); 148 debug logger.tracef(!restrict_pass, 149 "--file-restrict skipping: %s in %s", symbol, filename); 150 151 bool exclude_pass = exclude.length == 0 || !matchAny(filename, exclude); 152 debug logger.tracef(!exclude_pass, "--file-exclude skipping: %s in %s", symbol, filename); 153 154 return restrict_pass && exclude_pass; 155 } 156 157 bool doSymbol(string symbol) { 158 if (auto sym = symbols.lookup(FullyQualifiedNameType(symbol))) { 159 if (sym.filter == Symbol.FilterKind.exclude) { 160 return false; 161 } 162 } 163 164 return true; 165 } 166 167 // -- Parameters -- 168 169 DextoolVersion getToolVersion() { 170 import dextool.utility : dextoolVersion; 171 172 return dextoolVersion; 173 } 174 175 CustomHeader getCustomHeader() { 176 return custom_hdr; 177 } 178 179 // -- Products -- 180 181 void putFile(FileName fname, CppHModule hdr_data) { 182 file_data ~= FileData(fname, hdr_data.render()); 183 } 184 185 void putFile(FileName fname, CppModule impl_data, 186 WriteStrategy strategy = WriteStrategy.overwrite) { 187 file_data ~= FileData(fname, impl_data.render(), null, strategy); 188 } 189 190 void putFile(FileName fname, const(ubyte)[] raw_data) { 191 file_data ~= FileData(fname, null, raw_data); 192 } 193 194 void putFile(FileName fname, string raw_data, WriteStrategy strategy = WriteStrategy.overwrite) { 195 file_data ~= FileData(fname, null, raw_data, strategy); 196 } 197 198 // -- Transform -- 199 FileName createHeaderFile(string name) { 200 import std.path : buildPath; 201 202 return FileName(buildPath(output_dir, name ~ hdrExt)); 203 } 204 205 FileName createImplFile(string name) { 206 import std.path : buildPath; 207 208 return FileName(buildPath(output_dir, name ~ implExt)); 209 } 210 211 FileName createFuzzCase(string name, ulong id) { 212 import std.conv : to; 213 import std.path : buildPath; 214 215 return FileName(buildPath(output_dir, name ~ id.to!string ~ implExt)); 216 } 217 218 FileName createFuzzyDataFile(string name) { 219 import std.path : buildPath; 220 221 return FileName(buildPath(output_dir, "test_case", name ~ rawExt)); 222 } 223 224 // try the darnest to not overwrite an existing config. 225 FileName createXmlConfigFile(string name) { 226 import std.conv : to; 227 import std.path : buildPath; 228 import std.file : exists; 229 230 string p = buildPath(output_dir, name ~ xmlExt); 231 232 for (int i = 0; exists(p); ++i) { 233 p = buildPath(output_dir, name ~ i.to!string() ~ xmlExt); 234 } 235 236 return FileName(p); 237 } 238 } 239 240 auto genFuzzer(FuzzerFrontend frontend, in string[] in_cflags, 241 CompileCommandDB compile_db, InFiles in_files, Regex!char strip_incl) { 242 import dextool.io : writeFileData; 243 import dextool.plugin.fuzzer.backend.backend : Backend; 244 import dextool.utility : prependDefaultFlags, PreferLang; 245 246 const auto user_cflags = prependDefaultFlags(in_cflags, PreferLang.none); 247 const auto total_files = in_files.length; 248 auto backend = Backend(frontend, frontend, frontend, frontend, strip_incl); 249 250 foreach (idx, in_file; in_files) { 251 logger.infof("File %d/%d ", idx + 1, total_files); 252 string[] use_cflags; 253 AbsolutePath analyze_file; 254 255 if (compile_db.length > 0) { 256 auto db_search_result = compile_db.appendOrError(user_cflags, 257 in_file, frontend.getCompileCommandFilter); 258 if (db_search_result.isNull) { 259 return ExitStatusType.Errors; 260 } 261 use_cflags = db_search_result.get.cflags; 262 analyze_file = db_search_result.get.absoluteFile; 263 } else { 264 use_cflags = user_cflags.dup; 265 analyze_file = AbsolutePath(FileName(in_file)); 266 } 267 268 if (backend.analyzeFile(analyze_file, use_cflags) == ExitStatusType.Errors) { 269 return ExitStatusType.Errors; 270 } 271 } 272 273 backend.finalizeIncludes; 274 275 // Analyse and generate interceptors 276 backend.process(frontend.getSymbols, frontend.getCompileCommandFilter); 277 278 return writeFileData(frontend.getProducedFiles); 279 }