Skip to content

Commit f4d83c5

Browse files
committed
[Support] [Windows] Convert paths to the preferred form
This normalizes most paths (except ones input from the user as command line arguments) into the preferred form, if `real_style()` evaluates to `windows_forward`. Differential Revision: https://reviews.llvm.org/D111880
1 parent a8b5483 commit f4d83c5

File tree

6 files changed

+90
-8
lines changed

6 files changed

+90
-8
lines changed

llvm/lib/Support/Windows/Path.inc

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ std::error_code widenPath(const Twine &Path8, SmallVectorImpl<wchar_t> &Path16,
7474
SmallString<MAX_PATH> Path8Str;
7575
Path8.toVector(Path8Str);
7676

77+
// If the path is a long path, mangled into forward slashes, normalize
78+
// back to backslashes here.
79+
if (Path8Str.startswith("//?/"))
80+
llvm::sys::path::native(Path8Str, path::Style::windows_backslash);
81+
7782
if (std::error_code EC = UTF8ToUTF16(Path8Str, Path16))
7883
return EC;
7984

@@ -100,8 +105,10 @@ std::error_code widenPath(const Twine &Path8, SmallVectorImpl<wchar_t> &Path16,
100105
}
101106

102107
// Remove '.' and '..' because long paths treat these as real path components.
108+
// Explicitly use the backslash form here, as we're prepending the \\?\
109+
// prefix.
103110
llvm::sys::path::native(Path8Str, path::Style::windows);
104-
llvm::sys::path::remove_dots(Path8Str, true);
111+
llvm::sys::path::remove_dots(Path8Str, true, path::Style::windows);
105112

106113
const StringRef RootName = llvm::sys::path::root_name(Path8Str);
107114
assert(!RootName.empty() &&
@@ -145,6 +152,7 @@ std::string getMainExecutable(const char *argv0, void *MainExecAddr) {
145152
if (UTF16ToUTF8(PathName.data(), PathName.size(), PathNameUTF8))
146153
return "";
147154

155+
llvm::sys::path::make_preferred(PathNameUTF8);
148156
return std::string(PathNameUTF8.data());
149157
}
150158

@@ -207,7 +215,13 @@ std::error_code current_path(SmallVectorImpl<char> &result) {
207215
// On success, GetCurrentDirectoryW returns the number of characters not
208216
// including the null-terminator.
209217
cur_path.set_size(len);
210-
return UTF16ToUTF8(cur_path.begin(), cur_path.size(), result);
218+
219+
if (std::error_code EC =
220+
UTF16ToUTF8(cur_path.begin(), cur_path.size(), result))
221+
return EC;
222+
223+
llvm::sys::path::make_preferred(result);
224+
return std::error_code();
211225
}
212226

213227
std::error_code set_current_path(const Twine &path) {
@@ -388,7 +402,11 @@ static std::error_code realPathFromHandle(HANDLE H,
388402
}
389403

390404
// Convert the result from UTF-16 to UTF-8.
391-
return UTF16ToUTF8(Data, CountChars, RealPath);
405+
if (std::error_code EC = UTF16ToUTF8(Data, CountChars, RealPath))
406+
return EC;
407+
408+
llvm::sys::path::make_preferred(RealPath);
409+
return std::error_code();
392410
}
393411

394412
std::error_code is_local(int FD, bool &Result) {
@@ -1407,6 +1425,8 @@ static bool getKnownFolderPath(KNOWNFOLDERID folderId,
14071425

14081426
bool ok = !UTF16ToUTF8(path, ::wcslen(path), result);
14091427
::CoTaskMemFree(path);
1428+
if (ok)
1429+
llvm::sys::path::make_preferred(result);
14101430
return ok;
14111431
}
14121432

@@ -1467,6 +1487,7 @@ void system_temp_directory(bool ErasedOnReboot, SmallVectorImpl<char> &Result) {
14671487
// Fall back to a system default.
14681488
const char *DefaultResult = "C:\\Temp";
14691489
Result.append(DefaultResult, DefaultResult + strlen(DefaultResult));
1490+
llvm::sys::path::make_preferred(Result);
14701491
}
14711492
} // end namespace path
14721493

llvm/lib/Support/Windows/Process.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ windows::GetCommandLineArguments(SmallVectorImpl<const char *> &Args,
261261
EC = GetExecutableName(Filename);
262262
if (EC)
263263
return EC;
264+
sys::path::make_preferred(Arg0);
264265
sys::path::append(Arg0, Filename);
265266
Args[0] = Saver.save(Arg0).data();
266267
return std::error_code();

llvm/lib/Support/Windows/Program.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ ErrorOr<std::string> sys::findProgramByName(StringRef Name,
103103
if (U8Result.empty())
104104
return mapWindowsError(::GetLastError());
105105

106+
llvm::sys::path::make_preferred(U8Result);
106107
return std::string(U8Result.begin(), U8Result.end());
107108
}
108109

llvm/unittests/Support/CommandLineTest.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,11 @@ TEST(CommandLineTest, PositionalEatArgsError) {
11121112
}
11131113

11141114
#ifdef _WIN32
1115+
void checkSeparators(StringRef Path) {
1116+
char UndesiredSeparator = sys::path::get_separator()[0] == '/' ? '\\' : '/';
1117+
ASSERT_EQ(Path.find(UndesiredSeparator), StringRef::npos);
1118+
}
1119+
11151120
TEST(CommandLineTest, GetCommandLineArguments) {
11161121
int argc = __argc;
11171122
char **argv = __argv;
@@ -1121,6 +1126,7 @@ TEST(CommandLineTest, GetCommandLineArguments) {
11211126

11221127
EXPECT_EQ(llvm::sys::path::is_absolute(argv[0]),
11231128
llvm::sys::path::is_absolute(__argv[0]));
1129+
checkSeparators(argv[0]);
11241130

11251131
EXPECT_TRUE(
11261132
llvm::sys::path::filename(argv[0]).equals_insensitive("supporttests.exe"))

llvm/unittests/Support/Path.cpp

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ using namespace llvm::sys;
6464

6565
namespace {
6666

67+
void checkSeparators(StringRef Path) {
68+
#ifdef _WIN32
69+
char UndesiredSeparator = sys::path::get_separator()[0] == '/' ? '\\' : '/';
70+
ASSERT_EQ(Path.find(UndesiredSeparator), StringRef::npos);
71+
#endif
72+
}
73+
6774
struct FileDescriptorCloser {
6875
explicit FileDescriptorCloser(int FD) : FD(FD) {}
6976
~FileDescriptorCloser() { ::close(FD); }
@@ -436,6 +443,9 @@ std::string getEnvWin(const wchar_t *Var) {
436443
ArrayRef<char> ref{reinterpret_cast<char const *>(path),
437444
pathLen * sizeof(wchar_t)};
438445
convertUTF16ToUTF8String(ref, expected);
446+
SmallString<32> Buf(expected);
447+
path::make_preferred(Buf);
448+
expected.assign(Buf.begin(), Buf.end());
439449
}
440450
return expected;
441451
}
@@ -583,9 +593,15 @@ TEST(Support, TempDirectory) {
583593
#ifdef _WIN32
584594
static std::string path2regex(std::string Path) {
585595
size_t Pos = 0;
596+
bool Forward = path::get_separator()[0] == '/';
586597
while ((Pos = Path.find('\\', Pos)) != std::string::npos) {
587-
Path.replace(Pos, 1, "\\\\");
588-
Pos += 2;
598+
if (Forward) {
599+
Path.replace(Pos, 1, "/");
600+
Pos += 1;
601+
} else {
602+
Path.replace(Pos, 1, "\\\\");
603+
Pos += 2;
604+
}
589605
}
590606
return Path;
591607
}
@@ -732,10 +748,12 @@ TEST_F(FileSystemTest, RealPath) {
732748
// how we specified it. Make sure to compare against the real_path of the
733749
// TestDirectory, and not just the value of TestDirectory.
734750
ASSERT_NO_ERROR(fs::real_path(TestDirectory, RealBase));
751+
checkSeparators(RealBase);
735752
path::native(Twine(RealBase) + "/test1/test2", Expected);
736753

737754
ASSERT_NO_ERROR(fs::real_path(
738755
Twine(TestDirectory) + "/././test1/../test1/test2/./test3/..", Actual));
756+
checkSeparators(Actual);
739757

740758
EXPECT_EQ(Expected, Actual);
741759

@@ -744,7 +762,9 @@ TEST_F(FileSystemTest, RealPath) {
744762
// This can fail if $HOME is not set and getpwuid fails.
745763
bool Result = llvm::sys::path::home_directory(HomeDir);
746764
if (Result) {
765+
checkSeparators(HomeDir);
747766
ASSERT_NO_ERROR(fs::real_path(HomeDir, Expected));
767+
checkSeparators(Expected);
748768
ASSERT_NO_ERROR(fs::real_path("~", Actual, true));
749769
EXPECT_EQ(Expected, Actual);
750770
ASSERT_NO_ERROR(fs::real_path("~/", Actual, true));
@@ -2266,6 +2286,30 @@ TEST_F(FileSystemTest, widenPath) {
22662286

22672287
ASSERT_NO_ERROR(windows::widenPath(Input, Result));
22682288
EXPECT_EQ(Result, Expected);
2289+
// Pass a path with forward slashes, check that it ends up with
2290+
// backslashes when widened with the long path prefix.
2291+
SmallString<MAX_PATH + 16> InputForward(Input);
2292+
path::make_preferred(InputForward, path::Style::windows_slash);
2293+
ASSERT_NO_ERROR(windows::widenPath(InputForward, Result));
2294+
EXPECT_EQ(Result, Expected);
2295+
2296+
// Pass a path which has the long path prefix prepended originally, but
2297+
// which is short enough to not require the long path prefix. If such a
2298+
// path is passed with forward slashes, make sure it gets normalized to
2299+
// backslashes.
2300+
SmallString<MAX_PATH + 16> PrefixedPath("\\\\?\\C:\\foldername");
2301+
ASSERT_NO_ERROR(windows::UTF8ToUTF16(PrefixedPath, Expected));
2302+
// Mangle the input to forward slashes.
2303+
path::make_preferred(PrefixedPath, path::Style::windows_slash);
2304+
ASSERT_NO_ERROR(windows::widenPath(PrefixedPath, Result));
2305+
EXPECT_EQ(Result, Expected);
2306+
2307+
// A short path with an inconsistent prefix is passed through as-is; this
2308+
// is a degenerate case that we currently don't care about handling.
2309+
PrefixedPath.assign("/\\?/C:/foldername");
2310+
ASSERT_NO_ERROR(windows::UTF8ToUTF16(PrefixedPath, Expected));
2311+
ASSERT_NO_ERROR(windows::widenPath(PrefixedPath, Result));
2312+
EXPECT_EQ(Result, Expected);
22692313

22702314
// Test that UNC paths are handled correctly.
22712315
const std::string ShareName("\\\\sharename\\");

llvm/unittests/Support/ProgramTest.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,17 +110,26 @@ class ProgramEnvTest : public testing::Test {
110110
};
111111

112112
#ifdef _WIN32
113+
void checkSeparators(StringRef Path) {
114+
char UndesiredSeparator = sys::path::get_separator()[0] == '/' ? '\\' : '/';
115+
ASSERT_EQ(Path.find(UndesiredSeparator), StringRef::npos);
116+
}
117+
113118
TEST_F(ProgramEnvTest, CreateProcessLongPath) {
114119
if (getenv("LLVM_PROGRAM_TEST_LONG_PATH"))
115120
exit(0);
116121

117122
// getMainExecutable returns an absolute path; prepend the long-path prefix.
118-
std::string MyAbsExe =
119-
sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1);
123+
SmallString<128> MyAbsExe(
124+
sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1));
125+
checkSeparators(MyAbsExe);
126+
// Force a path with backslashes, when we are going to prepend the \\?\
127+
// prefix.
128+
sys::path::native(MyAbsExe, sys::path::Style::windows_backslash);
120129
std::string MyExe;
121130
if (!StringRef(MyAbsExe).startswith("\\\\?\\"))
122131
MyExe.append("\\\\?\\");
123-
MyExe.append(MyAbsExe);
132+
MyExe.append(std::string(MyAbsExe.begin(), MyAbsExe.end()));
124133

125134
StringRef ArgV[] = {MyExe,
126135
"--gtest_filter=ProgramEnvTest.CreateProcessLongPath"};

0 commit comments

Comments
 (0)