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