Tests: don't use libc++ std::regex as a reference where it is wrong

libc++'s std::regex ignores match_prev_avail (and match_not_bol with
it) for '^', matching it wherever a search resumes, and misses '\b' at
the end of the subject. Search_NextPrevVisitReferenceMatches now builds
its reference with std::cregex_iterator, which gets '^' right, and the
'\b' replace case runs only against libstdc++. A new test checks that
kte::Regex matches '^' only at the start of the subject; it is skipped
for std::regex builds on libc++, which have the bug.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 12:26:53 -07:00
co-authored by Claude Opus 5.5
parent fe4e2abb3a
commit c9e8b7dbf2
+29 -11
View File
@@ -1949,16 +1949,14 @@ TEST(Search_NextPrevVisitReferenceMatches)
for (std::size_t p = line.find(q); p != std::string::npos; p = line.find(q, p + q.size())) for (std::size_t p = line.find(q); p != std::string::npos; p = line.find(q, p + q.size()))
want.emplace_back(y, p); want.emplace_back(y, p);
} else { } else {
std::size_t from = 0; // regex_iterator rather than regex_search with
std::cmatch m; // match_prev_avail, which libc++ ignores for '^'.
while (from <= line.size() && // For these patterns an empty match is never
std::regex_search(line.data() + from, line.data() + line.size(), m, rx, // followed by a non-empty one at the same place,
from ? std::regex_constants::match_prev_avail // so its stepping matches the rule above.
: std::regex_constants::match_default)) { const char *lb = line.data();
const std::size_t pos = from + static_cast<std::size_t>(m.position(0)); for (std::cregex_iterator it(lb, lb + line.size(), rx), end; it != end; ++it)
want.emplace_back(y, pos); want.emplace_back(y, static_cast<std::size_t>(it->position(0)));
from = pos + std::max<std::size_t>(static_cast<std::size_t>(m.length(0)), 1);
}
} }
if (nl == std::string::npos) if (nl == std::string::npos)
break; break;
@@ -2196,9 +2194,11 @@ TEST(RegexEngine_ReplaceMatchesStdRegex)
const char *subjects[] = {"", "baaac", "one two three", "a-b-c", "xyz", "aaa", " lead", "tail "}; const char *subjects[] = {"", "baaac", "one two three", "a-b-c", "xyz", "aaa", " lead", "tail "};
const std::pair<const char *, const char *> cases[] = { const std::pair<const char *, const char *> cases[] = {
{"a*", "X"}, {"a", "[$&]"}, {"(\\w+)", "<$1>"}, {"(o)(n)?", "$2$1"}, {"-", "$`|$'"}, {"a*", "X"}, {"a", "[$&]"}, {"(\\w+)", "<$1>"}, {"(o)(n)?", "$2$1"}, {"-", "$`|$'"},
{"\\s+", "$$"}, {"^", ">"}, {"$", "<"}, {"b|", "_"}, {"\\b", "|"}, {"\\s+", "$$"}, {"^", ">"}, {"$", "<"}, {"b|", "_"},
{"([a-z])([a-z])", "$2$1"}, {"(a)(x)?", "[$2]"}, {"([a-z])([a-z])", "$2$1"}, {"(a)(x)?", "[$2]"},
#if defined(__GLIBCXX__) #if defined(__GLIBCXX__)
// libc++'s std::regex misses the \b at the end of the subject.
{"\\b", "|"},
// Read as libstdc++ reads them (libc++ differs): $0 is the match, // Read as libstdc++ reads them (libc++ differs): $0 is the match,
// two digits are read greedily, missing groups expand to nothing. // two digits are read greedily, missing groups expand to nothing.
{"b", "[$0]"}, {"(b?)", "<$10>"}, {"(a)", "[$2]"}, {"(a)(b)?", "$12"}, {"b", "[$0]"}, {"(b?)", "<$10>"}, {"(a)", "[$2]"}, {"(a)(b)?", "$12"},
@@ -2220,6 +2220,24 @@ TEST(RegexEngine_ReplaceMatchesStdRegex)
} }
// '^' matches only at the start of the subject, also when searching from
// inside it. Not with std::regex under libc++, which ignores
// match_prev_avail (and match_not_bol with it) for '^'.
#if defined(KTE_USE_PCRE2) || defined(__GLIBCXX__)
TEST(RegexEngine_CaretOnlyAtSubjectStart)
{
kte::Regex rx;
std::string err;
ASSERT_TRUE(rx.Compile("^", err));
std::size_t pos = 99, len = 99;
ASSERT_TRUE(rx.Search("ab", 0, pos, len));
ASSERT_EQ(pos, (std::size_t) 0);
ASSERT_TRUE(!rx.Search("ab", 1, pos, len));
ASSERT_TRUE(!rx.Search("ab", 2, pos, len));
}
#endif
// With PCRE2, catastrophic backtracking stops at the match limit and says // With PCRE2, catastrophic backtracking stops at the match limit and says
// so, instead of hanging the editor. // so, instead of hanging the editor.
TEST(RegexEngine_CatastrophicPatternStops) TEST(RegexEngine_CatastrophicPatternStops)