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     try {
39         type.info.match!((TypeKind.TypeRefInfo t) {
40             found = container.find!TypeKind(t.canonicalRef);
41         }, (_) {});
42     } catch (Exception e) {
43         return TypeKind.init;
44     }
45 
46     foreach (item; found)
47         rval = item;
48 
49     return rval.info.match!((TypeKind.TypeRefInfo t) => resolveTypedef(rval, container), _ => rval);
50 }
51 
52 auto filterMutable(RangeT)(RangeT range, ref Container container) {
53     import std.algorithm : filter, map;
54     import std.range : ElementType;
55 
56     static bool isNotConst(ref ElementType!RangeT element) {
57         auto info = element.type.kind.info;
58 
59         bool handler(T)(ref T info) {
60             // every pointer have at least one attribute.
61             // because the attribute is for the pointer itself.
62             assert(info.attrs.length != 0);
63             return !info.attrs[$ - 1].isConst;
64         }
65 
66         return info.match!((const TypeKind.FuncPtrInfo t) => handler(t),
67                 (const TypeKind.PointerInfo t) => handler(t), _ => !element.type.attr.isConst);
68     }
69 
70     return range.filter!(a => isNotConst(a))
71         .map!(a => MutableGlobal(a, resolveTypedef(a.type.kind, container)));
72 }
73 
74 /** Make a C++ "interface" class of all mutable globals.
75  *
76  * The range must NOT contain any const globals.
77  */
78 CppClass makeGlobalInterface(RangeT)(RangeT range, CppClassName main_if) @safe {
79     import std.algorithm : filter, map;
80     import cpptooling.data;
81     import cpptooling.data : makeSimple;
82 
83     auto globals_if = CppClass(main_if);
84     globals_if.put(CppDtor(makeUniqueUSR, CppMethodName("~" ~ globals_if.name),
85             CppAccess(AccessType.Public), CppVirtualMethod(MemberVirtualType.Virtual)));
86 
87     auto void_ = CxReturnType(makeSimple("void"));
88 
89     foreach (a; range) {
90         auto method = CppMethod(a.usr.get, CppMethodName(a.name), CxParam[].init,
91                 void_, CppAccess(AccessType.Public), CppConstMethod(false),
92                 CppVirtualMethod(MemberVirtualType.Pure));
93         globals_if.put(method);
94     }
95 
96     return globals_if;
97 }
98 
99 /// The range must NOT contain any const globals.
100 CppClass makeZeroGlobal(RangeT)(RangeT range, CppClassName main_if,
101         StubPrefix prefix, CppInherit inherit) @safe {
102     import std.algorithm : filter, map;
103     import cpptooling.data : TypeKind, isIncompleteArray, makeSimple;
104     import cpptooling.data;
105 
106     auto globals_if = CppClass(main_if, [inherit]);
107     globals_if.comment("Initialize all global variables that are mutable to zero.");
108 
109     globals_if.put(CppCtor(USRType(globals_if.name), CppMethodName(globals_if.name),
110             CxParam[].init, CppAccess(AccessType.Public)));
111     globals_if.put(CppDtor(USRType("~" ~ globals_if.name),
112             CppMethodName("~" ~ globals_if.name), CppAccess(AccessType.Public),
113             CppVirtualMethod(MemberVirtualType.Virtual)));
114 
115     auto void_ = CxReturnType(makeSimple("void"));
116 
117     foreach (a; range) {
118         auto method = CppMethod(a.usr.get, CppMethodName(a.name), CxParam[].init,
119                 void_, CppAccess(AccessType.Public), CppConstMethod(false),
120                 CppVirtualMethod(MemberVirtualType.Virtual));
121 
122         globals_if.put(method);
123     }
124 
125     return globals_if;
126 }
127 
128 /** Generate an implementation of InitGlobal that initialize all globals to zero.
129  *
130  * It thus emulates what the compiler do with the .bss-segment during cbegin.
131  */
132 void generateInitGlobalsToZero(LookupGlobalT)(ref CppClass c, CppModule impl,
133         StubPrefix prefix, LookupGlobalT lookup) @safe {
134     import std.typecons : No;
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.match!(
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 }