Add base::Regex (#5291)
- create base::regex a wrapper around std::regex (to be extended)
- Migrate all direct std::regex users to use base::Regex.
diff --git a/Android.bp b/Android.bp
index d4e4cea..cf5c255 100644
--- a/Android.bp
+++ b/Android.bp
@@ -96,6 +96,7 @@
":perfetto_protos_perfetto_trace_track_event_zero_gen",
":perfetto_protos_perfetto_trace_translation_zero_gen",
":perfetto_src_base_base",
+ ":perfetto_src_base_regex",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
@@ -239,6 +240,7 @@
":perfetto_include_perfetto_public_abi_base",
":perfetto_include_perfetto_public_base",
":perfetto_src_base_base",
+ ":perfetto_src_base_regex",
":perfetto_src_profiling_memory_malloc_interceptor_bionic_hooks",
":perfetto_src_profiling_memory_wrap_allocators",
],
@@ -273,6 +275,7 @@
":perfetto_include_perfetto_public_abi_base",
":perfetto_include_perfetto_public_base",
":perfetto_src_base_base",
+ ":perfetto_src_base_regex",
":perfetto_src_base_unix_socket",
":perfetto_src_profiling_memory_client",
":perfetto_src_profiling_memory_client_api",
@@ -395,6 +398,7 @@
":perfetto_protos_perfetto_trace_track_event_zero_gen",
":perfetto_protos_perfetto_trace_translation_zero_gen",
":perfetto_src_base_base",
+ ":perfetto_src_base_regex",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
@@ -530,6 +534,7 @@
":perfetto_include_perfetto_public_abi_base",
":perfetto_include_perfetto_public_base",
":perfetto_src_base_base",
+ ":perfetto_src_base_regex",
"src/ipc/protoc_plugin/ipc_plugin.cc",
],
static_libs: [
@@ -636,6 +641,7 @@
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
":perfetto_src_base_clock_snapshots",
+ ":perfetto_src_base_regex",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
@@ -921,6 +927,7 @@
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
":perfetto_src_base_clock_snapshots",
+ ":perfetto_src_base_regex",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
@@ -1136,6 +1143,7 @@
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
":perfetto_src_base_clock_snapshots",
+ ":perfetto_src_base_regex",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
@@ -1486,6 +1494,7 @@
":perfetto_src_android_stats_android_stats",
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
+ ":perfetto_src_base_regex",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
@@ -1868,6 +1877,7 @@
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
":perfetto_src_base_clock_snapshots",
+ ":perfetto_src_base_regex",
":perfetto_src_base_test_support",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
@@ -2236,6 +2246,7 @@
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
":perfetto_src_base_clock_snapshots",
+ ":perfetto_src_base_regex",
":perfetto_src_base_test_support",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
@@ -2877,6 +2888,7 @@
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
":perfetto_src_base_clock_snapshots",
+ ":perfetto_src_base_regex",
":perfetto_src_base_test_support",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
@@ -13572,6 +13584,14 @@
],
}
+// GN: //src/base:regex
+filegroup {
+ name: "perfetto_src_base_regex",
+ srcs: [
+ "src/base/regex/regex.cc",
+ ],
+}
+
// GN: //src/base:test_support
filegroup {
name: "perfetto_src_base_test_support",
@@ -13622,6 +13642,7 @@
"src/base/no_destructor_unittest.cc",
"src/base/paged_memory_unittest.cc",
"src/base/periodic_task_unittest.cc",
+ "src/base/regex/regex_unittest.cc",
"src/base/rt_mutex_unittest.cc",
"src/base/scoped_file_unittest.cc",
"src/base/scoped_mmap_unittest.cc",
@@ -14385,6 +14406,7 @@
":perfetto_protos_perfetto_common_zero_gen",
":perfetto_protos_perfetto_protovm_zero_gen",
":perfetto_src_base_base",
+ ":perfetto_src_base_regex",
":perfetto_src_base_version",
":perfetto_src_protozero_descriptor_diff_lib",
":perfetto_src_protozero_protozero",
@@ -14533,6 +14555,7 @@
":perfetto_include_perfetto_public_abi_base",
":perfetto_include_perfetto_public_base",
":perfetto_src_base_base",
+ ":perfetto_src_base_regex",
"src/protozero/protoc_plugin/cppgen_plugin.cc",
],
static_libs: [
@@ -17119,6 +17142,7 @@
":perfetto_src_base_base",
":perfetto_src_base_clock_snapshots",
":perfetto_src_base_http_http",
+ ":perfetto_src_base_regex",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_kernel_utils_kernel_wakelock_errors",
@@ -19504,6 +19528,7 @@
":perfetto_src_base_clock_snapshots",
":perfetto_src_base_http_http",
":perfetto_src_base_http_unittests",
+ ":perfetto_src_base_regex",
":perfetto_src_base_test_support",
":perfetto_src_base_threading_threading",
":perfetto_src_base_threading_unittests",
@@ -20152,6 +20177,7 @@
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
":perfetto_src_base_clock_snapshots",
+ ":perfetto_src_base_regex",
":perfetto_src_base_test_support",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
@@ -20404,6 +20430,7 @@
":perfetto_protos_perfetto_trace_android_winscope_common_zero_gen",
":perfetto_protos_perfetto_trace_android_winscope_extensions_zero_gen",
":perfetto_src_base_base",
+ ":perfetto_src_base_regex",
":perfetto_src_protozero_protozero",
],
host_supported: true,
@@ -20437,6 +20464,7 @@
":perfetto_include_perfetto_public_abi_base",
":perfetto_include_perfetto_public_base",
":perfetto_src_base_base",
+ ":perfetto_src_base_regex",
"src/protozero/protoc_plugin/protozero_plugin.cc",
],
static_libs: [
@@ -20524,6 +20552,7 @@
":perfetto_protos_third_party_simpleperf_zero_gen",
":perfetto_src_base_base",
":perfetto_src_base_clock_snapshots",
+ ":perfetto_src_base_regex",
":perfetto_src_kernel_utils_kernel_wakelock_errors",
":perfetto_src_kernel_utils_syscall_table",
":perfetto_src_protovm_protovm",
@@ -20916,6 +20945,7 @@
":perfetto_protos_perfetto_trace_track_event_zero_gen",
":perfetto_protos_perfetto_trace_translation_zero_gen",
":perfetto_src_base_base",
+ ":perfetto_src_base_regex",
":perfetto_src_protozero_protozero",
":perfetto_src_trace_processor_util_blob",
":perfetto_src_trace_processor_util_clock",
@@ -21057,6 +21087,7 @@
":perfetto_protos_third_party_simpleperf_zero_gen",
":perfetto_src_base_base",
":perfetto_src_base_clock_snapshots",
+ ":perfetto_src_base_regex",
":perfetto_src_base_version",
":perfetto_src_kernel_utils_kernel_wakelock_errors",
":perfetto_src_kernel_utils_syscall_table",
@@ -21396,6 +21427,7 @@
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
":perfetto_src_base_clock_snapshots",
+ ":perfetto_src_base_regex",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
@@ -21625,6 +21657,7 @@
":perfetto_protos_perfetto_trace_translation_zero_gen",
":perfetto_src_base_base",
":perfetto_src_base_clock_snapshots",
+ ":perfetto_src_base_regex",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
@@ -21732,6 +21765,7 @@
":perfetto_protos_perfetto_common_zero_gen",
":perfetto_protos_perfetto_protovm_zero_gen",
":perfetto_src_base_base",
+ ":perfetto_src_base_regex",
":perfetto_src_base_version",
":perfetto_src_protozero_multifile_error_collector",
":perfetto_src_protozero_protozero",
@@ -21846,6 +21880,7 @@
":perfetto_src_android_stats_android_stats",
":perfetto_src_android_stats_perfetto_atoms",
":perfetto_src_base_base",
+ ":perfetto_src_base_regex",
":perfetto_src_base_unix_socket",
":perfetto_src_base_version",
":perfetto_src_ipc_client",
diff --git a/BUILD b/BUILD
index 445e335..42930c5 100644
--- a/BUILD
+++ b/BUILD
@@ -1165,6 +1165,7 @@
"include/perfetto/ext/base/periodic_task.h",
"include/perfetto/ext/base/pipe.h",
"include/perfetto/ext/base/platform.h",
+ "include/perfetto/ext/base/regex.h",
"include/perfetto/ext/base/rt_mutex.h",
"include/perfetto/ext/base/scoped_file.h",
"include/perfetto/ext/base/scoped_mmap.h",
@@ -1637,6 +1638,7 @@
perfetto_cc_library(
name = "src_base_base",
srcs = [
+ ":src_base_regex",
"src/base/android_utils.cc",
"src/base/base64.cc",
"src/base/crash_keys.cc",
@@ -1708,6 +1710,15 @@
linkstatic = True,
)
+# GN target: //src/base:regex
+perfetto_filegroup(
+ name = "src_base_regex",
+ srcs = [
+ "src/base/regex/regex.cc",
+ "src/base/regex/regex_std.h",
+ ],
+)
+
# GN target: //src/base:unix_socket
perfetto_cc_library(
name = "src_base_unix_socket",
diff --git a/include/perfetto/ext/base/BUILD.gn b/include/perfetto/ext/base/BUILD.gn
index c9c49aa..7dd1648 100644
--- a/include/perfetto/ext/base/BUILD.gn
+++ b/include/perfetto/ext/base/BUILD.gn
@@ -47,6 +47,7 @@
"periodic_task.h",
"pipe.h",
"platform.h",
+ "regex.h",
"rt_mutex.h",
"scoped_file.h",
"scoped_mmap.h",
diff --git a/include/perfetto/ext/base/regex.h b/include/perfetto/ext/base/regex.h
new file mode 100644
index 0000000..dad9bd0
--- /dev/null
+++ b/include/perfetto/ext/base/regex.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2026 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_EXT_BASE_REGEX_H_
+#define INCLUDE_PERFETTO_EXT_BASE_REGEX_H_
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/ext/base/status_or.h"
+
+namespace perfetto {
+namespace base {
+
+// Forward declaration for the backend implementation (defined in
+// src/base/regex/). The active backend is selected at compile time.
+class RegexStd;
+using RegexImpl = RegexStd;
+
+// A regular expression abstraction that hides the underlying engine.
+//
+// Construct via the two static factories:
+// - Create() – fallible; returns StatusOr<Regex>.
+// - CreateOrCheck() – infallible; crashes on invalid pattern.
+// Use for compile-time constant patterns only.
+//
+// Regex is movable but NOT copyable. Use CopyableRegex when copy semantics
+// are needed (e.g. for storage in copyable containers).
+//
+// Patterns use ECMAScript syntax, supporting Perl-style shorthands (\d, \w,
+// \s).
+//
+// Operations:
+// GlobalReplace() – global substitution; returns a new string.
+// FullMatch() – anchored, no groups.
+// FullMatchWithGroups() – anchored, with capture groups.
+// PartialMatch() – unanchored, no groups.
+// PartialMatchWithGroups() – unanchored, with capture groups.
+// PartialMatchAll() – iterator over all non-overlapping matches.
+class Regex {
+ public:
+ // Controls whether matching is case-sensitive or case-insensitive.
+ enum class CaseSensitivity : uint8_t {
+ kSensitive,
+ kInsensitive,
+ };
+
+ Regex(Regex&&) noexcept;
+ Regex& operator=(Regex&&) noexcept;
+ ~Regex();
+
+ // Regex is not copyable. Use CopyableRegex for copy semantics.
+ Regex(const Regex&) = delete;
+ Regex& operator=(const Regex&) = delete;
+
+ // Compiles |pattern| into a Regex.
+ // Returns an error if the pattern syntax is invalid.
+ static StatusOr<Regex> Create(
+ std::string_view pattern,
+ CaseSensitivity cs = CaseSensitivity::kSensitive);
+
+ // Like Create(), but crashes (PERFETTO_CHECK) on an invalid pattern.
+ // Use only for patterns that are known at compile time.
+ static Regex CreateOrCheck(std::string_view pattern,
+ CaseSensitivity cs = CaseSensitivity::kSensitive);
+
+ // Replaces every occurrence of the regex in |s| with |replacement| and
+ // returns the resulting string. |s| is unchanged.
+ std::string GlobalReplace(std::string_view s,
+ std::string_view replacement) const;
+
+ // Returns true if the regex matches the entirety of |s|.
+ bool FullMatch(std::string_view s) const;
+
+ // Like FullMatch(), but also fills |out| with captured groups on success.
+ // out[0] – the full match.
+ // out[1..N] – the parenthesised sub-groups.
+ // Returns false (and clears |out|) when there is no match.
+ //
+ // The string_views in |out| point into |s|; they are invalidated if |s| is
+ // modified or destroyed.
+ bool FullMatchWithGroups(std::string_view s,
+ std::vector<std::string_view>& out) const;
+
+ // Returns true if the regex matches any substring of |s|.
+ bool PartialMatch(std::string_view s) const;
+
+ // Like PartialMatch(), but also fills |out| with captured groups on success.
+ // out[0] – the full match.
+ // out[1..N] – the parenthesised sub-groups.
+ // Returns false (and clears |out|) when there is no match.
+ //
+ // The string_views in |out| point into |s|; they are invalidated if |s| is
+ // modified or destroyed.
+ bool PartialMatchWithGroups(std::string_view s,
+ std::vector<std::string_view>& out) const;
+
+ // Iterator over all non-overlapping matches in a string.
+ class PartialMatchIterator {
+ public:
+ // Returns the next match, or std::nullopt when no more matches remain.
+ std::optional<std::string_view> Next();
+
+ // Returns the next match with capture groups. Groups are filled into
+ // |groups|. Returns std::nullopt when no more matches remain.
+ std::optional<std::string_view> NextWithGroups(
+ std::vector<std::string_view>& groups);
+
+ private:
+ friend class Regex;
+ PartialMatchIterator(const Regex* re, std::string_view input);
+ const Regex* re_;
+ std::string_view input_;
+ size_t offset_ = 0;
+ std::vector<std::string_view> groups_; // Cached for Next().
+ };
+
+ // Returns an iterator that yields all non-overlapping matches in |s|.
+ PartialMatchIterator PartialMatchAll(std::string_view s) const;
+
+ // Accessors for pattern and case sensitivity (used by CopyableRegex).
+ const std::string& pattern() const { return pattern_; }
+ CaseSensitivity case_sensitivity() const { return cs_; }
+
+ private:
+ Regex() = default;
+ std::unique_ptr<RegexImpl> impl_;
+ std::string pattern_;
+ CaseSensitivity cs_ = CaseSensitivity::kSensitive;
+};
+
+// A copyable wrapper around Regex. Copying re-compiles the pattern from the
+// stored string, so prefer moving when the source is no longer needed.
+//
+// Use this class when Regex needs to be stored in a copyable container
+// (e.g. std::optional in a copyable struct).
+class CopyableRegex {
+ public:
+ explicit CopyableRegex(Regex regex);
+
+ CopyableRegex(const CopyableRegex& other);
+ CopyableRegex& operator=(const CopyableRegex& other);
+ CopyableRegex(CopyableRegex&&) noexcept = default;
+ CopyableRegex& operator=(CopyableRegex&&) noexcept = default;
+ ~CopyableRegex() = default;
+
+ std::string GlobalReplace(std::string_view s,
+ std::string_view replacement) const {
+ return regex_.GlobalReplace(s, replacement);
+ }
+ bool FullMatch(std::string_view s) const { return regex_.FullMatch(s); }
+ bool FullMatchWithGroups(std::string_view s,
+ std::vector<std::string_view>& out) const {
+ return regex_.FullMatchWithGroups(s, out);
+ }
+ bool PartialMatch(std::string_view s) const { return regex_.PartialMatch(s); }
+ bool PartialMatchWithGroups(std::string_view s,
+ std::vector<std::string_view>& out) const {
+ return regex_.PartialMatchWithGroups(s, out);
+ }
+ Regex::PartialMatchIterator PartialMatchAll(std::string_view s) const {
+ return regex_.PartialMatchAll(s);
+ }
+
+ private:
+ Regex regex_;
+};
+
+} // namespace base
+} // namespace perfetto
+
+#endif // INCLUDE_PERFETTO_EXT_BASE_REGEX_H_
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index 3ced755..6081cc0 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -25,6 +25,7 @@
perfetto_component("base") {
deps = [
+ ":regex",
"../../gn:base_platform",
"../../gn:default_deps",
]
@@ -90,6 +91,18 @@
}
}
+source_set("regex") {
+ sources = [
+ "regex/regex.cc",
+ "regex/regex_std.h",
+ ]
+ deps = [ "../../gn:default_deps" ]
+ public_deps = [
+ "../../include/perfetto/base",
+ "../../include/perfetto/ext/base",
+ ]
+}
+
perfetto_component("clock_snapshots") {
deps = [ "../../gn:default_deps" ]
public_deps = [
@@ -198,6 +211,7 @@
testonly = true
deps = [
":base",
+ ":regex",
":test_support",
"../../gn:default_deps",
"../../gn:gtest_and_gmock",
@@ -226,6 +240,7 @@
"no_destructor_unittest.cc",
"paged_memory_unittest.cc",
"periodic_task_unittest.cc",
+ "regex/regex_unittest.cc",
"rt_mutex_unittest.cc",
"scoped_file_unittest.cc",
"scoped_mmap_unittest.cc",
@@ -307,11 +322,13 @@
deps = [
":base",
":flat_hash_map_benchmark",
+ ":regex",
"../../gn:benchmark",
"../../gn:default_deps",
]
sources = [
"flat_set_benchmark.cc",
+ "regex/regex_benchmark.cc",
"rt_mutex_benchmark.cc",
"task_runner_benchmark.cc",
]
diff --git a/src/base/regex/regex.cc b/src/base/regex/regex.cc
new file mode 100644
index 0000000..ad9de0a
--- /dev/null
+++ b/src/base/regex/regex.cc
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2026 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/ext/base/regex.h"
+
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "src/base/regex/regex_std.h"
+
+namespace perfetto {
+namespace base {
+
+Regex::Regex(Regex&&) noexcept = default;
+Regex& Regex::operator=(Regex&&) noexcept = default;
+Regex::~Regex() = default;
+
+StatusOr<Regex> Regex::Create(std::string_view pattern,
+ Regex::CaseSensitivity cs) {
+ bool insensitive = (cs == CaseSensitivity::kInsensitive);
+ auto impl_or = RegexImpl::Create(pattern, insensitive);
+ if (!impl_or.ok()) {
+ return base::ErrStatus("%s", impl_or.status().message().c_str());
+ }
+ Regex regex;
+ regex.pattern_ = std::string(pattern);
+ regex.cs_ = cs;
+ regex.impl_ = std::make_unique<RegexImpl>(std::move(*impl_or));
+ return std::move(regex);
+}
+
+Regex Regex::CreateOrCheck(std::string_view pattern,
+ Regex::CaseSensitivity cs) {
+ auto re_or = Create(pattern, cs);
+ PERFETTO_CHECK(re_or.ok());
+ return std::move(*re_or);
+}
+
+std::string Regex::GlobalReplace(std::string_view s,
+ std::string_view replacement) const {
+ PERFETTO_DCHECK(impl_);
+ return impl_->GlobalReplace(s, replacement);
+}
+
+bool Regex::FullMatch(std::string_view s) const {
+ PERFETTO_DCHECK(impl_);
+ return impl_->FullMatch(s);
+}
+
+bool Regex::FullMatchWithGroups(std::string_view s,
+ std::vector<std::string_view>& out) const {
+ PERFETTO_DCHECK(impl_);
+ return impl_->FullMatchWithGroups(s, out);
+}
+
+bool Regex::PartialMatch(std::string_view s) const {
+ PERFETTO_DCHECK(impl_);
+ return impl_->PartialMatch(s);
+}
+
+bool Regex::PartialMatchWithGroups(std::string_view s,
+ std::vector<std::string_view>& out) const {
+ PERFETTO_DCHECK(impl_);
+ return impl_->PartialMatchWithGroups(s, out);
+}
+
+// --- PartialMatchIterator ---
+
+Regex::PartialMatchIterator::PartialMatchIterator(const Regex* re,
+ std::string_view input)
+ : re_(re), input_(input) {}
+
+std::optional<std::string_view> Regex::PartialMatchIterator::Next() {
+ return NextWithGroups(groups_);
+}
+
+std::optional<std::string_view> Regex::PartialMatchIterator::NextWithGroups(
+ std::vector<std::string_view>& groups) {
+ if (offset_ > input_.size())
+ return std::nullopt;
+ PERFETTO_DCHECK(re_->impl_);
+ if (!re_->impl_->SearchWithOffset(input_, offset_, groups))
+ return std::nullopt;
+ if (groups.empty())
+ return std::nullopt;
+ std::string_view match = groups[0];
+ size_t match_end =
+ static_cast<size_t>(match.data() - input_.data()) + match.size();
+ // Advance past the match. If match is empty, advance by one to avoid
+ // infinite loops.
+ offset_ = match.size() == 0 ? match_end + 1 : match_end;
+ return match;
+}
+
+Regex::PartialMatchIterator Regex::PartialMatchAll(std::string_view s) const {
+ return PartialMatchIterator(this, s);
+}
+
+// --- CopyableRegex ---
+
+CopyableRegex::CopyableRegex(Regex regex) : regex_(std::move(regex)) {}
+
+CopyableRegex::CopyableRegex(const CopyableRegex& other)
+ : regex_(Regex::CreateOrCheck(other.regex_.pattern(),
+ other.regex_.case_sensitivity())) {}
+
+CopyableRegex& CopyableRegex::operator=(const CopyableRegex& other) {
+ if (this != &other) {
+ this->~CopyableRegex();
+ new (this) CopyableRegex(other);
+ }
+ return *this;
+}
+
+} // namespace base
+} // namespace perfetto
diff --git a/src/base/regex/regex_benchmark.cc b/src/base/regex/regex_benchmark.cc
new file mode 100644
index 0000000..f3bdded
--- /dev/null
+++ b/src/base/regex/regex_benchmark.cc
@@ -0,0 +1,106 @@
+// Copyright (C) 2026 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 <benchmark/benchmark.h>
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/ext/base/regex.h"
+#include "src/base/regex/regex_std.h"
+
+namespace perfetto {
+namespace base {
+namespace {
+
+// --- Backend-parameterized benchmarks ---
+
+template <typename Backend>
+static void BM_Regex_PartialMatch_Simple(benchmark::State& state) {
+ auto re = Backend::Create("abc", false);
+ PERFETTO_CHECK(re.ok());
+ std::string_view input = "xxabcyy";
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(re->PartialMatch(input));
+ }
+}
+
+template <typename Backend>
+static void BM_Regex_FullMatch_Simple(benchmark::State& state) {
+ auto re = Backend::Create("abc", false);
+ PERFETTO_CHECK(re.ok());
+ std::string_view input = "abc";
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(re->FullMatch(input));
+ }
+}
+
+template <typename Backend>
+static void BM_Regex_PartialMatchWithGroups(benchmark::State& state) {
+ auto re = Backend::Create("([a-z]+)-([0-9]+)", false);
+ PERFETTO_CHECK(re.ok());
+ std::string_view input = "hello-12345";
+ std::vector<std::string_view> groups;
+ for (auto _ : state) {
+ re->PartialMatchWithGroups(input, groups);
+ benchmark::DoNotOptimize(groups.data());
+ }
+}
+
+template <typename Backend>
+static void BM_Regex_GlobalReplace(benchmark::State& state) {
+ auto re = Backend::Create("[0-9]+", false);
+ PERFETTO_CHECK(re.ok());
+ std::string_view input = "abc123def456ghi789";
+ for (auto _ : state) {
+ auto result = re->GlobalReplace(input, "X");
+ benchmark::DoNotOptimize(result.data());
+ }
+}
+
+template <typename Backend>
+static void BM_Regex_PartialMatch_LongInput(benchmark::State& state) {
+ auto re = Backend::Create(R"(\d{4}-\d{2}-\d{2})", false);
+ PERFETTO_CHECK(re.ok());
+ // 1000-char string with a date near the end.
+ std::string input(990, 'x');
+ input += "2026-03-29";
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(re->PartialMatch(input));
+ }
+}
+
+template <typename Backend>
+static void BM_Regex_Compile(benchmark::State& state) {
+ for (auto _ : state) {
+ auto re = Backend::Create(R"(([a-zA-Z]+)(\d+)\.(\w+))", false);
+ benchmark::DoNotOptimize(re.ok());
+ }
+}
+
+// Register benchmarks for std::regex backend.
+BENCHMARK_TEMPLATE(BM_Regex_PartialMatch_Simple, RegexStd);
+BENCHMARK_TEMPLATE(BM_Regex_FullMatch_Simple, RegexStd);
+BENCHMARK_TEMPLATE(BM_Regex_PartialMatchWithGroups, RegexStd);
+BENCHMARK_TEMPLATE(BM_Regex_GlobalReplace, RegexStd);
+BENCHMARK_TEMPLATE(BM_Regex_PartialMatch_LongInput, RegexStd);
+BENCHMARK_TEMPLATE(BM_Regex_Compile, RegexStd);
+
+// When building with RE2 or PCRE2, register benchmarks for those backends too.
+// See src/base/regex/regex_re2.h and regex_pcre2.h.
+
+} // namespace
+} // namespace base
+} // namespace perfetto
diff --git a/src/base/regex/regex_std.h b/src/base/regex/regex_std.h
new file mode 100644
index 0000000..62e7800
--- /dev/null
+++ b/src/base/regex/regex_std.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2026 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 SRC_BASE_REGEX_REGEX_STD_H_
+#define SRC_BASE_REGEX_REGEX_STD_H_
+
+#include <regex>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/ext/base/status_or.h"
+
+namespace perfetto {
+namespace base {
+
+// std::regex backend for Regex. Uses ECMAScript syntax for Perl-style
+// shorthands (\d, \w, \s).
+class RegexStd {
+ public:
+ RegexStd(RegexStd&&) noexcept = default;
+ RegexStd& operator=(RegexStd&&) noexcept = default;
+ ~RegexStd() = default;
+
+ RegexStd(const RegexStd&) = delete;
+ RegexStd& operator=(const RegexStd&) = delete;
+
+ static StatusOr<RegexStd> Create(std::string_view pattern,
+ bool case_insensitive) {
+ RegexStd result;
+ auto flags = std::regex::ECMAScript;
+ if (case_insensitive) {
+ flags |= std::regex::icase;
+ }
+ result.re_ = std::regex(std::string(pattern), flags);
+ return std::move(result);
+ }
+
+ std::string GlobalReplace(std::string_view s,
+ std::string_view replacement) const {
+ return std::regex_replace(std::string(s), re_, std::string(replacement));
+ }
+
+ bool FullMatch(std::string_view s) const {
+ return std::regex_match(s.data(), s.data() + s.size(), re_);
+ }
+
+ bool FullMatchWithGroups(std::string_view s,
+ std::vector<std::string_view>& out) const {
+ out.clear();
+ if (!std::regex_match(s.data(), s.data() + s.size(), match_data_, re_))
+ return false;
+ FillGroups(s.data(), out);
+ return true;
+ }
+
+ bool PartialMatch(std::string_view s) const {
+ return std::regex_search(s.data(), s.data() + s.size(), re_);
+ }
+
+ bool PartialMatchWithGroups(std::string_view s,
+ std::vector<std::string_view>& out) const {
+ out.clear();
+ if (!std::regex_search(s.data(), s.data() + s.size(), match_data_, re_))
+ return false;
+ FillGroups(s.data(), out);
+ return true;
+ }
+
+ bool SearchWithOffset(std::string_view s,
+ size_t start_offset,
+ std::vector<std::string_view>& out) const {
+ out.clear();
+ const char* begin = s.data() + start_offset;
+ const char* end = s.data() + s.size();
+ if (begin > end)
+ return false;
+ if (!std::regex_search(begin, end, match_data_, re_))
+ return false;
+ FillGroups(begin, out);
+ return true;
+ }
+
+ private:
+ RegexStd() = default;
+
+ void FillGroups(const char* base, std::vector<std::string_view>& out) const {
+ for (size_t i = 0; i < match_data_.size(); ++i) {
+ if (match_data_[i].matched) {
+ out.emplace_back(base + match_data_.position(i),
+ static_cast<size_t>(match_data_.length(i)));
+ } else {
+ out.emplace_back();
+ }
+ }
+ }
+
+ std::regex re_;
+ mutable std::cmatch match_data_;
+};
+
+} // namespace base
+} // namespace perfetto
+
+#endif // SRC_BASE_REGEX_REGEX_STD_H_
diff --git a/src/base/regex/regex_unittest.cc b/src/base/regex/regex_unittest.cc
new file mode 100644
index 0000000..d4b3c0d
--- /dev/null
+++ b/src/base/regex/regex_unittest.cc
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2026 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/ext/base/regex.h"
+
+#include <optional>
+#include <string_view>
+#include <vector>
+
+#include "src/base/regex/regex_std.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace base {
+namespace {
+
+template <typename T>
+struct BackendTraits;
+
+template <>
+struct BackendTraits<RegexStd> {
+ static const char* name() { return "RegexStd"; }
+};
+
+template <typename Backend>
+class RegexBackendTest : public ::testing::Test {
+ protected:
+ StatusOr<Backend> Create(std::string_view pattern,
+ bool case_insensitive = false) {
+ return Backend::Create(pattern, case_insensitive);
+ }
+};
+
+using BackendTypes = ::testing::Types<RegexStd>;
+TYPED_TEST_SUITE(RegexBackendTest, BackendTypes, /* trailing ',' for GCC*/);
+
+TYPED_TEST(RegexBackendTest, PartialMatch) {
+ auto re_or = this->Create("abc");
+ ASSERT_TRUE(re_or.ok());
+ auto& re = re_or.value();
+ EXPECT_TRUE(re.PartialMatch("abc"));
+ EXPECT_TRUE(re.PartialMatch("xabcy"));
+ EXPECT_FALSE(re.PartialMatch("abd"));
+}
+
+TYPED_TEST(RegexBackendTest, FullMatch) {
+ auto re_or = this->Create("abc");
+ ASSERT_TRUE(re_or.ok());
+ auto& re = re_or.value();
+ EXPECT_TRUE(re.FullMatch("abc"));
+ EXPECT_FALSE(re.FullMatch("xabcy"));
+ EXPECT_FALSE(re.FullMatch("abcd"));
+}
+
+TYPED_TEST(RegexBackendTest, PartialMatchWithGroups) {
+ auto re_or = this->Create("([a-z]+)-([0-9]+)");
+ ASSERT_TRUE(re_or.ok());
+ auto& re = re_or.value();
+ std::vector<std::string_view> matches;
+ EXPECT_TRUE(re.PartialMatchWithGroups("foo-123", matches));
+ ASSERT_EQ(matches.size(), 3u);
+ EXPECT_EQ(matches[0], "foo-123");
+ EXPECT_EQ(matches[1], "foo");
+ EXPECT_EQ(matches[2], "123");
+}
+
+TYPED_TEST(RegexBackendTest, PartialMatchWithGroupsNoMatch) {
+ auto re_or = this->Create("([a-z]+)-([0-9]+)");
+ ASSERT_TRUE(re_or.ok());
+ std::vector<std::string_view> matches;
+ EXPECT_FALSE(re_or->PartialMatchWithGroups("foo-bar", matches));
+ EXPECT_TRUE(matches.empty());
+}
+
+TYPED_TEST(RegexBackendTest, PartialMatchWithGroupsOptionalGroup) {
+ auto re_or = this->Create("a(b)?c");
+ ASSERT_TRUE(re_or.ok());
+ std::vector<std::string_view> matches;
+ EXPECT_TRUE(re_or->PartialMatchWithGroups("ac", matches));
+ ASSERT_EQ(matches.size(), 2u);
+ EXPECT_EQ(matches[0], "ac");
+ EXPECT_TRUE(matches[1].empty());
+}
+
+TYPED_TEST(RegexBackendTest, FullMatchWithGroups) {
+ auto re_or = this->Create("([a-z]+)-([0-9]+)");
+ ASSERT_TRUE(re_or.ok());
+ std::vector<std::string_view> matches;
+ EXPECT_TRUE(re_or->FullMatchWithGroups("foo-123", matches));
+ ASSERT_EQ(matches.size(), 3u);
+ EXPECT_EQ(matches[0], "foo-123");
+ EXPECT_EQ(matches[1], "foo");
+ EXPECT_EQ(matches[2], "123");
+ EXPECT_FALSE(re_or->FullMatchWithGroups("xfoo-123x", matches));
+ EXPECT_TRUE(matches.empty());
+}
+
+TYPED_TEST(RegexBackendTest, GlobalReplace) {
+ auto re_or = this->Create("a");
+ ASSERT_TRUE(re_or.ok());
+ EXPECT_EQ(re_or->GlobalReplace("aba", "x"), "xbx");
+ EXPECT_EQ(re_or->GlobalReplace("bbb", "x"), "bbb");
+ EXPECT_EQ(re_or->GlobalReplace("aba", ""), "b");
+}
+
+TYPED_TEST(RegexBackendTest, PerlShorthands) {
+ {
+ auto re_or = this->Create(R"(\d+)");
+ ASSERT_TRUE(re_or.ok());
+ EXPECT_TRUE(re_or->PartialMatch("abc123"));
+ EXPECT_FALSE(re_or->PartialMatch("abcxyz"));
+ EXPECT_TRUE(re_or->FullMatch("42"));
+ }
+ {
+ auto re_or = this->Create(R"(\w+)");
+ ASSERT_TRUE(re_or.ok());
+ EXPECT_TRUE(re_or->FullMatch("hello_world123"));
+ EXPECT_FALSE(re_or->FullMatch("hello world"));
+ }
+ {
+ auto re_or = this->Create(R"(\s+)");
+ ASSERT_TRUE(re_or.ok());
+ EXPECT_TRUE(re_or->PartialMatch("hello world"));
+ EXPECT_FALSE(re_or->PartialMatch("helloworld"));
+ }
+}
+
+TYPED_TEST(RegexBackendTest, CaseInsensitive) {
+ auto re_or = this->Create("abc", /*case_insensitive=*/true);
+ ASSERT_TRUE(re_or.ok());
+ EXPECT_TRUE(re_or->PartialMatch("ABC"));
+ EXPECT_TRUE(re_or->PartialMatch("aBc"));
+ EXPECT_TRUE(re_or->FullMatch("ABC"));
+ EXPECT_FALSE(re_or->PartialMatch("abd"));
+}
+
+TYPED_TEST(RegexBackendTest, CaretMatchesOnlyStringStart) {
+ auto re_or = this->Create("^abc");
+ ASSERT_TRUE(re_or.ok());
+ EXPECT_TRUE(re_or->PartialMatch("abc"));
+ EXPECT_TRUE(re_or->PartialMatch("abcdef"));
+ // ^ should NOT match after a newline (no MULTILINE).
+ EXPECT_FALSE(re_or->PartialMatch("xyz\nabc"));
+}
+
+TYPED_TEST(RegexBackendTest, SearchWithOffset) {
+ auto re_or = this->Create("([0-9]+)");
+ ASSERT_TRUE(re_or.ok());
+ std::vector<std::string_view> matches;
+ std::string_view input = "abc123def456";
+ // From offset 0 — should find "123".
+ EXPECT_TRUE(re_or->SearchWithOffset(input, 0, matches));
+ ASSERT_GE(matches.size(), 1u);
+ EXPECT_EQ(matches[0], "123");
+ // From offset 6 (past "123") — should find "456".
+ EXPECT_TRUE(re_or->SearchWithOffset(input, 6, matches));
+ ASSERT_GE(matches.size(), 1u);
+ EXPECT_EQ(matches[0], "456");
+ // From offset 12 (end) — no match.
+ EXPECT_FALSE(re_or->SearchWithOffset(input, 12, matches));
+}
+
+// ============================================================================
+// Tests for the Regex wrapper class (uses the active backend via RegexImpl).
+// ============================================================================
+
+TEST(RegexTest, PartialMatch) {
+ auto re = Regex::CreateOrCheck("abc");
+ EXPECT_TRUE(re.PartialMatch("abc"));
+ EXPECT_TRUE(re.PartialMatch("xabcy"));
+ EXPECT_FALSE(re.PartialMatch("abd"));
+}
+
+TEST(RegexTest, FullMatch) {
+ auto re = Regex::CreateOrCheck("abc");
+ EXPECT_TRUE(re.FullMatch("abc"));
+ EXPECT_FALSE(re.FullMatch("xabcy"));
+}
+
+TEST(RegexTest, Move) {
+ Regex re1 = Regex::CreateOrCheck("abc");
+ EXPECT_TRUE(re1.PartialMatch("abc"));
+ Regex re2 = std::move(re1);
+ EXPECT_TRUE(re2.PartialMatch("abc"));
+}
+
+TEST(RegexTest, MoveAssignment) {
+ Regex re1 = Regex::CreateOrCheck("abc");
+ Regex re2 = Regex::CreateOrCheck("xyz");
+ re2 = std::move(re1);
+ EXPECT_TRUE(re2.PartialMatch("abc"));
+ EXPECT_FALSE(re2.PartialMatch("xyz"));
+}
+
+TEST(RegexTest, EmptyString) {
+ auto re = Regex::CreateOrCheck("abc");
+ EXPECT_FALSE(re.PartialMatch(""));
+ EXPECT_FALSE(re.FullMatch(""));
+ std::vector<std::string_view> matches;
+ EXPECT_FALSE(re.PartialMatchWithGroups("", matches));
+}
+
+TEST(RegexTest, Alternation) {
+ auto re = Regex::CreateOrCheck("cat|dog");
+ EXPECT_TRUE(re.PartialMatch("I have a cat"));
+ EXPECT_TRUE(re.PartialMatch("I have a dog"));
+ EXPECT_FALSE(re.PartialMatch("I have a bird"));
+}
+
+TEST(RegexTest, PartialMatchAll) {
+ auto re = Regex::CreateOrCheck("([0-9]+)");
+ auto it = re.PartialMatchAll("foo123bar456baz789");
+ std::vector<std::string_view> groups;
+
+ auto m1 = it.NextWithGroups(groups);
+ ASSERT_TRUE(m1.has_value());
+ EXPECT_EQ(*m1, "123");
+ ASSERT_EQ(groups.size(), 2u);
+ EXPECT_EQ(groups[1], "123");
+
+ auto m2 = it.Next();
+ ASSERT_TRUE(m2.has_value());
+ EXPECT_EQ(*m2, "456");
+
+ auto m3 = it.Next();
+ ASSERT_TRUE(m3.has_value());
+ EXPECT_EQ(*m3, "789");
+
+ EXPECT_FALSE(it.Next().has_value());
+}
+
+TEST(RegexTest, PartialMatchAllNoMatch) {
+ auto re = Regex::CreateOrCheck("[0-9]+");
+ auto it = re.PartialMatchAll("hello");
+ EXPECT_FALSE(it.Next().has_value());
+}
+
+// --- CopyableRegex tests ---
+
+TEST(CopyableRegexTest, CopyConstruct) {
+ CopyableRegex re1(Regex::CreateOrCheck("abc"));
+ CopyableRegex re2(re1);
+ EXPECT_TRUE(re2.PartialMatch("abc"));
+ EXPECT_FALSE(re2.PartialMatch("xyz"));
+ EXPECT_TRUE(re1.PartialMatch("abc"));
+}
+
+TEST(CopyableRegexTest, CopyAssignment) {
+ CopyableRegex re1(Regex::CreateOrCheck("abc"));
+ CopyableRegex re2(Regex::CreateOrCheck("xyz"));
+ re2 = re1;
+ EXPECT_TRUE(re2.PartialMatch("abc"));
+ EXPECT_FALSE(re2.PartialMatch("xyz"));
+ EXPECT_TRUE(re1.PartialMatch("abc"));
+}
+
+TEST(CopyableRegexTest, CaseInsensitiveCopy) {
+ CopyableRegex re1(
+ Regex::CreateOrCheck("abc", Regex::CaseSensitivity::kInsensitive));
+ CopyableRegex re2(re1);
+ EXPECT_TRUE(re2.PartialMatch("ABC"));
+ EXPECT_FALSE(re2.PartialMatch("abd"));
+}
+
+TEST(CopyableRegexTest, MoveConstruct) {
+ CopyableRegex re1(Regex::CreateOrCheck("abc"));
+ CopyableRegex re2(std::move(re1));
+ EXPECT_TRUE(re2.PartialMatch("abc"));
+}
+
+TEST(CopyableRegexTest, FullMatchWithGroups) {
+ CopyableRegex re(Regex::CreateOrCheck("([a-z]+)-([0-9]+)"));
+ std::vector<std::string_view> matches;
+ EXPECT_TRUE(re.FullMatchWithGroups("foo-123", matches));
+ ASSERT_EQ(matches.size(), 3u);
+ EXPECT_EQ(matches[1], "foo");
+ EXPECT_EQ(matches[2], "123");
+}
+
+} // namespace
+} // namespace base
+} // namespace perfetto
diff --git a/src/protozero/filtering/filter_util_unittest.cc b/src/protozero/filtering/filter_util_unittest.cc
index 038c473..23ed4a0 100644
--- a/src/protozero/filtering/filter_util_unittest.cc
+++ b/src/protozero/filtering/filter_util_unittest.cc
@@ -18,12 +18,12 @@
#include <cstdint>
#include <optional>
-#include <regex>
#include <string>
#include <vector>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/regex.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/temp_file.h"
#include "src/protozero/filtering/filter_bytecode_generator.h"
@@ -57,8 +57,10 @@
std::string output;
PERFETTO_CHECK(perfetto::base::ReadFile(tmp_path, &output));
// Make the output a bit more compact.
- output = std::regex_replace(output, std::regex(" +"), " ");
- return std::regex_replace(output, std::regex(" +\\n"), "\n");
+ auto re = perfetto::base::Regex::CreateOrCheck(" +");
+ output = re.GlobalReplace(output, " ");
+ re = perfetto::base::Regex::CreateOrCheck(" +\\n");
+ return re.GlobalReplace(output, "\n");
}
TEST(SchemaParserTest, SchemaToBytecode_Simple) {
diff --git a/src/protozero/filtering/string_filter.cc b/src/protozero/filtering/string_filter.cc
index 2c425df..1bafa8a 100644
--- a/src/protozero/filtering/string_filter.cc
+++ b/src/protozero/filtering/string_filter.cc
@@ -19,20 +19,16 @@
#include <algorithm>
#include <cstdint>
#include <cstring>
-#include <regex>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
-#include "perfetto/base/logging.h"
#include "perfetto/public/compiler.h"
namespace protozero {
namespace {
-using Matches = std::match_results<char*>;
-
constexpr std::string_view kRedacted = "P60REDACTED";
constexpr char kRedactedDash = '-';
@@ -88,21 +84,25 @@
return memcmp(ptr + 1, starts_with.data() + 1, len - 1) == 0;
}
-void RedactMatches(const Matches& matches) {
+void RedactMatches(const std::vector<std::string_view>& matches) {
// Go through every group in the matches.
for (size_t i = 1; i < matches.size(); ++i) {
const auto& match = matches[i];
- PERFETTO_CHECK(match.second >= match.first);
+ // Skip unmatched optional groups: empty string_view may have nullptr
+ // data(), and passing nullptr to memcpy/memset is UB even with size 0.
+ if (match.empty())
+ continue;
// Overwrite the match with characters from |kRedacted|. If match is
// smaller, we will not use all of |kRedacted| but that's fine (i.e. we
// will overwrite with a truncated |kRedacted|).
- auto match_len = static_cast<size_t>(match.second - match.first);
+ size_t match_len = match.size();
size_t redacted_len = std::min(match_len, kRedacted.size());
- memcpy(match.first, kRedacted.data(), redacted_len);
+ memcpy(const_cast<char*>(match.data()), kRedacted.data(), redacted_len);
// Overwrite any characters after |kRedacted| with |kRedactedDash|.
- memset(match.first + redacted_len, kRedactedDash, match_len - redacted_len);
+ memset(const_cast<char*>(match.data()) + redacted_len, kRedactedDash,
+ match_len - redacted_len);
}
}
@@ -113,12 +113,10 @@
std::string atrace_payload_starts_with,
std::string name,
SemanticTypeMask semantic_type_mask) {
- Rule new_rule{
- policy,
- std::regex(pattern_str.begin(), pattern_str.end(),
- std::regex::ECMAScript | std::regex_constants::optimize),
- std::move(atrace_payload_starts_with), std::move(name),
- semantic_type_mask};
+ perfetto::base::CopyableRegex re(
+ perfetto::base::Regex::CreateOrCheck(std::string(pattern_str)));
+ Rule new_rule{policy, std::move(re), std::move(atrace_payload_starts_with),
+ std::move(name), semantic_type_mask};
// If name is non-empty, look for existing rule with same name and replace.
if (!new_rule.name.empty()) {
for (Rule& existing : rules_) {
@@ -134,26 +132,26 @@
bool StringFilter::MaybeFilterInternal(char* ptr,
size_t len,
uint32_t semantic_type) const {
- std::match_results<char*> matches;
bool atrace_find_tried = false;
const char* atrace_payload_ptr = nullptr;
+ std::string_view input(ptr, len);
+
for (const Rule& rule : rules_) {
if (!rule.semantic_type_mask.IsSet(semantic_type)) {
continue;
}
switch (rule.policy) {
- case Policy::kMatchRedactGroups:
case Policy::kMatchBreak:
+ if (PERFETTO_UNLIKELY(rule.pattern->FullMatch(input)))
+ return false;
+ break;
+ case Policy::kMatchRedactGroups:
if (PERFETTO_UNLIKELY(
- std::regex_match(ptr, ptr + len, matches, rule.pattern))) {
- if (rule.policy == Policy::kMatchBreak) {
- return false;
- }
- RedactMatches(matches);
+ rule.pattern->FullMatchWithGroups(input, matches_))) {
+ RedactMatches(matches_);
return true;
}
break;
- case Policy::kAtraceMatchRedactGroups:
case Policy::kAtraceMatchBreak:
atrace_payload_ptr = atrace_find_tried
? atrace_payload_ptr
@@ -162,11 +160,21 @@
if (atrace_payload_ptr &&
StartsWith(atrace_payload_ptr, ptr + len,
rule.atrace_payload_starts_with) &&
- std::regex_match(ptr, ptr + len, matches, rule.pattern)) {
- if (rule.policy == Policy::kAtraceMatchBreak) {
- return false;
- }
- RedactMatches(matches);
+ rule.pattern->FullMatch(input)) {
+ return false;
+ }
+ break;
+ case Policy::kAtraceMatchRedactGroups:
+ atrace_payload_ptr = atrace_find_tried
+ ? atrace_payload_ptr
+ : FindAtracePayloadPtr(ptr, ptr + len);
+ atrace_find_tried = true;
+ if (atrace_payload_ptr &&
+ StartsWith(atrace_payload_ptr, ptr + len,
+ rule.atrace_payload_starts_with) &&
+ PERFETTO_UNLIKELY(
+ rule.pattern->FullMatchWithGroups(input, matches_))) {
+ RedactMatches(matches_);
return true;
}
break;
@@ -177,11 +185,11 @@
atrace_find_tried = true;
if (atrace_payload_ptr && StartsWith(atrace_payload_ptr, ptr + len,
rule.atrace_payload_starts_with)) {
- auto beg = std::regex_iterator<char*>(ptr, ptr + len, rule.pattern);
- auto end = std::regex_iterator<char*>();
- bool has_any_matches = beg != end;
- for (auto it = std::move(beg); it != end; ++it) {
- RedactMatches(*it);
+ bool has_any_matches = false;
+ for (auto iter = rule.pattern->PartialMatchAll(input);
+ iter.NextWithGroups(matches_);) {
+ RedactMatches(matches_);
+ has_any_matches = true;
}
if (has_any_matches) {
return true;
diff --git a/src/protozero/filtering/string_filter.h b/src/protozero/filtering/string_filter.h
index 76a4823..4026dfd 100644
--- a/src/protozero/filtering/string_filter.h
+++ b/src/protozero/filtering/string_filter.h
@@ -20,12 +20,13 @@
#include <array>
#include <cstddef>
#include <cstdint>
-#include <regex>
+#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/regex.h"
#include "perfetto/public/compiler.h"
namespace protozero {
@@ -122,7 +123,7 @@
private:
struct Rule {
Policy policy;
- std::regex pattern;
+ std::optional<perfetto::base::CopyableRegex> pattern;
std::string atrace_payload_starts_with;
std::string name;
SemanticTypeMask semantic_type_mask = SemanticTypeMask::Unspecified();
@@ -132,6 +133,10 @@
// All rules, in the order they were added.
std::vector<Rule> rules_;
+
+ // Reusable buffer for regex match groups, avoiding heap allocations on each
+ // filter call.
+ mutable std::vector<std::string_view> matches_;
};
} // namespace protozero
diff --git a/src/tools/ftrace_proto_gen/proto_gen_utils.cc b/src/tools/ftrace_proto_gen/proto_gen_utils.cc
index d650cd0..e598ef7 100644
--- a/src/tools/ftrace_proto_gen/proto_gen_utils.cc
+++ b/src/tools/ftrace_proto_gen/proto_gen_utils.cc
@@ -17,10 +17,10 @@
#include "src/tools/ftrace_proto_gen/proto_gen_utils.h"
#include <algorithm>
-#include <regex>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/regex.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/subprocess.h"
@@ -203,7 +203,8 @@
ProtoType InferProtoType(const FtraceEvent::Field& field) {
// Fixed length strings: "char foo[16]"
- if (std::regex_match(field.type_and_name, std::regex(R"(char \w+\[\d+\])")))
+ auto char_array_re = base::Regex::CreateOrCheck(R"(char \w+\[\d+\])");
+ if (char_array_re.FullMatch(field.type_and_name))
return ProtoType::String();
// String pointers: "__data_loc char[] foo" (as in
diff --git a/src/trace_processor/metrics/metrics.cc b/src/trace_processor/metrics/metrics.cc
index 4587a0e..563638b 100644
--- a/src/trace_processor/metrics/metrics.cc
+++ b/src/trace_processor/metrics/metrics.cc
@@ -25,7 +25,6 @@
#include <iterator>
#include <memory>
#include <optional>
-#include <regex>
#include <string>
#include <unordered_map>
#include <utility>
@@ -33,6 +32,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
+#include "perfetto/ext/base/regex.h"
#include "perfetto/ext/base/status_macros.h"
#include "perfetto/ext/base/status_or.h"
#include "perfetto/ext/base/string_view.h"
@@ -475,24 +475,25 @@
const std::string& raw_text,
const std::unordered_map<std::string, std::string>& substitutions,
std::string* out) {
- std::regex re(R"(\{\{\s*(\w*)\s*\}\})", std::regex_constants::ECMAScript);
+ auto re = base::Regex::CreateOrCheck(R"(\{\{\s*(\w*)\s*\}\})");
- auto it = std::sregex_iterator(raw_text.begin(), raw_text.end(), re);
- auto regex_end = std::sregex_iterator();
- auto start = raw_text.begin();
- for (; it != regex_end; ++it) {
- out->insert(out->end(), start, raw_text.begin() + it->position(0));
+ std::string_view input(raw_text);
+ size_t last_end = 0;
+ std::vector<std::string_view> groups;
+ for (auto iter = re.PartialMatchAll(input);
+ iter.NextWithGroups(groups).has_value();) {
+ // groups[0] is the full match, groups[1] is the capture group.
+ size_t match_pos = static_cast<size_t>(groups[0].data() - input.data());
+ out->append(input.substr(last_end, match_pos - last_end));
- auto value_it = substitutions.find(it->str(1));
+ auto value_it = substitutions.find(std::string(groups[1]));
if (value_it == substitutions.end()) {
return 1;
}
-
- const auto& value = value_it->second;
- std::copy(value.begin(), value.end(), std::back_inserter(*out));
- start = raw_text.begin() + it->position(0) + it->length(0);
+ out->append(value_it->second);
+ last_end = match_pos + groups[0].size();
}
- out->insert(out->end(), start, raw_text.end());
+ out->append(input.substr(last_end));
return 0;
}
diff --git a/src/traced/probes/packages_list/packages_list_data_source.cc b/src/traced/probes/packages_list/packages_list_data_source.cc
index 5a37876..ff48b7b 100644
--- a/src/traced/probes/packages_list/packages_list_data_source.cc
+++ b/src/traced/probes/packages_list/packages_list_data_source.cc
@@ -21,7 +21,6 @@
#include <cstdio>
#include <functional>
#include <memory>
-#include <regex>
#include <set>
#include <string>
#include <unordered_map>
@@ -31,6 +30,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/base/task_runner.h"
#include "perfetto/base/time.h"
+#include "perfetto/ext/base/regex.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/tracing/core/basic_types.h"
#include "perfetto/ext/tracing/core/trace_writer.h"
@@ -66,10 +66,10 @@
const std::set<std::string>& package_name_filter,
const std::vector<std::string>& package_name_regex_filter) {
// Pre-compile regex patterns.
- std::vector<std::regex> compiled_regexes;
+ std::vector<base::Regex> compiled_regexes;
compiled_regexes.reserve(package_name_regex_filter.size());
for (const auto& pattern : package_name_regex_filter) {
- compiled_regexes.emplace_back(pattern);
+ compiled_regexes.emplace_back(base::Regex::CreateOrCheck(pattern));
}
bool has_filter = !package_name_filter.empty() || !compiled_regexes.empty();
@@ -91,7 +91,7 @@
continue;
}
for (const auto& re : compiled_regexes) {
- if (std::regex_match(pkg_struct.name, re)) {
+ if (re.FullMatch(pkg_struct.name)) {
packages.insert({pkg_struct.uid, pkg_struct});
break;
}
diff --git a/src/tracing/service/tracing_service_impl.cc b/src/tracing/service/tracing_service_impl.cc
index b2fd3c3..f18225e 100644
--- a/src/tracing/service/tracing_service_impl.cc
+++ b/src/tracing/service/tracing_service_impl.cc
@@ -30,7 +30,6 @@
#include <map>
#include <memory>
#include <optional>
-#include <regex>
#include <set>
#include <string>
#include <tuple>
@@ -73,6 +72,7 @@
#include "perfetto/ext/base/fnv_hash.h"
#include "perfetto/ext/base/metatrace.h"
#include "perfetto/ext/base/periodic_task.h"
+#include "perfetto/ext/base/regex.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/scoped_sched_boost.h"
#include "perfetto/ext/base/string_utils.h" // IWYU pragma: keep
@@ -264,15 +264,15 @@
bool NameMatchesFilter(const std::string& name,
const std::vector<std::string>& name_filter,
const std::vector<std::string>& name_regex_filter) {
- bool filter_matches = std::find(name_filter.begin(), name_filter.end(),
- name) != name_filter.end();
- bool filter_regex_matches =
- std::find_if(name_regex_filter.begin(), name_regex_filter.end(),
- [&](const std::string& regex) {
- return std::regex_match(
- name, std::regex(regex, std::regex::extended));
- }) != name_regex_filter.end();
- return filter_matches || filter_regex_matches;
+ if (std::find(name_filter.begin(), name_filter.end(), name) !=
+ name_filter.end()) {
+ return true;
+ }
+ return std::any_of(
+ name_regex_filter.begin(), name_regex_filter.end(),
+ [&](const std::string& pattern) {
+ return base::Regex::CreateOrCheck(pattern).FullMatch(name);
+ });
}
// Used when TraceConfig.write_into_file == true and output_path is not empty.
@@ -1895,11 +1895,11 @@
// If this trigger requires a certain producer to have sent it
// (non-empty producer_name()) ensure the producer who sent this trigger
// matches.
- if (!iter->producer_name_regex().empty() &&
- !std::regex_match(
- producer->name_,
- std::regex(iter->producer_name_regex(), std::regex::extended))) {
- continue;
+ if (!iter->producer_name_regex().empty()) {
+ auto re = base::Regex::CreateOrCheck(iter->producer_name_regex());
+ if (!re.FullMatch(producer->name_)) {
+ continue;
+ }
}
// Use a random number between 0 and 1 to check if we should allow this
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index ffc1c8b..b2a99f7 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -22,12 +22,12 @@
#include <functional>
#include <list>
#include <mutex>
-#include <regex>
#include <string_view>
#include <thread>
#include <unordered_set>
#include <vector>
#include "perfetto/ext/base/flags.h"
+#include "perfetto/ext/base/regex.h"
// We also want to test legacy trace events.
#define PERFETTO_ENABLE_LEGACY_TRACE_EVENTS 1
@@ -6133,7 +6133,8 @@
// Ignore timestamps and process/thread ids.
std::string s(line.data() + 28);
// Filter out durations.
- s = std::regex_replace(s, std::regex(" [+][0-9]*ms"), "");
+ auto re = perfetto::base::Regex::CreateOrCheck(" [+][0-9]*ms");
+ s = re.GlobalReplace(s, "");
lines.push_back(std::move(s));
}
fclose(f);
diff --git a/test/stress_test/stress_test.cc b/test/stress_test/stress_test.cc
index 0fbbfe5..3dafa4e 100644
--- a/test/stress_test/stress_test.cc
+++ b/test/stress_test/stress_test.cc
@@ -19,8 +19,8 @@
#include <chrono>
#include <list>
#include <map>
+#include <optional>
#include <random>
-#include <regex>
#include <string>
#include <thread>
#include <vector>
@@ -30,6 +30,7 @@
#include "perfetto/base/time.h"
#include "perfetto/ext/base/ctrl_c_handler.h"
#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/regex.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/subprocess.h"
#include "perfetto/ext/base/temp_file.h"
@@ -439,16 +440,17 @@
void StressTestMain(int argc, char** argv) {
TestHarness th;
- std::regex filter;
- bool has_filter = false;
+ std::optional<base::Regex> filter;
bool verbose = false;
for (int i = 1; i < argc; ++i) {
if (!strcmp(argv[i], "-v")) {
verbose = true;
} else {
- filter = std::regex(argv[i], std::regex::ECMAScript | std::regex::icase);
- has_filter = true;
+ auto re = base::Regex::Create(std::string(argv[i]),
+ base::Regex::CaseSensitivity::kInsensitive);
+ PERFETTO_CHECK(re.ok());
+ filter = std::move(*re);
}
}
@@ -459,8 +461,7 @@
++i) {
const auto& cfg_blob = kStressTestConfigs[i];
StressTestConfig cfg;
- std::cmatch ignored;
- if (has_filter && !std::regex_search(cfg_blob.name, ignored, filter)) {
+ if (filter.has_value() && !filter->PartialMatch(cfg_blob.name)) {
continue;
}
PERFETTO_CHECK(cfg.ParseFromArray(cfg_blob.data, cfg_blob.size));