1 /** 2 Helper functions for working with $(I C strings). 3 4 This module is intended to provide fast, safe and garbage free 5 way to work with $(I C strings). 6 7 Copyright: Denis Shelomovskij 2013-2014 8 9 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 10 11 Authors: Denis Shelomovskij 12 13 Macros: 14 COREREF = $(HTTP dlang.org/phobos/core_$1.html#$2, `core.$1.$2`) 15 */ 16 module my.cstring; 17 18 /// 19 @safe unittest { 20 version (Posix) { 21 import core.stdc.stdlib : free; 22 import core.sys.posix.stdlib : setenv; 23 import std.exception : enforce; 24 25 void setEnvironment(scope const(char)[] name, scope const(char)[] value) { 26 enforce(setenv(name.tempCString(), value.tempCString(), 1) != -1); 27 } 28 } 29 30 version (Windows) { 31 import core.sys.windows.winbase : SetEnvironmentVariableW; 32 import std.exception : enforce; 33 34 void setEnvironment(scope const(char)[] name, scope const(char)[] value) { 35 enforce(SetEnvironmentVariableW(name.tempCStringW(), value.tempCStringW())); 36 } 37 } 38 } 39 40 import std.range; 41 import std.traits; 42 43 /** 44 Creates temporary 0-terminated $(I C string) with copy of passed text. 45 46 Params: 47 To = character type of returned C string 48 str = string or input range to be converted 49 50 Returns: 51 52 The value returned is implicitly convertible to $(D const To*) and 53 has two properties: `ptr` to access $(I C string) as $(D const To*) 54 and `buffPtr` to access it as `To*`. 55 56 The value returned can be indexed by [] to access it as an array. 57 58 The temporary $(I C string) is valid unless returned object is destroyed. 59 Thus if returned object is assigned to a variable the temporary is 60 valid unless the variable goes out of scope. If returned object isn't 61 assigned to a variable it will be destroyed at the end of creating 62 primary expression. 63 64 Implementation_note: 65 For small strings tempCString will use stack allocated buffer, 66 for large strings (approximately 250 characters and more) it will 67 allocate temporary one using C's `malloc`. 68 69 Note: 70 This function is intended to be used in function call expression (like 71 `strlen(str.tempCString())`). Incorrect usage of this function may 72 lead to memory corruption. 73 See $(RED WARNING) in $(B Examples) section. 74 */ 75 76 auto tempCString(To = char, From)(scope From str) 77 if (isSomeChar!To && (isInputRange!From || isSomeString!From) 78 && isSomeChar!(ElementEncodingType!From)) { 79 alias CF = Unqual!(ElementEncodingType!From); 80 81 auto res = TempCStringBuffer!To.trustedVoidInit(); // expensive to fill _buff[] 82 83 // Note: res._ptr can't point to res._buff as structs are movable. 84 85 // https://issues.dlang.org/show_bug.cgi?id=14980 86 static if (isSomeString!From) { 87 if (str is null) { 88 res._length = 0; 89 res._ptr = null; 90 return res; 91 } 92 } 93 94 // Use slice assignment if available. 95 static if (To.sizeof == CF.sizeof && is(typeof(res._buff[0 .. str.length] = str[]))) { 96 if (str.length < res._buff.length) { 97 res._buff[0 .. str.length] = str[]; 98 res._buff[str.length] = 0; 99 res._ptr = res.useStack; 100 } else { 101 import my.internal.memory : enforceMalloc; 102 103 if (false) { 104 // This code is removed by the compiler but causes `@safe`ty 105 // to be inferred correctly. 106 CF[0] x; 107 x[] = str[0 .. 0]; 108 } 109 res._ptr = () @trusted { 110 auto p = cast(CF*) enforceMalloc((str.length + 1) * CF.sizeof); 111 p[0 .. str.length] = str[]; 112 p[str.length] = 0; 113 return cast(To*) p; 114 }(); 115 } 116 res._length = str.length; 117 return res; 118 } else { 119 static assert(!(isSomeString!From && CF.sizeof == To.sizeof), 120 "Should be using slice assignment."); 121 To[] p = res._buff; 122 size_t i; 123 124 size_t strLength; 125 static if (hasLength!From) { 126 strLength = str.length; 127 } 128 import std.utf : byUTF; 129 130 static if (isSomeString!From) 131 auto r = cast(const(CF)[]) str; // because inout(CF) causes problems with byUTF 132 else 133 alias r = str; 134 To[] heapBuffer; 135 foreach (const c; byUTF!(Unqual!To)(r)) { 136 if (i + 1 == p.length) { 137 heapBuffer = trustedRealloc(p, strLength, heapBuffer is null); 138 p = heapBuffer; 139 } 140 p[i++] = c; 141 } 142 p[i] = 0; 143 res._length = i; 144 res._ptr = (heapBuffer is null ? res.useStack : &heapBuffer[0]); 145 return res; 146 } 147 } 148 149 /// 150 nothrow @nogc @system unittest { 151 import core.stdc.string; 152 153 string str = "abc"; 154 155 // Intended usage 156 assert(strlen(str.tempCString()) == 3); 157 158 // Correct usage 159 auto tmp = str.tempCString(); 160 assert(strlen(tmp) == 3); // or `tmp.ptr`, or `tmp.buffPtr` 161 162 // $(RED WARNING): $(RED Incorrect usage) 163 auto pInvalid1 = str.tempCString().ptr; 164 const char* pInvalid2 = str.tempCString(); 165 // Both pointers refer to invalid memory here as 166 // returned values aren't assigned to a variable and 167 // both primary expressions are ended. 168 } 169 170 @safe pure nothrow @nogc unittest { 171 static inout(C)[] arrayFor(C)(inout(C)* cstr) pure nothrow @nogc @trusted { 172 assert(cstr); 173 size_t length = 0; 174 while (cstr[length]) 175 ++length; 176 return cstr[0 .. length]; 177 } 178 179 assert(arrayFor("abc".tempCString()) == "abc"); 180 assert(arrayFor("abc"d.tempCString().ptr) == "abc"); 181 assert(arrayFor("abc".tempCString!wchar().buffPtr) == "abc"w); 182 183 import std.utf : byChar, byWchar; 184 185 char[300] abc = 'a'; 186 assert(arrayFor(tempCString(abc[].byChar).buffPtr) == abc); 187 assert(arrayFor(tempCString(abc[].byWchar).buffPtr) == abc); 188 assert(tempCString(abc[].byChar)[] == abc); 189 } 190 191 // https://issues.dlang.org/show_bug.cgi?id=14980 192 pure nothrow @nogc @safe unittest { 193 const(char[]) str = null; 194 auto res = tempCString(str); 195 const char* ptr = res; 196 assert(ptr is null); 197 } 198 199 version (Windows) { 200 import core.sys.windows.winnt : WCHAR; 201 202 alias tempCStringW = tempCString!(WCHAR, const(char)[]); 203 } 204 205 private struct TempCStringBuffer(To = char) { 206 @trusted pure nothrow @nogc: 207 208 @disable this(); 209 @disable this(this); 210 alias ptr this; /// implicitly covert to raw pointer 211 212 @property inout(To)* buffPtr() inout { 213 return _ptr == useStack ? _buff.ptr : _ptr; 214 } 215 216 @property const(To)* ptr() const { 217 return buffPtr; 218 } 219 220 const(To)[] opIndex() const pure { 221 return buffPtr[0 .. _length]; 222 } 223 224 ~this() { 225 if (_ptr != useStack) { 226 import core.memory : pureFree; 227 228 pureFree(_ptr); 229 } 230 } 231 232 private: 233 enum To* useStack = () @trusted { return cast(To*) size_t.max; }(); 234 235 To* _ptr; 236 size_t _length; // length of the string 237 version (StdUnittest) // the 'small string optimization' 238 { 239 // smaller size to trigger reallocations. Padding is to account for 240 // unittest/non-unittest cross-compilation (to avoid corruption) 241 To[16 / To.sizeof] _buff; 242 To[(256 - 16) / To.sizeof] _unittest_pad; 243 } else { 244 To[256 / To.sizeof] _buff; // production size 245 } 246 247 static TempCStringBuffer trustedVoidInit() { 248 TempCStringBuffer res = void; 249 return res; 250 } 251 } 252 253 private To[] trustedRealloc(To)(scope To[] buf, size_t strLength, bool bufIsOnStack) @trusted @nogc pure nothrow { 254 pragma(inline, false); // because it's rarely called 255 256 import my.internal.memory : enforceMalloc, enforceRealloc; 257 258 size_t newlen = buf.length * 3 / 2; 259 260 if (bufIsOnStack) { 261 if (newlen <= strLength) 262 newlen = strLength + 1; // +1 for terminating 0 263 auto ptr = cast(To*) enforceMalloc(newlen * To.sizeof); 264 ptr[0 .. buf.length] = buf[]; 265 return ptr[0 .. newlen]; 266 } else { 267 if (buf.length >= size_t.max / (2 * To.sizeof)) { 268 version (D_Exceptions) { 269 import core.exception : onOutOfMemoryError; 270 271 onOutOfMemoryError(); 272 } else { 273 assert(0, "Memory allocation failed"); 274 } 275 } 276 auto ptr = cast(To*) enforceRealloc(buf.ptr, newlen * To.sizeof); 277 return ptr[0 .. newlen]; 278 } 279 }