Escape comma and backslash for list props

Comma is used as a separator for list props. To support comma on
StringList properties, elements are now escaped with backslashes.

Bug: 147402003
Test: fuzzing parser and formatter locally, sysprop_test, boot
cuttlefish

Change-Id: I69f1b5e7ba12746d982e9b05a62945a8746d9487
diff --git a/CppGen.cpp b/CppGen.cpp
index 98ca12b..f047e7d 100644
--- a/CppGen.cpp
+++ b/CppGen.cpp
@@ -115,16 +115,19 @@
 
 template <typename Vec> [[maybe_unused]] Vec DoParseList(const char* str) {
     Vec ret;
+    if (*str == '\0') return ret;
     const char* p = str;
     for (;;) {
-        const char* found = p;
-        while (*found != '\0' && *found != ',') {
-            ++found;
+        const char* r = p;
+        std::string value;
+        while (*r != ',') {
+            if (*r == '\\') ++r;
+            if (*r == '\0') break;
+            value += *r++;
         }
-        std::string value(p, found);
         ret.emplace_back(DoParse<typename Vec::value_type>(value.c_str()));
-        if (*found == '\0') break;
-        p = found + 1;
+        if (*r == '\0') break;
+        p = r + 1;
     }
     return ret;
 }
@@ -164,10 +167,15 @@
     bool first = true;
 
     for (auto&& element : value) {
-        if (!first) ret += ",";
+        if (!first) ret += ',';
         else first = false;
         if constexpr(std::is_same_v<T, std::optional<std::string>>) {
-            if (element) ret += *element;
+            if (element) {
+                for (char c : *element) {
+                    if (c == '\\' || c == ',') ret += '\\';
+                    ret += c;
+                }
+            }
         } else {
             ret += FormatValue(element);
         }
diff --git a/JavaGen.cpp b/JavaGen.cpp
index 9a6bae8..4ac45b1 100644
--- a/JavaGen.cpp
+++ b/JavaGen.cpp
@@ -42,6 +42,7 @@
 constexpr const char* kJavaFileImports =
     R"(import android.os.SystemProperties;
 
+import java.lang.StringBuilder;
 import java.util.ArrayList;
 import java.util.function.Function;
 import java.util.List;
@@ -53,7 +54,7 @@
 )";
 
 constexpr const char* kJavaParsersAndFormatters =
-    R"(private static Boolean tryParseBoolean(String str) {
+    R"s(private static Boolean tryParseBoolean(String str) {
     switch (str.toLowerCase(Locale.US)) {
         case "1":
         case "true":
@@ -107,8 +108,17 @@
 
     List<T> ret = new ArrayList<>();
 
-    for (String element : str.split(",")) {
-        ret.add(elementParser.apply(element));
+    int p = 0;
+    for (;;) {
+        StringBuilder sb = new StringBuilder();
+        while (p < str.length() && str.charAt(p) != ',') {
+            if (str.charAt(p) == '\\') ++p;
+            if (p == str.length()) break;
+            sb.append(str.charAt(p++));
+        }
+        ret.add(elementParser.apply(sb.toString()));
+        if (p == str.length()) break;
+        ++p;
     }
 
     return ret;
@@ -126,11 +136,15 @@
     return ret;
 }
 
+private static String escape(String str) {
+    return str.replaceAll("([\\\\,])", "\\\\$1");
+}
+
 private static <T> String formatList(List<T> list) {
     StringJoiner joiner = new StringJoiner(",");
 
     for (T element : list) {
-        joiner.add(element == null ? "" : element.toString());
+        joiner.add(element == null ? "" : escape(element.toString()));
     }
 
     return joiner.toString();
@@ -145,7 +159,7 @@
 
     return joiner.toString();
 }
-)";
+)s";
 
 const std::regex kRegexDot{"\\."};
 const std::regex kRegexUnderscore{"_"};
