Skip to content

Commit

Permalink
Refactor narrow <-> wide string conversions
Browse files Browse the repository at this point in the history
  • Loading branch information
kinke committed May 1, 2020
1 parent fbd9e92 commit 3555ca0
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 82 deletions.
109 changes: 67 additions & 42 deletions dmd/root/filename.d
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ version (Windows)

version (IN_LLVM)
{
private enum codepage = CP_UTF8;
private enum CodePage = CP_UTF8;
}
else
{
// assume filenames encoded in system default Windows ANSI code page
private enum codepage = CP_ACP;
private enum CodePage = CP_ACP;
}
}

Expand Down Expand Up @@ -827,7 +827,7 @@ nothrow:
}
else version (Windows)
{
return name.toCStringThen!((cstr) => cstr.toWStringzThen!((wname)
return name.toWStringzThen!((wname)
{
const dw = GetFileAttributesW(&wname[0]);
if (dw == -1)
Expand Down Expand Up @@ -1009,26 +1009,15 @@ nothrow:
// First find out how long the buffer has to be.
const fullPathLength = GetFullPathNameW(&wname[0], 0, null, null);
if (!fullPathLength) return null;
auto fullPath = new wchar[fullPathLength];
auto fullPath = (cast(wchar*) mem.xmalloc_noscan((fullPathLength + 1) * wchar.sizeof))[0 .. fullPathLength + 1];
scope(exit) mem.xfree(fullPath.ptr);

// Actually get the full path name
const fullPathLengthNoTerminator = GetFullPathNameW(
&wname[0], fullPathLength, &fullPath[0], null /*filePart*/);
// Unfortunately, when the buffer is large enough the return value is the number of characters
// _not_ counting the null terminator, so fullPathLengthNoTerminator should be smaller
assert(fullPathLength > fullPathLengthNoTerminator);

// Find out size of the converted string
const retLength = WideCharToMultiByte(
codepage, 0 /*flags*/, &fullPath[0], fullPathLength, null, 0, null, null);
auto ret = new char[retLength];

// Actually convert to char
const retLength2 = WideCharToMultiByte(
codepage, 0 /*flags*/, &fullPath[0], fullPathLength, &ret[0], retLength, null, null);
assert(retLength == retLength2);

return ret;
const length = GetFullPathNameW(
&wname[0], cast(DWORD) fullPath.length, &fullPath[0], null /*filePart*/);
assert(length == fullPathLength);

return toNarrowStringz(fullPath[0 .. length]);
});
}
else
Expand Down Expand Up @@ -1176,6 +1165,60 @@ version(Windows)
});
}

/**********************************
* Converts a UTF-16 string to a (null-terminated) narrow string.
* Returns:
* If `buffer` is specified and the result fits, a slice of that buffer,
* otherwise a new buffer which can be released via `mem.xfree()`.
* Nulls are propagated, i.e., if `wide` is null, the returned slice is
* null too.
*/
char[] toNarrowStringz(const(wchar)[] wide, char[] buffer = null) nothrow
{
if (wide is null)
return null;

const requiredLength = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, buffer.ptr, cast(int) buffer.length, null, null);
if (requiredLength < buffer.length)
{
buffer[requiredLength] = 0;
return buffer[0 .. requiredLength];
}

char* newBuffer = cast(char*) mem.xmalloc_noscan(requiredLength + 1);
const length = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, newBuffer, requiredLength, null, null);
assert(length == requiredLength);
newBuffer[length] = 0;
return newBuffer[0 .. length];
}

/**********************************
* Converts a narrow string to a (null-terminated) UTF-16 string.
* Returns:
* If `buffer` is specified and the result fits, a slice of that buffer,
* otherwise a new buffer which can be released via `mem.xfree()`.
* Nulls are propagated, i.e., if `narrow` is null, the returned slice is
* null too.
*/
wchar[] toWStringz(const(char)[] narrow, wchar[] buffer = null) nothrow
{
if (narrow is null)
return null;

const requiredLength = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, buffer.ptr, cast(int) buffer.length);
if (requiredLength < buffer.length)
{
buffer[requiredLength] = 0;
return buffer[0 .. requiredLength];
}

