Skip to content

Commit ee23c1f

Browse files
committed
Refactor narrow <-> wide string conversions
1 parent fbd9e92 commit ee23c1f

File tree

2 files changed

+96
-83
lines changed

2 files changed

+96
-83
lines changed

dmd/root/filename.d

+68-43
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ version (Windows)
4141

4242
version (IN_LLVM)
4343
{
44-
private enum codepage = CP_UTF8;
44+
private enum CodePage = CP_UTF8;
4545
}
4646
else
4747
{
4848
// assume filenames encoded in system default Windows ANSI code page
49-
private enum codepage = CP_ACP;
49+
private enum CodePage = CP_ACP;
5050
}
5151
}
5252

@@ -827,7 +827,7 @@ nothrow:
827827
}
828828
else version (Windows)
829829
{
830-
return name.toCStringThen!((cstr) => cstr.toWStringzThen!((wname)
830+
return name.toWStringzThen!((wname)
831831
{
832832
const dw = GetFileAttributesW(&wname[0]);
833833
if (dw == -1)
@@ -836,7 +836,7 @@ nothrow:
836836
return 2;
837837
else
838838
return 1;
839-
}));
839+
});
840840
}
841841
else
842842
{
@@ -1009,26 +1009,15 @@ nothrow:
10091009
// First find out how long the buffer has to be.
10101010
const fullPathLength = GetFullPathNameW(&wname[0], 0, null, null);
10111011
if (!fullPathLength) return null;
1012-
auto fullPath = new wchar[fullPathLength];
1012+
auto fullPath = (cast(wchar*) mem.xmalloc_noscan((fullPathLength + 1) * wchar.sizeof))[0 .. fullPathLength + 1];
1013+
scope(exit) mem.xfree(fullPath.ptr);
10131014

10141015
// Actually get the full path name
1015-
const fullPathLengthNoTerminator = GetFullPathNameW(
1016-
&wname[0], fullPathLength, &fullPath[0], null /*filePart*/);
1017-
// Unfortunately, when the buffer is large enough the return value is the number of characters
1018-
// _not_ counting the null terminator, so fullPathLengthNoTerminator should be smaller
1019-
assert(fullPathLength > fullPathLengthNoTerminator);
1020-
1021-
// Find out size of the converted string
1022-
const retLength = WideCharToMultiByte(
1023-
codepage, 0 /*flags*/, &fullPath[0], fullPathLength, null, 0, null, null);
1024-
auto ret = new char[retLength];
1025-
1026-
// Actually convert to char
1027-
const retLength2 = WideCharToMultiByte(
1028-
codepage, 0 /*flags*/, &fullPath[0], fullPathLength, &ret[0], retLength, null, null);
1029-
assert(retLength == retLength2);
1030-
1031-
return ret;
1016+
const length = GetFullPathNameW(
1017+
&wname[0], cast(DWORD) fullPath.length, &fullPath[0], null /*filePart*/);
1018+
assert(length == fullPathLength);
1019+
1020+
return toNarrowStringz(fullPath[0 .. length]);
10321021
});
10331022
}
10341023
else
@@ -1176,6 +1165,60 @@ version(Windows)
11761165
});
11771166
}
11781167

