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