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 }