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 }