Windows: add blaze_util::AsAbsoluteWindowsPath

Replace blaze_util::AsWindowsPathWithUncPrefix
with AsAbsoluteWindowsPath, which always returns
an absolute path.

Fixes https://github.com/bazelbuild/bazel/issues/2935

RELNOTES: none
PiperOrigin-RevId: 162727218
diff --git a/src/main/cpp/blaze_util_windows.cc b/src/main/cpp/blaze_util_windows.cc
index 1598152..dce52d5 100644
--- a/src/main/cpp/blaze_util_windows.cc
+++ b/src/main/cpp/blaze_util_windows.cc
@@ -684,9 +684,9 @@
 #endif  // not COMPILER_MSVC
 
   wstring wdaemon_output;
-  if (!blaze_util::AsWindowsPathWithUncPrefix(daemon_output, &wdaemon_output)) {
+  if (!blaze_util::AsAbsoluteWindowsPath(daemon_output, &wdaemon_output)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "ExecuteDaemon(%s): AsWindowsPathWithUncPrefix(%s)", exe.c_str(),
+         "ExecuteDaemon(%s): AsAbsoluteWindowsPath(%s)", exe.c_str(),
          daemon_output.c_str());
   }
 
@@ -932,12 +932,14 @@
 #ifdef COMPILER_MSVC
   // The path may not be Windows-style and may not be normalized, so convert it.
   wstring wpath;
-  if (!blaze_util::AsWindowsPathWithUncPrefix(path, &wpath)) {
-    pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "ConvertPath(%s)",
-         path.c_str());
+  if (!blaze_util::AsAbsoluteWindowsPath(path, &wpath)) {
+    pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
+         "ConvertPath(%s): AsAbsoluteWindowsPath", path.c_str());
   }
   std::transform(wpath.begin(), wpath.end(), wpath.begin(), ::towlower);
-  return string(blaze_util::WstringToCstring(wpath.c_str()).get());
+  return string(blaze_util::WstringToCstring(
+                    blaze_util::RemoveUncPrefixMaybe(wpath.c_str()))
+                    .get());
 #else  // not COMPILER_MSVC
   // If the path looks like %USERPROFILE%/foo/bar, don't convert.
   if (path.empty() || path[0] == '%') {
@@ -977,15 +979,15 @@
 bool SymlinkDirectories(const string &posix_target, const string &posix_name) {
   wstring name;
   wstring target;
-  if (!blaze_util::AsWindowsPathWithUncPrefix(posix_name, &name)) {
+  if (!blaze_util::AsAbsoluteWindowsPath(posix_name, &name)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "SymlinkDirectories(%s, %s): AsWindowsPathWithUncPrefix(%s)",
+         "SymlinkDirectories(%s, %s): AsAbsoluteWindowsPath(%s)",
          posix_target.c_str(), posix_name.c_str(), posix_target.c_str());
     return false;
   }
-  if (!blaze_util::AsWindowsPathWithUncPrefix(posix_target, &target)) {
+  if (!blaze_util::AsAbsoluteWindowsPath(posix_target, &target)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "SymlinkDirectories(%s, %s): AsWindowsPathWithUncPrefix(%s)",
+         "SymlinkDirectories(%s, %s): AsAbsoluteWindowsPath(%s)",
          posix_target.c_str(), posix_name.c_str(), posix_name.c_str());
     return false;
   }
@@ -1277,9 +1279,9 @@
                      BlazeLock* blaze_lock) {
   string lockfile = blaze_util::JoinPath(output_base, "lock");
   wstring wlockfile;
-  if (!blaze_util::AsWindowsPathWithUncPrefix(lockfile, &wlockfile)) {
+  if (!blaze_util::AsAbsoluteWindowsPath(lockfile, &wlockfile)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "AcquireLock(%s): AsWindowsPathWithUncPrefix(%s)", output_base.c_str(),
+         "AcquireLock(%s): AsAbsoluteWindowsPath(%s)", output_base.c_str(),
          lockfile.c_str());
   }
 
diff --git a/src/main/cpp/util/file_platform.h b/src/main/cpp/util/file_platform.h
index 179eac3..db217f2 100644
--- a/src/main/cpp/util/file_platform.h
+++ b/src/main/cpp/util/file_platform.h
@@ -209,9 +209,9 @@
                            DirectoryEntryConsumer *consume);
 
 #if defined(COMPILER_MSVC) || defined(__CYGWIN__)
-// Like `AsWindowsPath` but the result is absolute and has UNC prefix if needed.
-bool AsWindowsPathWithUncPrefix(const std::string &path, std::wstring *wpath,
-                                size_t max_path = 260 /* MAX_PATH */);
+const wchar_t *RemoveUncPrefixMaybe(const wchar_t *ptr);
+
+bool AsAbsoluteWindowsPath(const std::string &path, std::wstring *wpath);
 
 // Same as `AsWindowsPath`, but returns a lowercase 8dot3 style shortened path.
 // Result will never have a UNC prefix, nor a trailing "/" or "\".
diff --git a/src/main/cpp/util/file_windows.cc b/src/main/cpp/util/file_windows.cc
index fb6eca3..ca1ff54 100644
--- a/src/main/cpp/util/file_windows.cc
+++ b/src/main/cpp/util/file_windows.cc
@@ -112,7 +112,7 @@
   }
 }
 
