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
7 changes: 7 additions & 0 deletions src/tsutil/Regex.cc
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ RegexMatches::operator[](size_t index) const
}

PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(_MatchData::get(_match_data));

// A group that did not participate in the match has an unset offset. This happens for an optional
// group that precedes a participating one, so a valid index is not enough to guarantee an offset.
if (PCRE2_UNSET == ovector[2 * index]) {
return std::string_view();
}

return std::string_view(_subject.data() + ovector[2 * index], ovector[2 * index + 1] - ovector[2 * index]);
Comment on lines +209 to 215
}

Expand Down
17 changes: 17 additions & 0 deletions src/tsutil/unit_tests/test_Regex.cc
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,23 @@ TEST_CASE("RegexMatches edge cases", "[libts][Regex][RegexMatches]")
CHECK(count >= 2); // At least whole match + first group
CHECK(matches[1] == "foo");
}

SECTION("RegexMatches with a non-participating group before a participating one")
{
// pcre2_match() returns one past the highest participating group, so an earlier optional group
// that did not participate is still within that count. Its offsets are unset.
Regex r;
REQUIRE(r.compile("(a)?(b)") == true);

RegexMatches matches;
int count = r.exec("b", matches);

CHECK(count == 3);
CHECK(matches[0] == "b");
CHECK(matches[1] == "");
CHECK(matches[2] == "b");
CHECK(matches[1].data() == nullptr);
}
}

TEST_CASE("Regex with special characters", "[libts][Regex][special]")
Expand Down