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.intercept.backend.backend; 11 12 import logger = std.experimental.logger; 13 14 import dextool.plugin.intercept.backend.analyzer; 15 import dextool.plugin.intercept.backend.interface_; 16 import dextool.type : StubPrefix; 17 18 struct Backend { 19 import std.typecons : Nullable; 20 import cpptooling.analyzer.clang.context : ClangContext; 21 import cpptooling.data.symbol : Container; 22 import dextool.type : ExitStatusType, AbsolutePath; 23 24 /// 25 this(Controller ctrl, Parameters params, Products products) { 26 import std.typecons : Yes; 27 28 this.analyze = AnalyzeData.make(); 29 this.ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly); 30 this.ctrl = ctrl; 31 this.params = params; 32 this.products = products; 33 } 34 35 ExitStatusType analyzeFile(const AbsolutePath abs_in_file, const string[] use_cflags) { 36 import std.typecons : NullableRef, scoped; 37 import dextool.utility : analyzeFile; 38 import cpptooling.data : MergeMode; 39 40 NullableRef!Container cont_ = &container; 41 auto visitor = new TUVisitor(cont_); 42 43 if (analyzeFile(abs_in_file, use_cflags, visitor, ctx) == ExitStatusType.Errors) { 44 return ExitStatusType.Errors; 45 } 46 47 debug logger.tracef("%u", visitor.root); 48 49 auto filtered = rawFilter(visitor.root, ctrl, products, 50 (USRType usr) => container.find!LocationTag(usr)); 51 52 analyze.root.merge(filtered, MergeMode.full); 53 54 return ExitStatusType.Ok; 55 } 56 57 void process() { 58 import cpptooling.data.symbol.types : USRType; 59 60 assert(!analyze.isNull); 61 62 debug logger.trace(container.toString); 63 64 logger.tracef("Filtered:\n%u", analyze.root); 65 66 auto impl_data = ImplData.make(); 67 translate(analyze.root, container, ctrl, params, impl_data); 68 analyze.nullify(); 69 70 logger.tracef("Translated to implementation:\n%u", impl_data.root); 71 72 generate(impl_data, gen_data, params, container); 73 74 postProcess(ctrl, params, products, gen_data); 75 } 76 77 private: 78 ClangContext ctx; 79 Container container; 80 Nullable!AnalyzeData analyze; 81 GeneratedData gen_data; 82 83 Controller ctrl; 84 Parameters params; 85 Products products; 86 } 87 88 private: 89 90 @safe: 91 92 import cpptooling.data : CppRoot, CFunction, LocationTag, Location; 93 import cpptooling.data.symbol : Container, USRType; 94 95 import dsrcgen.cpp : CppModule, CppHModule; 96 import dsrcgen.sh : ShModule, ShScriptModule; 97 98 import dextool.plugin.intercept.type : SymbolName; 99 100 struct ImplData { 101 CppRoot root; 102 103 static auto make() { 104 return ImplData(CppRoot.make); 105 } 106 107 StubPrefix[SymbolName] symbolPrefix; 108 } 109 110 /** Filter the raw IR according to the users desire. 111 * 112 * Params: 113 * ctrl = removes according to directives via ctrl 114 * prod = put locations of symbols that pass filtering 115 */ 116 AnalyzeData rawFilter(LookupT)(AnalyzeData input, Controller ctrl, Products prod, LookupT lookup) @safe { 117 import std.algorithm : each, filter; 118 import std.range : tee; 119 import dextool.type : FileName; 120 import cpptooling.data : StorageClass; 121 import cpptooling.generator.utility : filterAnyLocation; 122 import cpptooling.testdouble.header_filter : LocationType; 123 124 auto filtered = AnalyzeData.make; 125 126 // dfmt off 127 input.funcRange 128 // by definition static functions can't be replaced by test doubles 129 .filter!(a => a.storageClass != StorageClass.Static) 130 // ask controller if the symbol should be intercepted 131 .filter!(a => ctrl.doSymbol(a.name)) 132 // lookup the location of the symbol 133 .filterAnyLocation!(a => true)(lookup) 134 // pass on location as a product to be used to calculate #include 135 .tee!(a => prod.putLocation(FileName(a.location.file), LocationType.Leaf)) 136 .each!(a => filtered.put(a.value)); 137 // dfmt on 138 139 return filtered; 140 } 141 142 /** Structurally transform the input to an interceptor. 143 * 144 * It is intended to capture any transformations that are needed but atm it is 145 * pretty useless. 146 */ 147 void translate(CppRoot root, ref Container container, Controller ctrl, 148 Parameters params, ref ImplData impl) { 149 foreach (f; root.funcRange) { 150 impl.root.put(f); 151 impl.symbolPrefix[SymbolName(f.name)] = params.symbolPrefix(cast(string) f.name); 152 } 153 } 154 155 struct Code { 156 enum Kind { 157 hdr, 158 impl, 159 script 160 } 161 162 ShModule script; 163 CppModule cpp; 164 } 165 166 struct GeneratedData { 167 Code[Code.Kind] data; 168 169 auto make(Code.Kind kind) { 170 if (auto c = kind in data) { 171 return *c; 172 } 173 174 Code m; 175 176 final switch (kind) { 177 case Code.Kind.hdr: 178 goto case; 179 case Code.Kind.impl: 180 m.cpp = new CppModule; 181 break; 182 case Code.Kind.script: 183 m.script = new ShModule; 184 break; 185 } 186 187 data[kind] = m; 188 return m; 189 } 190 } 191 192 /** Translate the structure to code. */ 193 void generate(ref ImplData impl, ref GeneratedData gen, Parameters params, 194 ref const Container container) { 195 import std.algorithm : filter; 196 197 auto code_hdr = gen.make(Code.Kind.hdr).cpp; 198 auto code_impl = gen.make(Code.Kind.impl).cpp; 199 auto script = gen.make(Code.Kind.script).script; 200 201 generateRewriteScriptPreamble(script); 202 203 foreach (f; impl.root.funcRange) { 204 StubPrefix prefix = impl.symbolPrefix[SymbolName(f.name)]; 205 206 generateInterceptFuncDecl(f, code_hdr, prefix); 207 generateInterceptFunc(f, code_impl, prefix); 208 generateRewriteSymbol(f, script, prefix); 209 } 210 } 211 212 void generateInterceptFunc(CFunction f, CppModule code, StubPrefix prefix) { 213 import cpptooling.data : joinParams, joinParamNames; 214 import cpptooling.analyzer : toStringDecl; 215 import dsrcgen.c : E; 216 217 // assuming that a function declaration void a() in C is meant to be void 218 // a(void), not variadic. 219 string params; 220 auto p_range = f.paramRange(); 221 if (p_range.length == 1 && !f.isVariadic || p_range.length > 1) { 222 params = joinParams(p_range); 223 } 224 string names = joinParamNames(f.paramRange()); 225 226 with (code.func_body(f.returnType.toStringDecl, f.name, params)) { 227 auto expr = E(prefix ~ f.name)(E(names)); 228 if (f.returnType.toStringDecl == "void") { 229 stmt(expr); 230 } else { 231 return_(expr); 232 } 233 } 234 235 code.sep(2); 236 } 237 238 void generateInterceptFuncDecl(CFunction f, CppModule code, StubPrefix prefix) { 239 import cpptooling.analyzer : toStringDecl; 240 import cpptooling.data : joinParams; 241 import dsrcgen.c : E; 242 243 // assuming that a function declaration void a() in C is meant to be void 244 // a(void), not variadic. 245 string params; 246 auto p_range = f.paramRange(); 247 if (p_range.length == 1 && !f.isVariadic || p_range.length > 1) { 248 params = joinParams(p_range); 249 } 250 251 code.func(f.returnType.toStringDecl, prefix ~ f.name, params); 252 code.sep(2); 253 } 254 255 void generateRewriteSymbol(CFunction f, ShModule sh, StubPrefix prefix) { 256 sh.stmt(`objcopy --redefine-sym ` ~ f.name ~ "=" ~ prefix ~ f.name ~ ` "$DEST"`); 257 } 258 259 void generateRewriteScriptPreamble(ShModule sh) { 260 import std.ascii : newline; 261 262 sh.stmt("set -e"); 263 264 with (sh.suite("if [ $# -ne 2 ]; then")[$.begin = newline, $.end = "fi"]) { 265 stmt(`echo "Usage: $0 <original-lib> <rewrite-lib>"`); 266 stmt("exit 1"); 267 } 268 269 sh.stmt("ORIG=$1"); 270 sh.stmt("DEST=$2"); 271 272 sh.stmt(`hash objcopy || { echo "Missing objcopy"; exit 1; }`); 273 sh.stmt(`cp "$ORIG" "$DEST"`); 274 275 sh.sep(2); 276 } 277 278 void postProcess(Controller ctrl, Parameters params, Products prods, GeneratedData gen) { 279 import cpptooling.generator.includes : convToIncludeGuard, makeHeader; 280 import dextool.type : FileName, DextoolVersion, CustomHeader; 281 282 static auto outputHdr(CppModule hdr, FileName fname, FileName[] includes, 283 DextoolVersion ver, CustomHeader custom_hdr) { 284 auto o = CppHModule(convToIncludeGuard(fname)); 285 o.header.append(makeHeader(fname, ver, custom_hdr)); 286 287 auto code_inc = o.content.suite(`extern "C" `); 288 code_inc.suppressIndent(1); 289 290 foreach (incl; includes) { 291 code_inc.include(cast(string) incl); 292 } 293 code_inc.sep(2); 294 295 code_inc.append(hdr); 296 297 return o; 298 } 299 300 static auto output(CppModule code, FileName incl_fname, FileName dest, 301 DextoolVersion ver, CustomHeader custom_hdr) { 302 import std.path : baseName; 303 304 auto o = new CppModule; 305 o.suppressIndent(1); 306 o.append(makeHeader(dest, ver, custom_hdr)); 307 o.include(incl_fname.baseName); 308 o.sep(2); 309 o.append(code); 310 311 return o; 312 } 313 314 foreach (k, v; gen.data) { 315 final switch (k) with (Code) { 316 case Kind.hdr: 317 prods.putFile(params.getFiles.hdr, outputHdr(v.cpp, 318 params.getFiles.hdr, params.getIncludes, 319 params.getToolVersion, params.getCustomHeader)); 320 break; 321 case Kind.impl: 322 prods.putFile(params.getFiles.impl, output(v.cpp, 323 params.getFiles.hdr, params.getFiles.impl, 324 params.getToolVersion, params.getCustomHeader)); 325 break; 326 case Kind.script: 327 auto script = ShScriptModule.make(); 328 script.shebang.shebang("/bin/bash"); 329 script.content.append(v.script); 330 prods.putFile(params.getFiles.script, script); 331 break; 332 } 333 } 334 }