-static const wchar_t* RemoveUncPrefixMaybe(const wchar_t* ptr) {
+const wchar_t* RemoveUncPrefixMaybe(const wchar_t* ptr) {
   return ptr + (HasUncPrefix(ptr) ? 4 : 0);
 }
 
@@ -184,10 +184,9 @@
     return true;
   }
   wstring wpath;
-  if (!AsWindowsPathWithUncPrefix(path, &wpath)) {
+  if (!AsAbsoluteWindowsPath(path, &wpath)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "WindowsFileMtime::GetIfInDistantFuture(%s): "
-         "AsWindowsPathWithUncPrefix",
+         "WindowsFileMtime::GetIfInDistantFuture(%s): AsAbsoluteWindowsPath",
          path.c_str());
   }
 
@@ -237,9 +236,9 @@
     return false;
   }
   wstring wpath;
-  if (!AsWindowsPathWithUncPrefix(path, &wpath)) {
+  if (!AsAbsoluteWindowsPath(path, &wpath)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "WindowsFileMtime::Set(%s): AsWindowsPathWithUncPrefix", path.c_str());
+         "WindowsFileMtime::Set(%s): AsAbsoluteWindowsPath", path.c_str());
     return false;
   }
 
@@ -515,20 +514,24 @@
   return true;
 }
 
-bool AsWindowsPathWithUncPrefix(const string& path, wstring* wpath,
-                                size_t max_path) {
-  if (IsDevNull(path)) {
-    wpath->assign(L"NUL");
+bool AsAbsoluteWindowsPath(const string& path, wstring* result) {
+  if (path.empty()) {
+    result->clear();
     return true;
   }
-
-  if (!AsWindowsPath(path, wpath)) {
+  if (IsDevNull(path)) {
+    result->assign(L"NUL");
+    return true;
+  }
+  if (!AsWindowsPath(path, result)) {
     return false;
   }
-  if (!IsAbsolute(path)) {
-    wpath->assign(wstring(GetCwdW().get()) + L"\\" + *wpath);
+  if (!IsRootOrAbsolute(*result, /* must_be_root */ false)) {
+    *result = wstring(GetCwdW().get()) + L"\\" + *result;
   }
-  AddUncPrefixMaybe(wpath, max_path);
+  if (!HasUncPrefix(result->c_str())) {
+    *result = wstring(L"\\\\?\\") + *result;
+  }
   return true;
 }
 
@@ -541,9 +544,9 @@
   result->clear();
   wstring wpath;
   wstring wsuffix;
-  if (!AsWindowsPathWithUncPrefix(path, &wpath)) {
+  if (!AsAbsoluteWindowsPath(path, &wpath)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "AsShortWindowsPath(%s): AsWindowsPathWithUncPrefix", path.c_str());
+         "AsShortWindowsPath(%s): AsAbsoluteWindowsPath", path.c_str());
     return false;
   }
   DWORD size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0);
@@ -605,10 +608,9 @@
     return true;
   }
   wstring wfilename;
-  if (!AsWindowsPathWithUncPrefix(filename, &wfilename)) {
+  if (!AsAbsoluteWindowsPath(filename, &wfilename)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "OpenFileForReading(%s): AsWindowsPathWithUncPrefix",
-         filename.c_str());
+         "OpenFileForReading(%s): AsAbsoluteWindowsPath", filename.c_str());
   }
   *result = ::CreateFileW(
       /* lpFileName */ wfilename.c_str(),
@@ -675,9 +677,9 @@
     return true;  // mimic write(2) behavior with /dev/null
   }
   wstring wpath;
