Home | History | Annotate | Line # | Download | only in common
      1 /**
      2  * Common string functions including filename manipulation.
      3  *
      4  * Copyright: Copyright (C) 1999-2022 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 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(T)
     25 {
     26     import core.stdc.stdlib : malloc, free;
     27 
     28     private T[] _extent;
     29     private bool needsFree;
     30 
     31   nothrow:
     32 
     33     @disable this(); // no default ctor
     34     @disable this(ref const SmallBuffer!T); // noncopyable, nonassignable
     35 
     36     this(size_t len, T[] buffer)
     37     {
     38         if (len <= buffer.length)
     39         {
     40             _extent = buffer[0 .. len];
     41         }
     42         else
     43         {
     44             _extent = (cast(typeof(_extent.ptr)) malloc(len * _extent[0].sizeof))[0 .. len];
     45             _extent.ptr || assert(0, "Out of memory.");
     46             needsFree = true;
     47         }
     48         assert(this.length == len);
     49     }
     50 
     51     ~this()
     52     {
     53         if (needsFree)
     54             free(_extent.ptr);
     55     }
     56 
     57     void create(size_t len)
     58     {
     59         if (len <= _extent.length)
     60         {
     61             _extent = _extent[0 .. len];
     62         }
     63         else
     64         {
     65             __dtor();
     66             _extent = (cast(typeof(_extent.ptr)) malloc(len * _extent[0].sizeof))[0 .. len];
     67             _extent.ptr || assert(0, "Out of memory.");
     68             needsFree = true;
     69         }
     70         assert(this.length == len);
     71     }
     72 
     73     // Force accesses to extent to be scoped.
     74     scope inout extent()
     75     {
     76         return _extent;
     77     }
     78 
     79     alias extent this;
     80 }
     81 
     82 /// ditto
     83 unittest
     84 {
     85     char[230] buf = void;
     86     auto a = SmallBuffer!char(10, buf);
     87     assert(a[] is buf[0 .. 10]);
     88     auto b = SmallBuffer!char(1000, buf);
     89     assert(b[] !is buf[]);
     90     b.create(1000);
     91     assert(b.length == 1000);
     92     assert(b[] !is buf[]);
     93 }
     94 
     95 /**
     96 Converts a zero-terminated C string to a D slice. Takes linear time and allocates no memory.
     97 
     98 Params:
     99 stringz = the C string to be converted
    100 
    101 Returns:
    102 a slice comprehending the string. The terminating 0 is not part of the slice.
    103 */
    104 auto asDString(C)(C* stringz) pure @nogc nothrow
    105 {
    106     import core.stdc.string : strlen;
    107     return stringz[0 .. strlen(stringz)];
    108 }
    109 
    110 ///
    111 unittest
    112 {
    113     const char* p = "123".ptr;
    114     assert(p.asDString == "123");
    115 }
    116 
    117 /**
    118 (Windows only) Converts a narrow string to a wide string using `buffer` as strorage. Returns a slice managed by
    119 `buffer` containing the converted string. The terminating zero is not part of the returned slice,
    120 but is guaranteed to follow it.
    121 */
    122 version(Windows) wchar[] toWStringz(const(char)[] narrow, ref SmallBuffer!wchar buffer) nothrow
    123 {
    124     import core.sys.windows.winnls : CP_ACP, MultiByteToWideChar;
    125     // assume filenames encoded in system default Windows ANSI code page
    126     enum CodePage = CP_ACP;
    127 
    128     if (narrow is null)
    129         return null;
    130 
    131     const requiredLength = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, buffer.ptr, cast(int) buffer.length);
    132     if (requiredLength < cast(int) buffer.length)
    133     {
    134         buffer[requiredLength] = 0;
    135         return buffer[0 .. requiredLength];
    136     }
    137 
    138     buffer.create(requiredLength + 1);
    139     const length = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, buffer.ptr, requiredLength);
    140     assert(length == requiredLength);
    141     buffer[length] = 0;
    142     return buffer[0 .. length];
    143 }
    144 
    145 /**************************************
    146 * Converts a path to one suitable to be passed to Win32 API
    147 * functions that can deal with paths longer than 248
    148 * characters then calls the supplied function on it.
    149 *
    150 * Params:
    151 *  path = The Path to call F on.
    152 *
    153 * Returns:
    154 *  The result of calling F on path.
    155 *
    156 * References:
    157 *  https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
    158 */
    159 version(Windows) auto extendedPathThen(alias F)(const(char)[] path)
    160 {
    161     import core.sys.windows.winbase;
    162     import core.sys.windows.winnt;
    163 
    164     if (!path.length)
    165         return F((wchar[]).init);
    166 
    167     wchar[1024] buf = void;
    168     auto store = SmallBuffer!wchar(buf.length, buf);
    169     auto wpath = toWStringz(path, store);
    170 
    171     // GetFullPathNameW expects a sized buffer to store the result in. Since we don't
    172     // know how large it has to be, we pass in null and get the needed buffer length
    173     // as the return code.
    174     const pathLength = GetFullPathNameW(&wpath[0],
    175                                         0 /*length8*/,
    176                                         null /*output buffer*/,
    177                                         null /*filePartBuffer*/);
    178     if (pathLength == 0)
    179     {
    180         return F((wchar[]).init);
    181     }
    182 
    183     // wpath is the UTF16 version of path, but to be able to use
    184     // extended paths, we need to prefix with `\\?\` and the absolute
    185     // path.
    186     static immutable prefix = `\\?\`w;
    187 
    188     // prefix only needed for long names and non-UNC names
    189     const needsPrefix = pathLength >= MAX_PATH && (wpath[0] != '\\' || wpath[1] != '\\');
    190     const prefixLength = needsPrefix ? prefix.length : 0;
    191 
    192     // +1 for the null terminator
    193     const bufferLength = pathLength + prefixLength + 1;
    194 
    195     wchar[1024] absBuf = void;
    196     auto absPath = SmallBuffer!wchar(bufferLength, absBuf);
    197 
    198     absPath[0 .. prefixLength] = prefix[0 .. prefixLength];
    199 
    200     const absPathRet = GetFullPathNameW(&wpath[0],
    201         cast(uint)(absPath.length - prefixLength - 1),
    202         &absPath[prefixLength],
    203         null /*filePartBuffer*/);
    204 
    205     if (absPathRet == 0 || absPathRet > absPath.length - prefixLength)
    206     {
    207         return F((wchar[]).init);
    208     }
    209 
    210     absPath[$ - 1] = '\0';
    211     // Strip null terminator from the slice
    212     return F(absPath[0 .. $ - 1]);
    213 }
    214