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 }