Introduce base::StringSplitter
Also improve FsMount tests
Test: perfetto_unittests --getst_filter=StringSplitterTest*
Bug: 73625480
Change-Id: Ib542af6eaa8f27f0aac01c81632d6230b4d17f19
diff --git a/Android.bp b/Android.bp
index 12469e5..15f4e94 100644
--- a/Android.bp
+++ b/Android.bp
@@ -33,6 +33,7 @@
":perfetto_src_ipc_wire_protocol_gen",
"src/base/file_utils.cc",
"src/base/page_allocator.cc",
+ "src/base/string_splitter.cc",
"src/base/thread_checker.cc",
"src/base/unix_task_runner.cc",
"src/base/watchdog.cc",
@@ -130,6 +131,7 @@
"src/base/android_task_runner.cc",
"src/base/file_utils.cc",
"src/base/page_allocator.cc",
+ "src/base/string_splitter.cc",
"src/base/thread_checker.cc",
"src/base/unix_task_runner.cc",
"src/base/watchdog.cc",
@@ -240,6 +242,7 @@
"src/base/android_task_runner.cc",
"src/base/file_utils.cc",
"src/base/page_allocator.cc",
+ "src/base/string_splitter.cc",
"src/base/test/test_task_runner.cc",
"src/base/test/vm_test_utils.cc",
"src/base/thread_checker.cc",
@@ -2947,6 +2950,7 @@
":perfetto_src_ipc_wire_protocol_gen",
"src/base/file_utils.cc",
"src/base/page_allocator.cc",
+ "src/base/string_splitter.cc",
"src/base/thread_checker.cc",
"src/base/unix_task_runner.cc",
"src/base/watchdog.cc",
@@ -3059,6 +3063,8 @@
"src/base/page_allocator.cc",
"src/base/page_allocator_unittest.cc",
"src/base/scoped_file_unittest.cc",
+ "src/base/string_splitter.cc",
+ "src/base/string_splitter_unittest.cc",
"src/base/task_runner_unittest.cc",
"src/base/test/test_task_runner.cc",
"src/base/test/vm_test_utils.cc",
diff --git a/include/perfetto/base/BUILD.gn b/include/perfetto/base/BUILD.gn
index 2847412..1a4c10a 100644
--- a/include/perfetto/base/BUILD.gn
+++ b/include/perfetto/base/BUILD.gn
@@ -19,6 +19,7 @@
"logging.h",
"page_allocator.h",
"scoped_file.h",
+ "string_splitter.h",
"task_runner.h",
"thread_checker.h",
"unix_task_runner.h",
diff --git a/include/perfetto/base/string_splitter.h b/include/perfetto/base/string_splitter.h
new file mode 100644
index 0000000..cee1a27
--- /dev/null
+++ b/include/perfetto/base/string_splitter.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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_BASE_STRING_SPLITTER_H_
+#define INCLUDE_PERFETTO_BASE_STRING_SPLITTER_H_
+
+#include <string>
+
+namespace perfetto {
+namespace base {
+
+// C++ version of strtok(). Splits a string without making copies or any heap
+// allocations. Destructs the original string passed in input.
+// Supports the special case of using \0 as a delimiter.
+// The token returned in output are valid as long as the input string is valid.
+class StringSplitter {
+ public:
+ // Can take ownership of the string if passed via std::move(), e.g.:
+ // StringSplitter(std::move(str), '\n');
+ StringSplitter(std::string, char delimiter);
+
+ // Splits a C-string. The input string will be forcefully null-terminated (so
+ // str[size - 1] should be == '\0' or the last char will be truncated).
+ StringSplitter(char* str, size_t size, char delimiter);
+
+ // Splits the current token from an outer StringSplitter instance. This is to
+ // chain splitters as follows:
+ // for (base::StringSplitter lines(x, '\n'); ss.Next();)
+ // for (base::StringSplitter words(&lines, ' '); words.Next();)
+ StringSplitter(StringSplitter*, char delimiter);
+
+ // Returns true if a token is found (in which case it will be stored in
+ // cur_token()), false if no more tokens are found.
+ bool Next();
+
+ // Returns the current token iff last call to Next() returned true. In this
+ // case it guarantees that the returned string is always null terminated.
+ // In all other cases (before the 1st call to Next() and after Next() returns
+ // false) returns nullptr.
+ char* cur_token() { return cur_; }
+
+ // Returns the length of the current token (excluding the null terminator).
+ size_t cur_token_size() const { return cur_size_; }
+
+ private:
+ StringSplitter(const StringSplitter&) = delete;
+ StringSplitter& operator=(const StringSplitter&) = delete;
+ void Initialize(char* str, size_t size);
+
+ std::string str_;
+ char* cur_;
+ size_t cur_size_;
+ char* next_;
+ char* end_; // STL-style, points one past the last char.
+ const char delimiter_;
+};
+
+} // namespace base
+} // namespace perfetto
+
+#endif // INCLUDE_PERFETTO_BASE_STRING_SPLITTER_H_
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index 9fc5460..2698002 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -24,6 +24,7 @@
sources = [
"file_utils.cc",
"page_allocator.cc",
+ "string_splitter.cc",
"thread_checker.cc",
"unix_task_runner.cc",
]
@@ -99,6 +100,7 @@
sources = [
"page_allocator_unittest.cc",
"scoped_file_unittest.cc",
+ "string_splitter_unittest.cc",
"task_runner_unittest.cc",
"thread_checker_unittest.cc",
"utils_unittest.cc",
diff --git a/src/base/string_splitter.cc b/src/base/string_splitter.cc
new file mode 100644
index 0000000..aab47b1
--- /dev/null
+++ b/src/base/string_splitter.cc
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 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/string_splitter.h"
+
+#include <utility>
+
+#include "perfetto/base/logging.h"
+
+namespace perfetto {
+namespace base {
+
+StringSplitter::StringSplitter(std::string str, char delimiter)
+ : str_(std::move(str)), delimiter_(delimiter) {
+ // It's legal to access str[str.size()] in C++11 (it always returns \0),
+ // hence the +1 (which becomes just size() after the -1 in Initialize()).
+ Initialize(&str_[0], str_.size() + 1);
+}
+
+StringSplitter::StringSplitter(char* str, size_t size, char delimiter)
+ : delimiter_(delimiter) {
+ Initialize(str, size);
+}
+
+StringSplitter::StringSplitter(StringSplitter* outer, char delimiter)
+ : delimiter_(delimiter) {
+ Initialize(outer->cur_token(), outer->cur_token_size() + 1);
+}
+
+void StringSplitter::Initialize(char* str, size_t size) {
+ PERFETTO_DCHECK(!size || str);
+ next_ = str;
+ end_ = str + size;
+ cur_ = nullptr;
+ cur_size_ = 0;
+ if (size)
+ next_[size - 1] = '\0';
+}
+
+bool StringSplitter::Next() {
+ for (; next_ < end_; next_++) {
+ if (*next_ == delimiter_)
+ continue;
+ cur_ = next_;
+ for (;; next_++) {
+ if (*next_ == delimiter_) {
+ cur_size_ = static_cast<size_t>(next_ - cur_);
+ *(next_++) = '\0';
+ break;
+ }
+ if (*next_ == '\0') {
+ cur_size_ = static_cast<size_t>(next_ - cur_);
+ next_ = end_;
+ break;
+ }
+ }
+ if (*cur_)
+ return true;
+ break;
+ }
+ cur_ = nullptr;
+ cur_size_ = 0;
+ return false;
+}
+
+} // namespace base
+} // namespace perfetto
diff --git a/src/base/string_splitter_unittest.cc b/src/base/string_splitter_unittest.cc
new file mode 100644
index 0000000..c3a3796
--- /dev/null
+++ b/src/base/string_splitter_unittest.cc
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2018 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/string_splitter.h"
+
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace perfetto {
+namespace base {
+namespace {
+
+using testing::ElementsAreArray;
+
+TEST(StringSplitterTest, StdString) {
+ {
+ StringSplitter ss("", 'x');
+ EXPECT_EQ(nullptr, ss.cur_token());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ }
+ {
+ StringSplitter ss(std::string(), 'x');
+ EXPECT_EQ(nullptr, ss.cur_token());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ }
+ {
+ StringSplitter ss("a", 'x');
+ EXPECT_EQ(nullptr, ss.cur_token());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("a", ss.cur_token());
+ EXPECT_EQ(1u, ss.cur_token_size());
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ }
+ {
+ StringSplitter ss("abc", 'x');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("abc", ss.cur_token());
+ EXPECT_EQ(3u, ss.cur_token_size());
+ EXPECT_FALSE(ss.Next());
+ }
+ {
+ StringSplitter ss("ab,", ',');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("ab", ss.cur_token());
+ EXPECT_EQ(2u, ss.cur_token_size());
+ EXPECT_FALSE(ss.Next());
+ }
+ {
+ StringSplitter ss(",ab,", ',');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("ab", ss.cur_token());
+ EXPECT_EQ(2u, ss.cur_token_size());
+ EXPECT_FALSE(ss.Next());
+ }
+ {
+ StringSplitter ss("a,b,c", ',');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_EQ(1u, ss.cur_token_size());
+ EXPECT_STREQ("a", ss.cur_token());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("b", ss.cur_token());
+ EXPECT_EQ(1u, ss.cur_token_size());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("c", ss.cur_token());
+ EXPECT_EQ(1u, ss.cur_token_size());
+
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ }
+ {
+ StringSplitter ss("a,b,c,", ',');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("a", ss.cur_token());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("b", ss.cur_token());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("c", ss.cur_token());
+
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ }
+ {
+ StringSplitter ss(",,a,,b,,,,c,,,", ',');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("a", ss.cur_token());
+ EXPECT_EQ(1u, ss.cur_token_size());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("b", ss.cur_token());
+ EXPECT_EQ(1u, ss.cur_token_size());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("c", ss.cur_token());
+ EXPECT_EQ(1u, ss.cur_token_size());
+
+ for (int i = 0; i < 3; i++) {
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ }
+ }
+ {
+ StringSplitter ss(",,", ',');
+ for (int i = 0; i < 3; i++) {
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ }
+ }
+ {
+ StringSplitter ss(",,foo", ',');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("foo", ss.cur_token());
+ EXPECT_EQ(3u, ss.cur_token_size());
+ EXPECT_FALSE(ss.Next());
+ }
+}
+
+TEST(StringSplitterTest, CString) {
+ {
+ char buf[] = "\0x\0";
+ StringSplitter ss(buf, sizeof(buf), ',');
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ }
+ {
+ char buf[] = "foo\nbar\n\nbaz\n";
+ StringSplitter ss(buf, sizeof(buf), '\n');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_EQ(3u, ss.cur_token_size());
+ EXPECT_STREQ("foo", ss.cur_token());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_EQ(3u, ss.cur_token_size());
+ EXPECT_STREQ("bar", ss.cur_token());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_EQ(3u, ss.cur_token_size());
+ EXPECT_STREQ("baz", ss.cur_token());
+
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ }
+ {
+ char buf[] = "";
+ StringSplitter ss(buf, 0, ',');
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ }
+ {
+ char buf[] = "\0";
+ StringSplitter ss(buf, 1, ',');
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ }
+ {
+ char buf[] = ",,foo,bar\0,baz";
+ StringSplitter ss(buf, sizeof(buf), ',');
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("foo", ss.cur_token());
+ EXPECT_EQ(3u, ss.cur_token_size());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("bar", ss.cur_token());
+ EXPECT_EQ(3u, ss.cur_token_size());
+
+ for (int i = 0; i < 3; i++) {
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ }
+ }
+ {
+ char buf[] = ",,a\0,b,";
+ StringSplitter ss(buf, sizeof(buf), ',');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("a", ss.cur_token());
+ EXPECT_EQ(1u, ss.cur_token_size());
+ for (int i = 0; i < 3; i++) {
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ }
+ }
+ {
+ char buf[] = ",a,\0b";
+ StringSplitter ss(buf, sizeof(buf), ',');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("a", ss.cur_token());
+ EXPECT_EQ(1u, ss.cur_token_size());
+ for (int i = 0; i < 3; i++) {
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ }
+ }
+ {
+ char buf[] = ",a\0\0,x\0\0b";
+ StringSplitter ss(buf, sizeof(buf), ',');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("a", ss.cur_token());
+ EXPECT_EQ(1u, ss.cur_token_size());
+ for (int i = 0; i < 3; i++) {
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ }
+ }
+}
+
+TEST(StringSplitterTest, SplitOnNUL) {
+ {
+ StringSplitter ss(std::string(""), '\0');
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ }
+ {
+ std::string str;
+ str.resize(48);
+ memcpy(&str[0], "foo\0", 4);
+ memcpy(&str[4], "bar\0", 4);
+ memcpy(&str[20], "baz", 3);
+ StringSplitter ss(std::move(str), '\0');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("foo", ss.cur_token());
+ EXPECT_EQ(3u, ss.cur_token_size());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("bar", ss.cur_token());
+ EXPECT_EQ(3u, ss.cur_token_size());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_STREQ("baz", ss.cur_token());
+ EXPECT_EQ(3u, ss.cur_token_size());
+
+ for (int i = 0; i < 3; i++) {
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ }
+ }
+ {
+ char buf[] = "foo\0bar\0baz\0";
+ StringSplitter ss(buf, sizeof(buf), '\0');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_EQ(3u, ss.cur_token_size());
+ EXPECT_STREQ("foo", ss.cur_token());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_EQ(3u, ss.cur_token_size());
+ EXPECT_STREQ("bar", ss.cur_token());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_EQ(3u, ss.cur_token_size());
+ EXPECT_STREQ("baz", ss.cur_token());
+
+ for (int i = 0; i < 3; i++) {
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ }
+ }
+ {
+ char buf[] = "\0\0foo\0\0\0\0bar\0baz\0\0";
+ StringSplitter ss(buf, sizeof(buf), '\0');
+ EXPECT_TRUE(ss.Next());
+ EXPECT_EQ(3u, ss.cur_token_size());
+ EXPECT_STREQ("foo", ss.cur_token());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_EQ(3u, ss.cur_token_size());
+ EXPECT_STREQ("bar", ss.cur_token());
+
+ EXPECT_TRUE(ss.Next());
+ EXPECT_EQ(3u, ss.cur_token_size());
+ EXPECT_STREQ("baz", ss.cur_token());
+
+ for (int i = 0; i < 3; i++) {
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ }
+ }
+ {
+ char buf[] = "";
+ StringSplitter ss(buf, 0, '\0');
+ for (int i = 0; i < 3; i++) {
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ }
+ }
+ {
+ char buf[] = "\0";
+ StringSplitter ss(buf, 1, '\0');
+ for (int i = 0; i < 3; i++) {
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ }
+ }
+ {
+ char buf[] = "\0\0";
+ StringSplitter ss(buf, 2, '\0');
+ for (int i = 0; i < 3; i++) {
+ EXPECT_FALSE(ss.Next());
+ EXPECT_EQ(0u, ss.cur_token_size());
+ EXPECT_EQ(nullptr, ss.cur_token());
+ }
+ }
+}
+
+TEST(StringSplitterTest, NestedUsage) {
+ char text[] = R"(
+l1w1 l1w2 l1w3
+
+,l,2,w,1 l,2,,w,,2,,
+)";
+ std::vector<std::string> all_lines;
+ std::vector<std::string> all_words;
+ std::vector<std::string> all_tokens;
+ for (StringSplitter lines(text, sizeof(text), '\n'); lines.Next();) {
+ all_lines.push_back(lines.cur_token());
+ for (StringSplitter words(&lines, ' '); words.Next();) {
+ all_words.push_back(words.cur_token());
+ for (StringSplitter tokens(&words, ','); tokens.Next();) {
+ all_tokens.push_back(tokens.cur_token());
+ }
+ }
+ }
+ EXPECT_THAT(all_lines,
+ ElementsAreArray({"l1w1 l1w2 l1w3", ",l,2,w,1 l,2,,w,,2,,"}));
+ EXPECT_THAT(all_words, ElementsAreArray({"l1w1", "l1w2", "l1w3", ",l,2,w,1",
+ "l,2,,w,,2,,"}));
+ EXPECT_THAT(all_tokens, ElementsAreArray({"l1w1", "l1w2", "l1w3", "l", "2",
+ "w", "1", "l", "2", "w", "2"}));
+} // namespace
+
+} // namespace
+} // namespace base
+} // namespace perfetto
diff --git a/src/ftrace_reader/format_parser.cc b/src/ftrace_reader/format_parser.cc
index ff516e7..744ba56 100644
--- a/src/ftrace_reader/format_parser.cc
+++ b/src/ftrace_reader/format_parser.cc
@@ -25,6 +25,7 @@
#include <string>
#include <vector>
+#include "perfetto/base/string_splitter.h"
#include "perfetto/base/utils.h"
namespace perfetto {
@@ -80,9 +81,6 @@
}
bool ParseFtraceEvent(const std::string& input, FtraceEvent* output) {
- std::unique_ptr<char[], base::FreeDeleter> input_copy(strdup(input.c_str()));
- char* s = input_copy.get();
-
char buffer[MAX_FIELD_LENGTH + 1];
bool has_id = false;
@@ -93,7 +91,8 @@
std::vector<FtraceEvent::Field> common_fields;
std::vector<FtraceEvent::Field> fields;
- for (char* line = strtok(s, "\n"); line; line = strtok(nullptr, "\n")) {
+ for (base::StringSplitter ss(input, '\n'); ss.Next();) {
+ const char* line = ss.cur_token();
if (!has_id && sscanf(line, "ID: %d", &id) == 1) {
has_id = true;
continue;
diff --git a/src/process_stats/procfs_utils.cc b/src/process_stats/procfs_utils.cc
index e94716f..0411e20 100644
--- a/src/process_stats/procfs_utils.cc
+++ b/src/process_stats/procfs_utils.cc
@@ -11,6 +11,7 @@
#include "file_utils.h"
#include "perfetto/base/logging.h"
+#include "perfetto/base/string_splitter.h"
using file_utils::ForEachPidInProcPath;
using file_utils::ReadProcFile;
@@ -53,17 +54,6 @@
return atoi(line + strlen(status_string));
}
-inline std::vector<std::string> SplitOnNull(const char* input, size_t size) {
- std::vector<std::string> output;
- const char* end = input + size;
- do {
- // This works because it will only push the string up to a null character.
- output.push_back(std::string(input));
- input += output.back().size() + 1;
- } while (input[0] != 0 && input < end);
- return output;
-}
-
} // namespace
int ReadTgid(int pid) {
@@ -86,7 +76,9 @@
process->cmdline.push_back(name);
process->in_kernel = true;
} else {
- process->cmdline = SplitOnNull(cmdline_buf, sizeof(cmdline_buf));
+ using perfetto::base::StringSplitter;
+ for (StringSplitter ss(cmdline_buf, sizeof(cmdline_buf), '\0'); ss.Next();)
+ process->cmdline.push_back(ss.cur_token());
ReadExePath(pid, process->exe, sizeof(process->exe));
process->is_app = IsApp(process->cmdline[0].c_str(), process->exe);
}
diff --git a/src/traced/probes/filesystem/fs_mount.cc b/src/traced/probes/filesystem/fs_mount.cc
index 1187974..1db29ba 100644
--- a/src/traced/probes/filesystem/fs_mount.cc
+++ b/src/traced/probes/filesystem/fs_mount.cc
@@ -23,51 +23,31 @@
#include "perfetto/base/file_utils.h"
#include "perfetto/base/logging.h"
+#include "perfetto/base/string_splitter.h"
namespace perfetto {
-namespace {
-constexpr const char kMountsPath[] = "/proc/mounts";
-
-std::vector<std::string> split(const std::string& text, char s) {
- std::vector<std::string> result;
- size_t start = 0;
- size_t end = 0;
- do {
- end = text.find(s, start);
- if (end == std::string::npos)
- end = text.size();
- std::string sub = text.substr(start, end - start);
- if (!sub.empty())
- result.emplace_back(std::move(sub));
- start = end + 1;
- } while (start < text.size());
- return result;
-}
-} // namespace
-
-std::multimap<BlockDeviceID, std::string> ParseMounts() {
+std::multimap<BlockDeviceID, std::string> ParseMounts(const char* path) {
std::string data;
- if (!base::ReadFile(kMountsPath, &data)) {
- PERFETTO_ELOG("Failed to read %s.", kMountsPath);
+ if (!base::ReadFile(path, &data)) {
+ PERFETTO_ELOG("Failed to read %s", path);
return {};
}
std::multimap<BlockDeviceID, std::string> device_to_mountpoints;
- std::vector<std::string> lines = split(data, '\n');
- struct stat buf {};
- for (const std::string& line : lines) {
- std::vector<std::string> words = split(line, ' ');
- if (words.size() < 2) {
- PERFETTO_DLOG("Encountered incomplete row in %s: %s.", kMountsPath,
- line.c_str());
+
+ for (base::StringSplitter lines(std::move(data), '\n'); lines.Next();) {
+ base::StringSplitter words(&lines, ' ');
+ if (!words.Next() || !words.Next()) {
+ PERFETTO_DLOG("Invalid mount point: %s.", lines.cur_token());
continue;
}
- std::string& mountpoint = words[1];
- if (stat(mountpoint.c_str(), &buf) == -1) {
+ const char* mountpoint = words.cur_token();
+ struct stat buf {};
+ if (stat(mountpoint, &buf) == -1) {
PERFETTO_PLOG("stat");
continue;
}
- device_to_mountpoints.emplace(buf.st_dev, std::move(mountpoint));
+ device_to_mountpoints.emplace(buf.st_dev, mountpoint);
}
return device_to_mountpoints;
}
diff --git a/src/traced/probes/filesystem/fs_mount.h b/src/traced/probes/filesystem/fs_mount.h
index f59971c..8d4c49a 100644
--- a/src/traced/probes/filesystem/fs_mount.h
+++ b/src/traced/probes/filesystem/fs_mount.h
@@ -27,7 +27,10 @@
// On ARM, st_dev is not dev_t but unsigned long long.
using BlockDeviceID = decltype(stat::st_dev);
-std::multimap<BlockDeviceID, std::string> ParseMounts();
+constexpr char kMountsPath[] = "/proc/mounts";
+
+std::multimap<BlockDeviceID, std::string> ParseMounts(
+ const char* path = kMountsPath);
} // namespace perfetto
diff --git a/src/traced/probes/filesystem/fs_mount_unittest.cc b/src/traced/probes/filesystem/fs_mount_unittest.cc
index b6c395a..de5526c 100644
--- a/src/traced/probes/filesystem/fs_mount_unittest.cc
+++ b/src/traced/probes/filesystem/fs_mount_unittest.cc
@@ -16,8 +16,16 @@
#include "src/traced/probes/filesystem/fs_mount.h"
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "perfetto/base/build_config.h"
+#include "perfetto/base/scoped_file.h"
+#include "perfetto/base/utils.h"
namespace perfetto {
namespace {
@@ -25,12 +33,38 @@
using testing::Contains;
using testing::Pair;
-TEST(InodeTest, ParseMounts) {
- auto mounts = ParseMounts();
- struct stat buf;
+TEST(FsMountTest, ParseRealMounts) {
+ std::multimap<BlockDeviceID, std::string> mounts = ParseMounts();
+ struct stat buf = {};
ASSERT_NE(stat("/proc", &buf), -1);
EXPECT_THAT(mounts, Contains(Pair(buf.st_dev, "/proc")));
}
+TEST(FsMountTest, ParseSyntheticMounts) {
+ const char kMounts[] = R"(
+procfs /proc proc rw,nosuid,nodev,noexec,relatime 0 0
+#INVALIDLINE
+sysfs / sysfs rw,nosuid,nodev,noexec,relatime 0 0
+)";
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+ char tmp_path[PATH_MAX] = "/data/local/tmp/fake_mounts.XXXXXX";
+#else
+ char tmp_path[PATH_MAX] = "/tmp/fake_mounts.XXXXXX";
+#endif
+
+ base::ScopedFile tmp_fd(mkstemp(tmp_path));
+ ASSERT_GT(*tmp_fd, -1);
+ base::ignore_result(write(*tmp_fd, kMounts, sizeof(kMounts)));
+ tmp_fd.reset();
+
+ std::multimap<BlockDeviceID, std::string> mounts = ParseMounts(tmp_path);
+ unlink(tmp_path);
+ struct stat proc_stat = {}, root_stat = {};
+ ASSERT_NE(stat("/proc", &proc_stat), -1);
+ ASSERT_NE(stat("/", &root_stat), -1);
+ EXPECT_THAT(mounts, Contains(Pair(proc_stat.st_dev, "/proc")));
+ EXPECT_THAT(mounts, Contains(Pair(root_stat.st_dev, "/")));
+} // namespace
+
} // namespace
} // namespace perfetto