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.fuzzer.type; 24 25 import dextool.plugin.fuzzer.frontend.raw_args : RawConfiguration, XmlConfig, Symbols; 26 import dextool.plugin.fuzzer.backend.interface_ : Controller, Parameter, Product, Transform; 27 28 private struct FileData { 29 invariant { 30 // cant have data in both. 31 assert(str_data.length == 0 || raw_data.length == 0); 32 } 33 34 Path filename; 35 string str_data; 36 const(void)[] raw_data; 37 WriteStrategy strategy; 38 39 const(void)[] data() { 40 if (str_data.length != 0) { 41 return cast(void[]) str_data; 42 } else { 43 return raw_data; 44 } 45 } 46 } 47 48 class FuzzerFrontend : Controller, Parameter, Product, Transform { 49 import std.regex : regex, Regex; 50 import std.typecons : Flag; 51 import my.filter : ReFilter; 52 import dextool.compilation_db : CompileCommandFilter; 53 import dextool.type : Path; 54 import cpptooling.testdouble.header_filter : TestDoubleIncludes, LocationType; 55 import dsrcgen.cpp : CppModule, CppHModule; 56 57 private { 58 static const hdrExt = ".hpp"; 59 static const implExt = ".cpp"; 60 static const xmlExt = ".xml"; 61 static const rawExt = ".bin"; 62 63 CustomHeader custom_hdr; 64 65 /// Output directory to generate data in such as code. 66 Path output_dir; 67 68 /// Used to match symbols by their location. 69 string[] exclude; 70 string[] include; 71 ReFilter fileFilter; 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 .argFileInclude(args.fileInclude) 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 this.exclude = a; 96 fileFilter = ReFilter(include, exclude); 97 return this; 98 } 99 100 auto argFileInclude(string[] a) { 101 this.include = a; 102 fileFilter = ReFilter(include, exclude); 103 return this; 104 } 105 106 /// Ensure that the relevant information from the xml file is extracted. 107 auto argXmlConfig(Nullable!XmlConfig conf) { 108 import dextool.compilation_db : defaultCompilerFlagFilter; 109 110 if (conf.isNull) { 111 compiler_flag_filter = CompileCommandFilter(defaultCompilerFlagFilter, 0); 112 return this; 113 } 114 115 compiler_flag_filter = CompileCommandFilter(conf.get.filterClangFlags, 116 conf.get.skipCompilerArgs); 117 symbols = conf.get.symbols; 118 119 return this; 120 } 121 122 ref CompileCommandFilter getCompileCommandFilter() { 123 return compiler_flag_filter; 124 } 125 126 /// Data produced by the generatore intented to be written to specified file. 127 ref FileData[] getProducedFiles() { 128 return file_data; 129 } 130 131 ref Symbols getSymbols() { 132 return symbols; 133 } 134 135 // -- Controller -- 136 137 @safe bool doSymbolAtLocation(const string filename, const string symbol) { 138 return fileFilter.match(filename, (string s, string type) { 139 logger.tracef("matcher --file-%s removed %s for symbol %s. Skipping", s, symbol, type); 140 }); 141 } 142 143 bool doSymbol(string symbol) { 144 if (auto sym = symbols.lookup(FullyQualifiedNameType(symbol))) { 145 if (sym.filter == Symbol.FilterKind.exclude) { 146 return false; 147 } 148 } 149 150 return true; 151 } 152 153 // -- Parameters -- 154 155 DextoolVersion getToolVersion() { 156 import dextool.utility : dextoolVersion; 157 158 return dextoolVersion; 159 } 160 161 CustomHeader getCustomHeader() { 162 return custom_hdr; 163 } 164 165 // -- Products -- 166 167 void putFile(Path fname, CppHModule hdr_data) { 168 file_data ~= FileData(fname, hdr_data.render()); 169 } 170 171 void putFile(Path fname, CppModule impl_data, WriteStrategy strategy = WriteStrategy.overwrite) { 172 file_data ~= FileData(fname, impl_data.render(), null, strategy); 173 } 174 175 void putFile(Path fname, const(ubyte)[] raw_data) { 176 file_data ~= FileData(fname, null, raw_data); 177 } 178 179 void putFile(Path fname, string raw_data, WriteStrategy strategy = WriteStrategy.overwrite) { 180 file_data ~= FileData(fname, null, raw_data, strategy); 181 } 182 183 // -- Transform -- 184 Path createHeaderFile(string name) { 185 import std.path : buildPath; 186 187 return Path(buildPath(output_dir, name ~ hdrExt)); 188 } 189 190 Path createImplFile(string name) { 191 import std.path : buildPath; 192 193 return Path(buildPath(output_dir, name ~ implExt)); 194 } 195 196 Path createFuzzCase(string name, ulong id) { 197 import std.conv : to; 198 import std.path : buildPath; 199 200 return Path(buildPath(output_dir, name ~ id.to!string ~ implExt)); 201 } 202 203 Path createFuzzyDataFile(string name) { 204 import std.path : buildPath; 205 206 return Path(buildPath(output_dir, "test_case", name ~ rawExt)); 207 } 208 209 // try the darnest to not overwrite an existing config. 210 Path createXmlConfigFile(string name) { 211 import std.conv : to; 212 import std.path : buildPath; 213 import std.file : exists; 214 215 string p = buildPath(output_dir, name ~ xmlExt); 216 217 for (int i = 0; exists(p); ++i) { 218 p = buildPath(output_dir, name ~ i.to!string() ~ xmlExt); 219 } 220 221 return Path(p); 222 } 223 } 224 225 auto genFuzzer(FuzzerFrontend frontend, in string[] userCflags, 226 CompileCommandDB compile_db, Path[] inFiles, Regex!char strip_incl) { 227 import std.algorithm : map; 228 import std.array : array; 229 230 import dextool.clang : reduceMissingFiles; 231 import dextool.compilation_db : limitOrAllRange, parse, prependFlags, 232 addCompiler, replaceCompiler, addSystemIncludes, fileRange; 233 import dextool.io : writeFileData; 234 import dextool.plugin.fuzzer.backend.backend : Backend; 235 import dextool.utility : prependDefaultFlags, PreferLang; 236 237 auto backend = Backend(frontend, frontend, frontend, frontend, strip_incl); 238 239 auto compDbRange() { 240 if (compile_db.empty) { 241 return fileRange(inFiles, Compiler("/usr/bin/c++")); 242 } 243 return compile_db.fileRange; 244 } 245 246 auto fixedDb = compDbRange.parse(frontend.getCompileCommandFilter).addCompiler(Compiler("/usr/bin/c++")) 247 .addSystemIncludes.prependFlags(prependDefaultFlags(userCflags, PreferLang.none)).array; 248 249 auto limitRange = limitOrAllRange(fixedDb, inFiles.map!(a => cast(string) a).array) 250 .reduceMissingFiles(fixedDb); 251 252 if (!compile_db.empty && !limitRange.isMissingFilesEmpty) { 253 foreach (a; limitRange.missingFiles) { 254 logger.error("Unable to find any compiler flags for .", a); 255 } 256 return ExitStatusType.Errors; 257 } 258 259 foreach (pdata; limitRange.range) { 260 if (backend.analyzeFile(pdata.cmd.absoluteFile, 261 pdata.flags.completeFlags) == ExitStatusType.Errors) { 262 return ExitStatusType.Errors; 263 } 264 } 265 266 backend.finalizeIncludes; 267 268 // Analyse and generate interceptors 269 backend.process(frontend.getSymbols, frontend.getCompileCommandFilter); 270 271 return writeFileData(frontend.getProducedFiles); 272 }