Optionally allow Bazel to pass JVM options containing spaces directly through to the JVM instead of (almost certainly incorrectly) splitting the options along spaces.

This allows us to pass non-quote-delimited strings to the JVM, which is necessary for things like -XX:OnOutOfMemoryError="kill -3 %p" (normally bash strips those quotes, but they're not stripped when passed via --host_jvm_args).

--
MOS_MIGRATED_REVID=107820087
diff --git a/site/docs/bazel-user-manual.html b/site/docs/bazel-user-manual.html
index 1460d8a..9eb7d00 100644
--- a/site/docs/bazel-user-manual.html
+++ b/site/docs/bazel-user-manual.html
@@ -3282,16 +3282,17 @@
 
 <h4 id='flag--host_jvm_args'><code class='flag'>--host_jvm_args=<var>string</var></code></h4>
 <p>
-  Specifies a space-separated list of startup options to be passed to
-  the Java virtual machine in which <i>Bazel itself</i> runs.  This
-  can be used to set the stack size, for example:
+  Specifies a startup option to be passed to the Java virtual machine in which <i>Bazel itself</i>
+  runs.  This can be used to set the stack size, for example:
 </p>
 <pre>
   % bazel --host_jvm_args="-Xss256K" build //foo
 </pre>
 <p>
   This option can be used multiple times with individual arguments. Note that
-  setting this flag should rarely be needed.
+  setting this flag should rarely be needed. You can also pass a space-separated list of strings,
+  each of which will be interpreted as a separate JVM argument, but this feature will soon be
+  deprecated.
 
 </p>
 <p>
diff --git a/src/main/cpp/blaze.cc b/src/main/cpp/blaze.cc
index 911bd3d..ae8af0d 100644
--- a/src/main/cpp/blaze.cc
+++ b/src/main/cpp/blaze.cc
@@ -261,8 +261,25 @@
 
   vector<string> user_options;
 
-  blaze_util::SplitQuotedStringUsing(globals->options.host_jvm_args, ' ',
-                                     &user_options);
+  if (globals->options.preserve_spaces_in_host_jvm_args) {
+    user_options.insert(user_options.begin(),
+                        globals->options.host_jvm_args.begin(),
+                        globals->options.host_jvm_args.end());
+  } else {
+    for (const auto &arg : globals->options.host_jvm_args) {
+      // int num_segments =
+      blaze_util::SplitQuotedStringUsing(arg, ' ', &user_options);
+      // TODO(janakr): Enable this warning when users have been migrated.
+      //       if (num_segments > 1) {
+      //         fprintf(stderr, "WARNING: You are passing multiple jvm options"
+      //             " under a single --host_jvm_args option: %s. This will stop
+      //             working "
+      //             "soon. Instead, pass each option under its own
+      //             --host_jvm_args "
+      //             "option.\n", arg);
+      //
+    }
+  }
 
   // Add JVM arguments particular to building blaze64 and particular JVM
   // versions.
@@ -350,8 +367,13 @@
   if (!globals->options.host_jvm_profile.empty()) {
     result.push_back("--host_jvm_profile=" + globals->options.host_jvm_profile);
   }
+  if (globals->options.preserve_spaces_in_host_jvm_args) {
+    result.push_back("--experimental_preserve_spaces_in_host_jvm_args");
+  }
   if (!globals->options.host_jvm_args.empty()) {
-    result.push_back("--host_jvm_args=" + globals->options.host_jvm_args);
+    for (const auto &arg : globals->options.host_jvm_args) {
+      result.push_back("--host_jvm_args=" + arg);
+    }
   }
 
   if (globals->options.invocation_policy != NULL &&
diff --git a/src/main/cpp/blaze_startup_options.h b/src/main/cpp/blaze_startup_options.h
index f29ab3f..e61af4b 100644
--- a/src/main/cpp/blaze_startup_options.h
+++ b/src/main/cpp/blaze_startup_options.h
@@ -126,7 +126,9 @@
 
   string host_jvm_profile;
 
-  string host_jvm_args;
+  bool preserve_spaces_in_host_jvm_args;
+
+  std::vector<string> host_jvm_args;
 
   bool batch;
 
diff --git a/src/main/cpp/blaze_startup_options_common.cc b/src/main/cpp/blaze_startup_options_common.cc
index 914f7ab..8a5f6f1 100644
--- a/src/main/cpp/blaze_startup_options_common.cc
+++ b/src/main/cpp/blaze_startup_options_common.cc
@@ -41,6 +41,8 @@
   block_for_lock = true;
   host_jvm_debug = false;
   host_javabase = "";
+  // TODO(janakr): change this to true when ready, then delete it.
+  preserve_spaces_in_host_jvm_args = false;
   batch = false;
   batch_cpu_scheduling = false;
   blaze_cpu = false;
@@ -73,6 +75,7 @@
   lhs->host_jvm_debug = rhs.host_jvm_debug;
   lhs->host_jvm_profile = rhs.host_jvm_profile;
   lhs->host_javabase = rhs.host_javabase;
+  lhs->preserve_spaces_in_host_jvm_args = rhs.preserve_spaces_in_host_jvm_args;
   lhs->host_jvm_args = rhs.host_jvm_args;
   lhs->batch = rhs.batch;
   lhs->batch_cpu_scheduling = rhs.batch_cpu_scheduling;
@@ -126,13 +129,13 @@
     // and re-execing.
     host_javabase = MakeAbsolute(value);
     option_sources["host_javabase"] = rcfile;
+  } else if (GetNullaryOption(
+                 arg, "--experimental_preserve_spaces_in_host_jvm_args")) {
+    preserve_spaces_in_host_jvm_args = true;
+    option_sources["preserve_spaces_in_host_jvm_args"] = rcfile;
   } else if ((value = GetUnaryOption(arg, next_arg,
                                      "--host_jvm_args")) != NULL) {
-    if (host_jvm_args.empty()) {
-      host_jvm_args = value;
-    } else {
-      host_jvm_args = host_jvm_args + " " + value;
-    }
+    host_jvm_args.push_back(value);
     option_sources["host_jvm_args"] = rcfile;  // NB: This is incorrect
   } else if ((value = GetUnaryOption(arg, next_arg, "--blaze_cpu")) != NULL) {
     blaze_cpu = true;
diff --git a/src/main/cpp/util/strings.cc b/src/main/cpp/util/strings.cc
index 214f887..3df0b9e 100644
--- a/src/main/cpp/util/strings.cc
+++ b/src/main/cpp/util/strings.cc
@@ -128,11 +128,12 @@
   }
 }
 