wchar* newBuffer = cast(wchar*) mem.xmalloc_noscan((requiredLength + 1) * wchar.sizeof);
const length = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, newBuffer, requiredLength);
assert(length == requiredLength);
newBuffer[length] = 0;
return newBuffer[0 .. length];
}

/**********************************
* Converts a slice of UTF-8 characters to an array of wchar that's null
* terminated so it can be passed to Win32 APIs then calls the supplied
Expand All @@ -1191,28 +1234,10 @@ version(Windows)
{
if (!str.length) return F(""w.ptr);

import core.stdc.stdlib: malloc, free;
wchar[1024] buf = void;
wchar[] wide = toWStringz(str, buf);
scope(exit) wide.ptr != buf.ptr && mem.xfree(wide.ptr);

// first find out how long the buffer must be to store the result
const length = MultiByteToWideChar(codepage, 0 /*flags*/, &str[0], cast(int)str.length, null, 0);
if (!length) return F(""w);

wchar[] ret = length >= buf.length
? (cast(wchar*)malloc((length + 1) * wchar.sizeof))[0 .. length + 1]
: buf[0 .. length + 1];
scope (exit)
{
if (&ret[0] != &buf[0])
free(&ret[0]);
}
// actually do the conversion
const length2 = MultiByteToWideChar(
codepage, 0 /*flags*/, &str[0], cast(int)str.length, &ret[0], length);
assert(length == length2); // should always be true according to the API
// Add terminating `\0`
ret[$ - 1] = '\0';

return F(ret[0 .. $ - 1]);
return F(wide);
}
}
68 changes: 28 additions & 40 deletions dmd/vsoptions.d
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import dmd.root.file;
import dmd.root.filename;
import dmd.root.outbuffer;
import dmd.root.rmem;
import dmd.root.string;
import dmd.root.string : toDString;

version (IN_LLVM) enum supportedPre2017Versions = ["14.0".ptr];
else enum supportedPre2017Versions = ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"];
Expand Down Expand Up @@ -142,7 +142,7 @@ else
{
// debug info needs DLLs from $(VSInstallDir)\Common7\IDE for most linker versions
// so prepend it too the PATH environment variable
const path = getenv("PATH"w);
char* path = getenv("PATH"w);
const pathlen = strlen(path);
const addpathlen = strlen(addpath);

Expand Down Expand Up @@ -267,7 +267,7 @@ version (IN_LLVM)
// VS Build Tools 2017 (default installation path)
const numWritten = ExpandEnvironmentStringsW(r"%ProgramFiles(x86)%\Microsoft Visual Studio\2017\BuildTools"w.ptr, buffer.ptr, buffer.length);
if (numWritten <= buffer.length && exists(buffer.ptr))
VSInstallDir = toUTF8(buffer.ptr);
VSInstallDir = toNarrowStringz(buffer[0 .. numWritten - 1]).ptr;
}

