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