1168+
/**********************************
1169+
* Converts a UTF-16 string to a (null-terminated) narrow string.
1170+
* Returns:
1171+
* If `buffer` is specified and the result fits, a slice of that buffer,
1172+
* otherwise a new buffer which can be released via `mem.xfree()`.
1173+
* Nulls are propagated, i.e., if `wide` is null, the returned slice is
1174+
* null too.
1175+
*/
1176+
char[] toNarrowStringz(const(wchar)[] wide, char[] buffer = null) nothrow
1177+
{
1178+
if (wide is null)
1179+
return null;
1180+
1181+
const requiredLength = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, buffer.ptr, cast(int) buffer.length, null, null);
1182+
if (requiredLength < buffer.length)
1183+
{
1184+
buffer[requiredLength] = 0;
1185+
return buffer[0 .. requiredLength];
1186+
}
1187+
1188+
char* newBuffer = cast(char*) mem.xmalloc_noscan(requiredLength + 1);
1189+
const length = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, newBuffer, requiredLength, null, null);
1190+
assert(length == requiredLength);
1191+
newBuffer[length] = 0;
1192+
return newBuffer[0 .. length];
1193+
}
1194+
1195+
/**********************************
1196+
* Converts a narrow string to a (null-terminated) UTF-16 string.
1197+
* Returns:
1198+
* If `buffer` is specified and the result fits, a slice of that buffer,
1199+
* otherwise a new buffer which can be released via `mem.xfree()`.
1200+
* Nulls are propagated, i.e., if `narrow` is null, the returned slice is
1201+
* null too.
1202+
*/
1203+
wchar[] toWStringz(const(char)[] narrow, wchar[] buffer = null) nothrow
1204+
{
1205+
if (narrow is null)
1206+
return null;
1207+
1208+
const requiredLength = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, buffer.ptr, cast(int) buffer.length);
1209+
if (requiredLength < buffer.length)
1210+
{
1211+
buffer[requiredLength] = 0;
1212+
return buffer[0 .. requiredLength];
1213+
}
1214+
1215+
wchar* newBuffer = cast(wchar*) mem.xmalloc_noscan((requiredLength + 1) * wchar.sizeof);
1216+
const length = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, newBuffer, requiredLength);
1217+
assert(length == requiredLength);
1218+
newBuffer[length] = 0;
1219+
return newBuffer[0 .. length];
1220+
}
1221+
11791222
/**********************************
11801223
* Converts a slice of UTF-8 characters to an array of wchar that's null
11811224
* terminated so it can be passed to Win32 APIs then calls the supplied
@@ -1191,28 +1234,10 @@ version(Windows)
11911234
{
11921235
if (!str.length) return F(""w.ptr);
11931236

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

1197-
// first find out how long the buffer must be to store the result
1198-
const length = MultiByteToWideChar(codepage, 0 /*flags*/, &str[0], cast(int)str.length, null, 0);
1199-
if (!length) return F(""w);
1200-
1201-
wchar[] ret = length >= buf.length
1202-
? (cast(wchar*)malloc((length + 1) * wchar.sizeof))[0 .. length + 1]
1203-
: buf[0 .. length + 1];
1204-
scope (exit)
1205-
{
1206-
if (&ret[0] != &buf[0])
1207-
free(&ret[0]);
1208-
}
1209-
// actually do the conversion
1210-
const length2 = MultiByteToWideChar(
1211-
codepage, 0 /*flags*/, &str[0], cast(int)str.length, &ret[0], length);
1212-
assert(length == length2); // should always be true according to the API
1213-
// Add terminating `\0`
1214-
ret[$ - 1] = '\0';
1215-
1216-
return F(ret[0 .. $ - 1]);
1241+
return F(wide);
12171242
}
12181243
}

dmd/vsoptions.d

+28-40
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import dmd.root.file;
2525
import dmd.root.filename;
2626
import dmd.root.outbuffer;
2727
import dmd.root.rmem;
28-
import dmd.root.string;
28+
import dmd.root.string : toDString;
2929

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

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

