local-symbolizer: Parse Windows paths.

They contained colons in the filename, which broke the parsing logic.

Bug: 161203839
Change-Id: Id4edf76b896a86fb808680883156c8aca0190a2d
diff --git a/Android.bp b/Android.bp
index 12507ee..be1e483 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6415,6 +6415,14 @@
   ],
 }
 
+// GN: //src/profiling/symbolizer:unittests
+filegroup {
+  name: "perfetto_src_profiling_symbolizer_unittests",
+  srcs: [
+    "src/profiling/symbolizer/local_symbolizer_unittest.cc",
+  ],
+}
+
 // GN: //src/profiling:unittests
 filegroup {
   name: "perfetto_src_profiling_unittests",
@@ -7845,6 +7853,8 @@
     ":perfetto_src_profiling_perf_producer_unittests",
     ":perfetto_src_profiling_perf_regs_parsing",
     ":perfetto_src_profiling_perf_unwinding",
+    ":perfetto_src_profiling_symbolizer_symbolizer",
+    ":perfetto_src_profiling_symbolizer_unittests",
     ":perfetto_src_profiling_unittests",
     ":perfetto_src_protozero_protozero",
     ":perfetto_src_protozero_testing_messages_cpp_gen",
diff --git a/gn/perfetto_unittests.gni b/gn/perfetto_unittests.gni
index aae3085..41fcd00 100644
--- a/gn/perfetto_unittests.gni
+++ b/gn/perfetto_unittests.gni
@@ -21,6 +21,7 @@
   "src/protozero:unittests",
   "src/tracing/core:unittests",
   "src/profiling:unittests",
+  "src/profiling/symbolizer:unittests",
 ]
 
 if (enable_perfetto_ipc) {
diff --git a/src/profiling/symbolizer/BUILD.gn b/src/profiling/symbolizer/BUILD.gn
index 69c9446..79c770e 100644
--- a/src/profiling/symbolizer/BUILD.gn
+++ b/src/profiling/symbolizer/BUILD.gn
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import("../../../gn/perfetto.gni")
+import("../../../gn/test.gni")
 
 source_set("symbolizer") {
   public_deps = [ "../../../include/perfetto/ext/base" ]
@@ -42,3 +43,13 @@
     "symbolize_database.h",
   ]
 }
+
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  deps = [
+    ":symbolizer",
+    "../../../gn:default_deps",
+    "../../../gn:gtest_and_gmock",
+  ]
+  sources = [ "local_symbolizer_unittest.cc" ]
+}
diff --git a/src/profiling/symbolizer/local_symbolizer.cc b/src/profiling/symbolizer/local_symbolizer.cc
index 814b5a1..fce57fe 100644
--- a/src/profiling/symbolizer/local_symbolizer.cc
+++ b/src/profiling/symbolizer/local_symbolizer.cc
@@ -247,20 +247,6 @@
   void* ptr_;
 };
 
-bool ParseLine(std::string line, std::string* file_name, uint32_t* line_no) {
-  base::StringSplitter sp(std::move(line), ':');
-  if (!sp.Next())
-    return false;
-  *file_name = sp.cur_token();
-  if (!sp.Next())
-    return false;
-  char* endptr;
-  auto parsed_line_no = strtoll(sp.cur_token(), &endptr, 10);
-  if (parsed_line_no >= 0)
-    *line_no = static_cast<uint32_t>(parsed_line_no);
-  return *endptr == '\0' && parsed_line_no >= 0;
-}
-
 std::string SplitBuildID(const std::string& hex_build_id) {
   if (hex_build_id.size() < 3) {
     PERFETTO_DFATAL_OR_ELOG("Invalid build-id (< 3 char) %s",
@@ -273,6 +259,25 @@
 
 }  // namespace
 
+bool ParseLlvmSymbolizerLine(const std::string& line,
+                             std::string* file_name,
+                             uint32_t* line_no) {
+  size_t col_pos = line.rfind(':');
+  if (col_pos == std::string::npos || col_pos == 0)
+    return false;
+  size_t row_pos = line.rfind(':', col_pos - 1);
+  if (row_pos == std::string::npos || row_pos == 0)
+    return false;
+  *file_name = line.substr(0, row_pos);
+  auto line_no_str = line.substr(row_pos + 1, col_pos - row_pos - 1);
+
+  base::Optional<int32_t> opt_parsed_line_no = base::StringToInt32(line_no_str);
+  if (!opt_parsed_line_no || *opt_parsed_line_no < 0)
+    return false;
+  *line_no = static_cast<uint32_t>(*opt_parsed_line_no);
+  return true;
+}
+
 base::Optional<std::string> LocalBinaryFinder::FindBinary(
     const std::string& abspath,
     const std::string& build_id) {
@@ -459,7 +464,7 @@
     if (i % 2 == 0) {
       cur.function_name = lines[i];
     } else {
-      if (!ParseLine(lines[i], &cur.file_name, &cur.line)) {
+      if (!ParseLlvmSymbolizerLine(lines[i], &cur.file_name, &cur.line)) {
         PERFETTO_ELOG("Failed to parse llvm-symbolizer line: %s",
                       lines[i].c_str());
         cur.file_name = "";
diff --git a/src/profiling/symbolizer/local_symbolizer.h b/src/profiling/symbolizer/local_symbolizer.h
index eb3a717..fd33576 100644
--- a/src/profiling/symbolizer/local_symbolizer.h
+++ b/src/profiling/symbolizer/local_symbolizer.h
@@ -28,9 +28,13 @@
 namespace perfetto {
 namespace profiling {
 
+bool ParseLlvmSymbolizerLine(const std::string& line,
+                             std::string* file_name,
+                             uint32_t* line_no);
+
 class LocalBinaryFinder {
  public:
-  LocalBinaryFinder(std::vector<std::string> roots)
+  explicit LocalBinaryFinder(std::vector<std::string> roots)
       : roots_(std::move(roots)) {}
 
   base::Optional<std::string> FindBinary(const std::string& abspath,
@@ -79,7 +83,8 @@
 
 class LocalSymbolizer : public Symbolizer {
  public:
-  LocalSymbolizer(std::vector<std::string> roots) : finder_(std::move(roots)) {}
+  explicit LocalSymbolizer(std::vector<std::string> roots)
+      : finder_(std::move(roots)) {}
 
   std::vector<std::vector<SymbolizedFrame>> Symbolize(
       const std::string& mapping_name,
diff --git a/src/profiling/symbolizer/local_symbolizer_unittest.cc b/src/profiling/symbolizer/local_symbolizer_unittest.cc
new file mode 100644
index 0000000..b515866
--- /dev/null
+++ b/src/profiling/symbolizer/local_symbolizer_unittest.cc
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 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/base/build_config.h"
+#include "test/gtest_and_gmock.h"
+
+// This translation unit is built only on Linux and MacOS. See //gn/BUILD.gn.
+#if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
+
+#include "src/profiling/symbolizer/local_symbolizer.h"
+
+namespace perfetto {
+namespace profiling {
+namespace {
+
+TEST(LocalSymbolizerTest, ParseLineWindows) {
+  std::string file_name;
+  uint32_t lineno;
+  ASSERT_TRUE(
+      ParseLlvmSymbolizerLine("C:\\Foo\\Bar.cc:123:1", &file_name, &lineno));
+  EXPECT_EQ(file_name, "C:\\Foo\\Bar.cc");
+  EXPECT_EQ(lineno, 123u);
+}
+
+}  // namespace
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif