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.ctestdouble.backend.global;
11 
12 import dsrcgen.cpp : CppModule;
13 import my.sumtype;
14 
15 import cpptooling.type : StubPrefix;
16 import cpptooling.data : CppClass, CppClassName, CppInherit, CppVariable,
17     CxGlobalVariable, TypeKindAttr, USRType, TypeKind, Void;
18 import cpptooling.data.symbol : Container;
19 
20 import logger = std.experimental.logger;
21 
22 version (unittest) {
23     import unit_threaded.assertions : shouldEqual;
24 }
25 
26 struct MutableGlobal {
27     CxGlobalVariable mutable;
28     TypeKind underlying;
29 
30     alias mutable this;
31 }
32 
33 /// Recursive lookup until the underlying type is found.
34 TypeKind resolveTypedef(TypeKind type, ref Container container) @trusted nothrow {
35     TypeKind rval = type;
36     auto found = typeof(container.find!TypeKind(USRType.init)).init;
37 
38     type.info.match!((TypeKind.TypeRefInfo t) {
39         found = container.find!TypeKind(t.canonicalRef);
40     }, (_) {});
41 
42     foreach (item; found)
43         rval = item;
44 
45     return rval.info.match!((TypeKind.TypeRefInfo t) => resolveTypedef(rval, container), _ => rval);
46 }
47 
48 auto filterMutable(RangeT)(RangeT range, ref Container container) {
49     import std.algorithm : filter, map;
50     import std.range : ElementType;
51     import std.range : tee;
52 
53     static bool isNotConst(ref ElementType!RangeT element) {
54         auto info = element.type.kind.info;
55 
56         bool handler(T)(ref T info) {
57             // every pointer have at least one attribute.
58             // because the attribute is for the pointer itself.
59             assert(info.attrs.length != 0);
60             return !info.attrs[$ - 1].isConst;
61         }
62 
63         return info.match!((const TypeKind.FuncPtrInfo t) => handler(t),
64                 (const TypeKind.PointerInfo t) => handler(t), _ => !element.type.attr.isConst);
65     }
66 
67     resolveTypedef(range.front.type.kind, container);
68 
69     return range.filter!(a => isNotConst(a))
70         .map!(a => MutableGlobal(a, resolveTypedef(a.type.kind, container)));
71 }
72 
73 /** Make a C++ "interface" class of all mutable globals.
74  *
75  * The range must NOT contain any const globals.
76  */
77 CppClass makeGlobalInterface(RangeT)(RangeT range, CppClassName main_if) @safe {
78     import std.algorithm : filter, map;
79     import cpptooling.data;
80     import cpptooling.data : makeSimple;
81 
82     auto globals_if = CppClass(main_if);
83     globals_if.put(CppDtor(makeUniqueUSR, CppMethodName("~" ~ globals_if.name),
84             CppAccess(AccessType.Public), CppVirtualMethod(MemberVirtualType.Virtual)));
85 
86     auto void_ = CxReturnType(makeSimple("void"));
87 
88     foreach (a; range) {
89         auto method = CppMethod(a.usr.get, CppMethodName(a.name), CxParam[].init,
90                 void_, CppAccess(AccessType.Public), CppConstMethod(false),
91                 CppVirtualMethod(MemberVirtualType.Pure));
92         globals_if.put(method);
93     }
94 
95     return globals_if;
96 }
97 
98 /// The range must NOT contain any const globals.
99 CppClass makeZeroGlobal(RangeT)(RangeT range, CppClassName main_if,
100         StubPrefix prefix, CppInherit inherit) @safe {
101     import std.algorithm : filter, map;
102     import cpptooling.data : TypeKind, isIncompleteArray, makeSimple;
103     import cpptooling.data;
104 
105     auto globals_if = CppClass(main_if, [inherit]);
106     globals_if.comment("Initialize all global variables that are mutable to zero.");
107 
108     globals_if.put(CppCtor(USRType(globals_if.name), CppMethodName(globals_if.name),
109             CxParam[].init, CppAccess(AccessType.Public)));
110     globals_if.put(CppDtor(USRType("~" ~ globals_if.name),
111             CppMethodName("~" ~ globals_if.name), CppAccess(AccessType.Public),
112             CppVirtualMethod(MemberVirtualType.Virtual)));
113 
114     auto void_ = CxReturnType(makeSimple("void"));
115 
116     foreach (a; range) {
117         auto method = CppMethod(a.usr.get, CppMethodName(a.name), CxParam[].init,
118                 void_, CppAccess(AccessType.Public), CppConstMethod(false),
119                 CppVirtualMethod(MemberVirtualType.Virtual));
120 
121         globals_if.put(method);
122     }
123 
124     return globals_if;
125 }
126 
127 /** Generate an implementation of InitGlobal that initialize all globals to zero.
128  *
129  * It thus emulates what the compiler do with the .bss-segment during cbegin.
130  */
131 void generateInitGlobalsToZero(LookupGlobalT)(ref CppClass c, CppModule impl,
132         StubPrefix prefix, LookupGlobalT lookup) @safe {
133     import std.typecons : No;
134     import std.variant : visit;
135     import cpptooling.data : CppMethod, CppMethodOp, CppCtor, CppDtor;
136     import dsrcgen.c : E;
137 
138     static void noop() {
139     }
140 
141     static void genCtor(CppClassName name, CppModule impl) {
142         impl.ctor_body(name);
143         impl.sep(2);
144     }
145 
146     static void genDtor(CppClassName name, CppModule impl) {
147         impl.dtor_body(name);
148         impl.sep(2);
149     }
150 
151     void genMethod(ref CppClass c, ref CppMethod m, StubPrefix prefix,
152             CppModule impl, ref bool need_memzero) {
153         import std.range : takeOne;
154 
155         static import std.format;
156         import cpptooling.data : TypeKind, isIncompleteArray;
157 
158         auto fqn = "::" ~ m.name;
159         auto body_ = impl.method_body("void", c.name, m.name, No.isConst);
160         auto global = lookup(m.name);
161 
162         global.underlying.info.match!((TypeKind.ArrayInfo t) {
163             if (isIncompleteArray(t.indexes)) {
164                 body_.stmt(E("void** ptr") = E("(void**) &" ~ fqn));
165                 body_.stmt("*ptr = 0");
166             } else {
167                 // c-style cast needed. the compiler warnings about throwning away the const qualifiers otherwise.
168                 body_.stmt(E(prefix ~ "memzero")(std.format.format("(void*)(%s), %s",
169                     fqn, E("sizeof")(fqn))));
170                 need_memzero = true;
171             }
172         }, (TypeKind.PrimitiveInfo t) {
173             global.type.kind.info.match!((TypeKind.TypeRefInfo t) {
174                 // may be a typedef of an array which is classified as a
175                 // prmitive. This is a bug. Fix clang/type.d to resolve the
176                 // intermediate node as an array.
177                 body_.stmt(E(prefix ~ "memzero")(std.format.format("&%s, %s",
178                 fqn, E("sizeof")(fqn))));
179                 need_memzero = true;
180             }, (_) { body_.stmt(E(fqn) = E(0)); });
181         }, (TypeKind.FuncPtrInfo t) { body_.stmt(E(fqn) = E(0)); }, (TypeKind.PointerInfo t) {
182             body_.stmt(E(fqn) = E(0));
183         }, (_) {
184             body_.stmt(E(prefix ~ "memzero")(std.format.format("&%s, %s", fqn, E("sizeof")(fqn))));
185             need_memzero = true;
186         });
187 
188         impl.sep(2);
189     }
190 
191     void makeMemzero(CppModule hook) {
192         hook.suppressIndent(1);
193         auto memzero = hook.namespace("");
194         memzero.suppressIndent(1);
195 
196         with (memzero.func_body("void", prefix ~ "memzero", "void* s", "unsigned int n")) {
197             stmt("char* iter = reinterpret_cast<char*>(s)");
198             stmt("char* end = reinterpret_cast<char*>(s) + n");
199 
200             // overflow check that isn't an undefinied behavior.
201             // why this implementation and not another?
202             // if (ptr + len < ptr || ptr + len > max) ..;
203             // The first part would be removed because the compiler can prove that
204             // it invokes UB.
205 
206             comment("crash if the address ptr overflows");
207             with (if_("n > end - iter")) {
208                 stmt("*((char*) -1) = 'x'");
209                 stmt("return");
210             }
211             with (for_("", "iter < end", "++iter")) {
212                 stmt("*iter = 0");
213             }
214         }
215 
216         hook.sep(2);
217     }
218 
219     // need to create the hook before generating functions that may need it.
220     auto memzero_hook = impl.base;
221 
222     bool need_memzero;
223     foreach (m; c.methodPublicRange()) {
224         // dfmt off
225         () @trusted{
226             m.visit!(
227                 (CppMethod m) => genMethod(c, m, prefix, impl, need_memzero),
228                 (CppMethodOp m) => noop,
229                 (CppCtor m) => genCtor(c.name, impl),
230                 (CppDtor m) => genDtor(c.name, impl));
231         }();
232         // dfmt on
233     }
234 
235     if (need_memzero) {
236         makeMemzero(memzero_hook);
237     }
238 }
239 
240 string variableToString(CppVariable name, TypeKindAttr type) @safe pure {
241     import cpptooling.data : toStringDecl;
242 
243     // example: extern int extern_a[4];
244     return type.kind.info.match!(restrictTo!(TypeKind.ArrayInfo, TypeKind.FuncPtrInfo,
245             TypeKind.PointerInfo, TypeKind.PrimitiveInfo, TypeKind.RecordInfo,
246             TypeKind.SimpleInfo, TypeKind.TypeRefInfo, (a) {
247             return type.toStringDecl(name);
248         }), restrictTo!(TypeKind.FuncInfo, TypeKind.FuncSignatureInfo,
249             TypeKind.CtorInfo, TypeKind.DtorInfo, (a) {
250             assert(0);
251             return string.init;
252         }), (Void a) {
253         debug logger.errorf("Variable has type null_. USR:%s name:%s", type.kind.usr, name);
254         return string.init;
255     });
256 }
257 
258 void generateGlobalExterns(RangeT)(RangeT range, CppModule impl, ref Container container) {
259     import std.algorithm : map, joiner;
260 
261     auto externs = impl.base;
262     externs.suppressIndent(1);
263     externs.sep;
264     impl.sep;
265 
266     foreach (ref global; range) {
267         externs.stmt("extern " ~ variableToString(global.name, global.type));
268     }
269 }
270 
271 @("Should be an interface of globals")
272 unittest {
273     import std.array;
274     import cpptooling.data;
275 
276     immutable dummyUSR = USRType("dummyUSR1");
277 
278     auto v0 = CxGlobalVariable(dummyUSR, TypeKindVariable(makeSimple("int"), CppVariable("x")));
279 
280     auto if_ = makeGlobalInterface([v0], CppClassName("TestDouble"));
281 
282     if_.toString.shouldEqual("class TestDouble { // Pure
283 public:
284   virtual ~TestDouble();
285   virtual void x() = 0;
286 }; //Class:TestDouble");
287 }