1 /**
2  * Common string functions including filename manipulation.
3  *
4  * Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
5  * Authors:   Walter Bright, https://www.digitalmars.com
6  * License:   $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:    $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/common/string.d, common/_string.d)
8  * Documentation: https://dlang.org/phobos/dmd_common_string.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/common/string.d
10  */
11 module dmd.common..string;
12 
13 nothrow:
14 
15 /**
16 Defines a temporary array of `Element`s using a fixed-length buffer as back store. If the length
17 of the buffer suffices, it is readily used. Otherwise, `malloc` is used to
18 allocate memory for the array and `free` is used for deallocation in the
19 destructor.
20 
21 This type is meant to use exclusively as an automatic variable. It is not
22 default constructible or copyable.
23 */
24 struct SmallBuffer(Element)
25 {
26     import core.stdc.stdlib : malloc, free;
27 
28     private Element[] _extent;
29     private bool needsFree;
30 
31   nothrow:
32   @nogc:
33 
34     @disable this(); // no default ctor
35     @disable this(ref const SmallBuffer!Element); // noncopyable, nonassignable
36 
37     /***********
38      * Construct a SmallBuffer
39      * Params:
40      *  len = number of elements in array
41      *  buffer = slice to use as backing-store, if len will fit in it
42      */
43     scope this(size_t len, return scope Element[] buffer)
44     {
45         if (len <= buffer.length)
46         {
47             _extent = buffer[0 .. len];
48         }
49         else
50         {
51             assert(len < sizeof.max / Element.sizeof);
52             _extent = (cast(typeof(_extent.ptr)) malloc(len * Element.sizeof))[0 .. len];
53             _extent.ptr || assert(0, "Out of memory.");
54             needsFree = true;
55         }
56         assert(this.length == len);
57     }
58 
59     ~this()
60     {
61         if (needsFree)
62             free(_extent.ptr);
63     }
64 
65     /******
66      * Resize existing SmallBuffer.
67      * Params:
68      *  len = number of elements after resize
69      */
70     scope void create(size_t len)
71     {
72         if (len <= _extent.length)
73         {
74             _extent = _extent[0 .. len]; // reuse existing storage
75         }
76         else
77         {
78             __dtor();
79             assert(len < sizeof.max / Element.sizeof);
80             _extent = (cast(typeof(_extent.ptr)) malloc(len * Element.sizeof))[0 .. len];
81             _extent.ptr || assert(0, "Out of memory.");
82             needsFree = true;
83         }
84         assert(this.length == len);
85     }
86 
87     // Force accesses to extent to be scoped.
88     scope inout extent()
89     {
90         return _extent;
91     }
92 
93     alias extent this;
94 }
95 
96 /// ditto
97 unittest
98 {
99     char[230] buf = void;
100     auto a = SmallBuffer!char(10, buf);
101     assert(a[] is buf[0 .. 10]);
102     auto b = SmallBuffer!char(1000, buf);
103     assert(b[] !is buf[]);
104     b.create(1000);
105     assert(b.length == 1000);
106     assert(b[] !is buf[]);
107 }
108 
109 /**
110 Converts a zero-terminated C string to a D slice. Takes linear time and allocates no memory.
111 
112 Params:
113 stringz = the C string to be converted
114 
115 Returns:
116 a slice comprehending the string. The terminating 0 is not part of the slice.
117 */
118 auto asDString(C)(C* stringz) pure @nogc nothrow
119 {
120     import core.stdc.string : strlen;
121     return stringz[0 .. strlen(stringz)];
122 }
123 
124 ///
125 unittest
126 {
127     const char* p = "123".ptr;
128     assert(p.asDString == "123");
129 }
130 
131 /**
132 (Windows only) Converts a narrow string to a wide string using `buffer` as strorage. Returns a slice managed by
133 `buffer` containing the converted string. The terminating zero is not part of the returned slice,
134 but is guaranteed to follow it.
135 */
136 version(Windows) wchar[] toWStringz(const(char)[] narrow, ref SmallBuffer!wchar buffer) nothrow
137 {
138     import core.sys.windows.winnls : CP_ACP, MultiByteToWideChar;
139     // assume filenames encoded in system default Windows ANSI code page
140     enum CodePage = CP_ACP;
141 
142     if (narrow is null)
143         return null;
144 
145     const requiredLength = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, buffer.ptr, cast(int) buffer.length);
146     if (requiredLength < cast(int) buffer.length)
147     {
148         buffer[requiredLength] = 0;
149         return buffer[0 .. requiredLength];
150     }
151 
152     buffer.create(requiredLength + 1);
153     const length = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, buffer.ptr, requiredLength);
154     assert(length == requiredLength);
155     buffer[length] = 0;
156     return buffer[0 .. length];
157 }
158 
159 /**************************************
160 * Converts a path to one suitable to be passed to Win32 API
161 * functions that can deal with paths longer than 248
162 * characters then calls the supplied function on it.
163 *
164 * Params:
165 *  path = The Path to call F on.
166 *
167 * Returns:
168 *  The result of calling F on path.
169 *
170 * References:
171 *  https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
172 */
173 version(Windows) auto extendedPathThen(alias F)(const(char)[] path)
174 {
175     import core.sys.windows.winbase;
176     import core.sys.windows.winnt;
177 
178     if (!path.length)
179         return F((wchar[]).init);
180 
181     wchar[1024] buf = void;
182     auto store = SmallBuffer!wchar(buf.length, buf);
183     auto wpath = toWStringz(path, store);
184 
185     // GetFullPathNameW expects a sized buffer to store the result in. Since we don't
186     // know how large it has to be, we pass in null and get the needed buffer length
187     // as the return code.
188     const pathLength = GetFullPathNameW(&wpath[0],
189                                         0 /*length8*/,
190                                         null /*output buffer*/,
191                                         null /*filePartBuffer*/);
192     if (pathLength == 0)
193     {
194         return F((wchar[]).init);
195     }
196 
197     // wpath is the UTF16 version of path, but to be able to use
198     // extended paths, we need to prefix with `\\?\` and the absolute
199     // path.
200     static immutable prefix = `\\?\`w;
201 
202     // prefix only needed for long names and non-UNC names
203     const needsPrefix = pathLength >= MAX_PATH && (wpath[0] != '\\' || wpath[1] != '\\');
204     const prefixLength = needsPrefix ? prefix.length : 0;
205 
206     // +1 for the null terminator
207     const bufferLength = pathLength + prefixLength + 1;
208 
209     wchar[1024] absBuf = void;
210     auto absPath = SmallBuffer!wchar(bufferLength, absBuf);
211 
212     absPath[0 .. prefixLength] = prefix[0 .. prefixLength];
213 
214     const absPathRet = GetFullPathNameW(&wpath[0],
215         cast(uint)(absPath.length - prefixLength - 1),
216         &absPath[prefixLength],
217         null /*filePartBuffer*/);
218 
219     if (absPathRet == 0 || absPathRet > absPath.length - prefixLength)
220     {
221         return F((wchar[]).init);
222     }
223 
224     absPath[$ - 1] = '\0';
225     // Strip null terminator from the slice
226     return F(absPath[0 .. $ - 1]);
227 }