diff --git a/re2/filtered_re2.cc b/re2/filtered_re2.cc index f0995a10..8e741d71 100644 --- a/re2/filtered_re2.cc +++ b/re2/filtered_re2.cc @@ -43,8 +43,10 @@ FilteredRE2::FilteredRE2(FilteredRE2&& other) } FilteredRE2& FilteredRE2::operator=(FilteredRE2&& other) { - this->~FilteredRE2(); - (void) new (this) FilteredRE2(std::move(other)); + if (this != &other) { + this->~FilteredRE2(); + (void) new (this) FilteredRE2(std::move(other)); + } return *this; } @@ -112,6 +114,11 @@ int FilteredRE2::FirstMatch(absl::string_view text, bool FilteredRE2::AllMatches(absl::string_view text, const std::vector& atoms, std::vector* matching_regexps) const { + if (!compiled_) { + ABSL_LOG(DFATAL) << "AllMatches called before Compile."; + matching_regexps->clear(); + return false; + } matching_regexps->clear(); std::vector regexps; prefilter_tree_->RegexpsGivenStrings(atoms, ®exps); @@ -123,6 +130,11 @@ bool FilteredRE2::AllMatches(absl::string_view text, void FilteredRE2::AllPotentials(const std::vector& atoms, std::vector* potential_regexps) const { + if (!compiled_) { + ABSL_LOG(DFATAL) << "AllPotentials called before Compile."; + potential_regexps->clear(); + return; + } prefilter_tree_->RegexpsGivenStrings(atoms, potential_regexps); } diff --git a/re2/set.cc b/re2/set.cc index 3f2a1f07..9064a96e 100644 --- a/re2/set.cc +++ b/re2/set.cc @@ -50,8 +50,10 @@ RE2::Set::Set(Set&& other) } RE2::Set& RE2::Set::operator=(Set&& other) { - this->~Set(); - (void) new (this) Set(std::move(other)); + if (this != &other) { + this->~Set(); + (void) new (this) Set(std::move(other)); + } return *this; } diff --git a/re2/testing/filtered_re2_test.cc b/re2/testing/filtered_re2_test.cc index 6da76b22..9da37f02 100644 --- a/re2/testing/filtered_re2_test.cc +++ b/re2/testing/filtered_re2_test.cc @@ -340,4 +340,29 @@ TEST(FilteredRE2Test, MoveSemantics) { EXPECT_EQ(size_t{0}, v1.matches.size()); } +// Regression test: self-move-assignment in FilteredRE2 must not trigger UB. +TEST(FilteredRE2, SelfMoveAssignment) { + FilteredRE2 f1; + int id; + ASSERT_EQ(absl::OkStatus(), f1.Add("foo.*bar", RE2::DefaultOptions, &id)); + f1.Compile(nullptr); + + // Self-move: previously triggered use-after-free. + f1 = std::move(f1); + + std::vector atoms, matches; + f1.AllPotentials(atoms, &matches); // must not crash +} + +// Regression test: AllMatches/AllPotentials before Compile must not crash. +TEST(FilteredRE2, AllMatchesBeforeCompile) { + FilteredRE2 f; + int id; + ASSERT_EQ(absl::OkStatus(), f.Add("test", RE2::DefaultOptions, &id)); + // Do NOT call Compile() — AllMatches must return false gracefully. + std::vector atoms, matches; + EXPECT_FALSE(f.AllMatches("test", atoms, &matches)); +} + + } // namespace re2 diff --git a/re2/testing/set_test.cc b/re2/testing/set_test.cc index 3282c169..2053c2d7 100644 --- a/re2/testing/set_test.cc +++ b/re2/testing/set_test.cc @@ -232,4 +232,25 @@ TEST(Set, MoveSemantics) { ASSERT_EQ(s1.Match("abc bar2 xyz", NULL), false); } +// Regression test: self-move-assignment must not trigger use-after-free. +// RE2::Set::operator=(Set&&) previously called this->~Set() before checking +// for self-assignment, resulting in undefined behavior when this == &other. +TEST(Set, SelfMoveAssignment) { + RE2::Set s(RE2::DefaultOptions, RE2::UNANCHORED); + std::string err; + ASSERT_EQ(s.Add("foo.*bar", &err), 0); + ASSERT_EQ(s.Add("baz[0-9]+", &err), 1); + ASSERT_TRUE(s.Compile()); + + // Self-move-assignment: previously UB (use-after-free via placement new + // reading destroyed object). Must leave the set in a valid state. + s = std::move(s); + + std::vector matches; + // After self-move, the object must still be usable (or at least not crash). + // We rely on ASAN/MSan to detect any use-after-free. + (void)s.Match("fooXbar", &matches); +} + + } // namespace re2