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