Windows, refactor: move WindowsEscapeArg function

Move this function from the native
py/sh/java_binary launcher into the windows JNI
library.

The latter is slowly becoming Bazel's and its
tools' de-facto base library, so it's the right
place for this function.

Follow-up to https://github.com/bazelbuild/bazel/commit/da557f96c697102ad787e57bbf7db2460f6a60a8

Closes #9131.

PiperOrigin-RevId: 262540258
diff --git a/src/BUILD b/src/BUILD
index 795d830..500c9cd 100644
--- a/src/BUILD
+++ b/src/BUILD
@@ -446,7 +446,7 @@
         "//src/test/cpp:srcs",
         "//src/test/gen:srcs",
         "//src/test/res:srcs",
-        "//src/test/native:srcs",
+        "//src/test/native/windows:srcs",
         "//src/test/starlark:srcs",
         "//src/test/java/com/google/devtools/build/android:srcs",
         "//src/test/java/com/google/devtools/build/docgen:srcs",
@@ -510,7 +510,7 @@
         "//src/test/java/com/google/devtools/build/lib:all_windows_tests",
         "//src/test/java/com/google/devtools/build/skyframe:all_windows_tests",
         "//src/test/java/com/google/devtools/common/options:all_windows_tests",
-        "//src/test/native:all_windows_tests",
+        "//src/test/native/windows:all_windows_tests",
         "//src/test/py/bazel:all_windows_tests",
         "//src/test/res:all_windows_tests",
         "//src/test/shell:all_windows_tests",
diff --git a/src/main/cpp/BUILD b/src/main/cpp/BUILD
index 9e18a2b..7de595d 100644
--- a/src/main/cpp/BUILD
+++ b/src/main/cpp/BUILD
@@ -64,7 +64,6 @@
         "//src/main/cpp/util:logging",
     ] + select({
         "//src/conditions:windows": [
-            "//src/tools/launcher/util",
             "//src/main/native/windows:lib-file",
             "//src/main/native/windows:lib-process",
         ],
diff --git a/src/main/cpp/blaze_util_windows.cc b/src/main/cpp/blaze_util_windows.cc
index a2751d3..cc14a4a 100644
--- a/src/main/cpp/blaze_util_windows.cc
+++ b/src/main/cpp/blaze_util_windows.cc
@@ -52,7 +52,6 @@
 #include "src/main/native/windows/file.h"
 #include "src/main/native/windows/process.h"
 #include "src/main/native/windows/util.h"
-#include "src/tools/launcher/util/launcher_util.h"
 
 namespace blaze {
 
@@ -689,7 +688,7 @@
   wesc_args_vector.reserve(args_vector.size());
   for (const string& a : args_vector) {
     std::wstring wa = blaze_util::CstringToWstring(a.c_str()).get();
-    std::wstring wesc = bazel::launcher::WindowsEscapeArg2(wa);
+    std::wstring wesc = bazel::windows::WindowsEscapeArg(wa);
     wesc_args_vector.push_back(wesc);
   }
 
@@ -774,7 +773,7 @@
   wargs.reserve(server_jvm_args.size());
   for (const string& a : server_jvm_args) {
     std::wstring wa = blaze_util::CstringToWstring(a.c_str()).get();
-    std::wstring wesc = bazel::launcher::WindowsEscapeArg2(wa);
+    std::wstring wesc = bazel::windows::WindowsEscapeArg(wa);
     wargs.push_back(wesc);
   }
 
diff --git a/src/main/cpp/util/BUILD b/src/main/cpp/util/BUILD
index c1094da..faafd9b 100644
--- a/src/main/cpp/util/BUILD
+++ b/src/main/cpp/util/BUILD
@@ -58,6 +58,7 @@
         ":ijar",
         "//src/main/tools:__pkg__",
         "//src/test/cpp/util:__pkg__",
+        "//src/test/native/windows:__pkg__",
         "//src/tools/launcher:__subpackages__",
         "//src/tools/singlejar:__pkg__",
         "//third_party/def_parser:__pkg__",
diff --git a/src/main/native/windows/BUILD b/src/main/native/windows/BUILD
index 7b9c273..931db45 100644
--- a/src/main/native/windows/BUILD
+++ b/src/main/native/windows/BUILD
@@ -45,6 +45,8 @@
     hdrs = ["process.h"],
     visibility = [
         "//src/main/cpp:__pkg__",
+        "//src/test/native:__subpackages__",
+        "//src/tools/launcher:__pkg__",
         "//tools/test:__pkg__",
     ],
     deps = [":lib-file"],
diff --git a/src/main/native/windows/process.cc b/src/main/native/windows/process.cc
index c863907..5f24c16 100644
--- a/src/main/native/windows/process.cc
+++ b/src/main/native/windows/process.cc
@@ -367,5 +367,95 @@
   return true;
 }
 
+// Escape arguments for CreateProcessW.
+//
+// This algorithm is based on information found in
+// http://daviddeley.com/autohotkey/parameters/parameters.htm
+//
+// The following source specifies a similar algorithm:
+// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
+// unfortunately I found this algorithm only after creating the one below, but
+// fortunately they seem to do the same.
+std::wstring WindowsEscapeArg(const std::wstring& s) {
+  if (s.empty()) {
+    return L"\"\"";
+  } else {
+    bool needs_escaping = false;
+    for (const auto& c : s) {
+      if (c == ' ' || c == '"') {
+        needs_escaping = true;
+        break;
+      }
+    }
+    if (!needs_escaping) {
+      return s;
+    }
+  }
+
+  std::wostringstream result;
+  result << L'"';
+  int start = 0;
+  for (int i = 0; i < s.size(); ++i) {
+    char c = s[i];
+    if (c == '"' || c == '\\') {
+      // Copy the segment since the last special character.
+      if (start >= 0) {
+        result << s.substr(start, i - start);
+        start = -1;
+      }
+
+      // Handle the current special character.
+      if (c == '"') {
+        // This is a quote character. Escape it with a single backslash.
+        result << L"\\\"";
+      } else {
+        // This is a backslash (or the first one in a run of backslashes).
+        // Whether we escape it depends on whether the run ends with a quote.
+        int run_len = 1;
+        int j = i + 1;
+        while (j < s.size() && s[j] == '\\') {
+          run_len++;
+          j++;
+        }
+        if (j == s.size()) {
+          // The run of backslashes goes to the end.
+          // We have to escape every backslash with another backslash.
+          for (int k = 0; k < run_len * 2; ++k) {
+            result << L'\\';
+          }
+          break;
+        } else if (j < s.size() && s[j] == '"') {
+          // The run of backslashes is terminated by a quote.
+          // We have to escape every backslash with another backslash, and
+          // escape the quote with one backslash.
+          for (int k = 0; k < run_len * 2; ++k) {
+            result << L'\\';
+          }
+          result << L"\\\"";
+          i += run_len;  // 'i' is also increased in the loop iteration step
+        } else {
+          // No quote found. Each backslash counts for itself, they must not be
+          // escaped.
+          for (int k = 0; k < run_len; ++k) {
+            result << L'\\';
+          }
+          i += run_len - 1;  // 'i' is also increased in the loop iteration step
+        }
+      }
+    } else {
+      // This is not a special character. Start the segment if necessary.
+      if (start < 0) {
+        start = i;
+      }
+    }
+  }
+  // Save final segment after the last special character.
+  if (start != -1) {
+    result << s.substr(start);
+  }
+  result << L'"';
+  return result.str();
+}
+
 }  // namespace windows
 }  // namespace bazel
