Windows, JNI: duplicate stdout HANDLE when needed

When stderr is redirected into stdout, then don't
pass just a copy of stdout's HANDLE, make a proper
duplicate so the subprocess can close the HANDLEs
independently.

Closes #6748.

PiperOrigin-RevId: 222799815
diff --git a/src/main/native/windows/processes-jni.cc b/src/main/native/windows/processes-jni.cc
index 35fe931..7cff192 100644
--- a/src/main/native/windows/processes-jni.cc
+++ b/src/main/native/windows/processes-jni.cc
@@ -275,6 +275,16 @@
     return PtrAsJlong(result);
   }
 
+  const bool stdout_is_stream = stdout_redirect.empty();
+  const bool stderr_is_stream =
+      redirectErrorStream ? stdout_is_stream : stderr_redirect.empty();
+  const bool stderr_same_handle_as_stdout =
+      redirectErrorStream ||
+      (!stderr_redirect.empty() &&
+       stderr_redirect.size() == stdout_redirect.size() &&
+       _wcsnicmp(stderr_redirect.c_str(), stdout_redirect.c_str(),
+                 stderr_redirect.size()) == 0);
+
   std::unique_ptr<WCHAR[]> mutable_commandline(
       new WCHAR[commandline.size() + 1]);
   wcsncpy(mutable_commandline.get(), commandline.c_str(),
@@ -315,17 +325,19 @@
     }
   }
 