-void SplitQuotedStringUsing(const string &contents, const char delimeter,
-                            std::vector<string> *output) {
+size_t SplitQuotedStringUsing(const string &contents, const char delimeter,
+                              std::vector<string> *output) {
   size_t len = contents.length();
   size_t start = 0;
   size_t quote = string::npos;  // quote position
+  size_t num_segments = 0;
 
   for (size_t pos = 0; pos < len; ++pos) {
     if (start == pos && contents[start] == delimeter) {
@@ -147,13 +148,16 @@
     } else if (quote == string::npos && contents[pos] == delimeter) {
       output->push_back(string(contents, start, pos - start));
       start = pos + 1;
+      num_segments++;
     }
   }
 
   // A trailing element
   if (start < len) {
     output->push_back(string(contents, start));
+    num_segments++;
   }
+  return num_segments;
 }
 
 void Replace(const string &oldsub, const string &newsub, string *str) {
diff --git a/src/main/cpp/util/strings.h b/src/main/cpp/util/strings.h
index 18d53b1..0dca219 100644
--- a/src/main/cpp/util/strings.h
+++ b/src/main/cpp/util/strings.h
@@ -80,9 +80,9 @@
 void SplitStringUsing(
     const string &contents, const char delimeter, std::vector<string> *output);
 
-// Same as above, but adds results to output.
-void SplitQuotedStringUsing(const string &contents, const char delimeter,
-                            std::vector<string> *output);
+// Same as above, but adds results to output. Returns number of elements added.
+size_t SplitQuotedStringUsing(const string &contents, const char delimeter,
+                              std::vector<string> *output);
 
 // Global replace of oldsub with newsub.
 void Replace(const string &oldsub, const string &newsub, string *str);
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java
index 69d69da..2621a53 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java
@@ -213,6 +213,16 @@
       help = "Unused.")
   public String unusedSkyframe;
 
+  @Option(
+    name = "experimental_preserve_spaces_in_host_jvm_args",
+    defaultValue = "false",
+    category = "undocumented",
+    help =
+        "If this option is true, each argument to --host_jvm_args is considered a single JVM "
+            + "flag, even if it has spaces in it."
+  )
+  public boolean unusedPreserveSpacesInHostJvmArgs;
+
   @Option(name = "fatal_event_bus_exceptions",
       defaultValue = "false",  // NOTE: purely decorative!
       category = "undocumented",