Repository :
https://github.com/colorer/Colorer-library
On branch : cregexp_refactoring
Link :
https://github.com/colorer/Colorer-library/commit/a12ba6afce4e8fad300cad417c38619aab2e77a8
>---------------------------------------------------------------
commit a12ba6afce4e8fad300cad417c38619aab2e77a8
Author: Aleksey Dobrunov <
cta...@ctapmex.com>
Date: Mon Aug 17 00:18:40 2026 +0500
Guard CRegExp backtrace copies with sanitized bounds and share the captured-range matcher.
>---------------------------------------------------------------
a12ba6afce4e8fad300cad417c38619aab2e77a8
src/colorer/cregexp/cregexp.cpp | 84 ++++++++++++++++++++---------------------
src/colorer/cregexp/cregexp.h | 1 +
tests/unit/test_cregexp.cpp | 19 ++++++++++
3 files changed, 60 insertions(+), 44 deletions(-)
diff --git a/src/colorer/cregexp/cregexp.cpp b/src/colorer/cregexp/cregexp.cpp
index 0d399db..4ad5b48 100644
--- a/src/colorer/cregexp/cregexp.cpp
+++ b/src/colorer/cregexp/cregexp.cpp
@@ -928,6 +928,27 @@ void CRegExp::check_stack(bool res, SRegInfo** re, SRegInfo** prev, int* toParse
*leftenter = ne.leftenter;
}
+bool CRegExp::matchCopiedRange(const UnicodeString& src, int from, int to, int& toParse, bool icase) const
+{
+ // Unmatched groups are stored as -1,-1; the classic copy loop then does nothing.
+ if (from < 0 || to < 0)
+ return true;
+ const UnicodeString& pattern = *global_pattern;
+ for (int i = from; i < to; i++) {
+ if (toParse >= end)
+ return false;
+ if (icase) {
+ if (Character::toLowerCase(pattern[toParse]) != Character::toLowerCase(src[i]))
+ return false;
+ }
+ else if (pattern[toParse] != src[i]) {
+ return false;
+ }
+ toParse++;
+ }
+ return true;
+}
+
void CRegExp::insert_stack(SRegInfo** re, SRegInfo** prev, int* toParse, bool* leftenter, ReAction ifTrueReturn,
ReAction ifFalseReturn, SRegInfo** re2, SRegInfo** prev2, int toParse2)
{
@@ -1059,61 +1080,43 @@ bool CRegExp::lowParse(SRegInfo* re, SRegInfo* prev, int toParse)
#ifdef COLORERMODE
case EOps::ReBkTrace:
sv = re->param0;
- if (!backStr || !backTrace || sv == -1) {
+ if (!backStr || !backTrace || sv < 0 || sv >= MATCHES_NUM) {
check_stack(false, &re, &prev, &toParse, &leftenter, &action);
continue;
}
- br = false;
- for (i = backTrace->s[sv]; i < backTrace->e[sv]; i++) {
- if (toParse >= end || pattern[toParse] != (*backStr)[i]) {
- check_stack(false, &re, &prev, &toParse, &leftenter, &action);
- br = true;
- break;
- }
- toParse++;
- }
- if (br)
+ backTrace->topseSanitize(sv);
+ if (!matchCopiedRange(*backStr, backTrace->s[sv], backTrace->e[sv], toParse, false)) {
+ check_stack(false, &re, &prev, &toParse, &leftenter, &action);
continue;
+ }
break;
case EOps::ReBkTraceN:
sv = re->param0;
- if (!backStr || !backTrace || sv == -1) {
+ if (!backStr || !backTrace || sv < 0 || sv >= MATCHES_NUM) {
check_stack(false, &re, &prev, &toParse, &leftenter, &action);
continue;
}
- br = false;
- for (i = backTrace->s[sv]; i < backTrace->e[sv]; i++) {
- if (toParse >= end || Character::toLowerCase(pattern[toParse]) != Character::toLowerCase((*backStr)[i])) {
- check_stack(false, &re, &prev, &toParse, &leftenter, &action);
- br = true;
- break;
- }
- toParse++;
- }
- if (br)
+ backTrace->topseSanitize(sv);
+ if (!matchCopiedRange(*backStr, backTrace->s[sv], backTrace->e[sv], toParse, true)) {
+ check_stack(false, &re, &prev, &toParse, &leftenter, &action);
continue;
+ }
break;
case EOps::ReBkTraceName:
sv = re->param0;
- if (!backStr || !backTrace || sv == -1) {
+ if (!backStr || !backTrace || sv < 0 || sv >= NAMED_MATCHES_NUM) {
check_stack(false, &re, &prev, &toParse, &leftenter, &action);
continue;
}
- br = false;
- for (i = backTrace->ns[sv]; i < backTrace->ne[sv]; i++) {
- if (toParse >= end || pattern[toParse] != (*backStr)[i]) {
- check_stack(false, &re, &prev, &toParse, &leftenter, &action);
- br = true;
- break;
- }
- toParse++;
- }
- if (br)
+ backTrace->topnseSanitize(sv);
+ if (!matchCopiedRange(*backStr, backTrace->ns[sv], backTrace->ne[sv], toParse, false)) {
+ check_stack(false, &re, &prev, &toParse, &leftenter, &action);
continue;
+ }
break;
case EOps::ReBkTraceNName:
sv = re->param0;
- if (!backStr || !backTrace || sv == -1 || backTrace->cnMatch <= sv) {
+ if (!backStr || !backTrace || sv < 0 || sv >= NAMED_MATCHES_NUM || backTrace->cnMatch <= sv) {
check_stack(false, &re, &prev, &toParse, &leftenter, &action);
continue;
}
@@ -1122,17 +1125,10 @@ bool CRegExp::lowParse(SRegInfo* re, SRegInfo* prev, int toParse)
check_stack(false, &re, &prev, &toParse, &leftenter, &action);
continue;
}
- br = false;
- for (i = backTrace->ns[sv]; i < backTrace->ne[sv]; i++) {
- if (toParse >= end || Character::toLowerCase(pattern[toParse]) != Character::toLowerCase((*backStr)[i])) {
- check_stack(false, &re, &prev, &toParse, &leftenter, &action);
- br = true;
- break;
- }
- toParse++;
- }
- if (br)
+ if (!matchCopiedRange(*backStr, backTrace->ns[sv], backTrace->ne[sv], toParse, true)) {
+ check_stack(false, &re, &prev, &toParse, &leftenter, &action);
continue;
+ }
break;
#endif // COLORERMODE
diff --git a/src/colorer/cregexp/cregexp.h b/src/colorer/cregexp/cregexp.h
index 785b493..8ab011d 100644
--- a/src/colorer/cregexp/cregexp.h
+++ b/src/colorer/cregexp/cregexp.h
@@ -333,6 +333,7 @@ class CRegExp
bool quickCheck(int toParse);
bool isWordBoundary(int toParse);
bool checkMetaSymbol(EMetaSymbols metaSymbol, int& toParse);
+ bool matchCopiedRange(const UnicodeString& src, int from, int to, int& toParse, bool icase) const;
bool lowParse(SRegInfo* re, SRegInfo* prev, int toParse);
bool parseRE(int toParse);
diff --git a/tests/unit/test_cregexp.cpp b/tests/unit/test_cregexp.cpp
index 1a2d511..b575878 100644
--- a/tests/unit/test_cregexp.cpp
+++ b/tests/unit/test_cregexp.cpp
@@ -545,6 +545,25 @@ TEST_CASE("CRegExp Colorer backtrace \\y", "[cregexp]")
REQUIRE_FALSE(end.setRE(&end_re));
REQUIRE(end.getError() == EError::EERROR);
}
+
+ SECTION("numeric \\yN outside captured groups does not read uninitialized slots")
+ {
+ const auto start_re = ustr(u"/(foo)/");
+ const auto text = ustr(u"foo");
+ CRegExp start(&start_re);
+ REQUIRE(start.isOk());
+ SMatches start_match;
+ REQUIRE(start.parse(&text, &start_match));
+
+ const auto end_re = ustr(u"/\\yF/");
+ CRegExp end;
+ end.setBackTrace(&text, &start_match);
+ REQUIRE(end.setRE(&end_re));
+ SMatches end_match;
+ REQUIRE(end.parse(&text, &end_match));
+ REQUIRE(end_match.s[0] == 0);
+ REQUIRE(end_match.e[0] == 0);
+ }
}
TEST_CASE("CRegExp canStartWith", "[cregexp]")