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