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.raw_args; 11 12 import std.typecons : Nullable; 13 14 import logger = std.experimental.logger; 15 16 import dextool.type : FileName; 17 18 import dextool.plugin.fuzzer.type : FullyQualifiedNameType, Param, Symbol, Fuzz, 19 SequenceId; 20 21 struct RawConfiguration { 22 import std.getopt : getopt, GetoptResult; 23 24 Nullable!XmlConfig xmlConfig; 25 26 string[] fileExclude; 27 string[] fileRestrict; 28 string[] testDoubleInclude; 29 string[] inFiles; 30 string[] cflags; 31 string[] compileDb; 32 string header; 33 string headerFile; 34 string mainFileName = "intercept"; 35 string stripInclude; 36 string out_; 37 string config; 38 bool help; 39 bool helpRegex; 40 bool shortPluginHelp; 41 42 private GetoptResult help_info; 43 44 void parse(string[] args) { 45 static import std.getopt; 46 47 try { 48 // dfmt off 49 help_info = getopt(args, std.getopt.config.keepEndOfOptions, 50 "compile-db", "Retrieve compilation parameters from the file", &compileDb, 51 "config", "Use configuration file", &config, 52 "file-exclude", "Exclude files from generation matching the regex", &fileExclude, 53 "file-restrict", "Restrict the scope of the test double to those files matching the regex", &fileRestrict, 54 "header", "Prepend generated files with the string", &header, 55 "header-file", "Prepend generated files with the header read from the file", &headerFile, 56 "help-regex", "Extended help for regex's used as parameters", &helpRegex, 57 "in", "Input file to parse (at least one)", &inFiles, 58 "main-fname", "Used as part of filename for generated files [default: intercept]", &mainFileName, 59 "out", "directory for generated files [default: ./]", &out_, 60 "short-plugin-help", "short description of the plugin", &shortPluginHelp, 61 "strip-incl", "A regex used to strip the include paths", &stripInclude, 62 "td-include", "User supplied includes used instead of those found", &testDoubleInclude, 63 ); 64 // dfmt on 65 help = help_info.helpWanted; 66 } 67 catch (std.getopt.GetOptException ex) { 68 logger.error(ex.msg); 69 help = true; 70 } 71 72 if (helpRegex) 73 help = true; 74 75 // default arguments 76 if (stripInclude.length == 0) { 77 stripInclude = r".*/(.*)"; 78 logger.trace("--strip-incl: using default regex to strip include path (basename)"); 79 } 80 81 if (config.length != 0) { 82 xmlConfig = readRawConfig(FileName(config)); 83 if (xmlConfig.isNull) { 84 help = true; 85 } else { 86 debug logger.trace(xmlConfig.get); 87 } 88 } 89 90 import std.algorithm : find; 91 import std.array : array; 92 import std.range : drop; 93 94 // at this point args contain "what is left". What is interesting then is those after "--". 95 cflags = args.find("--").drop(1).array(); 96 } 97 98 void printHelp() { 99 import std.getopt : defaultGetoptPrinter; 100 import std.stdio : writeln; 101 102 defaultGetoptPrinter("Usage: dextool fuzzer [options] [--in=] [-- CFLAGS...]", 103 help_info.options); 104 105 if (helpRegex) { 106 writeln(" 107 REGEX 108 The regex syntax is found at http://dlang.org/phobos/std_regex.html 109 110 Information about --strip-incl. 111 Default regexp is: .*/(.*) 112 113 To allow the user to selectively extract parts of the include path dextool 114 applies the regex and then concatenates all the matcher groups found. It is 115 turned into the replacement include path. 116 117 Important to remember then is that this approach requires that at least one 118 matcher group exists. 119 120 Information about --file-exclude. 121 The regex must fully match the filename the AST node is located in. 122 If it matches all data from the file is excluded from the generated code. 123 124 Information about --file-restrict. 125 The regex must fully match the filename the AST node is located in. 126 Only symbols from files matching the restrict affect the generated test double. 127 "); 128 } 129 } 130 } 131 132 /// Symbols with range data. 133 @safe struct Symbols { 134 Symbol[FullyQualifiedNameType] syms; 135 alias syms this; 136 137 void put(Symbol data) { 138 syms[data.fullyQualifiedName] = data; 139 } 140 141 Symbol* lookup(FullyQualifiedNameType fqn) { 142 return fqn in syms; 143 } 144 } 145 146 /** Extracted configuration data from an XML file. 147 * 148 * It is not inteded to be used as is but rather further processed. 149 */ 150 struct XmlConfig { 151 import dextool.type : DextoolVersion, RawCliArguments, FilterClangFlag; 152 153 DextoolVersion version_; 154 int skipCompilerArgs; 155 RawCliArguments command; 156 FilterClangFlag[] filterClangFlags; 157 158 /// Symbols to intercept. 159 Symbols symbols; 160 } 161 162 void logUnknownXml(T)(ref T xml, string suggestion = null) { 163 import std.xml; 164 165 xml.onStartTag[null] = (ElementParser ep) { 166 logger.warning("Unknown xml element in config: ", ep.tag.toString); 167 logger.warningf(suggestion.length != 0, " did you mean '%s'", suggestion); 168 }; 169 } 170 171 auto parseRawConfig(T)(T xml) @trusted { 172 import std.conv : to, ConvException; 173 import std.xml; 174 import dextool.utility : DextoolVersion; 175 import dextool.type : RawCliArguments, FilterClangFlag; 176 177 DextoolVersion version_; 178 int skip_flags = 1; 179 RawCliArguments command; 180 FilterClangFlag[] filter_clang_flags; 181 Symbols syms; 182 183 if (auto tag = "version" in xml.tag.attr) { 184 version_ = *tag; 185 } 186 187 // dfmt off 188 xml.onStartTag["compiler_flag_filter"] = (ElementParser filter_flags) { 189 if (auto tag = "skip_compiler_args" in xml.tag.attr) { 190 try { 191 skip_flags = (*tag).to!int; 192 } 193 catch (ConvException ex) { 194 logger.info(ex.msg); 195 logger.info(" using fallback '1'"); 196 } 197 } 198 199 xml.onEndTag["exclude"] = (const Element e) { filter_clang_flags ~= FilterClangFlag(e.text()); }; 200 }; 201 xml.onStartTag["symbols"] = (ElementParser symbols_tag) { 202 symbols_tag.onStartTag["symbol"] = (ElementParser sym_tag) { 203 Symbol sym; 204 205 if (auto v = "name" in sym_tag.tag.attr) { 206 sym.fullyQualifiedName = FullyQualifiedNameType(*v); 207 } else { 208 logger.warningf("xml-config: missing the attribute name for symbol '%s'", sym_tag.tag); 209 return; 210 } 211 if (auto v = "filter" in sym_tag.tag.attr) { 212 if (*v == "exclude") 213 sym.filter = Symbol.FilterKind.exclude; 214 } 215 if (auto v = "id" in sym_tag.tag.attr) { 216 try { 217 sym.sequenceId = SequenceId(true, to!ulong(*v)); 218 } catch (ConvException ex) { 219 logger.warning("Invalid sequence id (%s) for symbol '%s': ", *v, sym_tag.tag); 220 } 221 } 222 223 sym_tag.onStartTag["fuzz"] = (ElementParser e) { 224 Fuzz fuzz; 225 if (auto v = "use" in e.tag.attr) { 226 fuzz.use = FullyQualifiedNameType(*v); 227 } 228 if (auto v = "include" in e.tag.attr) { 229 fuzz.include = FileName(*v); 230 } 231 232 sym.fuzz = fuzz; 233 e.logUnknownXml; 234 }; 235 236 sym_tag.onStartTag["param"] = (ElementParser param_tag) { 237 Param p; 238 239 if (auto v = "name" in param_tag.tag.attr) { 240 p.identifier = Param.Identifier(*v); 241 } 242 243 param_tag.onStartTag["valid"] = (ElementParser e) { 244 if (auto v = "check" in e.tag.attr) { 245 p.check = Param.Check(*v); 246 } 247 if (auto v = "condition" in e.tag.attr) { 248 p.condition = Param.Condition(*v); 249 } 250 251 e.logUnknownXml; 252 }; 253 254 param_tag.onStartTag["fuzz"] = (ElementParser e) { 255 Fuzz fuzz; 256 if (auto v = "use" in e.tag.attr) { 257 fuzz.use = FullyQualifiedNameType(*v); 258 } 259 if (auto v = "include" in e.tag.attr) { 260 fuzz.include = FileName(*v); 261 } 262 if (auto v = "param" in e.tag.attr) { 263 fuzz.param = *v; 264 } 265 266 p.fuzz = fuzz; 267 e.logUnknownXml; 268 }; 269 270 param_tag.logUnknownXml("param"); 271 param_tag.parse; 272 273 sym.limits ~= p; 274 }; 275 276 sym_tag.logUnknownXml("symbol"); 277 sym_tag.parse; 278 syms.put(sym); 279 }; 280 symbols_tag.logUnknownXml("symbols"); 281 symbols_tag.parse; 282 }; 283 // dfmt on 284 xml.parse(); 285 286 return XmlConfig(version_, skip_flags, command, filter_clang_flags, syms); 287 } 288 289 static import dextool.xml; 290 291 alias readRawConfig = dextool.xml.readRawConfig!(XmlConfig, parseRawConfig);