diff --git a/stl/inc/regex b/stl/inc/regex index 3b1b5662be..7e8525f82e 100644 --- a/stl/inc/regex +++ b/stl/inc/regex @@ -1287,15 +1287,15 @@ _BITMASK_OPS(_EMPTY_ARGUMENT, _Node_flags) enum _Node_type { // type flag for nfa nodes _N_none, - _N_nop, // TRANSITION, ABI: preserved for binary compatibility, the parser no longer generates _N_nop nodes + _N_nop, // TRANSITION, ABI: preserved for binary compatibility, the parser no longer generates such nodes _N_bol, _N_eol, _N_wbound, _N_dot, _N_str, _N_class, - _N_group, - _N_end_group, + _N_group, // TRANSITION, ABI: preserved for binary compatibility, the parser no longer generates such nodes + _N_end_group, // TRANSITION, ABI: preserved for binary compatibility, the parser no longer generates such nodes _N_assert, _N_neg_assert, _N_end_assert, @@ -1607,7 +1607,6 @@ public: void _Add_named_class(typename _RxTraits::char_class_type, _Rx_char_class_kind); void _Add_equiv(const _Elem*, const _Elem*); void _Add_coll(const _Elem*, const _Elem*); - _Node_base* _Begin_group(); void _End_group(_Node_base* _Back); _Node_assert* _Begin_assert_group(bool _Neg); void _End_assert_group(_Node_assert* _Assert_start); @@ -1615,7 +1614,8 @@ public: void _Add_backreference(unsigned int _Idx); _Node_base* _Begin_if(_Node_base* _Start); void _Else_if(_Node_base*, _Node_base*); - void _Add_rep(int _Min, int _Max, bool _Greedy); + void _Add_group_rep(_Node_base* _Start, int _Min, int _Max, bool _Greedy); + void _Add_nongroup_rep(int _Min, int _Max, bool _Greedy); void _Negate(); _Root_node* _End_pattern(); @@ -1628,6 +1628,7 @@ private: void _Add_char_to_array(_Elem _Ch); void _Add_elts(_Node_class<_Elem, _RxTraits>*, typename _RxTraits::char_class_type, bool); void _Char_to_elts(const _Elem*, const _Elem*, _Sequence<_Elem>**); + void _Generate_rep(_Node_base* _First_inner, int _Min, int _Max, bool _Greedy); _Root_node* _Root; _Node_base* _Current; @@ -2238,6 +2239,7 @@ private: void _Disjunction(); void _Calculate_loop_simplicity(_Node_base* _Nx, _Node_base* _Ne, _Node_rep* _Outer_rep, bool _Nonreentrant); + _Node_base* _Group_start{}; _FwdIt _Pat; _FwdIt _End; unsigned int _Grp_idx = 0; @@ -3597,20 +3599,14 @@ void _Builder3<_FwdIt, _Elem, _RxTraits>::_Add_coll(const _Elem* const _First, c _Char_to_elts(_First, _Last, _Cur); } -template -_Node_base* _Builder3<_FwdIt, _Elem, _RxTraits>::_Begin_group() { // add group node - return _New_node(_N_group); -} - template void _Builder3<_FwdIt, _Elem, _RxTraits>::_End_group(_Node_base* _Back) { // add end of group node _Emit_str_node(); _Node_type _Elt; - if (_Back->_Kind == _N_group) { - _Elt = _N_end_group; - } else if (_Back->_Kind == _N_assert || _Back->_Kind == _N_neg_assert) { + if (_Back->_Kind == _N_assert || _Back->_Kind == _N_neg_assert) { _Elt = _N_end_assert; } else { + _STL_INTERNAL_CHECK(_Back->_Kind == _N_capture); _Elt = _N_end_capture; } @@ -3685,8 +3681,19 @@ void _Builder3<_FwdIt, _Elem, _RxTraits>::_Else_if(_Node_base* const _Start, _No } template -void _Builder3<_FwdIt, _Elem, _RxTraits>::_Add_rep(int _Min, int _Max, bool _Greedy) { // add repeat node +void _Builder3<_FwdIt, _Elem, _RxTraits>::_Add_group_rep(_Node_base* const _Start, int _Min, int _Max, bool _Greedy) { + // add repeat node around a group beginning after _Start + _Emit_str_node(); + if (_Start != _Current) { // not an empty group + _Generate_rep(_Start->_Next, _Min, _Max, _Greedy); + } +} + +template +void _Builder3<_FwdIt, _Elem, _RxTraits>::_Add_nongroup_rep(int _Min, int _Max, bool _Greedy) { + // add repeat node around current node if (!_Chars.empty()) { + // repeat applies to last character in character sequence only const _Elem _Last_char = _Chars.back(); _Chars.pop_back(); _Emit_str_node(); @@ -3695,17 +3702,28 @@ void _Builder3<_FwdIt, _Elem, _RxTraits>::_Add_rep(int _Min, int _Max, bool _Gre _Node->_Data._Insert2(_Last_char); } - _Node_base* _Pos = _Current; - if (_Pos->_Kind == _N_end_group || _Pos->_Kind == _N_end_capture) { - _Pos = static_cast<_Node_end_group*>(_Pos)->_Back; - } else if (_Min == 0 && _Max == 1) { + return _Generate_rep(_Current, _Min, _Max, _Greedy); +} + +template +void _Builder3<_FwdIt, _Elem, _RxTraits>::_Generate_rep(_Node_base* _First_node, int _Min, int _Max, bool _Greedy) { + // generates a rep around the range from given start node to current node + if (_Min == 0 && _Max == 1 && _First_node == _Current && _First_node->_Kind != _N_assert) { // Rewrite zero-or-one quantifiers as alternations to make the // "simple loop" optimization more likely to engage. // // GH-5490: This rewrite becomes observably incorrect - // if the subexpression contains capture groups, - // so we don't apply it if the subexpression is surrounded - // by a capturing or non-capturing group. + // if the subexpression contains capture groups + // that may be matched at the end of a repetition. + // Since a capture group is represented by two nodes, + // a capture group can only appear in this range + // if it contains at least two nodes or an assertion. + // Captures in negative assertions are guaranteed + // to be unmatched at the end of a repetition, + // but those in positive assertions might be matched. + // Hence, we disable this optimization + // if the range consists of more than one node + // or a single positive assertion. _Node_endif* _End = new _Node_endif; _Node_if* _If_expr = new _Node_if(_End); _Node_if* _If_empty_str = new _Node_if(_End); @@ -3713,7 +3731,7 @@ void _Builder3<_FwdIt, _Elem, _RxTraits>::_Add_rep(int _Min, int _Max, bool _Gre _If_expr->_Child = _If_empty_str; _Link_node(_End); - _Insert_node(_Pos, _If_expr); + _Insert_node(_First_node, _If_expr); if (!_Greedy) { _If_expr->_Next->_Prev = _If_empty_str; @@ -3731,7 +3749,7 @@ void _Builder3<_FwdIt, _Elem, _RxTraits>::_Add_rep(int _Min, int _Max, bool _Gre _Link_node(_Node0); _Node_rep* _Nx = new _Node_rep(_Greedy, _Min, _Max, _Node0, _Root->_Loops++); _Node0->_Begin_rep = _Nx; - _Insert_node(_Pos, _Nx); + _Insert_node(_First_node, _Nx); } template @@ -5532,9 +5550,7 @@ void _Parser3<_FwdIt, _Elem, _RxTraits>::_Do_capture_group() { // add capture gr template void _Parser3<_FwdIt, _Elem, _RxTraits>::_Do_noncapture_group() { // add non-capture group - _Node_base* _Pos1 = _Nfa._Begin_group(); _Disjunction(); - _Nfa._End_group(_Pos1); } template @@ -5551,6 +5567,7 @@ bool _Parser3<_FwdIt, _Elem, _RxTraits>::_Wrapped_disjunction() { // add disjunc _Error(regex_constants::error_stack); } + auto _Start = _Nfa._Getmark(); if (!(_L_flags & _L_empty_grp) && _Mchar == _Meta_rpar) { _Error(regex_constants::error_paren); } else if ((_L_flags & _L_nc_asrt) && _Mchar == _Meta_query) { // check for valid ECMAScript (?x ... ) group @@ -5576,6 +5593,7 @@ bool _Parser3<_FwdIt, _Elem, _RxTraits>::_Wrapped_disjunction() { // add disjunc _Do_capture_group(); } + _Group_start = _Start; --_Disj_count; return true; } @@ -5807,7 +5825,11 @@ void _Parser3<_FwdIt, _Elem, _RxTraits>::_Quantifier() { // check for quantifier _Next(); } - _Nfa._Add_rep(_Min, _Max, _Greedy); + if (_Group_start) { + _Nfa._Add_group_rep(_Group_start, _Min, _Max, _Greedy); + } else { + _Nfa._Add_nongroup_rep(_Min, _Max, _Greedy); + } } template @@ -5874,7 +5896,8 @@ void _Parser3<_FwdIt, _Elem, _RxTraits>::_Alternative() { // check for valid alt _Quantifier(); } - _Found = true; + _Group_start = nullptr; + _Found = true; } } diff --git a/tests/std/tests/VSO_0000000_regex_use/test.cpp b/tests/std/tests/VSO_0000000_regex_use/test.cpp index 09bdca395a..7d15d97706 100644 --- a/tests/std/tests/VSO_0000000_regex_use/test.cpp +++ b/tests/std/tests/VSO_0000000_regex_use/test.cpp @@ -2679,6 +2679,53 @@ void test_gh_6289() { g_regexTester.should_not_match("abc", "a|b|c"); } +void test_gh_6359() { + // GH-6359: Avoid generating group nodes for non-capturing groups + g_regexTester.should_match("abc", "ab*c"); + g_regexTester.should_match("abbbbc", "ab*c"); + g_regexTester.should_match("ac", "ab*c"); + g_regexTester.should_not_match("", "ab*c"); + g_regexTester.should_not_match("a", "ab*c"); + g_regexTester.should_not_match("c", "ab*c"); + + g_regexTester.should_match("abcd", "a(?:bc)*d"); + g_regexTester.should_match("abcbcd", "a(?:bc)*d"); + g_regexTester.should_match("ad", "a(?:bc)*d"); + g_regexTester.should_not_match("", "a(?:bc)*d"); + g_regexTester.should_not_match("a", "a(?:bc)*d"); + g_regexTester.should_not_match("d", "a(?:bc)*d"); + g_regexTester.should_not_match("abccd", "a(?:bc)*d"); + + g_regexTester.should_capture("ad", "a(bc)*d", ""); + g_regexTester.should_not_match("", "a(bc)*d"); + g_regexTester.should_not_match("a", "a(bc)*d"); + g_regexTester.should_not_match("d", "a(bc)*d"); + g_regexTester.should_not_match("abccd", "a(bc)*d"); + + { + test_regex re_with_capture_group{&g_regexTester, "^a(bc)*d$"}; + re_with_capture_group.should_search_match_capture_groups("abcd", "abcd", match_default, {{1, 3}}); + re_with_capture_group.should_search_match_capture_groups("abcbcd", "abcbcd", match_default, {{3, 5}}); + } + + g_regexTester.should_match("abcbcd", "a(?:(?:bc)+){2}d"); + g_regexTester.should_match("abcbcbcbcd", "a(?:(?:bc)+){2}d"); + g_regexTester.should_not_match("ad", "a(?:(?:bc)+){2}d"); + g_regexTester.should_not_match("", "a(?:(?:bc)+){2}d"); + g_regexTester.should_not_match("a", "a(?:(?:bc)+){2}d"); + g_regexTester.should_not_match("d", "a(?:(?:bc)+){2}d"); + g_regexTester.should_not_match("abcd", "a(?:(?:bc)+){2}d"); + g_regexTester.should_not_match("abcbc", "a(?:(?:bc)+){2}d"); + g_regexTester.should_not_match("bcbcd", "a(?:(?:bc)+){2}d"); + g_regexTester.should_match("abcd", "a(?:(?:bc)*){2}d"); + + g_regexTester.should_match("bca", "(?:bc)+a+"); + g_regexTester.should_match("bcbcaa", "(?:bc)+a+"); + g_regexTester.should_not_match("bcabca", "(?:bc)+a+"); + + g_regexTester.should_capture("a", "(?:(?=(a)))?a", ""); +} + int main() { test_dev10_449367_case_insensitivity_should_work(); test_dev11_462743_regex_collate_should_not_disable_regex_icase(); @@ -2750,6 +2797,7 @@ int main() { test_gh_6262(); test_gh_6267(); test_gh_6289(); + test_gh_6359(); return g_regexTester.result(); }