-  if (!AsWindowsPathWithUncPrefix(filename, &wpath)) {
+  if (!AsAbsoluteWindowsPath(filename, &wpath)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "WriteFile(%s): AsWindowsPathWithUncPrefix", filename.c_str());
+         "WriteFile(%s): AsAbsoluteWindowsPath", filename.c_str());
     return false;
   }
 
@@ -717,18 +719,18 @@
 
 int RenameDirectory(const std::string& old_name, const std::string& new_name) {
   wstring wold_name;
-  if (!AsWindowsPathWithUncPrefix(old_name, &wold_name)) {
+  if (!AsAbsoluteWindowsPath(old_name, &wold_name)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "RenameDirectory(%s, %s): AsWindowsPathWithUncPrefix(%s)",
-         old_name.c_str(), new_name.c_str(), old_name.c_str());
+         "RenameDirectory(%s, %s): AsAbsoluteWindowsPath(%s)", old_name.c_str(),
+         new_name.c_str(), old_name.c_str());
     return kRenameDirectoryFailureOtherError;
   }
 
   wstring wnew_name;
-  if (!AsWindowsPathWithUncPrefix(new_name, &wnew_name)) {
+  if (!AsAbsoluteWindowsPath(new_name, &wnew_name)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "RenameDirectory(%s, %s): AsWindowsPathWithUncPrefix(%s)",
-         old_name.c_str(), new_name.c_str(), new_name.c_str());
+         "RenameDirectory(%s, %s): AsAbsoluteWindowsPath(%s)", old_name.c_str(),
+         new_name.c_str(), new_name.c_str());
     return kRenameDirectoryFailureOtherError;
   }
 
@@ -767,9 +769,9 @@
   }
 
   wstring wpath;
-  if (!AsWindowsPathWithUncPrefix(file_path, &wpath)) {
+  if (!AsAbsoluteWindowsPath(file_path, &wpath)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "UnlinkPath(%s): AsWindowsPathWithUncPrefix", file_path.c_str());
+         "UnlinkPath(%s): AsAbsoluteWindowsPath", file_path.c_str());
     return false;
   }
   return UnlinkPathW(wpath);
@@ -899,9 +901,9 @@
 
 bool ReadDirectorySymlink(const string& name, string* result) {
   wstring wname;
-  if (!AsWindowsPathWithUncPrefix(name, &wname)) {
+  if (!AsAbsoluteWindowsPath(name, &wname)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "ReadDirectorySymlink(%s): AsWindowsPathWithUncPrefix", name.c_str());
+         "ReadDirectorySymlink(%s): AsAbsoluteWindowsPath", name.c_str());
     return false;
   }
   unique_ptr<WCHAR[]> result_ptr;
@@ -920,9 +922,9 @@
     return true;
   }
   wstring wpath;
-  if (!AsWindowsPathWithUncPrefix(path, &wpath)) {
+  if (!AsAbsoluteWindowsPath(path, &wpath)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "PathExists(%s): AsWindowsPathWithUncPrefix", path.c_str());
+         "PathExists(%s): AsAbsoluteWindowsPath", path.c_str());
     return false;
   }
   return JunctionResolver().Resolve(wpath.c_str(), nullptr);
@@ -936,9 +938,9 @@
   if (path == nullptr || path[0] == 0) {
     return "";
   }
-  if (!AsWindowsPathWithUncPrefix(path, &wpath)) {
+  if (!AsAbsoluteWindowsPath(path, &wpath)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "MakeCanonical(%s): AsWindowsPathWithUncPrefix", path);
+         "MakeCanonical(%s): AsAbsoluteWindowsPath", path);
   }
 
   // Resolve all segments of the path. Do this from leaf to root, so we always
