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 }