Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions re2/filtered_re2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -112,6 +114,11 @@ int FilteredRE2::FirstMatch(absl::string_view text,
bool FilteredRE2::AllMatches(absl::string_view text,
const std::vector<int>& atoms,
std::vector<int>* matching_regexps) const {
if (!compiled_) {
ABSL_LOG(DFATAL) << "AllMatches called before Compile.";
matching_regexps->clear();
return false;
}
matching_regexps->clear();
std::vector<int> regexps;
prefilter_tree_->RegexpsGivenStrings(atoms, &regexps);
Expand All @@ -123,6 +130,11 @@ bool FilteredRE2::AllMatches(absl::string_view text,

void FilteredRE2::AllPotentials(const std::vector<int>& atoms,
std::vector<int>* potential_regexps) const {
if (!compiled_) {
ABSL_LOG(DFATAL) << "AllPotentials called before Compile.";
potential_regexps->clear();
return;
}
prefilter_tree_->RegexpsGivenStrings(atoms, potential_regexps);
}

Expand Down
6 changes: 4 additions & 2 deletions re2/set.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
25 changes: 25 additions & 0 deletions re2/testing/filtered_re2_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> 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<int> atoms, matches;
EXPECT_FALSE(f.AllMatches("test", atoms, &matches));
}


} // namespace re2
21 changes: 21 additions & 0 deletions re2/testing/set_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> 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