Merge "Add parser for proguard maps."
diff --git a/Android.bp b/Android.bp
index 3cfd830..469fec7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -832,6 +832,11 @@
   name: "perfetto_include_perfetto_ext_tracing_ipc_ipc",
 }
 
+// GN: //include/perfetto/profiling:deobfuscator
+filegroup {
+  name: "perfetto_include_perfetto_profiling_deobfuscator",
+}
+
 // GN: //include/perfetto/profiling:normalize
 filegroup {
   name: "perfetto_include_perfetto_profiling_normalize",
@@ -4272,6 +4277,14 @@
   ],
 }
 
+// GN: //src/profiling:deobfuscator
+filegroup {
+  name: "perfetto_src_profiling_deobfuscator",
+  srcs: [
+    "src/profiling/deobfuscator.cc",
+  ],
+}
+
 // GN: //src/profiling/memory:client
 filegroup {
   name: "perfetto_src_profiling_memory_client",
@@ -4368,6 +4381,14 @@
   ],
 }
 
+// GN: //src/profiling:unittests
+filegroup {
+  name: "perfetto_src_profiling_unittests",
+  srcs: [
+    "src/profiling/deobfuscator_unittest.cc",
+  ],
+}
+
 // GN: //src/protozero/protoc_plugin:cppgen_plugin
 cc_binary_host {
   name: "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
@@ -5376,6 +5397,7 @@
     ":perfetto_include_perfetto_ext_traced_traced",
     ":perfetto_include_perfetto_ext_tracing_core_core",
     ":perfetto_include_perfetto_ext_tracing_ipc_ipc",
+    ":perfetto_include_perfetto_profiling_deobfuscator",
     ":perfetto_include_perfetto_profiling_normalize",
     ":perfetto_include_perfetto_protozero_protozero",
     ":perfetto_include_perfetto_trace_processor_basic_types",
@@ -5460,6 +5482,7 @@
     ":perfetto_src_perfetto_cmd_protos_gen",
     ":perfetto_src_perfetto_cmd_trigger_producer",
     ":perfetto_src_perfetto_cmd_unittests",
+    ":perfetto_src_profiling_deobfuscator",
     ":perfetto_src_profiling_memory_client",
     ":perfetto_src_profiling_memory_daemon",
     ":perfetto_src_profiling_memory_proc_utils",
@@ -5468,6 +5491,7 @@
     ":perfetto_src_profiling_memory_scoped_spinlock",
     ":perfetto_src_profiling_memory_unittests",
     ":perfetto_src_profiling_memory_wire_protocol",
+    ":perfetto_src_profiling_unittests",
     ":perfetto_src_protozero_protozero",
     ":perfetto_src_protozero_testing_messages_cpp_gen",
     ":perfetto_src_protozero_testing_messages_lite_gen",
diff --git a/gn/perfetto_unittests.gni b/gn/perfetto_unittests.gni
index bdcf82f..b8e033f 100644
--- a/gn/perfetto_unittests.gni
+++ b/gn/perfetto_unittests.gni
@@ -20,6 +20,7 @@
   "src/base:unittests",
   "src/protozero:unittests",
   "src/tracing:unittests",
+  "src/profiling:unittests",
 ]
 
 if (enable_perfetto_tools && current_toolchain == host_toolchain) {
diff --git a/include/perfetto/profiling/BUILD.gn b/include/perfetto/profiling/BUILD.gn
index 23e19c9..03f8350 100644
--- a/include/perfetto/profiling/BUILD.gn
+++ b/include/perfetto/profiling/BUILD.gn
@@ -27,3 +27,9 @@
     "normalize.h",
   ]
 }