diff --git a/tests/CppGenTest.cpp b/tests/CppGenTest.cpp
index e1ce0fa..f8994b6 100644
--- a/tests/CppGenTest.cpp
+++ b/tests/CppGenTest.cpp
@@ -327,16 +327,19 @@
 
 template <typename Vec> [[maybe_unused]] Vec DoParseList(const char* str) {
     Vec ret;
+    if (*str == '\0') return ret;
     const char* p = str;
     for (;;) {
-        const char* found = p;
-        while (*found != '\0' && *found != ',') {
-            ++found;
+        const char* r = p;
+        std::string value;
+        while (*r != ',') {
+            if (*r == '\\') ++r;
+            if (*r == '\0') break;
+            value += *r++;
         }
-        std::string value(p, found);
         ret.emplace_back(DoParse<typename Vec::value_type>(value.c_str()));
-        if (*found == '\0') break;
-        p = found + 1;
+        if (*r == '\0') break;
+        p = r + 1;
     }
     return ret;
 }
@@ -376,10 +379,15 @@
     bool first = true;
 
     for (auto&& element : value) {
-        if (!first) ret += ",";
+        if (!first) ret += ',';
         else first = false;
         if constexpr(std::is_same_v<T, std::optional<std::string>>) {
-            if (element) ret += *element;
+            if (element) {
+                for (char c : *element) {
+                    if (c == '\\' || c == ',') ret += '\\';
+                    ret += c;
+                }
+            }
         } else {
             ret += FormatValue(element);
         }
diff --git a/tests/JavaGenTest.cpp b/tests/JavaGenTest.cpp
index a0d01c4..eadc187 100644
--- a/tests/JavaGenTest.cpp
+++ b/tests/JavaGenTest.cpp
@@ -101,12 +101,13 @@
 )";
 
 constexpr const char* kExpectedPublicOutput =
-    R"(// Generated by the sysprop generator. DO NOT EDIT!
+    R"s(// Generated by the sysprop generator. DO NOT EDIT!
 
 package com.somecompany;
 
 import android.os.SystemProperties;
 
+import java.lang.StringBuilder;
 import java.util.ArrayList;
 import java.util.function.Function;
 import java.util.List;
@@ -172,8 +173,17 @@
 
         List<T> ret = new ArrayList<>();
 
-        for (String element : str.split(",")) {
-            ret.add(elementParser.apply(element));
+        int p = 0;
+        for (;;) {
+            StringBuilder sb = new StringBuilder();
+            while (p < str.length() && str.charAt(p) != ',') {
+                if (str.charAt(p) == '\\') ++p;
+                if (p == str.length()) break;
+                sb.append(str.charAt(p++));
+            }
+            ret.add(elementParser.apply(sb.toString()));
+            if (p == str.length()) break;
+            ++p;
         }
 
         return ret;
@@ -191,11 +201,15 @@
         return ret;
     }
 
+    private static String escape(String str) {
+        return str.replaceAll("([\\\\,])", "\\\\$1");
+    }
+
     private static <T> String formatList(List<T> list) {
         StringJoiner joiner = new StringJoiner(",");
 
         for (T element : list) {
-            joiner.add(element == null ? "" : element.toString());
+            joiner.add(element == null ? "" : escape(element.toString()));
         }
 
         return joiner.toString();
@@ -267,15 +281,16 @@
         SystemProperties.set("vendor.test_strlist", value == null ? "" : formatList(value));
     }
 }
-)";
+)s";
 
 constexpr const char* kExpectedInternalOutput =
-    R"(// Generated by the sysprop generator. DO NOT EDIT!
+    R"s(// Generated by the sysprop generator. DO NOT EDIT!
 
 package com.somecompany;
 
 import android.os.SystemProperties;
 
+import java.lang.StringBuilder;
 import java.util.ArrayList;
 import java.util.function.Function;
 import java.util.List;
@@ -341,8 +356,17 @@
 
         List<T> ret = new ArrayList<>();
 
-        for (String element : str.split(",")) {
-            ret.add(elementParser.apply(element));
+        int p = 0;
+        for (;;) {
+            StringBuilder sb = new StringBuilder();
+            while (p < str.length() && str.charAt(p) != ',') {
+                if (str.charAt(p) == '\\') ++p;
+                if (p == str.length()) break;
+                sb.append(str.charAt(p++));
+            }
+            ret.add(elementParser.apply(sb.toString()));
+            if (p == str.length()) break;
+            ++p;
         }
 
         return ret;
@@ -360,11 +384,15 @@
         return ret;
     }
 
+    private static String escape(String str) {
+        return str.replaceAll("([\\\\,])", "\\\\$1");
+    }
+
     private static <T> String formatList(List<T> list) {
         StringJoiner joiner = new StringJoiner(",");
 
         for (T element : list) {
-            joiner.add(element == null ? "" : element.toString());
+            joiner.add(element == null ? "" : escape(element.toString()));
         }
 
         return joiner.toString();
@@ -504,7 +532,7 @@
         SystemProperties.set("vendor.el", value == null ? "" : formatEnumList(value, el_values::getPropValue));
     }
 }
-)";
+)s";
 
 }  // namespace