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 {
version(Posix)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
version(Windows)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 {
inout(C)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
version(Windows)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
versionTempCStringBuffer259 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
trustedVoidInitTempCStringBuffer272 static TempCStringBuffer trustedVoidInit() { TempCStringBuffer res = void; return res; }
273 }
274
trustedRealloc(To)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
trustedReallocStack(To)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 }
313