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