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 This file contains the C++ code generators.
11 */
12 module dextool.plugin.fuzzer.backend.generate_cpp;
13 
14 import logger = std.experimental.logger;
15 
16 import dsrcgen.cpp : CppModule, E, Et;
17 import sumtype;
18 
19 import cpptooling.data : CppRoot, CFunction, USRType, CxParam, CppVariable, CppNs, Language;
20 
21 import dextool.plugin.fuzzer.type : Param, Symbol;
22 import dextool.plugin.fuzzer.backend.type;
23 
24 @safe:
25 
26 void generateMain(ref ImplData impl, CppModule m) {
27     immutable dextool_hpp = "dextool/dextool.hpp";
28     immutable afl_integration_hpp = "dextool/afl_integration.hpp";
29     immutable guide_data = "guide_data";
30     immutable default_src = "stdin_src";
31 
32     m.include(dextool_hpp);
33     m.include(afl_integration_hpp);
34     m.sep(2);
35 
36     with (m.namespace("dextool")) {
37         suppressIndent(1);
38         with (namespace("")) {
39             suppressIndent(1);
40             stmt(E("dextool::DefaultSource*") ~ E(default_src));
41         }
42         sep(2);
43 
44         with (func_body("DefaultSource&", "get_default_source")) {
45             return_("*" ~ default_src);
46         }
47     }
48     m.sep(2);
49 
50     with (m.func_body("int", "main", "int argc", "char** argv")) {
51         return_(E("dextool::afl_main")("argc", "argv", "&dextool::" ~ default_src));
52     }
53 }
54 
55 private FuzzCase makeFuzzCase(IncludeT)(ulong index, IncludeT[] target_includes) {
56     import std.conv : to;
57     import dextool.type : Path;
58 
59     auto r = FuzzCase(new CppModule);
60     r.testCaseId = index;
61 
62     r.filename = Path("dextool_fuzz_case");
63     r.includes = r.cpp.base;
64     r.includes.suppressIndent(1);
65 
66     r.includes.include("dextool/dextool.hpp");
67     r.includes.sep(2);
68 
69     generateFuzzerTargetIncludes(target_includes, r.includes);
70     r.includes.sep(2);
71 
72     r.body_ = r.cpp.suite(
73             "FUZZ_TEST_S(Generated, Test" ~ index.to!string ~ ", " ~ index.to!string ~ ")");
74 
75     return r;
76 }
77 
78 void generateFuzzCases(IncludeT)(ref ImplData impl, IncludeT[] target_includes, ref GeneratedData gen) {
79     foreach (f; impl.root.funcRange) {
80         auto test_case_id = impl.symbolId[FullyQualifiedNameType(f.name)];
81         auto fc = makeFuzzCase(test_case_id, target_includes);
82         generateFunctionFuzzTest(f, fc, impl.symbols);
83         gen.fuzzCases ~= fc;
84     }
85 }
86 
87 private void generateFunctionFuzzTest(SymbolsT)(CFunction f, FuzzCase fc, SymbolsT symbols) {
88     import std.typecons : No;
89     import cpptooling.data;
90     import dextool.plugin.fuzzer.type : FullyQualifiedNameType, Param;
91 
92     if (auto sym = FullyQualifiedNameType(f.name) in symbols) {
93         if (sym.hasFuzz) {
94             generateFuncUserFuzzer(f, sym, fc.body_, fc.includes);
95         } else if (sym.filter == Symbol.FilterKind.keep) {
96             Param[] params = sym.limits;
97             generateFuncParamFuzzer(f, params, fc.body_, fc.includes);
98         }
99     } else {
100         generateFuncParamFuzzer(f, Param[].init, fc.body_, fc.includes);
101     }
102 }
103 
104 /// Generate the parameters with random values.
105 void generateFuncParamFuzzer(ParamsT)(CFunction f, ParamsT param_limits,
106         CppModule f_, CppModule extra_includes) {
107     import std.algorithm : joiner;
108     import std.conv : to, text;
109     import std.range : enumerate;
110     import std.typecons : Yes, No;
111     import cpptooling.data : getName, unpackParam, TypeKind, TypeKindAttr, toStringDecl;
112 
113     struct Instantiate {
114         string name;
115         bool success;
116     }
117 
118     Instantiate instantiateParamType(TypeT)(size_t idx, Param[] params,
119             TypeT type, ref string[] param_id, CppModule m) {
120         Instantiate rval;
121         rval.name = "param" ~ idx.to!string;
122 
123         type.attr.isConst = No.isConst;
124         type.attr.isPtr = No.isPtr;
125         type.attr.isRef = No.isRef;
126         type.attr.isFuncPtr = No.isFuncPtr;
127 
128         type.kind.info.match!((TypeKind.PointerInfo t) {
129             rval.success = false;
130             param_id ~= rval.name;
131             m.comment("Unable to fuzz a pointer. Use a custom fuzz function. Example:");
132             m.comment(`<fuzz use="my_fuzz" include="my_include.hpp"/>`);
133             m.stmt(E(type.toStringDecl(rval.name)) = E(0));
134         }, (_) {
135             rval.success = true;
136             param_id ~= rval.name;
137             m.stmt(type.toStringDecl(rval.name));
138         });
139 
140         return rval;
141     }
142 
143     void injectUserFuzzer(Param p, string varname, CppModule m) {
144         if (!p.hasFuzz) {
145             m.stmt(E("dextool::fuzz")(varname));
146             return;
147         }
148 
149         if (p.fuzz.include.length != 0) {
150             extra_includes.include(p.fuzz.include);
151         }
152 
153         m.stmt(E(p.fuzz.use)(varname, p.fuzz.param));
154     }
155 
156     void injectCheck(Param p, string varname, CppModule m) {
157         if (!p.hasCheck)
158             return;
159 
160         with (m.if_("!" ~ E(p.check)(p.condition, varname))) {
161             return_;
162         }
163     }
164 
165     // generate the random values.
166     with (f_) {
167         string[] param_id;
168         string[] params;
169 
170         foreach (pidx, p; f.paramRange.enumerate) {
171             auto utype = p.unpackParam;
172             if (utype.isVariadic)
173                 continue;
174             auto type = utype.type;
175 
176             // create the local variable for the parameters
177             auto ident = instantiateParamType(pidx, param_limits, type, param_id, f_);
178 
179             if (!ident.success)
180                 continue;
181 
182             if (pidx >= param_limits.length) {
183                 stmt(E("dextool::fuzz")(ident.name));
184             } else {
185                 Param param = param_limits[pidx];
186                 injectUserFuzzer(param, ident.name, f_);
187                 injectCheck(param, ident.name, f_);
188             }
189         }
190 
191         stmt(E(f.name)(param_id.joiner(", ").text));
192     }
193 }
194 
195 void generateFuzzerTargetIncludes(IncludeT)(IncludeT[] includes, CppModule m) {
196     foreach (incl; includes) {
197         auto incl_code = m;
198         if (incl.payload == Language.c) {
199             incl_code = m.suite(`extern "C"`);
200             incl_code.suppressIndent(1);
201         }
202         incl_code.include(cast(string) incl);
203     }
204     m.sep(2);
205 }
206 
207 void generateFuncUserFuzzer(SymbolT)(CFunction f, SymbolT sym, CppModule f_,
208         CppModule extra_includes) {
209     import std.algorithm : map, joiner;
210     import std.range : iota;
211     import std.conv : to, text;
212 
213     if (sym.hasInclude) {
214         extra_includes.include(sym.fuzz.include);
215     }
216 
217     immutable user_ctx = "ctx";
218     immutable prefix = "param";
219 
220     f_.stmt(E(sym.fuzz.use) ~ E(user_ctx));
221 
222     with (f_.if_("!" ~ user_ctx ~ ".is_valid")) {
223         return_;
224     }
225     f_.sep(2);
226 
227     f_.stmt(E(f.name)(iota(f.paramRange.length)
228             .map!(a => user_ctx ~ "." ~ prefix ~ a.text).joiner(", ").text));
229 }