blob: 65adec68f45ee8005062fa4f89d34027808249c6 [file] [log] [blame]
/**
* Common string functions including filename manipulation.
*
* Copyright: Copyright (C) 1999-2025 by The D Language Foundation, All Rights Reserved
* Authors: Walter Bright, https://www.digitalmars.com
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/common/smallbuffer.d, common/_smallbuffer.d)
* Documentation: https://dlang.org/phobos/dmd_common_smallbuffer.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/common/smallbuffer.d
*/
module dmd.common.smallbuffer;
nothrow:
/**
Defines a temporary array of `Element`s using a fixed-length buffer as back store. If the length
of the buffer suffices, it is readily used. Otherwise, `malloc` is used to
allocate memory for the array and `free` is used for deallocation in the
destructor.
This type is meant to use exclusively as an automatic variable. It is not
default constructible or copyable.
*/
struct SmallBuffer(Element)
{
import core.stdc.stdlib : malloc, free;
private Element[] _extent;
private bool needsFree;
nothrow:
@nogc:
@disable this(); // no default ctor
@disable this(ref const SmallBuffer!Element); // noncopyable, nonassignable
/***********
* Construct a SmallBuffer
* Params:
* len = number of elements in array
* buffer = slice to use as backing-store, if len will fit in it
*/
scope this(size_t len, return scope Element[] buffer)
{
if (len <= buffer.length)
{
_extent = buffer[0 .. len];
}
else
{
assert(len < size_t.max / (2 * Element.sizeof));
_extent = (cast(typeof(_extent.ptr)) malloc(len * Element.sizeof))[0 .. len];
_extent.ptr || assert(0, "Out of memory.");
needsFree = true;
}
assert(this.length == len);
}
~this()
{
if (needsFree)
free(_extent.ptr);
}
/******
* Resize existing SmallBuffer.
* Params:
* len = number of elements after resize
*/
scope void create(size_t len)
{
if (len <= _extent.length)
{
_extent = _extent[0 .. len]; // reuse existing storage
}
else
{
__dtor();
assert(len < size_t.max / Element.sizeof);
_extent = (cast(typeof(_extent.ptr)) malloc(len * Element.sizeof))[0 .. len];
_extent.ptr || assert(0, "Out of memory.");
needsFree = true;
}
assert(this.length == len);
}
// Force accesses to extent to be scoped.
scope inout extent()
{
return _extent;
}
alias extent this;
}
/// ditto
unittest
{
char[230] buf = void;
auto a = SmallBuffer!char(10, buf);
assert(a[] is buf[0 .. 10]);
auto b = SmallBuffer!char(1000, buf);
assert(b[] !is buf[]);
b.create(1000);
assert(b.length == 1000);
assert(b[] !is buf[]);
}
/**
* (Windows only) Converts a narrow string to a wide string using `buffer` as strorage.
* Params:
* narrow = string to be converted
* buffer = where to place the converted string
* Returns: a slice of `buffer` containing the converted string. A zero follows the slice.
*/
version(Windows) wchar[] toWStringz(scope const(char)[] narrow, ref SmallBuffer!wchar buffer) nothrow
{
if (narrow is null)
return null;
size_t charsToWchars(scope const(char)[] narrow, scope wchar[] buffer)
{
// https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
import core.sys.windows.winnls : MultiByteToWideChar, CP_ACP;
return MultiByteToWideChar(CP_ACP, 0, narrow.ptr, cast(int) narrow.length, buffer.ptr, cast(int) buffer.length);
}
size_t length = charsToWchars(narrow, buffer[]);
if (length >= buffer.length) // not enough room in buffer[]
{
buffer.create(length + 1); // extend buffer length
length = charsToWchars(narrow, buffer[]); // try again
assert(length < buffer.length);
}
buffer[length] = 0;
return buffer[0 .. length];
}
/**************************************
* Converts a path to one suitable to be passed to Win32 API
* functions that can deal with paths longer than 248
* characters then calls the supplied function on it.
*
* Params:
* path = The Path to call F on.
*
* Returns:
* The result of calling F on path.
*
* References:
* https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
*/
version(Windows) auto extendedPathThen(alias F)(const(char)[] path)
{
import core.sys.windows.winbase;
import core.sys.windows.winnt;
if (!path.length)
return F((wchar[]).init);
wchar[1024] buf = void;
auto store = SmallBuffer!wchar(buf.length, buf);
auto wpath = toWStringz(path, store);
// GetFullPathNameW expects a sized buffer to store the result in. Since we don't
// know how large it has to be, we pass in null and get the needed buffer length
// as the return code.
const pathLength = GetFullPathNameW(&wpath[0],
0 /*length8*/,
null /*output buffer*/,
null /*filePartBuffer*/);
if (pathLength == 0)
{
return F((wchar[]).init);
}
// wpath is the UTF16 version of path, but to be able to use
// extended paths, we need to prefix with `\\?\` and the absolute
// path.
static immutable prefix = `\\?\`w;
// prefix only needed for long names and non-UNC names
const needsPrefix = pathLength >= MAX_PATH && (wpath[0] != '\\' || wpath[1] != '\\');
const prefixLength = needsPrefix ? prefix.length : 0;
// +1 for the null terminator
const bufferLength = pathLength + prefixLength + 1;
wchar[1024] absBuf = void;
auto absPath = SmallBuffer!wchar(bufferLength, absBuf);
absPath[0 .. prefixLength] = prefix[0 .. prefixLength];
const absPathRet = GetFullPathNameW(&wpath[0],
cast(uint)(absPath.length - prefixLength - 1),
&absPath[prefixLength],
null /*filePartBuffer*/);
if (absPathRet == 0 || absPathRet > absPath.length - prefixLength)
{
return F((wchar[]).init);
}
absPath[$ - 1] = '\0';
// Strip null terminator from the slice
return F(absPath[0 .. $ - 1]);
}