273273
if (VSInstallDir is null)
@@ -507,7 +507,7 @@ extern(D):
507507
// one with the largest version that also contains the test file
508508
static const(char)* findLatestSDKDir(const(char)* baseDir, const(char)* testfile)
509509
{
510-
wchar[] wbase = toUTF16(baseDir);
510+
wchar[] wbase = toWStringz(baseDir.toDString);
511511
wchar* allfiles = cast(wchar*) mem.xmalloc_noscan((wbase.length + 3) * wchar.sizeof);
512512
scope(exit) mem.xfree(allfiles);
513513
allfiles[0 .. wbase.length] = wbase;
@@ -524,16 +524,16 @@ extern(D):
524524
{
525525
if (fileinfo.cFileName[0] >= '1' && fileinfo.cFileName[0] <= '9')
526526
{
527-
auto name = toUTF8(fileinfo.cFileName.ptr);
528-
if ((!res || strcmp(res, name) < 0) &&
529-
FileName.exists(buildPath(baseDir, name, testfile)))
527+
char[] name = toNarrowStringz(fileinfo.cFileName.ptr.toDString);
528+
if ((!res || strcmp(res, name.ptr) < 0) &&
529+
FileName.exists(buildPath(baseDir, name.ptr, testfile)))
530530
{
531531
if (res)
532532
mem.xfree(res);
533-
res = name;
533+
res = name.ptr;
534534
}
535535
else
536-
mem.xfree(name);
536+
mem.xfree(name.ptr);
537537
}
538538
}
539539
while(FindNextFileW(h, &fileinfo));
@@ -551,7 +551,7 @@ extern(D):
551551
* softwareKeyPath = path below HKLM\SOFTWARE
552552
* valueName = name of the value to read
553553
* Returns:
554-
* the registry value (in UTF8) if it exists and has string type
554+
* the registry value if it exists and has string type
555555
*/
556556
const(char)* GetRegistryString(const(char)* softwareKeyPath, wstring valueName) const
557557
{
@@ -581,20 +581,23 @@ extern(D):
581581
DWORD size = buf.sizeof;
582582
DWORD type;
583583
int hr = RegQueryValueExW(key, valueName.ptr, null, &type, cast(ubyte*) buf.ptr, &size);
584-
if (type != REG_SZ)
584+
if (type != REG_SZ || size == 0)
585585
return null;
586586

587587
wchar* wideValue = buf.ptr;
588588
scope(exit) wideValue != buf.ptr && mem.xfree(wideValue);
589589
if (hr == ERROR_MORE_DATA)
590590
{
591-
wideValue = cast(wchar*) mem.xmalloc_noscan((size + 1) * wchar.sizeof);
591+
wideValue = cast(wchar*) mem.xmalloc_noscan(size);
592592
hr = RegQueryValueExW(key, valueName.ptr, null, &type, cast(ubyte*) wideValue, &size);
593593
}
594-
if (hr != 0 || size <= 0)
594+
if (hr != 0)
595595
return null;
596596

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

600603
/***
@@ -630,36 +633,17 @@ extern(D):
630633

631634
private:
632635

633-
char* toUTF8(const(wchar)* wide)
636+
inout(wchar)[] toDString(inout(wchar)* s)
634637
{
635-
if (!wide)
636-
return null;
637-
638-
const requiredSize = WideCharToMultiByte(CP_UTF8, 0, wide, -1, null, 0, null, null);
639-
char* value = cast(char*) mem.xmalloc_noscan(requiredSize);
640-
const size = WideCharToMultiByte(CP_UTF8, 0, wide, -1, value, requiredSize, null, null);
641-
assert(size == requiredSize);
642-
return value;
643-
}
644-
645-
wchar[] toUTF16(const(char)* utf8)
646-
{
647-
if (!utf8)
648-
return null;
649-
650-
const requiredCount = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, null, 0);
651-
wchar* wide = cast(wchar*) mem.xmalloc_noscan(requiredCount * wchar.sizeof);
652-
const count = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, requiredCount);
653-
assert(count == requiredCount);
654-
return wide[0 .. count-1];
638+
return s ? s[0 .. wcslen(s)] : null;
655639
}
656640

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

659643
char* getenv(wstring name)
660644
{
661645
if (auto wide = _wgetenv(name.ptr))
662-
return toUTF8(wide);
646+
return toNarrowStringz(wide.toDString).ptr;
663647
return null;
664648
}
665649

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

726709
pragma(lib, "ole32.lib");
@@ -787,9 +770,14 @@ const(char)* detectVSInstallDirViaCOM()
787770
BSTR ptr;
788771
this(this) @disable;
789772
~this() { SysFreeString(ptr); }
790-
bool opCast(T : bool)() { return ptr !is null; }
773+
bool opCast(T : bool)() const { return ptr !is null; }
791774
size_t length() { return SysStringLen(ptr); }
792-
void moveTo(ref WrappedBString other) { SysFreeString(other.ptr); other.ptr = ptr; ptr = null; }
775+
void moveTo(ref WrappedBString other)
776+
{
777+
SysFreeString(other.ptr);
778+
other.ptr = ptr;
779+
ptr = null;
780+
}
793781
}
794782

795783
WrappedBString versionString, installDir;
@@ -818,5 +806,5 @@ const(char)* detectVSInstallDirViaCOM()
818806
thisInstallDir.moveTo(installDir);
819807
}
820808

821-
return installDir ? toUTF8(installDir.ptr) : null;
809+
return !installDir ? null : toNarrowStringz(installDir.ptr[0 .. installDir.length]).ptr;
822810
}

0 commit comments

Comments
 (0)