-  HANDLE temp_stdin_handle = INVALID_HANDLE_VALUE;
-  if (!CreatePipe(&temp_stdin_handle, &result->stdin_, &sa, 0)) {
-    DWORD err_code = GetLastError();
-    result->error_ = bazel::windows::MakeErrorMessage(
-        WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code);
-    CloseHandle(temp_stdin_handle);
-    return PtrAsJlong(result);
+  {
+    HANDLE pipe_read_h, pipe_write_h;
+    if (!CreatePipe(&pipe_read_h, &pipe_write_h, &sa, 0)) {
+      DWORD err_code = GetLastError();
+      result->error_ = bazel::windows::MakeErrorMessage(
+          WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code);
+      return PtrAsJlong(result);
+    }
+    stdin_process = pipe_read_h;
+    result->stdin_ = pipe_write_h;
   }
-  stdin_process = temp_stdin_handle;
 
-  if (!stdout_redirect.empty()) {
+  if (!stdout_is_stream) {
     result->stdout_.close();
 
     stdout_process = CreateFileW(
@@ -344,65 +356,60 @@
       return PtrAsJlong(result);
     }
   } else {
-    HANDLE temp_stdout_handle = INVALID_HANDLE_VALUE;
-    if (!CreatePipe(&result->stdout_.handle_, &temp_stdout_handle, &sa, 0)) {
+    HANDLE pipe_read_h, pipe_write_h;
+    if (!CreatePipe(&pipe_read_h, &pipe_write_h, &sa, 0)) {
       DWORD err_code = GetLastError();
       result->error_ = bazel::windows::MakeErrorMessage(
           WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code);
-      CloseHandle(temp_stdout_handle);
       return PtrAsJlong(result);
     }
-    stdout_process = temp_stdout_handle;
+    result->stdout_.handle_ = pipe_read_h;
+    stdout_process = pipe_write_h;
   }
 
-  // The value of the stderr HANDLE.
-  // If stdout and stderr are redirected to the same file, this will be equal to
-  // stdout_process.handle and stderr_process.handle will be
-  // INVALID_HANDLE_VALUE, so the two AutoHandle objects' d'tors will not
-  // attempt to close stdout's handle twice.
-  // If stdout != stderr, then stderr_handle = stderr_process.handle, and these
-  // are distinct from stdout_process.handle, so again the d'tors will do the
-  // right thing, closing the handles.
-  // In both cases, we DO NOT close stderr_handle, since it's either
-  // stdout_process's or stderr_process's d'tor doing so.
-  HANDLE stderr_handle = INVALID_HANDLE_VALUE;
-
-  if (redirectErrorStream) {
-    stderr_handle = stdout_process;
-  } else if (!stderr_redirect.empty()) {
-    result->stderr_.close();
-    if (stdout_redirect == stderr_redirect) {
-      stderr_handle = stdout_process;
-      // do not set stderr_process.handle; it equals stdout_process.handle and
-      // the AutoHandle d'tor would attempt to close it again
-    } else {
-      stderr_handle = CreateFileW(
-          /* lpFileName */ stderr_redirect.c_str(),
-          /* dwDesiredAccess */ FILE_APPEND_DATA,
-          /* dwShareMode */ 0,
-          /* lpSecurityAttributes */ &sa,
-          /* dwCreationDisposition */ OPEN_ALWAYS,
-          /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL,
-          /* hTemplateFile */ NULL);
-
-      if (stderr_handle == INVALID_HANDLE_VALUE) {
-        DWORD err_code = GetLastError();
-        result->error_ = bazel::windows::MakeErrorMessage(
-            WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code);
-        return PtrAsJlong(result);
-      }
-      // stderr_process != stdout_process, so set its handle, so the AutoHandle
-      // d'tor will close it
-      stderr_process = stderr_handle;
-    }
-  } else {
-    if (!CreatePipe(&result->stderr_.handle_, &stderr_handle, &sa, 0)) {
+  if (stderr_same_handle_as_stdout) {
+    HANDLE stdout_process_dup_h;
+    if (!DuplicateHandle(GetCurrentProcess(), stdout_process,
+                         GetCurrentProcess(), &stdout_process_dup_h, 0, TRUE,
+                         DUPLICATE_SAME_ACCESS)) {
       DWORD err_code = GetLastError();
       result->error_ = bazel::windows::MakeErrorMessage(
           WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code);
       return PtrAsJlong(result);
     }
-    stderr_process = stderr_handle;
+    if (!stderr_is_stream) {
+      result->stderr_.close();
+    }
+
+    stderr_process = stdout_process_dup_h;
+  } else if (!stderr_redirect.empty()) {
+    result->stderr_.close();
+    stderr_process = CreateFileW(
+        /* lpFileName */ stderr_redirect.c_str(),
+        /* dwDesiredAccess */ FILE_APPEND_DATA,
+        /* dwShareMode */ 0,
+        /* lpSecurityAttributes */ &sa,
+        /* dwCreationDisposition */ OPEN_ALWAYS,
+        /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL,
+        /* hTemplateFile */ NULL);
+
+    if (!stderr_process.IsValid()) {
+      DWORD err_code = GetLastError();
+      result->error_ = bazel::windows::MakeErrorMessage(
+          WSTR(__FILE__), __LINE__, L"nativeCreateProcess", stderr_redirect,
+          err_code);
+      return PtrAsJlong(result);
+    }
+  } else {
+    HANDLE pipe_read_h, pipe_write_h;
+    if (!CreatePipe(&pipe_read_h, &pipe_write_h, &sa, 0)) {
+      DWORD err_code = GetLastError();
+      result->error_ = bazel::windows::MakeErrorMessage(
+          WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code);
+      return PtrAsJlong(result);
+    }
+    result->stderr_.handle_ = pipe_read_h;
+    stderr_process = pipe_write_h;
   }
 
   // MDSN says that the default for job objects is that breakaway is not
@@ -430,17 +437,17 @@
   startup_info.cb = sizeof(STARTUPINFOW);
   startup_info.hStdInput = stdin_process;
   startup_info.hStdOutput = stdout_process;
-  startup_info.hStdError = stderr_handle;
+  startup_info.hStdError = stderr_process;
   startup_info.dwFlags |= STARTF_USESTDHANDLES;
 
-  HANDLE handlesToInherit[3] = {stdin_process, stdout_process, stderr_handle};
+  HANDLE handlesToInherit[3] = {stdin_process, stdout_process, stderr_process};
   std::wstring err_msg(CreateProcessWithExplicitHandles(
       /* lpCommandLine */ mutable_commandline.get(),
       /* lpEnvironment */ env_map.ptr(),
       /* lpCurrentDirectory */ cwd.empty() ? nullptr : cwd.c_str(),
       /* lpStartupInfo */ &startup_info,
       /* lpProcessInformation */ &process_info,
-      /* cHandlesToInherit */ (stderr_handle == stdout_process) ? 2 : 3,
+      /* cHandlesToInherit */ 3,
       /* handlesToInherit */ handlesToInherit));
 
   if (!err_msg.empty()) {
diff --git a/src/test/java/com/google/devtools/build/lib/windows/WindowsProcessesTest.java b/src/test/java/com/google/devtools/build/lib/windows/WindowsProcessesTest.java
index b2c4198..91e4fe4 100644
--- a/src/test/java/com/google/devtools/build/lib/windows/WindowsProcessesTest.java
+++ b/src/test/java/com/google/devtools/build/lib/windows/WindowsProcessesTest.java
@@ -249,6 +249,7 @@
             null,
             null,
             null);
+    assertNoProcessError();
 
     byte[] buf = new byte[4];
     assertThat(readStdout(buf, 0, 4)).isEqualTo(4);