if (VSInstallDir is null)
Expand Down Expand Up @@ -507,7 +507,7 @@ extern(D):
// one with the largest version that also contains the test file
static const(char)* findLatestSDKDir(const(char)* baseDir, const(char)* testfile)
{
wchar[] wbase = toUTF16(baseDir);
wchar[] wbase = toWStringz(baseDir.toDString);
wchar* allfiles = cast(wchar*) mem.xmalloc_noscan((wbase.length + 3) * wchar.sizeof);
scope(exit) mem.xfree(allfiles);
allfiles[0 .. wbase.length] = wbase;
Expand All @@ -524,16 +524,16 @@ extern(D):
{
if (fileinfo.cFileName[0] >= '1' && fileinfo.cFileName[0] <= '9')
{
auto name = toUTF8(fileinfo.cFileName.ptr);
if ((!res || strcmp(res, name) < 0) &&
FileName.exists(buildPath(baseDir, name, testfile)))
char[] name = toNarrowStringz(fileinfo.cFileName.ptr.toDString);
if ((!res || strcmp(res, name.ptr) < 0) &&
FileName.exists(buildPath(baseDir, name.ptr, testfile)))
{
if (res)
mem.xfree(res);
res = name;
res = name.ptr;
}
else
mem.xfree(name);
mem.xfree(name.ptr);
}
}
while(FindNextFileW(h, &fileinfo));
Expand All @@ -551,7 +551,7 @@ extern(D):
* softwareKeyPath = path below HKLM\SOFTWARE
* valueName = name of the value to read
* Returns:
* the registry value (in UTF8) if it exists and has string type
* the registry value if it exists and has string type
*/
const(char)* GetRegistryString(const(char)* softwareKeyPath, wstring valueName) const
{
Expand Down Expand Up @@ -581,20 +581,23 @@ extern(D):
DWORD size = buf.sizeof;
DWORD type;
int hr = RegQueryValueExW(key, valueName.ptr, null, &type, cast(ubyte*) buf.ptr, &size);
if (type != REG_SZ)
if (type != REG_SZ || size == 0)
return null;

wchar* wideValue = buf.ptr;
scope(exit) wideValue != buf.ptr && mem.xfree(wideValue);
if (hr == ERROR_MORE_DATA)
{
wideValue = cast(wchar*) mem.xmalloc_noscan((size + 1) * wchar.sizeof);
wideValue = cast(wchar*) mem.xmalloc_noscan(size);
hr = RegQueryValueExW(key, valueName.ptr, null, &type, cast(ubyte*) wideValue, &size);
}
if (hr != 0 || size <= 0)
if (hr != 0)
return null;

return toUTF8(wideValue);
auto wideLength = size / wchar.sizeof;
if (wideValue[wideLength - 1] == 0) // may or may not be null-terminated
--wideLength;
return toNarrowStringz(wideValue[0 .. wideLength]).ptr;
}

/***
Expand Down Expand Up @@ -630,36 +633,17 @@ extern(D):

private:

char* toUTF8(const(wchar)* wide)
inout(wchar)[] toDString(inout(wchar)* s)
{
if (!wide)
return null;

const requiredSize = WideCharToMultiByte(CP_UTF8, 0, wide, -1, null, 0, null, null);
char* value = cast(char*) mem.xmalloc_noscan(requiredSize);
const size = WideCharToMultiByte(CP_UTF8, 0, wide, -1, value, requiredSize, null, null);
assert(size == requiredSize);
return value;
}

wchar[] toUTF16(const(char)* utf8)
{
if (!utf8)
return null;

const requiredCount = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, null, 0);
wchar* wide = cast(wchar*) mem.xmalloc_noscan(requiredCount * wchar.sizeof);
const count = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, requiredCount);
assert(count == requiredCount);
return wide[0 .. count-1];
return s ? s[0 .. wcslen(s)] : null;
}

extern(C) wchar* _wgetenv(const(wchar)* name);

char* getenv(wstring name)
{
if (auto wide = _wgetenv(name.ptr))
return toUTF8(wide);
return toNarrowStringz(wide.toDString).ptr;
return null;
}

Expand Down Expand Up @@ -720,7 +704,6 @@ bool exists(const(wchar)* path)
// COM interfaces to find VS2017+ installations
import core.sys.windows.com;
import core.sys.windows.wtypes : BSTR;
import core.sys.windows.winnls : MultiByteToWideChar, WideCharToMultiByte, CP_UTF8;
import core.sys.windows.oleauto : SysFreeString, SysStringLen;

pragma(lib, "ole32.lib");
Expand Down Expand Up @@ -787,9 +770,14 @@ const(char)* detectVSInstallDirViaCOM()
BSTR ptr;
this(this) @disable;
~this() { SysFreeString(ptr); }
bool opCast(T : bool)() { return ptr !is null; }
bool opCast(T : bool)() const { return ptr !is null; }
size_t length() { return SysStringLen(ptr); }
void moveTo(ref WrappedBString other) { SysFreeString(other.ptr); other.ptr = ptr; ptr = null; }
void moveTo(ref WrappedBString other)
{
SysFreeString(other.ptr);
other.ptr = ptr;
ptr = null;
}
}

WrappedBString versionString, installDir;
Expand Down Expand Up @@ -818,5 +806,5 @@ const(char)* detectVSInstallDirViaCOM()
thisInstallDir.moveTo(installDir);
}

return installDir ? toUTF8(installDir.ptr) : null;
return !installDir ? null : toNarrowStringz(installDir.ptr[0 .. installDir.length]).ptr;
}

0 comments on commit 3555ca0

Please sign in to comment.