Windows, JNI: allow empty cwd for processes

More specifically, change windows_util.AsShortPath
to accept empty inputs, as well as paths with
forward slashes.

Also output more accurate error messages for bad
input paths than before.

This fixes //src/test/java/com/google/devtools/build/lib:windows-tests
but not //src/test/java/com/google/devtools/build/lib:standalone-tests

--
PiperOrigin-RevId: 149399449
MOS_MIGRATED_REVID=149399449
diff --git a/src/main/native/windows_util.cc b/src/main/native/windows_util.cc
index cc0d029..7d82002 100644
--- a/src/main/native/windows_util.cc
+++ b/src/main/native/windows_util.cc
@@ -16,6 +16,7 @@
 #include <stdlib.h>
 #include <windows.h>
 
+#include <algorithm>
 #include <functional>
 #include <memory>
 #include <string>
@@ -64,32 +65,44 @@
   *result = string("\"") + path + "\"";
 }
 
-string AsShortPath(const string& path, function<wstring()> path_as_wstring,
+static bool IsSeparator(char c) { return c == '/' || c == '\\'; }
+
+static bool HasSeparator(const string& s) {
+  return s.find_first_of('/') != string::npos ||
+         s.find_first_of('\\') != string::npos;
+}
+
+static bool Contains(const string& s, const char* substr) {
+  return s.find(substr) != string::npos;
+}
+
+string AsShortPath(string path, function<wstring()> path_as_wstring,
                    string* result) {
   if (path.empty()) {
-    return string("argv[0] should not be empty");
+    result->clear();
+    return "";
   }
   if (path[0] == '"') {
-    return string("argv[0] should not be quoted");
+    return string("path should not be quoted");
   }
-  if (path[0] == '\\' ||                 // absolute, but without drive letter
-      path.find("/") != string::npos ||  // has "/"
-      path.find("\\.\\") != string::npos ||   // not normalized
-      path.find("\\..\\") != string::npos ||  // not normalized
-      // at least MAX_PATH long, but just a file name
-      (path.size() >= MAX_PATH && path.find_first_of('\\') == string::npos) ||
-      // not just a file name, but also not absolute
-      (path.find_first_of('\\') != string::npos &&
-       !(isalpha(path[0]) && path[1] == ':' && path[2] == '\\'))) {
-    return string("argv[0]='" + path +
-                  "'; should have been either an absolute, "
-                  "normalized, Windows-style path with drive letter (e.g. "
-                  "'c:\\foo\\bar.exe'), or just a file name (e.g. "
-                  "'cmd.exe') shorter than MAX_PATH.");
+  if (IsSeparator(path[0])) {
+    return string("path='") + path + "' is absolute";
+  }
+  if (Contains(path, "/./") || Contains(path, "\\.\\") ||
+      Contains(path, "/..") || Contains(path, "\\..")) {
+    return string("path='") + path + "' is not normalized";
+  }
+  if (path.size() >= MAX_PATH && !HasSeparator(path)) {
+    return string("path='") + path + "' is just a file name but too long";
+  }
+  if (HasSeparator(path) &&
+      !(isalpha(path[0]) && path[1] == ':' && IsSeparator(path[2]))) {
+    return string("path='") + path + "' is not an absolute path";
   }
   // At this point we know the path is either just a file name (shorter than
   // MAX_PATH), or an absolute, normalized, Windows-style path (of any length).
 
+  std::replace(path.begin(), path.end(), '/', '\\');
   // Fast-track: the path is already short.
   if (path.size() < MAX_PATH) {
     *result = path;
@@ -142,6 +155,9 @@
 string AsExecutablePathForCreateProcess(const string& path,
                                         function<wstring()> path_as_wstring,
                                         string* result) {
+  if (path.empty()) {
+    return string("path should not be empty");
+  }
   string error = AsShortPath(path, path_as_wstring, result);
   if (error.empty()) {
     // Quote the path in case it's something like "c:\foo\app name.exe".
diff --git a/src/main/native/windows_util.h b/src/main/native/windows_util.h
index ea632e7..d34211f 100644
--- a/src/main/native/windows_util.h
+++ b/src/main/native/windows_util.h
@@ -49,7 +49,7 @@
 string GetLastErrorString(const string& cause);
 
 // Same as `AsExecutablePathForCreateProcess` except it won't quote the result.
-string AsShortPath(const string& path, function<wstring()> path_as_wstring,
+string AsShortPath(string path, function<wstring()> path_as_wstring,
                    string* result);
 
 // Computes a path suitable as the executable part in CreateProcessA's cmdline.