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 }