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