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 }