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