diff --git a/src/main/native/windows/process.h b/src/main/native/windows/process.h
index 0f43eda..cceb780 100644
--- a/src/main/native/windows/process.h
+++ b/src/main/native/windows/process.h
@@ -67,6 +67,13 @@
   DWORD pid_, exit_code_;
 };
 
+// Escape a command line argument using Windows escaping syntax.
+//
+// This escaping lets us safely pass arguments to subprocesses created with
+// CreateProcessW. (The escaping rules are a bit complex, look at the function
+// implementation.)
+std::wstring WindowsEscapeArg(const std::wstring& arg);
+
 }  // namespace windows
 }  // namespace bazel
 
diff --git a/src/test/native/BUILD b/src/test/native/BUILD
deleted file mode 100644
index 868270d..0000000
--- a/src/test/native/BUILD
+++ /dev/null
@@ -1,50 +0,0 @@
-# Description:
-#   C++ utility tests for Bazel
-package(default_visibility = ["//visibility:public"])
-
-load("@rules_cc//cc:defs.bzl", "cc_test")
-
-filegroup(
-    name = "srcs",
-    srcs = glob(["**"]),
-    visibility = ["//src:__pkg__"],
-)
-
-cc_test(
-    name = "windows_jni_test",
-    size = "small",
-    srcs = select({
-        "//src/conditions:windows": [
-            "windows/util_test.cc",
-            "windows/file_test.cc",
-        ],
-        "//conditions:default": ["dummy_test.cc"],
-    }),
-    deps = select({
-        "//src/conditions:windows": [
-            "//src/main/native/windows:lib-file",
-            "//src/test/cpp/util:windows_test_util",
-            "@com_google_googletest//:gtest_main",
-        ],
-        "//conditions:default": [],
-    }),
-)
-
-test_suite(name = "all_tests")
-
-test_suite(
-    name = "windows_tests",
-    tags = [
-        "-no_windows",
-        "-slow",
-    ],
-    visibility = ["//visibility:private"],
-)
-
-test_suite(
-    name = "all_windows_tests",
-    tests = [
-        ":windows_tests",
-    ],
-    visibility = ["//src:__pkg__"],
-)
diff --git a/src/test/native/windows/BUILD b/src/test/native/windows/BUILD
new file mode 100644
index 0000000..dce5090
--- /dev/null
+++ b/src/test/native/windows/BUILD
@@ -0,0 +1,94 @@
+filegroup(
+    name = "srcs",
+    srcs = glob(["**"]),
+    visibility = ["//src:__pkg__"],
+)
+
+cc_binary(
+    name = "printarg",
+    testonly = 1,
+    srcs = ["printarg.cc"],
+)
+
+cc_library(
+    name = "test_deps",
+    testonly = 1,
+    deps = select({
+        "//src/conditions:windows": [
+            "//src/test/cpp/util:windows_test_util",
+            "@com_google_googletest//:gtest_main",
+        ],
+        "//conditions:default": [],
+    }),
+)
+
+cc_test(
+    name = "file_test",
+    size = "small",
+    srcs = select({
+        "//src/conditions:windows": ["file_test.cc"],
+        "//conditions:default": ["dummy_test.cc"],
+    }),
+    deps = select({
+        "//src/conditions:windows": [
+            "//src/main/native/windows:lib-file",
+            ":test_deps",
+        ],
+        "//conditions:default": [],
+    }),
+)
+
+cc_test(
+    name = "util_test",
+    size = "small",
+    srcs = select({
+        "//src/conditions:windows": ["util_test.cc"],
+        "//conditions:default": ["dummy_test.cc"],
+    }),
+    deps = select({
+        "//src/conditions:windows": [
+            "//src/main/native/windows:lib-file",
+            ":test_deps",
+        ],
+        "//conditions:default": [],
+    }),
+)
+
+cc_test(
+    name = "process_test",
+    size = "small",
+    srcs = select({
+        "//src/conditions:windows": ["process_test.cc"],
+        "//conditions:default": ["dummy_test.cc"],
+    }),
+    data = [":printarg"],
+    deps = select({
+        "//src/conditions:windows": [
+            ":test_deps",
+            "//src/main/native/windows:lib-file",
+            "//src/main/native/windows:lib-process",
+            "//src/main/cpp/util:filesystem",
+            "@bazel_tools//tools/cpp/runfiles",
+        ],
+        "//conditions:default": [],
+    }),
+)
+
+test_suite(name = "all_tests")
+
+test_suite(
+    name = "windows_tests",
+    tags = [
+        "-no_windows",
+        "-slow",
+    ],
+    visibility = ["//visibility:private"],
+)
+
+test_suite(
+    name = "all_windows_tests",
+    tests = [
+        ":windows_tests",
+    ],
+    visibility = ["//src/test/native:__pkg__"],
+)
diff --git a/src/test/native/dummy_test.cc b/src/test/native/windows/dummy_test.cc
similarity index 100%
rename from src/test/native/dummy_test.cc
rename to src/test/native/windows/dummy_test.cc
diff --git a/src/tools/launcher/util/printarg.cc b/src/test/native/windows/printarg.cc
similarity index 100%
rename from src/tools/launcher/util/printarg.cc
rename to src/test/native/windows/printarg.cc
diff --git a/src/test/native/windows/process_test.cc b/src/test/native/windows/process_test.cc
new file mode 100644
index 0000000..5b18c22
--- /dev/null
+++ b/src/test/native/windows/process_test.cc
@@ -0,0 +1,241 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include "src/main/native/windows/process.h"
+
+#include <wchar.h>
+#include <windows.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/main/cpp/util/path.h"
+#include "src/main/native/windows/util.h"
+#include "tools/cpp/runfiles/runfiles.h"
+
+namespace {
+
+// Asserts argument escaping for subprocesses.
+//
+// For each pair in 'args', this method:
+// 1. asserts that WindowsEscapeArg(pair.first) == pair.second
+// 2. asserts that passing pair.second to a subprocess results in the subprocess
+//    receiving pair.first
+//
+// The method performs the second assertion by running "printarg.exe" (a
+// data-dependency of this test) once for each argument.
+void AssertSubprocessReceivesArgsAsIntended(
+    const std::vector<std::pair<std::wstring, std::wstring> >& args) {
+  // Assert that the WindowsEscapeArg produces what we expect.
+  for (const auto& i : args) {
+    ASSERT_EQ(bazel::windows::WindowsEscapeArg(i.first), i.second);
+  }
+
+  // Create a Runfiles object.
+  std::string error;
+  std::unique_ptr<bazel::tools::cpp::runfiles::Runfiles> runfiles(
+      bazel::tools::cpp::runfiles::Runfiles::CreateForTest(&error));
+  ASSERT_NE(runfiles.get(), nullptr) << error;
+
+  // Look up the path of the printarg.exe utility.
+  std::string printarg =
+      runfiles->Rlocation("io_bazel/src/test/native/windows/printarg.exe");
+  ASSERT_NE(printarg, "");
+
+  // Convert printarg.exe's path to a wchar_t Windows path.
+  std::wstring wprintarg;
+  bool success =
+      blaze_util::AsAbsoluteWindowsPath(printarg, &wprintarg, &error);
+  ASSERT_TRUE(success) << error;
+
+  // SECURITY_ATTRIBUTES for inheritable HANDLEs.
+  SECURITY_ATTRIBUTES sa;
+  sa.nLength = sizeof(sa);
+  sa.lpSecurityDescriptor = NULL;
+  sa.bInheritHandle = TRUE;
+
+  // Open /dev/null that will be redirected into the subprocess' stdin.
+  bazel::windows::AutoHandle devnull(
+      CreateFileW(L"NUL", GENERIC_READ,
+                  FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &sa,
+                  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL));
+  ASSERT_TRUE(devnull.IsValid());
+
+  // Create a pipe that the subprocess' stdout will be redirected to.
+  HANDLE pipe_read_h, pipe_write_h;
+  if (!CreatePipe(&pipe_read_h, &pipe_write_h, &sa, 0x10000)) {
+    DWORD err = GetLastError();
+    ASSERT_EQ(err, 0);
+  }
+  bazel::windows::AutoHandle pipe_read(pipe_read_h), pipe_write(pipe_write_h);
+
+  // Duplicate stderr, where the subprocess' stderr will be redirected to.
+  HANDLE stderr_h;
+  if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_ERROR_HANDLE),
+                       GetCurrentProcess(), &stderr_h, 0, TRUE,
+                       DUPLICATE_SAME_ACCESS)) {
+    DWORD err = GetLastError();
+    ASSERT_EQ(err, 0);
+  }
+  bazel::windows::AutoHandle stderr_dup(stderr_h);
+
+  // Create the attribute object for the process creation. This object describes
+  // exactly which handles the subprocess shall inherit.
+  STARTUPINFOEXW startupInfo;
+  std::unique_ptr<bazel::windows::AutoAttributeList> attrs;
+  std::wstring werror;
+  ASSERT_TRUE(bazel::windows::AutoAttributeList::Create(
+      devnull, pipe_write, stderr_dup, &attrs, &werror));
+  attrs->InitStartupInfoExW(&startupInfo);
+
+  // MSDN says the maximum command line is 32767 characters, with a null
+  // terminator that is exactly 2^15 (= 0x8000).
+  static constexpr size_t kMaxCmdline = 0x8000;
+  wchar_t cmdline[kMaxCmdline];
+
+  // Copy printarg.exe's escaped path into the 'cmdline', and append a space.
+  // We will append arguments to this command line in the for-loop below.
+  wprintarg = bazel::windows::WindowsEscapeArg(wprintarg);
+  wcsncpy(cmdline, wprintarg.c_str(), wprintarg.size());
+  wchar_t* pcmdline = cmdline + wprintarg.size();
+  *pcmdline++ = L' ';
+
+  // Run a subprocess for each of the arguments and assert that the argument
+  // arrived to the subprocess as intended.
+  for (const auto& i : args) {
+    // We already asserted for every element that WindowsEscapeArg(i.first)
+    // produces the same output as i.second, so just use i.second instead of
+    // converting i.first again.
+    wcsncpy(pcmdline, i.second.c_str(), i.second.size());
+    pcmdline[i.second.size()] = 0;
+
+    // Run the subprocess.
+    PROCESS_INFORMATION processInfo;
+    BOOL ok = CreateProcessW(
+        NULL, cmdline, NULL, NULL, TRUE,
+        CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, NULL, NULL,
+        &startupInfo.StartupInfo, &processInfo);
+    if (!ok) {
+      DWORD err = GetLastError();
+      ASSERT_EQ(err, 0);
+    }
+    CloseHandle(processInfo.hThread);
+    bazel::windows::AutoHandle process(processInfo.hProcess);
+
+    // Wait for the subprocess to exit. Timeout is 5 seconds, which should be
+    // more than enough for the subprocess to finish.
+    ASSERT_EQ(WaitForSingleObject(process, 5000), WAIT_OBJECT_0);
+
+    // The subprocess printed its argv[1] (without a newline) to its stdout,
+    // which is redirected into the pipe.
+    // Let's write a null-terminator to the pipe to separate the output from the
+    // output of the subsequent subprocess. The null-terminator also yields
+    // null-terminated strings in the pipe, making it easy to read them out
+    // later.
+    DWORD dummy;
+    ASSERT_TRUE(WriteFile(pipe_write, "\0", 1, &dummy, NULL));
+  }
+
+  // Read the output of the subprocesses from the pipe. They are divided by
+  // null-terminators, so 'buf' will contain a sequence of null-terminated
+  // strings.  We close the writing end so that ReadFile won't block until the
+  // desired amount of bytes is available.
+  DWORD total_output_len;
+  char buf[0x10000];
+  pipe_write = INVALID_HANDLE_VALUE;
+  if (!ReadFile(pipe_read, buf, 0x10000, &total_output_len, NULL)) {
+    DWORD err = GetLastError();
+    ASSERT_EQ(err, 0);
+  }
+
+  // Assert that the subprocesses produced exactly the *unescaped* arguments.
+  size_t start = 0;
+  for (const auto& arg : args) {
+    // Assert that there was enough data produced by the subprocesses.
+    ASSERT_LT(start, total_output_len);
+
+    // Find the output of the corresponding subprocess. Since all subprocesses
+    // printed into the same pipe and we added null-terminators between them,
+    // the output is already there, conveniently as a null-terminated string.
+    std::string actual_arg(buf + start);
+    start += actual_arg.size() + 1;
+
+    // 'args' contains wchar_t strings, but the subprocesses printed ASCII
+    // (char) strings. To compare, we convert arg.first to a char-string.
+    std::string expected_arg;
+    expected_arg.reserve(arg.first.size());
+    for (const auto& wc : arg.first) {
+      expected_arg.append(1, static_cast<char>(wc));
+    }
+
+    // Assert that the subprocess printed exactly the *unescaped* argument.
+    EXPECT_EQ(expected_arg, actual_arg);
+  }
+}
+
+TEST(ProcessTest, WindowsEscapeArgTest) {
+  AssertSubprocessReceivesArgsAsIntended({
+      {L"", L"\"\""},
+      {L" ", L"\" \""},
+      {L"\"", L"\"\\\"\""},
+      {L"\"\\", L"\"\\\"\\\\\""},
+      {L"\\", L"\\"},
+      {L"\\\"", L"\"\\\\\\\"\""},
+      {L"with space", L"\"with space\""},
+      {L"with^caret", L"with^caret"},
+      {L"space ^caret", L"\"space ^caret\""},
+      {L"caret^ space", L"\"caret^ space\""},
+      {L"with\"quote", L"\"with\\\"quote\""},
+      {L"with\\backslash", L"with\\backslash"},
+      {L"one\\ backslash and \\space", L"\"one\\ backslash and \\space\""},
+      {L"two\\\\backslashes", L"two\\\\backslashes"},
+      {L"two\\\\ backslashes \\\\and space",
+       L"\"two\\\\ backslashes \\\\and space\""},
+      {L"one\\\"x", L"\"one\\\\\\\"x\""},
+      {L"two\\\\\"x", L"\"two\\\\\\\\\\\"x\""},
+      {L"a \\ b", L"\"a \\ b\""},
+      {L"a \\\" b", L"\"a \\\\\\\" b\""},
+      {L"A", L"A"},
+      {L"\"a\"", L"\"\\\"a\\\"\""},
+      {L"B C", L"\"B C\""},
+      {L"\"b c\"", L"\"\\\"b c\\\"\""},
+      {L"D\"E", L"\"D\\\"E\""},
+      {L"\"d\"e\"", L"\"\\\"d\\\"e\\\"\""},
+      {L"C:\\F G", L"\"C:\\F G\""},
+      {L"\"C:\\f g\"", L"\"\\\"C:\\f g\\\"\""},
+      {L"C:\\H\"I", L"\"C:\\H\\\"I\""},
+      {L"\"C:\\h\"i\"", L"\"\\\"C:\\h\\\"i\\\"\""},
+      {L"C:\\J\\\"K", L"\"C:\\J\\\\\\\"K\""},
+      {L"\"C:\\j\\\"k\"", L"\"\\\"C:\\j\\\\\\\"k\\\"\""},
+      {L"C:\\L M ", L"\"C:\\L M \""},
+      {L"\"C:\\l m \"", L"\"\\\"C:\\l m \\\"\""},
+      {L"C:\\N O\\", L"\"C:\\N O\\\\\""},
+      {L"\"C:\\n o\\\"", L"\"\\\"C:\\n o\\\\\\\"\""},
+      {L"C:\\P Q\\ ", L"\"C:\\P Q\\ \""},
+      {L"\"C:\\p q\\ \"", L"\"\\\"C:\\p q\\ \\\"\""},
+      {L"C:\\R\\S\\", L"C:\\R\\S\\"},
+      {L"C:\\R x\\S\\", L"\"C:\\R x\\S\\\\\""},
+      {L"\"C:\\r\\s\\\"", L"\"\\\"C:\\r\\s\\\\\\\"\""},
+      {L"\"C:\\r x\\s\\\"", L"\"\\\"C:\\r x\\s\\\\\\\"\""},
+      {L"C:\\T U\\W\\", L"\"C:\\T U\\W\\\\\""},
+      {L"\"C:\\t u\\w\\\"", L"\"\\\"C:\\t u\\w\\\\\\\"\""},
+  });
+}
+
+}  // namespace
diff --git a/src/tools/launcher/BUILD b/src/tools/launcher/BUILD
index 9383128..587dcc8 100644
--- a/src/tools/launcher/BUILD
+++ b/src/tools/launcher/BUILD
@@ -38,14 +38,20 @@
     name = "java_launcher",
     srcs = ["java_launcher.cc"],
     hdrs = ["java_launcher.h"],
