1181254a7Smrg /**
2181254a7Smrg Helper functions for working with $(I C strings).
3181254a7Smrg
4181254a7Smrg This module is intended to provide fast, safe and garbage free
5181254a7Smrg way to work with $(I C strings).
6181254a7Smrg
7181254a7Smrg Copyright: Denis Shelomovskij 2013-2014
8181254a7Smrg
9181254a7Smrg License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
10181254a7Smrg
11181254a7Smrg Authors: Denis Shelomovskij
12181254a7Smrg
13181254a7Smrg Macros:
14*b1e83836Smrg COREREF = $(HTTP dlang.org/phobos/core_$1.html#$2, `core.$1.$2`)
15181254a7Smrg */
16181254a7Smrg module std.internal.cstring;
17181254a7Smrg
18181254a7Smrg ///
19181254a7Smrg @safe unittest
20181254a7Smrg {
version(Posix)21181254a7Smrg version (Posix)
22181254a7Smrg {
23181254a7Smrg import core.stdc.stdlib : free;
24181254a7Smrg import core.sys.posix.stdlib : setenv;
25181254a7Smrg import std.exception : enforce;
26181254a7Smrg
27*b1e83836Smrg void setEnvironment(scope const(char)[] name, scope const(char)[] value)
28181254a7Smrg { enforce(setenv(name.tempCString(), value.tempCString(), 1) != -1); }
29181254a7Smrg }
30181254a7Smrg
version(Windows)31181254a7Smrg version (Windows)
32181254a7Smrg {
33*b1e83836Smrg import core.sys.windows.winbase : SetEnvironmentVariableW;
34181254a7Smrg import std.exception : enforce;
35181254a7Smrg
36*b1e83836Smrg void setEnvironment(scope const(char)[] name, scope const(char)[] value)
37181254a7Smrg { enforce(SetEnvironmentVariableW(name.tempCStringW(), value.tempCStringW())); }
38181254a7Smrg }
39181254a7Smrg }
40181254a7Smrg
41181254a7Smrg import std.range;
42181254a7Smrg import std.traits;
43181254a7Smrg
44181254a7Smrg /**
45181254a7Smrg Creates temporary 0-terminated $(I C string) with copy of passed text.
46181254a7Smrg
47181254a7Smrg Params:
48181254a7Smrg To = character type of returned C string
49181254a7Smrg str = string or input range to be converted
50181254a7Smrg
51181254a7Smrg Returns:
52181254a7Smrg
53181254a7Smrg The value returned is implicitly convertible to $(D const To*) and
54*b1e83836Smrg has two properties: `ptr` to access $(I C string) as $(D const To*)
55*b1e83836Smrg and `buffPtr` to access it as `To*`.
56181254a7Smrg
57181254a7Smrg The value returned can be indexed by [] to access it as an array.
58181254a7Smrg
59181254a7Smrg The temporary $(I C string) is valid unless returned object is destroyed.
60181254a7Smrg Thus if returned object is assigned to a variable the temporary is
61181254a7Smrg valid unless the variable goes out of scope. If returned object isn't
62181254a7Smrg assigned to a variable it will be destroyed at the end of creating
63181254a7Smrg primary expression.
64181254a7Smrg
65181254a7Smrg Implementation_note:
66181254a7Smrg For small strings tempCString will use stack allocated buffer,
67181254a7Smrg for large strings (approximately 250 characters and more) it will
68*b1e83836Smrg allocate temporary one using C's `malloc`.
69181254a7Smrg
70181254a7Smrg Note:
71181254a7Smrg This function is intended to be used in function call expression (like
72*b1e83836Smrg `strlen(str.tempCString())`). Incorrect usage of this function may
73181254a7Smrg lead to memory corruption.
74181254a7Smrg See $(RED WARNING) in $(B Examples) section.
75181254a7Smrg */
76181254a7Smrg
77*b1e83836Smrg auto tempCString(To = char, From)(scope From str)
78181254a7Smrg if (isSomeChar!To && (isInputRange!From || isSomeString!From) &&
79181254a7Smrg isSomeChar!(ElementEncodingType!From))
80181254a7Smrg {
81181254a7Smrg alias CF = Unqual!(ElementEncodingType!From);
82181254a7Smrg
83*b1e83836Smrg auto res = TempCStringBuffer!To.trustedVoidInit(); // expensive to fill _buff[]
84181254a7Smrg
85181254a7Smrg // Note: res._ptr can't point to res._buff as structs are movable.
86181254a7Smrg
87*b1e83836Smrg // https://issues.dlang.org/show_bug.cgi?id=14980
88*b1e83836Smrg static if (isSomeString!From)
89181254a7Smrg {
90*b1e83836Smrg if (str is null)
91181254a7Smrg {
92*b1e83836Smrg res._length = 0;
93*b1e83836Smrg res._ptr = null;
94*b1e83836Smrg return res;
95*b1e83836Smrg }
96*b1e83836Smrg }
97*b1e83836Smrg
98*b1e83836Smrg // Use slice assignment if available.
99*b1e83836Smrg static if (To.sizeof == CF.sizeof && is(typeof(res._buff[0 .. str.length] = str[])))
100*b1e83836Smrg {
101*b1e83836Smrg if (str.length < res._buff.length)
102*b1e83836Smrg {
103*b1e83836Smrg res._buff[0 .. str.length] = str[];
104*b1e83836Smrg res._buff[str.length] = 0;
105*b1e83836Smrg res._ptr = res.useStack;
106181254a7Smrg }
107181254a7Smrg else
108181254a7Smrg {
109*b1e83836Smrg import std.internal.memory : enforceMalloc;
110*b1e83836Smrg if (false)
111*b1e83836Smrg {
112*b1e83836Smrg // This code is removed by the compiler but causes `@safe`ty
113*b1e83836Smrg // to be inferred correctly.
114*b1e83836Smrg CF[0] x;
115*b1e83836Smrg x[] = str[0 .. 0];
116181254a7Smrg }
117*b1e83836Smrg res._ptr = () @trusted {
118*b1e83836Smrg auto p = cast(CF*) enforceMalloc((str.length + 1) * CF.sizeof);
119*b1e83836Smrg p[0 .. str.length] = str[];
120*b1e83836Smrg p[str.length] = 0;
121*b1e83836Smrg return cast(To*) p;
122*b1e83836Smrg }();
123181254a7Smrg }
124*b1e83836Smrg res._length = str.length;
125*b1e83836Smrg return res;
126*b1e83836Smrg }
127*b1e83836Smrg else
128*b1e83836Smrg {
129*b1e83836Smrg static assert(!(isSomeString!From && CF.sizeof == To.sizeof), "Should be using slice assignment.");
130*b1e83836Smrg To[] p = res._buff;
131*b1e83836Smrg size_t i;
132181254a7Smrg
133181254a7Smrg size_t strLength;
134181254a7Smrg static if (hasLength!From)
135181254a7Smrg {
136181254a7Smrg strLength = str.length;
137181254a7Smrg }
138181254a7Smrg import std.utf : byUTF;
139181254a7Smrg static if (isSomeString!From)
140181254a7Smrg auto r = cast(const(CF)[])str; // because inout(CF) causes problems with byUTF
141181254a7Smrg else
142181254a7Smrg alias r = str;
143*b1e83836Smrg To[] heapBuffer;
144181254a7Smrg foreach (const c; byUTF!(Unqual!To)(r))
145181254a7Smrg {
146*b1e83836Smrg if (i + 1 == p.length)
147181254a7Smrg {
148*b1e83836Smrg if (heapBuffer is null)
149*b1e83836Smrg heapBuffer = trustedReallocStack(p, strLength);
150*b1e83836Smrg else
151*b1e83836Smrg heapBuffer = trustedRealloc(heapBuffer);
152*b1e83836Smrg p = heapBuffer;
153181254a7Smrg }
154*b1e83836Smrg p[i++] = c;
155181254a7Smrg }
156*b1e83836Smrg p[i] = 0;
157181254a7Smrg res._length = i;
158*b1e83836Smrg res._ptr = (heapBuffer is null ? res.useStack : &heapBuffer[0]);
159181254a7Smrg return res;
160181254a7Smrg }
161*b1e83836Smrg }
162181254a7Smrg
163181254a7Smrg ///
164181254a7Smrg nothrow @nogc @system unittest
165181254a7Smrg {
166181254a7Smrg import core.stdc.string;
167181254a7Smrg
168181254a7Smrg string str = "abc";
169181254a7Smrg
170181254a7Smrg // Intended usage
171181254a7Smrg assert(strlen(str.tempCString()) == 3);
172181254a7Smrg
173181254a7Smrg // Correct usage
174181254a7Smrg auto tmp = str.tempCString();
175181254a7Smrg assert(strlen(tmp) == 3); // or `tmp.ptr`, or `tmp.buffPtr`
176181254a7Smrg
177181254a7Smrg // $(RED WARNING): $(RED Incorrect usage)
178181254a7Smrg auto pInvalid1 = str.tempCString().ptr;
179181254a7Smrg const char* pInvalid2 = str.tempCString();
180181254a7Smrg // Both pointers refer to invalid memory here as
181181254a7Smrg // returned values aren't assigned to a variable and
182181254a7Smrg // both primary expressions are ended.
183181254a7Smrg }
184181254a7Smrg
185*b1e83836Smrg @safe pure nothrow @nogc unittest
186181254a7Smrg {
inout(C)187*b1e83836Smrg static inout(C)[] arrayFor(C)(inout(C)* cstr) pure nothrow @nogc @trusted
188*b1e83836Smrg {
189*b1e83836Smrg assert(cstr);
190*b1e83836Smrg size_t length = 0;
191*b1e83836Smrg while (cstr[length])
192*b1e83836Smrg ++length;
193*b1e83836Smrg return cstr[0 .. length];
194*b1e83836Smrg }
195*b1e83836Smrg
196*b1e83836Smrg assert(arrayFor("abc".tempCString()) == "abc");
197*b1e83836Smrg assert(arrayFor("abc"d.tempCString().ptr) == "abc");
198*b1e83836Smrg assert(arrayFor("abc".tempCString!wchar().buffPtr) == "abc"w);
199181254a7Smrg
200181254a7Smrg import std.utf : byChar, byWchar;
201181254a7Smrg char[300] abc = 'a';
202*b1e83836Smrg assert(arrayFor(tempCString(abc[].byChar).buffPtr) == abc);
203*b1e83836Smrg assert(arrayFor(tempCString(abc[].byWchar).buffPtr) == abc);
204181254a7Smrg assert(tempCString(abc[].byChar)[] == abc);
205181254a7Smrg }
206181254a7Smrg
207*b1e83836Smrg // https://issues.dlang.org/show_bug.cgi?id=14980
208*b1e83836Smrg pure nothrow @nogc @safe unittest
209181254a7Smrg {
210181254a7Smrg const(char[]) str = null;
211181254a7Smrg auto res = tempCString(str);
212181254a7Smrg const char* ptr = res;
213181254a7Smrg assert(ptr is null);
214181254a7Smrg }
215181254a7Smrg
version(Windows)216181254a7Smrg version (Windows)
217*b1e83836Smrg {
218*b1e83836Smrg import core.sys.windows.winnt : WCHAR;
219*b1e83836Smrg alias tempCStringW = tempCString!(WCHAR, const(char)[]);
220*b1e83836Smrg }
221*b1e83836Smrg
222*b1e83836Smrg private struct TempCStringBuffer(To = char)
223*b1e83836Smrg {
224*b1e83836Smrg @trusted pure nothrow @nogc:
225*b1e83836Smrg
226*b1e83836Smrg @disable this();
227*b1e83836Smrg @disable this(this);
228*b1e83836Smrg alias ptr this; /// implicitly covert to raw pointer
229*b1e83836Smrg
230*b1e83836Smrg @property inout(To)* buffPtr() return inout
231*b1e83836Smrg {
232*b1e83836Smrg return _ptr == useStack ? _buff.ptr : _ptr;
233*b1e83836Smrg }
234*b1e83836Smrg
235*b1e83836Smrg @property const(To)* ptr() const
236*b1e83836Smrg {
237*b1e83836Smrg return buffPtr;
238*b1e83836Smrg }
239*b1e83836Smrg
240*b1e83836Smrg const(To)[] opIndex() const pure
241*b1e83836Smrg {
242*b1e83836Smrg return buffPtr[0 .. _length];
243*b1e83836Smrg }
244*b1e83836Smrg
245*b1e83836Smrg ~this()
246*b1e83836Smrg {
247*b1e83836Smrg if (_ptr != useStack)
248*b1e83836Smrg {
249*b1e83836Smrg import core.memory : pureFree;
250*b1e83836Smrg pureFree(_ptr);
251*b1e83836Smrg }
252*b1e83836Smrg }
253*b1e83836Smrg
254*b1e83836Smrg private:
255*b1e83836Smrg enum To* useStack = () @trusted { return cast(To*) size_t.max; }();
256*b1e83836Smrg
257*b1e83836Smrg To* _ptr;
258*b1e83836Smrg size_t _length; // length of the string
versionTempCStringBuffer259*b1e83836Smrg version (StdUnittest)
260*b1e83836Smrg // the 'small string optimization'
261*b1e83836Smrg {
262*b1e83836Smrg // smaller size to trigger reallocations. Padding is to account for
263*b1e83836Smrg // unittest/non-unittest cross-compilation (to avoid corruption)
264*b1e83836Smrg To[16 / To.sizeof] _buff;
265*b1e83836Smrg To[(256 - 16) / To.sizeof] _unittest_pad;
266*b1e83836Smrg }
267*b1e83836Smrg else
268*b1e83836Smrg {
269*b1e83836Smrg To[256 / To.sizeof] _buff; // production size
270*b1e83836Smrg }
271*b1e83836Smrg
trustedVoidInitTempCStringBuffer272*b1e83836Smrg static TempCStringBuffer trustedVoidInit() { TempCStringBuffer res = void; return res; }
273*b1e83836Smrg }
274*b1e83836Smrg
trustedRealloc(To)275*b1e83836Smrg private To[] trustedRealloc(To)(return scope To[] buf)
276*b1e83836Smrg @trusted @nogc pure nothrow
277*b1e83836Smrg {
278*b1e83836Smrg pragma(inline, false); // because it's rarely called
279*b1e83836Smrg import std.internal.memory : enforceRealloc;
280*b1e83836Smrg
281*b1e83836Smrg const size_t newlen = buf.length * 3 / 2;
282*b1e83836Smrg if (buf.length >= size_t.max / (2 * To.sizeof))
283*b1e83836Smrg {
284*b1e83836Smrg version (D_Exceptions)
285*b1e83836Smrg {
286*b1e83836Smrg import core.exception : onOutOfMemoryError;
287*b1e83836Smrg onOutOfMemoryError();
288*b1e83836Smrg }
289*b1e83836Smrg else
290*b1e83836Smrg {
291*b1e83836Smrg assert(0, "Memory allocation failed");
292*b1e83836Smrg }
293*b1e83836Smrg }
294*b1e83836Smrg auto ptr = cast(To*) enforceRealloc(buf.ptr, newlen * To.sizeof);
295*b1e83836Smrg return ptr[0 .. newlen];
296*b1e83836Smrg
297*b1e83836Smrg }
298*b1e83836Smrg
trustedReallocStack(To)299*b1e83836Smrg private To[] trustedReallocStack(To)(scope To[] buf, size_t strLength)
300*b1e83836Smrg @trusted @nogc pure nothrow
301*b1e83836Smrg {
302*b1e83836Smrg pragma(inline, false); // because it's rarely called
303*b1e83836Smrg
304*b1e83836Smrg import std.internal.memory : enforceMalloc;
305*b1e83836Smrg
306*b1e83836Smrg size_t newlen = buf.length * 3 / 2;
307*b1e83836Smrg if (newlen <= strLength)
308*b1e83836Smrg newlen = strLength + 1; // +1 for terminating 0
309*b1e83836Smrg auto ptr = cast(To*) enforceMalloc(newlen * To.sizeof);
310*b1e83836Smrg ptr[0 .. buf.length] = buf[];
311*b1e83836Smrg return ptr[0 .. newlen];
312*b1e83836Smrg }
313