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 }