@@ -1039,9 +1041,9 @@
 
 bool CanReadFile(const std::string& path) {
   wstring wpath;
-  if (!AsWindowsPathWithUncPrefix(path, &wpath)) {
+  if (!AsAbsoluteWindowsPath(path, &wpath)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "CanReadFile(%s): AsWindowsPathWithUncPrefix", path.c_str());
+         "CanReadFile(%s): AsAbsoluteWindowsPath", path.c_str());
     return false;
   }
   return CanReadFileW(wpath);
@@ -1049,9 +1051,9 @@
 
 bool CanExecuteFile(const std::string& path) {
   wstring wpath;
-  if (!AsWindowsPathWithUncPrefix(path, &wpath)) {
+  if (!AsAbsoluteWindowsPath(path, &wpath)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "CanExecuteFile(%s): AsWindowsPathWithUncPrefix", path.c_str());
+         "CanExecuteFile(%s): AsAbsoluteWindowsPath", path.c_str());
     return false;
   }
   return CanReadFileW(wpath) && (ends_with(wpath, wstring(L".exe")) ||
@@ -1062,9 +1064,9 @@
 
 bool CanAccessDirectory(const std::string& path) {
   wstring wpath;
-  if (!AsWindowsPathWithUncPrefix(path, &wpath)) {
+  if (!AsAbsoluteWindowsPath(path, &wpath)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "CanAccessDirectory(%s): AsWindowsPathWithUncPrefix", path.c_str());
+         "CanAccessDirectory(%s): AsAbsoluteWindowsPath", path.c_str());
     return false;
   }
   DWORD attr = ::GetFileAttributesW(wpath.c_str());
@@ -1125,9 +1127,9 @@
     return false;
   }
   wstring wpath;
-  if (!AsWindowsPathWithUncPrefix(path, &wpath)) {
+  if (!AsAbsoluteWindowsPath(path, &wpath)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "IsDirectory(%s): AsWindowsPathWithUncPrefix", path.c_str());
+         "IsDirectory(%s): AsAbsoluteWindowsPath", path.c_str());
     return false;
   }
   return IsDirectoryW(wpath);
@@ -1176,9 +1178,9 @@
   wstring wpath;
   // According to MSDN, CreateDirectory's limit without the UNC prefix is
   // 248 characters (so it could fit another filename before reaching MAX_PATH).
-  if (!AsWindowsPathWithUncPrefix(path, &wpath, 248)) {
+  if (!AsAbsoluteWindowsPath(path, &wpath)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
-         "MakeDirectories(%s): AsWindowsPathWithUncPrefix", path.c_str());
+         "MakeDirectories(%s): AsAbsoluteWindowsPath", path.c_str());
     return false;
   }
   return MakeDirectoriesW(wpath);
diff --git a/src/test/cpp/util/file_windows_test.cc b/src/test/cpp/util/file_windows_test.cc
index cab1947..496c24d 100644
--- a/src/test/cpp/util/file_windows_test.cc
+++ b/src/test/cpp/util/file_windows_test.cc
@@ -23,6 +23,7 @@
 #include "src/main/cpp/util/file.h"
 #include "src/main/cpp/util/file_platform.h"
 #include "src/main/native/windows/file.h"
+#include "src/main/native/windows/util.h"
 #include "src/test/cpp/util/test_util.h"
 #include "src/test/cpp/util/windows_test_util.h"
 
@@ -244,6 +245,29 @@
   ASSERT_EQ(wlongpath, actual);
 }
 