+
+source_set("deobfuscator") {
+  sources = [
+    "deobfuscator.h",
+  ]
+}
diff --git a/include/perfetto/profiling/deobfuscator.h b/include/perfetto/profiling/deobfuscator.h
new file mode 100644
index 0000000..a6f467a
--- /dev/null
+++ b/include/perfetto/profiling/deobfuscator.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDE_PERFETTO_PROFILING_DEOBFUSCATOR_H_
+#define INCLUDE_PERFETTO_PROFILING_DEOBFUSCATOR_H_
+
+#include <map>
+#include <string>
+
+namespace perfetto {
+namespace profiling {
+
+struct ObfuscatedClass {
+  ObfuscatedClass(std::string d) : deobfuscated_name(std::move(d)) {}
+  ObfuscatedClass(std::string d, std::map<std::string, std::string> f)
+      : deobfuscated_name(std::move(d)), deobfuscated_fields(std::move(f)) {}
+
+  std::string deobfuscated_name;
+  std::map<std::string, std::string> deobfuscated_fields;
+};
+
+class ProguardParser {
+ public:
+  // A return value of false means this line failed to parse. This leaves the
+  // parser in an undefined state and it should no longer be used.
+  bool AddLine(std::string line);
+
+  std::map<std::string, ObfuscatedClass> ConsumeMapping() {
+    return std::move(mapping_);
+  }
+
+ private:
+  std::map<std::string, ObfuscatedClass> mapping_;
+  ObfuscatedClass* current_class_ = nullptr;
+};
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_PROFILING_DEOBFUSCATOR_H_
diff --git a/src/profiling/BUILD.gn b/src/profiling/BUILD.gn
new file mode 100644
index 0000000..3ddfe8d
--- /dev/null
+++ b/src/profiling/BUILD.gn
@@ -0,0 +1,43 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../gn/perfetto.gni")
+import("../../gn/test.gni")
+
+source_set("deobfuscator") {
+  sources = [
+    "deobfuscator.cc",
+  ]
+  deps = [
+    "../..//include/perfetto/ext/base:base",
+    "../../gn:default_deps",
+  ]
+  public_deps = [
+    "../../../include/perfetto/profiling:deobfuscator",
+  ]
+}
+
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  deps = [
+    ":deobfuscator",
+    "../../gn:default_deps",
+    "../../gn:gtest_and_gmock",
+    "../base",
+    "../base:test_support",
+  ]
+  sources = [
+    "deobfuscator_unittest.cc",
+  ]
+}
diff --git a/src/profiling/deobfuscator.cc b/src/profiling/deobfuscator.cc
new file mode 100644
index 0000000..8306ded
--- /dev/null
+++ b/src/profiling/deobfuscator.cc
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/profiling/deobfuscator.h"
+#include "perfetto/ext/base/string_splitter.h"
+
+#include "perfetto/ext/base/optional.h"
+
+namespace perfetto {
+namespace profiling {
+namespace {
+
+base::Optional<std::pair<std::string, std::string>> ParseClass(
+    std::string line) {
+  base::StringSplitter ss(std::move(line), ' ');
+
+  if (!ss.Next()) {
+    PERFETTO_ELOG("Missing deobfuscated name.");
+    return base::nullopt;
+  }
+  std::string deobfuscated_name(ss.cur_token(), ss.cur_token_size());
+
+  if (!ss.Next() || ss.cur_token_size() != 2 ||
+      strncmp("->", ss.cur_token(), 2) != 0) {
+    PERFETTO_ELOG("Missing ->");
+    return base::nullopt;
+  }
+
+  if (!ss.Next()) {
+    PERFETTO_ELOG("Missing obfuscated name.");
+    return base::nullopt;
+  }
+  std::string obfuscated_name(ss.cur_token(), ss.cur_token_size());
+  if (obfuscated_name.size() == 0) {
+    PERFETTO_ELOG("Empty obfuscated name.");
+    return base::nullopt;
+  }
+  if (obfuscated_name.back() != ':') {
+    PERFETTO_ELOG("Expected colon.");
+    return base::nullopt;
+  }
+
+  obfuscated_name.resize(obfuscated_name.size() - 1);
+  if (ss.Next()) {
+    PERFETTO_ELOG("Unexpected data.");
+    return base::nullopt;
+  }
+  return std::make_pair(std::move(obfuscated_name),
+                        std::move(deobfuscated_name));
+}
+
+base::Optional<std::pair<std::string, std::string>> ParseMember(
+    std::string line) {
+  base::StringSplitter ss(std::move(line), ' ');
+
+  if (!ss.Next()) {
+    PERFETTO_ELOG("Missing type name.");
+    return base::nullopt;
+  }
+  std::string type_name(ss.cur_token(), ss.cur_token_size());
+
+  if (!ss.Next()) {
+    PERFETTO_ELOG("Missing deobfuscated name.");
+    return base::nullopt;
+  }
+  std::string deobfuscated_name(ss.cur_token(), ss.cur_token_size());
+
+  if (!ss.Next() || ss.cur_token_size() != 2 ||
+      strncmp("->", ss.cur_token(), 2) != 0) {
+    PERFETTO_ELOG("Missing ->");
+    return base::nullopt;
+  }
+
+  if (!ss.Next()) {
+    PERFETTO_ELOG("Missing obfuscated name.");
+    return base::nullopt;
+  }
+  std::string obfuscated_name(ss.cur_token(), ss.cur_token_size());
+
+  if (ss.Next()) {
+    PERFETTO_ELOG("Unexpected data.");
+    return base::nullopt;
+  }
+  return std::make_pair(std::move(obfuscated_name),
+                        std::move(deobfuscated_name));
+}
+
+}  // namespace
+
+bool ProguardParser::AddLine(std::string line) {
+  if (line.length() == 0)
+    return true;
+  bool is_member = line[0] == ' ';
+  if (is_member && !current_class_) {
+    PERFETTO_ELOG("Failed to parse proguard map. Saw member before class.");
+    return false;
+  }
+  if (!is_member) {
+    std::string obfuscated_name;
+    std::string deobfuscated_name;
+    auto opt_pair = ParseClass(std::move(line));
+    if (!opt_pair)
+      return false;
+    std::tie(obfuscated_name, deobfuscated_name) = *opt_pair;
+    auto p = mapping_.emplace(std::move(obfuscated_name),
+                              std::move(deobfuscated_name));
+    if (!p.second)
+      return false;
+    current_class_ = &p.first->second;
+  } else {
+    // TODO(fmayer): Teach this to properly parse methods.
+    std::string obfuscated_name;
+    std::string deobfuscated_name;
+    auto opt_pair = ParseMember(std::move(line));
+    if (!opt_pair)
+      return false;
+    std::tie(obfuscated_name, deobfuscated_name) = *opt_pair;
+    auto p = current_class_->deobfuscated_fields.emplace(
+        std::move(obfuscated_name), std::move(deobfuscated_name));
+    if (!p.second)
+      return false;
+  }
+  return true;
+}
+
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/deobfuscator_unittest.cc b/src/profiling/deobfuscator_unittest.cc
new file mode 100644
index 0000000..0b7833e
--- /dev/null
+++ b/src/profiling/deobfuscator_unittest.cc
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/profiling/deobfuscator.h"
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace profiling {
+
+bool operator==(const ObfuscatedClass& a, const ObfuscatedClass& b);
+bool operator==(const ObfuscatedClass& a, const ObfuscatedClass& b) {
+  return a.deobfuscated_name == b.deobfuscated_name &&
+         a.deobfuscated_fields == b.deobfuscated_fields;
+}
+
+namespace {
+
+using ::testing::ElementsAre;
+
+TEST(ProguardParserTest, ReadClass) {
+  ProguardParser p;
+  ASSERT_TRUE(p.AddLine(
+      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
+  ASSERT_THAT(p.ConsumeMapping(),
+              ElementsAre(std::pair<std::string, ObfuscatedClass>(
+                  "android.arch.a.a.a",
+                  "android.arch.core.executor.ArchTaskExecutor")));
+}
+
+TEST(ProguardParserTest, MissingColon) {
+  ProguardParser p;
+  ASSERT_FALSE(p.AddLine(
+      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a"));
+}
+
+TEST(ProguardParserTest, UnexpectedMember) {
+  ProguardParser p;
+  ASSERT_FALSE(
+      p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate -> b"));
+}
+
+TEST(ProguardParserTest, Member) {
+  ProguardParser p;
+  ASSERT_TRUE(p.AddLine(
+      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
+  ASSERT_TRUE(
+      p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate -> b"));
+  std::map<std::string, std::string> deobfuscated_fields{{"b", "mDelegate"}};
+  ASSERT_THAT(
+      p.ConsumeMapping(),
+      ElementsAre(std::pair<std::string, ObfuscatedClass>(
+          "android.arch.a.a.a", {"android.arch.core.executor.ArchTaskExecutor",
+                                 std::move(deobfuscated_fields)})));
+}
+
+TEST(ProguardParserTest, Method) {
+  ProguardParser p;
+  ASSERT_TRUE(p.AddLine(
+      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
+  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b"));
+}
+
+TEST(ProguardParserTest, DuplicateClass) {
+  ProguardParser p;
+  ASSERT_TRUE(p.AddLine(
+      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
+  ASSERT_FALSE(p.AddLine(
+      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
+}
+
+TEST(ProguardParserTest, DuplicateField) {
+  ProguardParser p;
+  ASSERT_TRUE(p.AddLine(
+      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
+  ASSERT_TRUE(
+      p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate -> b"));
+  ASSERT_FALSE(
+      p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate -> b"));
+}
+
+}  // namespace
+}  // namespace profiling
+}  // namespace perfetto