This is an automated email from the ASF dual-hosted git repository.
moonchen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new e3bd689202 Return an empty view for a non-participating capture group
(#13441)
e3bd689202 is described below
commit e3bd6892026e9356076dfa84b0aecfa558cccacb
Author: Mo Chen <[email protected]>
AuthorDate: Fri Aug 7 09:48:52 2026 -0500
Return an empty view for a non-participating capture group (#13441)
RegexMatches::operator[] only checked the index against the ovector
count. A group that does not participate in the match has unset
offsets, and an optional group that precedes a participating one is
still within that count, so the check passes and the subject pointer is
advanced by PCRE2_UNSET.
The resulting view has length zero, so callers see an empty string
today, but the pointer is invalid.
---
src/tsutil/Regex.cc | 7 +++++++
src/tsutil/unit_tests/test_Regex.cc | 17 +++++++++++++++++
2 files changed, 24 insertions(+)
diff --git a/src/tsutil/Regex.cc b/src/tsutil/Regex.cc
index 34bfc5447d..2c84b3fa08 100644
--- a/src/tsutil/Regex.cc
+++ b/src/tsutil/Regex.cc
@@ -228,6 +228,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]);
}
diff --git a/src/tsutil/unit_tests/test_Regex.cc
b/src/tsutil/unit_tests/test_Regex.cc
index f5cddd47a0..76273e2322 100644
--- a/src/tsutil/unit_tests/test_Regex.cc
+++ b/src/tsutil/unit_tests/test_Regex.cc
@@ -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]")