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 }