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 }