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