Make blaze_util::AsAbsoluteWindowsPath support wstring as input

Now we have:
bool AsAbsoluteWindowsPath(const std::wstring& path, std::wstring* result, std::string* error);

This change helps making the C++ native launcher work with UTF-16.

See https://github.com/bazelbuild/bazel/issues/4473

Closes #5406.

Change-Id: I7eaf55f9fe5a4d41e3dd09edc2a21e9b3cc9277c
PiperOrigin-RevId: 201352866
diff --git a/src/main/cpp/util/BUILD b/src/main/cpp/util/BUILD
index 6f53b4d..eed2a60 100644
--- a/src/main/cpp/util/BUILD
+++ b/src/main/cpp/util/BUILD
@@ -107,7 +107,10 @@
     srcs = ["logging.cc"],
     hdrs = ["logging.h"],
     visibility = ["//visibility:public"],
-    deps = [":blaze_exit_code"],
+    deps = [
+        ":blaze_exit_code",
+        ":strings",
+    ],
 )
 
 cc_library(
diff --git a/src/main/cpp/util/logging.cc b/src/main/cpp/util/logging.cc
index 40a54ae..c2e7565 100644
--- a/src/main/cpp/util/logging.cc
+++ b/src/main/cpp/util/logging.cc
@@ -21,6 +21,7 @@
 #include <memory>
 
 #include "src/main/cpp/util/exit_code.h"
+#include "src/main/cpp/util/strings.h"
 
 namespace blaze_util {
 
@@ -68,6 +69,12 @@
 DECLARE_STREAM_OPERATOR(double)
 DECLARE_STREAM_OPERATOR(long double)
 DECLARE_STREAM_OPERATOR(void*)
+
+LogMessage& LogMessage::operator<<(const std::wstring& wstr) {
+  message_ << WstringToString(wstr);
+  return *this;
+}
+
 #undef DECLARE_STREAM_OPERATOR
 
 void LogMessage::Finish() {
diff --git a/src/main/cpp/util/logging.h b/src/main/cpp/util/logging.h
index b2cd459..7346c18 100644
--- a/src/main/cpp/util/logging.h
+++ b/src/main/cpp/util/logging.h
@@ -53,6 +53,7 @@
              int exit_code);
 
   LogMessage& operator<<(const std::string& value);
+  LogMessage& operator<<(const std::wstring& wstr);
   LogMessage& operator<<(const char* value);
   LogMessage& operator<<(char value);
   LogMessage& operator<<(bool value);
diff --git a/src/main/cpp/util/path_platform.h b/src/main/cpp/util/path_platform.h
index 3c4671f..e2efe09 100644
--- a/src/main/cpp/util/path_platform.h
+++ b/src/main/cpp/util/path_platform.h
@@ -77,9 +77,6 @@
 
 bool IsRootDirectoryW(const std::wstring &path);
 
-bool AsWindowsPath(const std::string &path, std::string *result,
-                   std::string *error);
-
 // Returns a normalized form of the input `path`.
 //
 // `path` must be a relative or absolute Windows path, it may use "/" instead of
@@ -95,7 +92,14 @@
 // "foo".
 //
 // Visible for testing, would be static otherwise.
-std::string NormalizeWindowsPath(std::string path);
+template <typename char_type>
+std::basic_string<char_type> NormalizeWindowsPath(
+    std::basic_string<char_type> path);
+
+template <typename char_type>
+std::basic_string<char_type> NormalizeWindowsPath(const char_type *path) {
+  return NormalizeWindowsPath(std::basic_string<char_type>(path));
+}
 
 // Converts a UTF8-encoded `path` to a normalized, widechar Windows path.
 //
@@ -116,8 +120,32 @@
 bool AsWindowsPath(const std::string &path, std::wstring *result,
                    std::string *error);
 
-bool AsAbsoluteWindowsPath(const std::string &path, std::wstring *wpath,
-                           std::string *error);
+template <typename char_type>
+bool AsWindowsPath(const std::basic_string<char_type> &path,
+                   std::basic_string<char_type> *result, std::string *error);
+
+template <typename char_type>
+bool AsWindowsPath(const char_type *path, std::basic_string<char_type> *result,
+                   std::string *error) {
+  return AsWindowsPath(std::basic_string<char_type>(path), result, error);
+}
+
+template <typename char_type>
+bool AsAbsoluteWindowsPath(const std::basic_string<char_type> &path,
+                           std::wstring *result, std::string *error);
+
+template <typename char_type>
+bool AsAbsoluteWindowsPath(const char_type *path, std::wstring *result,
+                           std::string *error) {
+  return AsAbsoluteWindowsPath(std::basic_string<char_type>(path), result,
+                               error);
+}
+
+// Explicit instantiate AsAbsoluteWindowsPath for char and wchar_t.
+template bool AsAbsoluteWindowsPath<char>(const char *, std::wstring *,
+                                          std::string *);
+template bool AsAbsoluteWindowsPath<wchar_t>(const wchar_t *, std::wstring *,
+                                             std::string *);
 
 // 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/path_windows.cc b/src/main/cpp/util/path_windows.cc
index 15dd0dd..d115b6e 100644
--- a/src/main/cpp/util/path_windows.cc
+++ b/src/main/cpp/util/path_windows.cc
@@ -215,14 +215,19 @@
   return SplitPathImpl(path);
 }
 
-bool AsWindowsPath(const std::string& path, std::string* result,
-                   std::string* error) {
+void assignNUL(std::string* s) { s->assign("NUL"); }
+
+void assignNUL(std::wstring* s) { s->assign(L"NUL"); }
+
+template <typename char_type>
+bool AsWindowsPath(const std::basic_string<char_type>& path,
+                   std::basic_string<char_type>* result, std::string* error) {
   if (path.empty()) {
     result->clear();
     return true;
   }
   if (IsDevNull(path.c_str())) {
-    result->assign("NUL");
+    assignNUL(result);
     return true;
   }
   if (HasUncPrefix(path.c_str())) {
@@ -247,7 +252,7 @@
     return false;
   }
 
-  std::string mutable_path = path;
+  std::basic_string<char_type> mutable_path = path;
   if (path[0] == '/') {
     if (error) {
       *error = "Unix-style paths are unsupported";
@@ -257,9 +262,10 @@
 
   if (path[0] == '\\') {
     // This is an absolute Windows path on the current drive, e.g. "\foo\bar".
-    mutable_path = std::string(1, GetCurrentDrive()) + ":" + path;
+    std::basic_string<char_type> drive(1, GetCurrentDrive());
+    drive.push_back(':');
+    mutable_path = drive + path;
   }  // otherwise this is a relative path, or absolute Windows path.
-
   result->assign(NormalizeWindowsPath(mutable_path));
   return true;
 }
@@ -275,8 +281,9 @@
   return true;
 }
 
-bool AsAbsoluteWindowsPath(const std::string& path, std::wstring* result,
-                           std::string* error) {
+template <typename char_type>
+bool AsAbsoluteWindowsPath(const std::basic_string<char_type>& path,
+                           std::wstring* result, std::string* error) {
   if (path.empty()) {
     result->clear();
     return true;
@@ -373,6 +380,14 @@
            (path[2] == 'L' || path[2] == 'l') && path[3] == 0));
 }
 
+bool IsDevNull(const wchar_t* path) {
+  return path != NULL && *path != 0 &&
+         (wcsncmp(L"/dev/null\0", path, 10) == 0 ||
+          ((path[0] == L'N' || path[0] == L'n') &&
+           (path[1] == L'U' || path[1] == L'u') &&
+           (path[2] == L'L' || path[2] == L'l') && path[3] == 0));
+}
+
 bool IsRootDirectory(const std::string& path) {
   return IsRootOrAbsolute(path, true);
 }
@@ -392,9 +407,11 @@
   return 'a' + wdrive - offset;
 }
 
-std::string NormalizeWindowsPath(std::string path) {
+template <typename char_type>
+std::basic_string<char_type> NormalizeWindowsPath(
+    std::basic_string<char_type> path) {
   if (path.empty()) {
-    return "";
+    return std::basic_string<char_type>();
   }
   if (path[0] == '/') {
     // This is an absolute MSYS path, error out.
@@ -405,10 +422,10 @@
     path = path.substr(4);
   }
 
-  static const std::string dot(".");
-  static const std::string dotdot("..");
+  static const std::basic_string<char_type> dot(1, '.');
+  static const std::basic_string<char_type> dotdot(2, '.');
 
-  std::vector<std::string> segments;
+  std::vector<std::basic_string<char_type>> segments;
   int segment_start = -1;
   // Find the path segments in `path` (separated by "/").
   for (int i = 0;; ++i) {
@@ -421,7 +438,8 @@
     } else if (segment_start >= 0 && i > segment_start) {
       // The current character is "/" or "\0", so this ends a segment.
       // Add that to `segments` if there's anything to add; handle "." and "..".
-      std::string segment(path, segment_start, i - segment_start);
+      std::basic_string<char_type> segment(path, segment_start,
+                                           i - segment_start);
       segment_start = -1;
       if (segment == dotdot) {
         if (!segments.empty() &&
@@ -441,12 +459,13 @@
   // form of it, e.g. "c:\..").
   if (segments.size() == 1 && segments[0].size() == 2 &&
       HasDriveSpecifierPrefix(segments[0].c_str())) {
-    return segments[0] + '\\';
+    segments[0].push_back('\\');
+    return segments[0];
   }
 
   // Join all segments.
   bool first = true;
-  std::ostringstream result;
+  std::basic_ostringstream<char_type> result;
   for (const auto& s : segments) {
     if (!first) {
       result << '\\';
diff --git a/src/test/cpp/util/path_windows_test.cc b/src/test/cpp/util/path_windows_test.cc
index c78c4ab..bcf6262 100644
--- a/src/test/cpp/util/path_windows_test.cc
+++ b/src/test/cpp/util/path_windows_test.cc
@@ -41,9 +41,6 @@
 using std::unique_ptr;
 using std::wstring;
 
-// Methods defined in path_windows.cc that are only visible for testing.
-string NormalizeWindowsPath(string path);
-
 TEST(PathWindowsTest, TestNormalizeWindowsPath) {
   ASSERT_EQ(string(""), NormalizeWindowsPath(""));
   ASSERT_EQ(string(""), NormalizeWindowsPath("."));
@@ -56,6 +53,19 @@
   ASSERT_EQ(string("c:\\"), NormalizeWindowsPath("c:/"));
   ASSERT_EQ(string("c:\\"), NormalizeWindowsPath("c:\\"));
   ASSERT_EQ(string("c:\\foo\\bar"), NormalizeWindowsPath("c:\\..//foo/./bar/"));
+
+  ASSERT_EQ(wstring(L""), NormalizeWindowsPath(L""));
+  ASSERT_EQ(wstring(L""), NormalizeWindowsPath(L"."));
+  ASSERT_EQ(wstring(L"foo"), NormalizeWindowsPath(L"foo"));
+  ASSERT_EQ(wstring(L"foo"), NormalizeWindowsPath(L"foo/"));
+  ASSERT_EQ(wstring(L"foo\\bar"), NormalizeWindowsPath(L"foo//bar"));
+  ASSERT_EQ(wstring(L"foo\\bar"), NormalizeWindowsPath(L"../..//foo/./bar"));
+  ASSERT_EQ(wstring(L"foo\\bar"), NormalizeWindowsPath(L"../foo/baz/../bar"));
+  ASSERT_EQ(wstring(L"c:\\"), NormalizeWindowsPath(L"c:"));
+  ASSERT_EQ(wstring(L"c:\\"), NormalizeWindowsPath(L"c:/"));
+  ASSERT_EQ(wstring(L"c:\\"), NormalizeWindowsPath(L"c:\\"));
+  ASSERT_EQ(wstring(L"c:\\foo\\bar"),
+            NormalizeWindowsPath(L"c:\\..//foo/./bar/"));
 }
 
 TEST(PathWindowsTest, TestDirname) {
@@ -201,9 +211,16 @@
   ASSERT_TRUE(AsAbsoluteWindowsPath("c:/", &actual, nullptr));
   ASSERT_EQ(L"\\\\?\\c:\\", actual);
 
+  ASSERT_TRUE(AsAbsoluteWindowsPath(L"c:/", &actual, nullptr));
+  ASSERT_EQ(L"\\\\?\\c:\\", actual);
+
   ASSERT_TRUE(AsAbsoluteWindowsPath("c:/..\\non-existent//", &actual, nullptr));
   ASSERT_EQ(L"\\\\?\\c:\\non-existent", actual);
 
+  ASSERT_TRUE(
+      AsAbsoluteWindowsPath(L"c:/..\\non-existent//", &actual, nullptr));
+  ASSERT_EQ(L"\\\\?\\c:\\non-existent", actual);
+
   WCHAR cwd[MAX_PATH];
   wstring cwdw(CstringToWstring(GetCwd().c_str()).get());
   wstring expected =
@@ -211,6 +228,9 @@
       ((cwdw.back() == L'\\') ? L"non-existent" : L"\\non-existent");
   ASSERT_TRUE(AsAbsoluteWindowsPath("non-existent", &actual, nullptr));
   ASSERT_EQ(actual, expected);
+
+  ASSERT_TRUE(AsAbsoluteWindowsPath(L"non-existent", &actual, nullptr));
+  ASSERT_EQ(actual, expected);
 }
 
 TEST(PathWindowsTest, TestAsShortWindowsPath) {