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);