dexdump: escape inlined string values in output

Escapes control characters, double-quotes, and backslash.

Fix: 219965275
Test: art/test/dexdump/run-all-tests
Change-Id: I05f98f54d2e89f39482d102b521f1394bd5dcce2
diff --git a/dexdump/dexdump.cc b/dexdump/dexdump.cc
index 409ae55..d80809a 100644
--- a/dexdump/dexdump.cc
+++ b/dexdump/dexdump.cc
@@ -41,6 +41,7 @@
 #include <iomanip>
 #include <memory>
 #include <sstream>
+#include <string_view>
 #include <vector>
 
 #include "android-base/file.h"
@@ -360,32 +361,92 @@
   *out = '\0';
 }
 
+/* clang-format off */
+constexpr char kEscapedLength[256] = {
+    4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 4, 2, 2, 4, 4,  // \a, \b, \t, \n, \r
+    4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+    1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // ",
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // '0'..'9'
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 'A'..'O'
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1,  // 'P'..'Z', '\'
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 'a'..'o'
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4,  // 'p'..'z', DEL
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // Unicode range, keep
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+};
+/* clang-format on */
+
+/*
+ * Check if a UTF8 string contains characters we should quote.
+ */
+static bool needsEscape(std::string_view s) {
+  for (unsigned char c : s) {
+    if (kEscapedLength[c] != 1) {
+      return true;
+    }
+  }
+  return false;
+}
+
+std::string escapeString(std::string_view s) {
+  std::ostringstream oss;
+  for (unsigned char c : s) {
+    switch (kEscapedLength[c]) {
+      case 1:
+        oss << static_cast<char>(c);
+        break;
+      case 2:
+        switch (c) {
+          case '\b':
+            oss << '\\' << 'b';
+            break;
+          case '\f':
+            oss << '\\' << 'f';
+            break;
+          case '\n':
+            oss << '\\' << 'n';
+            break;
+          case '\r':
+            oss << '\\' << 'r';
+            break;
+          case '\t':
+            oss << '\\' << 't';
+            break;
+          case '\"':
+            oss << '\\' << '"';
+            break;
+          case '\\':
+            oss << '\\' << '\\';
+            break;
+        }
+        break;
+      case 4:
+        oss << '\\' << '0' + (c / 64) << '0' + ((c % 64) / 8) << '0' + (c % 8);
+        break;
+    }
+  }
+  return oss.str();
+}
+
 /*
  * Dumps a string value with some escape characters.
  */
-static void dumpEscapedString(std::string_view p) {
+static void dumpEscapedString(std::string_view s) {
   fputs("\"", gOutFile);
-  for (char c : p) {
-    switch (c) {
-      case '\\':
-        fputs("\\\\", gOutFile);
-        break;
-      case '\"':
-        fputs("\\\"", gOutFile);
-        break;
-      case '\t':
-        fputs("\\t", gOutFile);
-        break;
-      case '\n':
-        fputs("\\n", gOutFile);
-        break;
-      case '\r':
-        fputs("\\r", gOutFile);
-        break;
-      default:
-        putc(c, gOutFile);
-    }  // switch
-  }  // for
+  if (needsEscape(s)) {
+    std::string e = escapeString(s);
+    fputs(e.c_str(), gOutFile);
+  } else {
+    for (char c : s) {
+      fputc(c, gOutFile);
+    }
+  }
   fputs("\"", gOutFile);
 }
 
@@ -858,7 +919,13 @@
     case Instruction::kIndexStringRef:
       if (index < pDexFile->GetHeader().string_ids_size_) {
         const char* st = pDexFile->StringDataByIdx(dex::StringIndex(index));
-        outSize = snprintf(buf.get(), bufSize, "\"%s\" // string@%0*x", st, width, index);
+        if (needsEscape(std::string_view(st))) {
+          std::string escaped = escapeString(st);
+          outSize =
+              snprintf(buf.get(), bufSize, "\"%s\" // string@%0*x", escaped.c_str(), width, index);
+        } else {
+          outSize = snprintf(buf.get(), bufSize, "\"%s\" // string@%0*x", st, width, index);
+        }
       } else {
         outSize = snprintf(buf.get(), bufSize, "<string?> // string@%0*x", width, index);
       }