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