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