-    deps = [":launcher_base"],
+    deps = [
+        ":launcher_base",
+        "//src/main/native/windows:lib-process",
+    ],
 )
 
 win_cc_library(
     name = "python_launcher",
     srcs = ["python_launcher.cc"],
     hdrs = ["python_launcher.h"],
-    deps = [":launcher_base"],
+    deps = [
+        ":launcher_base",
+        "//src/main/native/windows:lib-process",
+    ],
 )
 
 win_cc_library(
diff --git a/src/tools/launcher/java_launcher.cc b/src/tools/launcher/java_launcher.cc
index 01af0cf..2af0be2 100644
--- a/src/tools/launcher/java_launcher.cc
+++ b/src/tools/launcher/java_launcher.cc
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tools/launcher/java_launcher.h"
+
 #include <memory>
 #include <sstream>
 #include <string>
@@ -23,7 +25,7 @@
 #include "src/main/cpp/util/path_platform.h"
 #include "src/main/cpp/util/strings.h"
 #include "src/main/native/windows/file.h"
-#include "src/tools/launcher/java_launcher.h"
+#include "src/main/native/windows/process.h"
 #include "src/tools/launcher/util/launcher_util.h"
 
 namespace bazel {
@@ -411,7 +413,7 @@
   vector<wstring> escaped_arguments;
   // Quote the arguments if having spaces
   for (const auto& arg : arguments) {
-    escaped_arguments.push_back(WindowsEscapeArg2(arg));
+    escaped_arguments.push_back(bazel::windows::WindowsEscapeArg(arg));
   }
 
   ExitCode exit_code = this->LaunchProcess(java_bin, escaped_arguments);
diff --git a/src/tools/launcher/python_launcher.cc b/src/tools/launcher/python_launcher.cc
index b29173a..d96f208 100644
--- a/src/tools/launcher/python_launcher.cc
+++ b/src/tools/launcher/python_launcher.cc
@@ -12,10 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tools/launcher/python_launcher.h"
+
 #include <string>
 #include <vector>
 
-#include "src/tools/launcher/python_launcher.h"
+#include "src/main/native/windows/process.h"
 #include "src/tools/launcher/util/launcher_util.h"
 
 namespace bazel {
@@ -64,7 +66,7 @@
   args[0] = python_file;
 
   for (int i = 1; i < args.size(); i++) {
-    args[i] = WindowsEscapeArg2(args[i]);
+    args[i] = bazel::windows::WindowsEscapeArg(args[i]);
   }
 
   return this->LaunchProcess(python_binary, args);
diff --git a/src/tools/launcher/util/BUILD b/src/tools/launcher/util/BUILD
index c4a66ef..74a5b77 100644
--- a/src/tools/launcher/util/BUILD
+++ b/src/tools/launcher/util/BUILD
@@ -19,20 +19,12 @@
     name = "util",
     srcs = ["launcher_util.cc"],
     hdrs = ["launcher_util.h"],
-    visibility = [
-        "//src/main/cpp:__pkg__",
-        "//src/tools/launcher:__subpackages__",
-        "//tools/test:__pkg__",
-    ],
     deps = ["//src/main/cpp/util:filesystem"],
 )
 
 win_cc_test(
     name = "util_test",
     srcs = ["launcher_util_test.cc"],
-    data = [
-        ":printarg",
-    ],
     deps = [
         ":util",
         "//src/main/native/windows:lib-file",
@@ -50,12 +42,6 @@
     ],
 )
 
-win_cc_binary(
-    name = "printarg",
-    testonly = 1,
-    srcs = ["printarg.cc"],
-)
-
 test_suite(
     name = "windows_tests",
     tags = [
diff --git a/src/tools/launcher/util/launcher_util.cc b/src/tools/launcher/util/launcher_util.cc
index da51471..19ec6f2 100644
--- a/src/tools/launcher/util/launcher_util.cc
+++ b/src/tools/launcher/util/launcher_util.cc
@@ -170,96 +170,6 @@
   return escaped_arg;
 }
 
-// Escape arguments for CreateProcessW.
-//
-// This algorithm is based on information found in
-// http://daviddeley.com/autohotkey/parameters/parameters.htm
-//
-// The following source specifies a similar algorithm:
-// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
-// unfortunately I found this algorithm only after creating the one below, but
-// fortunately they seem to do the same.
-std::wstring WindowsEscapeArg2(const std::wstring& s) {
-  if (s.empty()) {
-    return L"\"\"";
-  } else {
-    bool needs_escaping = false;
-    for (const auto& c : s) {
-      if (c == ' ' || c == '"') {
-        needs_escaping = true;
-        break;
-      }
-    }
-    if (!needs_escaping) {
-      return s;
-    }
-  }
-
-  std::wostringstream result;
-  result << L'"';
-  int start = 0;
-  for (int i = 0; i < s.size(); ++i) {
-    char c = s[i];
-    if (c == '"' || c == '\\') {
-      // Copy the segment since the last special character.
-      if (start >= 0) {
-        result << s.substr(start, i - start);
-        start = -1;
-      }
-
-      // Handle the current special character.
-      if (c == '"') {
-        // This is a quote character. Escape it with a single backslash.
-        result << L"\\\"";
-      } else {
-        // This is a backslash (or the first one in a run of backslashes).
-        // Whether we escape it depends on whether the run ends with a quote.
-        int run_len = 1;
-        int j = i + 1;
-        while (j < s.size() && s[j] == '\\') {
-          run_len++;
-          j++;
-        }
-        if (j == s.size()) {
-          // The run of backslashes goes to the end.
-          // We have to escape every backslash with another backslash.
-          for (int k = 0; k < run_len * 2; ++k) {
-            result << L'\\';
-          }
-          break;
-        } else if (j < s.size() && s[j] == '"') {
-          // The run of backslashes is terminated by a quote.
-          // We have to escape every backslash with another backslash, and
-          // escape the quote with one backslash.
-          for (int k = 0; k < run_len * 2; ++k) {
-            result << L'\\';
-          }
-          result << L"\\\"";
-          i += run_len;  // 'i' is also increased in the loop iteration step
-        } else {
-          // No quote found. Each backslash counts for itself, they must not be
-          // escaped.
-          for (int k = 0; k < run_len; ++k) {
-            result << L'\\';
-          }
-          i += run_len - 1;  // 'i' is also increased in the loop iteration step
-        }
-      }
-    } else {
-      // This is not a special character. Start the segment if necessary.
-      if (start < 0) {
-        start = i;
-      }
-    }
-  }
-  // Save final segment after the last special character.
-  if (start != -1) {
-    result << s.substr(start);
-  }
-  result << L'"';
-  return result.str();
-}
-
 // An environment variable has a maximum size limit of 32,767 characters
 // https://msdn.microsoft.com/en-us/library/ms683188.aspx
 static const int BUFFER_SIZE = 32767;
diff --git a/src/tools/launcher/util/launcher_util.h b/src/tools/launcher/util/launcher_util.h
index e641f42..7353612 100644
--- a/src/tools/launcher/util/launcher_util.h
+++ b/src/tools/launcher/util/launcher_util.h
@@ -48,13 +48,6 @@
 // to \\).
 std::wstring BashEscapeArg(const std::wstring& arg);
 
-// Escape a command line argument using Windows escaping syntax.
-//
-// This escaping lets us safely pass arguments to subprocesses created with
-// CreateProcessW. (The escaping rules are a bit complex, look at the function
-// implementation.)
-std::wstring WindowsEscapeArg2(const std::wstring& arg);
-
 // Convert a path to an absolute Windows path with \\?\ prefix.
 // This method will print an error and exit if it cannot convert the path.
 std::wstring AsAbsoluteWindowsPath(const wchar_t* path);
diff --git a/src/tools/launcher/util/launcher_util_test.cc b/src/tools/launcher/util/launcher_util_test.cc
index 3d516cc..d2d9ce7 100644
--- a/src/tools/launcher/util/launcher_util_test.cc
+++ b/src/tools/launcher/util/launcher_util_test.cc
@@ -97,216 +97,6 @@
             BashEscapeArg(L"C:\\foo foo\\bar\\"));
 }
 
-// Asserts argument escaping for subprocesses.
-//
-// For each pair in 'args', this method:
-// 1. asserts that WindowsEscapeArg(pair.first) == pair.second
-// 2. asserts that passing pair.second to a subprocess results in the subprocess
-//    receiving pair.first
-//
-// The method performs the second assertion by running "printarg.exe" (a
-// data-dependency of this test) once for each argument.
-void AssertSubprocessReceivesArgsAsIntended(
-    std::wstring (*escape_func)(const std::wstring& s),
-    const std::vector<std::pair<wstring, wstring> >& args) {
-  // Assert that the WindowsEscapeArg produces what we expect.
-  for (const auto& i : args) {
-    ASSERT_EQ(escape_func(i.first), i.second);
-  }
-
-  // Create a Runfiles object.
-  string error;
-  std::unique_ptr<bazel::tools::cpp::runfiles::Runfiles> runfiles(
-      bazel::tools::cpp::runfiles::Runfiles::CreateForTest(&error));
-  ASSERT_NE(runfiles.get(), nullptr) << error;
-
-  // Look up the path of the printarg.exe utility.
-  string printarg =
-      runfiles->Rlocation("io_bazel/src/tools/launcher/util/printarg.exe");
-  ASSERT_NE(printarg, "");
-
-  // Convert printarg.exe's path to a wchar_t Windows path.
-  wstring wprintarg;
-  bool success =
-      blaze_util::AsAbsoluteWindowsPath(printarg, &wprintarg, &error);
-  ASSERT_TRUE(success) << error;
-
-  // SECURITY_ATTRIBUTES for inheritable HANDLEs.
-  SECURITY_ATTRIBUTES sa;
-  sa.nLength = sizeof(sa);
-  sa.lpSecurityDescriptor = NULL;
-  sa.bInheritHandle = TRUE;
-
-  // Open /dev/null that will be redirected into the subprocess' stdin.
-  bazel::windows::AutoHandle devnull(
-      CreateFileW(L"NUL", GENERIC_READ,
-                  FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &sa,
-                  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL));
-  ASSERT_TRUE(devnull.IsValid());
-
-  // Create a pipe that the subprocess' stdout will be redirected to.
-  HANDLE pipe_read_h, pipe_write_h;
-  if (!CreatePipe(&pipe_read_h, &pipe_write_h, &sa, 0x10000)) {
-    DWORD err = GetLastError();
-    ASSERT_EQ(err, 0);
-  }
-  bazel::windows::AutoHandle pipe_read(pipe_read_h), pipe_write(pipe_write_h);
-
-  // Duplicate stderr, where the subprocess' stderr will be redirected to.
-  HANDLE stderr_h;
-  if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_ERROR_HANDLE),
-                       GetCurrentProcess(), &stderr_h, 0, TRUE,
-                       DUPLICATE_SAME_ACCESS)) {
-    DWORD err = GetLastError();
-    ASSERT_EQ(err, 0);
-  }
-  bazel::windows::AutoHandle stderr_dup(stderr_h);
-
-  // Create the attribute object for the process creation. This object describes
-  // exactly which handles the subprocess shall inherit.
-  STARTUPINFOEXW startupInfo;
-  std::unique_ptr<bazel::windows::AutoAttributeList> attrs;
-  wstring werror;
-  ASSERT_TRUE(bazel::windows::AutoAttributeList::Create(
-      devnull, pipe_write, stderr_dup, &attrs, &werror));
-  attrs->InitStartupInfoExW(&startupInfo);
-
-  // MSDN says the maximum command line is 32767 characters, with a null
-  // terminator that is exactly 2^15 (= 0x8000).
-  static constexpr size_t kMaxCmdline = 0x8000;
-  wchar_t cmdline[kMaxCmdline];
-
-  // Copy printarg.exe's escaped path into the 'cmdline', and append a space.
-  // We will append arguments to this command line in the for-loop below.
-  wprintarg = escape_func(wprintarg);
-  wcsncpy(cmdline, wprintarg.c_str(), wprintarg.size());
-  wchar_t* pcmdline = cmdline + wprintarg.size();
-  *pcmdline++ = L' ';
-
-  // Run a subprocess for each of the arguments and assert that the argument
-  // arrived to the subprocess as intended.
-  for (const auto& i : args) {
-    // We already asserted for every element that escape_func(i.first)
-    // produces the same output as i.second, so just use i.second instead of
-    // converting i.first again.
-    wcsncpy(pcmdline, i.second.c_str(), i.second.size());
-    pcmdline[i.second.size()] = 0;
-
-    // Run the subprocess.
-    PROCESS_INFORMATION processInfo;
-    BOOL ok = CreateProcessW(
-        NULL, cmdline, NULL, NULL, TRUE,
-        CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, NULL, NULL,
-        &startupInfo.StartupInfo, &processInfo);
-    if (!ok) {
-      DWORD err = GetLastError();
-      ASSERT_EQ(err, 0);
-    }
-    CloseHandle(processInfo.hThread);
-    bazel::windows::AutoHandle process(processInfo.hProcess);
-
-    // Wait for the subprocess to exit. Timeout is 5 seconds, which should be
-    // more than enough for the subprocess to finish.
-    ASSERT_EQ(WaitForSingleObject(process, 5000), WAIT_OBJECT_0);
-
-    // The subprocess printed its argv[1] (without a newline) to its stdout,
-    // which is redirected into the pipe.
-    // Let's write a null-terminator to the pipe to separate the output from the
-    // output of the subsequent subprocess. The null-terminator also yields
-    // null-terminated strings in the pipe, making it easy to read them out
-    // later.
-    DWORD dummy;
-    ASSERT_TRUE(WriteFile(pipe_write, "\0", 1, &dummy, NULL));
-  }
-
-  // Read the output of the subprocesses from the pipe. They are divided by
-  // null-terminators, so 'buf' will contain a sequence of null-terminated
-  // strings.  We close the writing end so that ReadFile won't block until the
-  // desired amount of bytes is available.
-  DWORD total_output_len;
-  char buf[0x10000];
-  pipe_write = INVALID_HANDLE_VALUE;
-  if (!ReadFile(pipe_read, buf, 0x10000, &total_output_len, NULL)) {
-    DWORD err = GetLastError();
-    ASSERT_EQ(err, 0);
-  }
-
-  // Assert that the subprocesses produced exactly the *unescaped* arguments.
-  size_t start = 0;
-  for (const auto& arg : args) {
-    // Assert that there was enough data produced by the subprocesses.
-    ASSERT_LT(start, total_output_len);
-
-    // Find the output of the corresponding subprocess. Since all subprocesses
-    // printed into the same pipe and we added null-terminators between them,
-    // the output is already there, conveniently as a null-terminated string.
-    string actual_arg(buf + start);
-    start += actual_arg.size() + 1;
-
-    // 'args' contains wchar_t strings, but the subprocesses printed ASCII
-    // (char) strings. To compare, we convert arg.first to a char-string.
-    string expected_arg;
-    expected_arg.reserve(arg.first.size());
-    for (const auto& wc : arg.first) {
-      expected_arg.append(1, static_cast<char>(wc));
-    }
-
-    // Assert that the subprocess printed exactly the *unescaped* argument.
-    EXPECT_EQ(expected_arg, actual_arg);
-  }
-}
-
-TEST_F(LaunchUtilTest, WindowsEscapeArg2Test) {
-  AssertSubprocessReceivesArgsAsIntended(
-      WindowsEscapeArg2,
-      {
-          {L"", L"\"\""},
-          {L" ", L"\" \""},
-          {L"\"", L"\"\\\"\""},
-          {L"\"\\", L"\"\\\"\\\\\""},
-          {L"\\", L"\\"},
-          {L"\\\"", L"\"\\\\\\\"\""},
-          {L"with space", L"\"with space\""},
-          {L"with^caret", L"with^caret"},
-          {L"space ^caret", L"\"space ^caret\""},
-          {L"caret^ space", L"\"caret^ space\""},
-          {L"with\"quote", L"\"with\\\"quote\""},
-          {L"with\\backslash", L"with\\backslash"},
-          {L"one\\ backslash and \\space", L"\"one\\ backslash and \\space\""},
-          {L"two\\\\backslashes", L"two\\\\backslashes"},
-          {L"two\\\\ backslashes \\\\and space",
-           L"\"two\\\\ backslashes \\\\and space\""},
-          {L"one\\\"x", L"\"one\\\\\\\"x\""},
-          {L"two\\\\\"x", L"\"two\\\\\\\\\\\"x\""},
-          {L"a \\ b", L"\"a \\ b\""},
-          {L"a \\\" b", L"\"a \\\\\\\" b\""},
-          {L"A", L"A"},
-          {L"\"a\"", L"\"\\\"a\\\"\""},
-          {L"B C", L"\"B C\""},
-          {L"\"b c\"", L"\"\\\"b c\\\"\""},
-          {L"D\"E", L"\"D\\\"E\""},
-          {L"\"d\"e\"", L"\"\\\"d\\\"e\\\"\""},
-          {L"C:\\F G", L"\"C:\\F G\""},
-          {L"\"C:\\f g\"", L"\"\\\"C:\\f g\\\"\""},
-          {L"C:\\H\"I", L"\"C:\\H\\\"I\""},
-          {L"\"C:\\h\"i\"", L"\"\\\"C:\\h\\\"i\\\"\""},
-          {L"C:\\J\\\"K", L"\"C:\\J\\\\\\\"K\""},
-          {L"\"C:\\j\\\"k\"", L"\"\\\"C:\\j\\\\\\\"k\\\"\""},
-          {L"C:\\L M ", L"\"C:\\L M \""},
-          {L"\"C:\\l m \"", L"\"\\\"C:\\l m \\\"\""},
-          {L"C:\\N O\\", L"\"C:\\N O\\\\\""},
-          {L"\"C:\\n o\\\"", L"\"\\\"C:\\n o\\\\\\\"\""},
-          {L"C:\\P Q\\ ", L"\"C:\\P Q\\ \""},
-          {L"\"C:\\p q\\ \"", L"\"\\\"C:\\p q\\ \\\"\""},
-          {L"C:\\R\\S\\", L"C:\\R\\S\\"},
-          {L"C:\\R x\\S\\", L"\"C:\\R x\\S\\\\\""},
-          {L"\"C:\\r\\s\\\"", L"\"\\\"C:\\r\\s\\\\\\\"\""},
-          {L"\"C:\\r x\\s\\\"", L"\"\\\"C:\\r x\\s\\\\\\\"\""},
-          {L"C:\\T U\\W\\", L"\"C:\\T U\\W\\\\\""},
-          {L"\"C:\\t u\\w\\\"", L"\"\\\"C:\\t u\\w\\\\\\\"\""},
-      });
-}
-
 TEST_F(LaunchUtilTest, DoesFilePathExistTest) {
   wstring file1 = GetTmpDir() + L"/foo";
   wstring file2 = GetTmpDir() + L"/bar";
diff --git a/tools/test/BUILD b/tools/test/BUILD
index 8d33e18..f01ac67 100644
--- a/tools/test/BUILD
+++ b/tools/test/BUILD
@@ -82,7 +82,6 @@
             "//src/main/cpp/util:strings",
             "//src/main/native/windows:lib-file",
             "//src/main/native/windows:lib-process",
-            "//src/tools/launcher/util",
             "//third_party/ijar:zip",
             "@bazel_tools//tools/cpp/runfiles",
         ],
diff --git a/tools/test/windows/tw.cc b/tools/test/windows/tw.cc
index 2bd3e8e..d2c81d7 100644
--- a/tools/test/windows/tw.cc
+++ b/tools/test/windows/tw.cc
@@ -46,7 +46,6 @@
 #include "src/main/native/windows/file.h"
 #include "src/main/native/windows/process.h"
 #include "src/main/native/windows/util.h"
-#include "src/tools/launcher/util/launcher_util.h"
 #include "third_party/ijar/common.h"
 #include "third_party/ijar/platform_utils.h"
 #include "third_party/ijar/zip.h"
@@ -1378,7 +1377,7 @@
   *out_test_path_arg = argv[0];
   std::wstringstream stm;
   for (int i = 1; i < argc; i++) {
-    stm << L' ' << bazel::launcher::WindowsEscapeArg2(argv[i]);
+    stm << L' ' << bazel::windows::WindowsEscapeArg(argv[i]);
   }
   *out_args = stm.str();
   return true;