+TEST_F(FileWindowsTest, TestAsAbsoluteWindowsPath) {
+  SetEnvironmentVariableA("BAZEL_SH", "c:\\some\\long/path\\bin\\bash.exe");
+  ResetMsysRootForTesting();
+  wstring actual;
+
+  ASSERT_TRUE(AsAbsoluteWindowsPath("c:/", &actual));
+  ASSERT_EQ(L"\\\\?\\c:\\", actual);
+
+  ASSERT_TRUE(AsAbsoluteWindowsPath("c:/..\\non-existent//", &actual));
+  ASSERT_EQ(L"\\\\?\\c:\\non-existent", actual);
+
+  WCHAR cwd[MAX_PATH];
+  ASSERT_TRUE(::GetCurrentDirectoryW(MAX_PATH, cwd));
+  ASSERT_FALSE(bazel::windows::HasUncPrefix(cwd));
+  wstring cwdw(cwd);
+  ASSERT_EQ(cwdw.find_first_of(L'/'), wstring::npos);
+  wstring expected =
+      wstring(L"\\\\?\\") + cwdw +
+      ((cwdw.back() == L'\\') ? L"non-existent" : L"\\non-existent");
+  ASSERT_TRUE(AsAbsoluteWindowsPath("non-existent", &actual));
+  ASSERT_EQ(expected, actual);
+}
+
 TEST_F(FileWindowsTest, TestAsShortWindowsPath) {
   string actual;
   ASSERT_TRUE(AsShortWindowsPath("/dev/null", &actual));
@@ -516,7 +540,7 @@
   // Create a dummy file: $TEST_TMPDIR/directory/subdirectory/foo.txt
   string foo(JoinPath(dir2, "foo.txt"));
   wstring wfoo;
-  EXPECT_TRUE(AsWindowsPathWithUncPrefix(foo, &wfoo));
+  EXPECT_TRUE(AsAbsoluteWindowsPath(foo, &wfoo));
   EXPECT_TRUE(CreateDummyFile(wfoo));
   EXPECT_TRUE(CanReadFile(foo));
   // Create junctions next to directory and subdirectory, pointing to them.
diff --git a/third_party/ijar/mapped_file_windows.cc b/third_party/ijar/mapped_file_windows.cc
index 253065e..abc93c2 100644
--- a/third_party/ijar/mapped_file_windows.cc
+++ b/third_party/ijar/mapped_file_windows.cc
@@ -45,9 +45,8 @@
   errmsg_ = errmsg;
 
   wstring wname;
-  if (!blaze_util::AsWindowsPathWithUncPrefix(name, &wname)) {
-    blaze_util::pdie(255, "MappedInputFile(%s): AsWindowsPathWithUncPrefix",
-                     name);
+  if (!blaze_util::AsAbsoluteWindowsPath(name, &wname)) {
+    blaze_util::pdie(255, "MappedInputFile(%s): AsAbsoluteWindowsPath", name);
   }
   HANDLE file = CreateFileW(wname.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
                             OPEN_EXISTING, 0, NULL);
@@ -120,9 +119,8 @@
   errmsg_ = errmsg;
 
   wstring wname;
-  if (!blaze_util::AsWindowsPathWithUncPrefix(name, &wname)) {
-    blaze_util::pdie(255, "MappedOutputFile(%s): AsWindowsPathWithUncPrefix",
-                     name);
+  if (!blaze_util::AsAbsoluteWindowsPath(name, &wname)) {
+    blaze_util::pdie(255, "MappedOutputFile(%s): AsAbsoluteWindowsPath", name);
   }
   HANDLE file = CreateFileW(wname.c_str(), GENERIC_READ | GENERIC_WRITE, 0,
                             NULL, CREATE_ALWAYS, 0, NULL);
diff --git a/third_party/ijar/platform_utils.cc b/third_party/ijar/platform_utils.cc
index a998cab..dde8868 100644
--- a/third_party/ijar/platform_utils.cc
+++ b/third_party/ijar/platform_utils.cc
@@ -38,8 +38,8 @@
 bool stat_file(const char* path, Stat* result) {
 #if defined(COMPILER_MSVC) || defined(__CYGWIN__)
   std::wstring wpath;
-  if (!blaze_util::AsWindowsPathWithUncPrefix(path, &wpath)) {
-    blaze_util::pdie(255, "stat_file: AsWindowsPathWithUncPrefix(%s)", path);
+  if (!blaze_util::AsAbsoluteWindowsPath(path, &wpath)) {
+    blaze_util::pdie(255, "stat_file: AsAbsoluteWindowsPath(%s)", path);
   }
   bool success = false;
   BY_HANDLE_FILE_INFORMATION info;