Fix quoting preprocessor defines for compilation database

If a preprocessor define has a space character, which usually happens
with double-quoted text, current compilation database writer
cannot handle correctly. These spaces should be escaped.

Tested that gn is generating correctly compile_commands.json on WebRTC
with Linux.

Bug: gn:109
Change-Id: I4ba2ed4c807e4cb9e078720df3b64834a4f9ec8e
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/6780
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/AUTHORS b/AUTHORS
index b78d6a5..0640d6c 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -9,6 +9,7 @@
 # See python fnmatch module documentation for more information.
 
 Google Inc. <*@google.com>
+HyperConnect Inc. <*@hpcnt.com>
 IBM Inc. <*@*.ibm.com>
 Loongson Technology Corporation Limited. <*@loongson.cn>
 MIPS Technologies, Inc. <*@mips.com>
diff --git a/src/gn/compile_commands_writer.cc b/src/gn/compile_commands_writer.cc
index 10984cc..9ff67ca 100644
--- a/src/gn/compile_commands_writer.cc
+++ b/src/gn/compile_commands_writer.cc
@@ -57,9 +57,9 @@
       target->config_values().has_precompiled_headers();
 
   std::ostringstream defines_out;
-  RecursiveTargetConfigToStream<std::string>(
-      target, &ConfigValues::defines,
-      DefineWriter(ESCAPE_NINJA_PREFORMATTED_COMMAND, true), defines_out);
+  RecursiveTargetConfigToStream<std::string>(target, &ConfigValues::defines,
+                                             DefineWriter(ESCAPE_SPACE, true),
+                                             defines_out);
   base::EscapeJSONString(defines_out.str(), false, &flags.defines);
 
   std::ostringstream includes_out;
diff --git a/src/gn/compile_commands_writer_unittest.cc b/src/gn/compile_commands_writer_unittest.cc
index 96ef9eb..99dc55d 100644
--- a/src/gn/compile_commands_writer_unittest.cc
+++ b/src/gn/compile_commands_writer_unittest.cc
@@ -239,7 +239,7 @@
   target.sources().push_back(SourceFile("//foo/input.cc"));
   target.config_values().defines().push_back("BOOL_DEF");
   target.config_values().defines().push_back("INT_DEF=123");
-  target.config_values().defines().push_back("STR_DEF=\"ABCD-1\"");
+  target.config_values().defines().push_back("STR_DEF=\"ABCD 1\"");
   ASSERT_TRUE(target.OnResolved(&err));
   targets.push_back(&target);
 
@@ -248,7 +248,7 @@
   writer.RenderJSON(build_settings(), targets, &out);
 
   const char expected[] =
-      "-DBOOL_DEF -DINT_DEF=123 -DSTR_DEF=\\\\\\\"ABCD-1\\\\\\\"";
+      "-DBOOL_DEF -DINT_DEF=123 -DSTR_DEF=\\\\\\\"ABCD\\\\ 1\\\\\\\"";
   EXPECT_TRUE(out.find(expected) != std::string::npos);
 }
 
diff --git a/src/gn/escape.cc b/src/gn/escape.cc
index 9f567fb..98f77d3 100644
--- a/src/gn/escape.cc
+++ b/src/gn/escape.cc
@@ -41,6 +41,19 @@
     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0};
 // clang-format on
 
+size_t EscapeStringToString_Space(const std::string_view& str,
+                                  const EscapeOptions& options,
+                                  char* dest,
+                                  bool* needed_quoting) {
+  size_t i = 0;
+  for (const auto& elem : str) {
+    if (elem == ' ')
+      dest[i++] = '\\';
+    dest[i++] = elem;
+  }
+  return i;
+}
+
 // Uses the stack if the space needed is small and the heap otherwise.
 class StackOrHeapBuffer {
  public:
@@ -204,6 +217,8 @@
     case ESCAPE_NONE:
       strncpy(dest, str.data(), str.size());
       return str.size();
+    case ESCAPE_SPACE:
+      return EscapeStringToString_Space(str, options, dest, needed_quoting);
     case ESCAPE_NINJA:
       return EscapeStringToString_Ninja(str, options, dest, needed_quoting);
     case ESCAPE_DEPFILE:
diff --git a/src/gn/escape.h b/src/gn/escape.h
index 36a9c5c..28f31bf 100644
--- a/src/gn/escape.h
+++ b/src/gn/escape.h
@@ -12,6 +12,9 @@
   // No escaping.
   ESCAPE_NONE,
 
+  // Space only.
+  ESCAPE_SPACE,
+
   // Ninja string escaping.
   ESCAPE_NINJA,
 
diff --git a/src/gn/escape_unittest.cc b/src/gn/escape_unittest.cc
index 48f6941..ca42ac9 100644
--- a/src/gn/escape_unittest.cc
+++ b/src/gn/escape_unittest.cc
@@ -69,3 +69,12 @@
   // Only $ is escaped.
   EXPECT_EQ("a: \"$$\\b<;", EscapeString("a: \"$\\b<;", opts, nullptr));
 }
+
+TEST(Escape, Space) {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_SPACE;
+
+  // ' ' is escaped.
+  EXPECT_EQ("-VERSION=\"libsrtp2\\ 2.1.0-pre\"",
+            EscapeString("-VERSION=\"libsrtp2 2.1.0-pre\"", opts, nullptr));
+}