From 84af59040c37e9a0cc3639106564e6f50cd950a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20M=C3=BCller?= Date: Sat, 10 Jan 2026 13:56:29 +0100 Subject: [PATCH 1/5] ``: Revise complexity limit --- stl/inc/regex | 109 ++++++++---------- .../std/tests/VSO_0000000_regex_use/test.cpp | 17 +++ 2 files changed, 62 insertions(+), 64 deletions(-) diff --git a/stl/inc/regex b/stl/inc/regex index 8343f4cf69b..4aff5ee411b 100644 --- a/stl/inc/regex +++ b/stl/inc/regex @@ -43,10 +43,6 @@ _STL_DISABLE_CLANG_WARNINGS #define _REGEX_LEGACY_MULTILINE_MODE 0 #endif -#ifndef _REGEX_MAX_COMPLEXITY_COUNT -#define _REGEX_MAX_COMPLEXITY_COUNT 10000000L // set to 0 to disable -#endif // !defined(_REGEX_MAX_COMPLEXITY_COUNT) - #ifndef _ENHANCED_REGEX_VISUALIZER #ifdef _DEBUG #define _ENHANCED_REGEX_VISUALIZER 1 @@ -1720,8 +1716,9 @@ public: _Char_class_d = _Lookup_char_class(static_cast<_Elem>('D')); } - _Input_length = _STD distance(_Pfirst, _Plast); - _Frames_limit = _Calculate_frames_limit(); + _Remaining_complexity_input_count = _STD distance(_Pfirst, _Plast); + _Frames_limit = _Calculate_frames_limit(); + _Complexity_limit_for_next_char = 300000; // sanitize multiline mode setting #if _REGEX_LEGACY_MULTILINE_MODE @@ -1761,9 +1758,8 @@ public: _Tgt_state._Grp_valid.resize(_Ncap); _Tgt_state._Grps.resize(_Ncap); } - _Full = _Full_match; - _Max_complexity_count = _REGEX_MAX_COMPLEXITY_COUNT; - _Frames_count = 0; + _Full = _Full_match; + _Frames_count = 0; _Matched = false; @@ -1817,13 +1813,12 @@ private: vector<_Rx_state_frame_t<_It>> _Frames; size_t _Frames_count; size_t _Frames_limit; - _Iter_diff_t<_It> _Input_length; + _Iter_diff_t<_It> _Remaining_complexity_input_count; size_t _Push_frame(_Rx_unwind_ops _Code, _Node_base* _Node); - void _Pop_frame(size_t); size_t _Calculate_frames_limit(); - void _Increase_complexity_count(); + void _Increase_complexity_count(_Iter_diff_t<_It> _Count); void _Prepare_rep(_Node_rep*); bool _Find_first_inner_capture_group(_Node_base*, _Loop_vals_v3_t<_Iter_diff_t<_It>>*); @@ -1844,7 +1839,7 @@ private: bool _Longest; const _RxTraits& _Traits; bool _Full; - long _Max_complexity_count; + int _Complexity_limit_for_next_char; typename _RxTraits::char_class_type _Char_class_w{}; typename _RxTraits::char_class_type _Char_class_s{}; typename _RxTraits::char_class_type _Char_class_d{}; @@ -3400,17 +3395,11 @@ size_t _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Push_frame(_Rx_unwind_ return _Frames_count++; } -template -void _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Pop_frame(size_t _Idx) { - _STL_INTERNAL_CHECK(_Idx + 1 == _Frames_count); - _Frames_count = _Idx; -} - template size_t _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Calculate_frames_limit() { constexpr size_t _Fixed_part = 10000000U / sizeof(_Rx_state_frame_t<_It>); constexpr size_t _Divisor = sizeof(_Rx_state_frame_t<_It>) / 10U; - const auto _Variable_part = _Input_length / static_cast<_Iter_diff_t<_It>>(_Divisor); + const auto _Variable_part = _Remaining_complexity_input_count / static_cast<_Iter_diff_t<_It>>(_Divisor); const size_t _Max_frames_size = _Frames.max_size(); if (PTRDIFF_MAX < _Variable_part) { @@ -3421,9 +3410,18 @@ size_t _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Calculate_frames_limit } template -void _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Increase_complexity_count() { - if (0 < _Max_complexity_count && --_Max_complexity_count <= 0) { - _Xregex_error(regex_constants::error_complexity); +void _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Increase_complexity_count(_Iter_diff_t<_It> _Count) { + constexpr int _Limit_per_char = 256; + if (_Complexity_limit_for_next_char < _Count) { + _Count -= static_cast<_Iter_diff_t<_It>>(_Complexity_limit_for_next_char); + auto _Exhausted_input_chars = static_cast<_Iter_diff_t<_It>>(1 + _Count / _Limit_per_char); + if (_Remaining_complexity_input_count < _Exhausted_input_chars) { + _Xregex_error(regex_constants::error_complexity); + } + _Remaining_complexity_input_count -= _Exhausted_input_chars; + _Complexity_limit_for_next_char = _Limit_per_char - static_cast(_Count % _Limit_per_char); + } else { + _Complexity_limit_for_next_char -= static_cast(_Count); } } @@ -3538,11 +3536,12 @@ void _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Reset_capture_groups(uns } } -template -_BidIt1 _Cmp_chrange(_BidIt1 _Begin1, _BidIt1 _End1, _BidIt2 _Begin2, _BidIt2 _End2, _Pr _Pred) { +template +_BidIt1 _Cmp_chrange(_BidIt1 _Begin1, _BidIt1 _End1, _BidIt2 _Begin2, _BidIt2 _End2, _Counter& _Count, _Pr _Pred) { // compare character ranges _BidIt1 _Res = _Begin1; while (_Begin1 != _End1 && _Begin2 != _End2) { + ++_Count; if (!_Pred(*_Begin1++, *_Begin2++)) { return _Res; } @@ -3550,33 +3549,34 @@ _BidIt1 _Cmp_chrange(_BidIt1 _Begin1, _BidIt1 _End1, _BidIt2 _Begin2, _BidIt2 _E return _Begin2 == _End2 ? _Begin1 : _Res; } -template +template _BidIt1 _Compare_translate_both(_BidIt1 _Begin1, _BidIt1 _End1, _BidIt2 _Begin2, _BidIt2 _End2, - const _RxTraits& _Traits, regex_constants::syntax_option_type _Sflags) { + const _RxTraits& _Traits, regex_constants::syntax_option_type _Sflags, _Counter& _Count) { // compare character ranges, translating characters in both ranges according to syntax options if (_Sflags & regex_constants::icase) { - return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, _Cmp_icase<_RxTraits>{_Traits}); + return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, _Count, _Cmp_icase<_RxTraits>{_Traits}); } else if constexpr (_Is_any_of_v<_RxTraits, regex_traits, regex_traits>) { - return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, equal_to{}); + return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, _Count, equal_to{}); } else if (_Sflags & regex_constants::collate) { - return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, _Cmp_collate<_RxTraits>{_Traits}); + return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, _Count, _Cmp_collate<_RxTraits>{_Traits}); } else { - return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, equal_to{}); + return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, _Count, equal_to{}); } } -template +template _BidIt1 _Compare_translate_left(_BidIt1 _Begin1, _BidIt1 _End1, _BidIt2 _Begin2, _BidIt2 _End2, - const _RxTraits& _Traits, regex_constants::syntax_option_type _Sflags) { + const _RxTraits& _Traits, regex_constants::syntax_option_type _Sflags, _Counter& _Count) { // compare character ranges, translating characters in the left range according to syntax options if (_Sflags & regex_constants::icase) { - return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, _Cmp_icase_translateleft<_RxTraits>{_Traits}); + return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, _Count, _Cmp_icase_translateleft<_RxTraits>{_Traits}); } else if constexpr (_Is_any_of_v<_RxTraits, regex_traits, regex_traits>) { - return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, equal_to{}); + return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, _Count, equal_to{}); } else if (_Sflags & regex_constants::collate) { - return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, _Cmp_collate_translateleft<_RxTraits>{_Traits}); + return _STD _Cmp_chrange( + _Begin1, _End1, _Begin2, _End2, _Count, _Cmp_collate_translateleft<_RxTraits>{_Traits}); } else { - return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, equal_to{}); + return _STD _Cmp_chrange(_Begin1, _End1, _Begin2, _End2, _Count, equal_to{}); } } @@ -3831,12 +3831,11 @@ bool _Is_ecmascript_line_terminator(_Elem _Ch) { template bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _Nx) { // check for match - _Increase_complexity_count(); - bool _Failed = false; while (_Nx) { do { // match current node + _Increase_complexity_count(_Iter_diff_t<_It>{1}); _Node_base* _Next = _Nx->_Next; switch (_Nx->_Kind) { // handle current node's type case _N_nop: @@ -3890,13 +3889,15 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N { // check for string match _Node_str<_Elem>* _Node = static_cast<_Node_str<_Elem>*>(_Nx); _It _Res0; + _Iter_diff_t<_It> _Count{}; if ((_Res0 = _STD _Compare_translate_left(_Tgt_state._Cur, _End, _Node->_Data._Str(), - _Node->_Data._Str() + _Node->_Data._Size(), _Traits, _Sflags)) + _Node->_Data._Str() + _Node->_Data._Size(), _Traits, _Sflags, _Count)) != _Tgt_state._Cur) { _Tgt_state._Cur = _Res0; } else { _Failed = true; } + _Increase_complexity_count(static_cast<_Iter_diff_t<_It>>(_Count / 64)); break; } @@ -3923,8 +3924,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N auto _Node = static_cast<_Node_assert*>(_Nx); _Push_frame(_Rx_unwind_ops::_After_assert, _Node); _Next = _Node->_Child; - - _Increase_complexity_count(); break; } @@ -3933,8 +3932,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N auto _Node = static_cast<_Node_assert*>(_Nx); _Push_frame(_Rx_unwind_ops::_After_neg_assert, _Node); _Next = _Node->_Child; - - _Increase_complexity_count(); break; } @@ -4035,13 +4032,16 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N _It _Res0 = _Tgt_state._Cur; _It _Bx = _Tgt_state._Grps[_Node->_Idx]._Begin; _It _Ex = _Tgt_state._Grps[_Node->_Idx]._End; + _Iter_diff_t<_It> _Count{}; if (_Bx != _Ex // _Bx == _Ex for zero-length match - && (_Res0 = _STD _Compare_translate_both(_Tgt_state._Cur, _End, _Bx, _Ex, _Traits, _Sflags)) + && (_Res0 = _STD _Compare_translate_both( + _Tgt_state._Cur, _End, _Bx, _Ex, _Traits, _Sflags, _Count)) == _Tgt_state._Cur) { _Failed = true; } else { _Tgt_state._Cur = _Res0; } + _Increase_complexity_count(static_cast<_Iter_diff_t<_It>>(_Count / 64)); } else if (_Sflags & (regex_constants::basic | regex_constants::grep)) { _Failed = true; } @@ -4053,7 +4053,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N auto _Node = static_cast<_Node_if*>(_Nx); if (_Node->_Child) { _Push_frame(_Rx_unwind_ops::_Disjunction_eval_alternative, _Node->_Child); - _Increase_complexity_count(); } break; } @@ -4070,7 +4069,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N if (_Node->_Simple_loop == 1) { _Sav._Loop_frame_idx = _Push_frame(_Rx_unwind_ops::_Do_nothing, _Node); - _Increase_complexity_count(); if (_Node->_Min > 0 || (_Greedy && !_Longest && _Node->_Max != 0)) { // try a rep first _Sav._Loop_idx = 1; // _Next is already assigned correctly for matching a rep @@ -4100,11 +4098,9 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N _Frame._Loop_frame_idx_sav = _Sav._Loop_frame_idx; _Sav._Loop_idx = 1; _Sav._Loop_frame_idx = _Frame_idx; - _Increase_complexity_count(); // _Next is already assigned correctly for matching a rep } else { // try tail first _Next = _Node->_End_rep->_Next; - _Increase_complexity_count(); // set up stack unwinding for non-greedy matching if at least one rep is allowed if (_Node->_Max != 0) { auto _Frame_idx = _Push_frame(_Rx_unwind_ops::_Loop_nongreedy, _Node); @@ -4136,8 +4132,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N // _Next is already assigned correctly for matching tail if (!(_Sflags & regex_constants::_Any_posix) && _Nr->_Min == 0) { _Failed = true; - } else { - _Increase_complexity_count(); } break; } @@ -4197,8 +4191,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N } // _Next is already assigned correctly for matching tail } - - _Increase_complexity_count(); } else { const bool _Progress = _Frames[_Sav._Loop_frame_idx]._Pos != _Tgt_state._Cur; if (_Sav._Loop_idx < _Nr->_Min) { // try another required match @@ -4218,7 +4210,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N _Reset_capture_groups(_Sav._Group_first); _Next = _Nr->_Next; - _Increase_complexity_count(); } else if (!_Progress) { // latest rep match empty // An empty match is allowed if it is needed to reach the minimum number of reps. // Moreover, POSIX allows an empty repetition if the subexpression is matched only once. @@ -4226,8 +4217,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N if (_Sav._Loop_idx != _Nr->_Min && !((_Sflags & regex_constants::_Any_posix) && _Sav._Loop_idx == 1)) { _Failed = true; - } else { - _Increase_complexity_count(); } // _Next is already assigned correctly for matching tail } else if (_Greedy && !_Longest && _Sav._Loop_idx != _Nr->_Max) { // one more rep to try next @@ -4243,10 +4232,8 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N _Reset_capture_groups(_Sav._Group_first); _Next = _Nr->_Next; - _Increase_complexity_count(); } else { // non-greedy matching or greedy matching with maximum reached // set up stack unwinding for non-greedy matching if one more rep is allowed - _Increase_complexity_count(); if (_Sav._Loop_idx != _Nr->_Max) { auto _Frame_idx = _Push_frame(_Rx_unwind_ops::_Loop_nongreedy, _Nr); auto& _Frame = _Frames[_Frame_idx]; @@ -4315,7 +4302,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N _Nx = _Node->_Next; _Tgt_state._Cur = _Frame._Pos; _Failed = false; - _Increase_complexity_count(); if (_Node->_Child) { _Frame._Node = _Node->_Child; ++_Frames_count; @@ -4328,7 +4314,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N auto _Node = static_cast<_Node_rep*>(_Frame._Node); auto& _Sav = _Loop_vals[_Node->_Loop_number]; - _Increase_complexity_count(); _Nx = _Node->_Next; _Tgt_state._Cur = _Frame._Pos; _Failed = false; @@ -4342,7 +4327,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N { // try tail after backtracking from first rep auto _Node = static_cast<_Node_rep*>(_Frame._Node); - _Increase_complexity_count(); _Nx = _Node->_End_rep->_Next; _Tgt_state._Cur = _Frame._Pos; _Failed = false; @@ -4377,7 +4361,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N // when backtracking from last attempted rep auto _Node = static_cast<_Node_rep*>(_Frame._Node); - _Increase_complexity_count(); _Nx = _Node->_End_rep->_Next; _Tgt_state._Cur = _Frame._Pos; _Failed = false; @@ -4397,7 +4380,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N { // try tail auto _Node = static_cast<_Node_rep*>(_Frame._Node); - _Increase_complexity_count(); _Nx = _Node->_End_rep->_Next; _Tgt_state._Cur = _Frame._Pos; _Failed = false; @@ -4419,7 +4401,6 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N auto _Node = static_cast<_Node_rep*>(_Frame._Node); auto& _Sav = _Loop_vals[_Node->_Loop_number]; - _Increase_complexity_count(); _Nx = _Node->_Next; _Tgt_state._Cur = _Frame._Pos; _Failed = false; diff --git a/tests/std/tests/VSO_0000000_regex_use/test.cpp b/tests/std/tests/VSO_0000000_regex_use/test.cpp index 7b9ee69b694..a2459c98edd 100644 --- a/tests/std/tests/VSO_0000000_regex_use/test.cpp +++ b/tests/std/tests/VSO_0000000_regex_use/test.cpp @@ -2392,6 +2392,22 @@ void test_gh_5939() { g_regexTester.should_not_match("abcbbacdab", R"((?:([abc])([abc])){2,}abbacd\1\2)"); } +void test_gh_5944() { + // GH-5944: : Revising the stack and complexity limits + + // long strings should be matched successfully if the regex is simple + g_regexTester.should_match(string(20000000, 'a'), "a+"); + + // too much backtracking in complex regex expressions must result in a complexity exception + try { + regex re("a*[^b]*a*[^b]*a*[^b]*a*[^b]*a*[^b]*a*[^b]*"); + (void) regex_match("aaaaaaaaaaaaaaaaaaaaaaaaaaaaab", re); + assert(false); + } catch (regex_error& ex) { + assert(ex.code() == error_complexity); + } +} + int main() { test_dev10_449367_case_insensitivity_should_work(); test_dev11_462743_regex_collate_should_not_disable_regex_icase(); @@ -2452,6 +2468,7 @@ int main() { test_gh_5865(); test_gh_5918(); test_gh_5939(); + test_gh_5944(); return g_regexTester.result(); } From 4eb77f933a5aa87910b58550d3bef9bba8e0612a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20M=C3=BCller?= Date: Mon, 12 Jan 2026 21:03:54 +0100 Subject: [PATCH 2/5] alternative: use long long to represent complexity limit --- stl/inc/regex | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/stl/inc/regex b/stl/inc/regex index 4aff5ee411b..d967702a582 100644 --- a/stl/inc/regex +++ b/stl/inc/regex @@ -1716,9 +1716,9 @@ public: _Char_class_d = _Lookup_char_class(static_cast<_Elem>('D')); } - _Remaining_complexity_input_count = _STD distance(_Pfirst, _Plast); - _Frames_limit = _Calculate_frames_limit(); - _Complexity_limit_for_next_char = 300000; + _Iter_diff_t<_It> _Input_length = _STD distance(_Pfirst, _Plast); + _Frames_limit = _Calculate_frames_limit(_Input_length); + _Complexity_limit = _Calculate_complexity_limit(_Input_length); // sanitize multiline mode setting #if _REGEX_LEGACY_MULTILINE_MODE @@ -1813,11 +1813,12 @@ private: vector<_Rx_state_frame_t<_It>> _Frames; size_t _Frames_count; size_t _Frames_limit; - _Iter_diff_t<_It> _Remaining_complexity_input_count; + long long _Complexity_limit; size_t _Push_frame(_Rx_unwind_ops _Code, _Node_base* _Node); - size_t _Calculate_frames_limit(); + size_t _Calculate_frames_limit(_Iter_diff_t<_It> _Input_length); + long long _Calculate_complexity_limit(_Iter_diff_t<_It> _Input_length); void _Increase_complexity_count(_Iter_diff_t<_It> _Count); void _Prepare_rep(_Node_rep*); @@ -1839,7 +1840,6 @@ private: bool _Longest; const _RxTraits& _Traits; bool _Full; - int _Complexity_limit_for_next_char; typename _RxTraits::char_class_type _Char_class_w{}; typename _RxTraits::char_class_type _Char_class_s{}; typename _RxTraits::char_class_type _Char_class_d{}; @@ -3396,10 +3396,10 @@ size_t _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Push_frame(_Rx_unwind_ } template -size_t _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Calculate_frames_limit() { +size_t _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Calculate_frames_limit(_Iter_diff_t<_It> _Input_length) { constexpr size_t _Fixed_part = 10000000U / sizeof(_Rx_state_frame_t<_It>); constexpr size_t _Divisor = sizeof(_Rx_state_frame_t<_It>) / 10U; - const auto _Variable_part = _Remaining_complexity_input_count / static_cast<_Iter_diff_t<_It>>(_Divisor); + const auto _Variable_part = _Input_length / static_cast<_Iter_diff_t<_It>>(_Divisor); const size_t _Max_frames_size = _Frames.max_size(); if (PTRDIFF_MAX < _Variable_part) { @@ -3409,19 +3409,23 @@ size_t _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Calculate_frames_limit return (_STD min) (_Max_frames_size, static_cast(_Variable_part) + _Fixed_part); } +template +long long _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Calculate_complexity_limit( + _Iter_diff_t<_It> _Input_length) { + constexpr long long _Intercept = 300000LL; + constexpr long long _Slope = 256LL; + if ((LLONG_MAX - _Intercept) / _Slope < _Input_length) { + return LLONG_MAX; + } + return _Slope * static_cast(_Input_length) + _Intercept; +} + template void _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Increase_complexity_count(_Iter_diff_t<_It> _Count) { - constexpr int _Limit_per_char = 256; - if (_Complexity_limit_for_next_char < _Count) { - _Count -= static_cast<_Iter_diff_t<_It>>(_Complexity_limit_for_next_char); - auto _Exhausted_input_chars = static_cast<_Iter_diff_t<_It>>(1 + _Count / _Limit_per_char); - if (_Remaining_complexity_input_count < _Exhausted_input_chars) { - _Xregex_error(regex_constants::error_complexity); - } - _Remaining_complexity_input_count -= _Exhausted_input_chars; - _Complexity_limit_for_next_char = _Limit_per_char - static_cast(_Count % _Limit_per_char); + if (_Complexity_limit < _Count) { + _Xregex_error(regex_constants::error_complexity); } else { - _Complexity_limit_for_next_char -= static_cast(_Count); + _Complexity_limit -= static_cast(_Count); } } From 9f2323774485fd9edc6e1ebea9a7c7c200ec5559 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Jan 2026 09:19:01 -0800 Subject: [PATCH 3/5] Rename to `_Gradient`. --- stl/inc/regex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stl/inc/regex b/stl/inc/regex index d967702a582..f288629e545 100644 --- a/stl/inc/regex +++ b/stl/inc/regex @@ -3413,11 +3413,11 @@ template long long _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Calculate_complexity_limit( _Iter_diff_t<_It> _Input_length) { constexpr long long _Intercept = 300000LL; - constexpr long long _Slope = 256LL; - if ((LLONG_MAX - _Intercept) / _Slope < _Input_length) { + constexpr long long _Gradient = 256LL; + if ((LLONG_MAX - _Intercept) / _Gradient < _Input_length) { return LLONG_MAX; } - return _Slope * static_cast(_Input_length) + _Intercept; + return _Gradient * static_cast(_Input_length) + _Intercept; } template From c5f28fc8b178729c87e3c5301a98de61dd57571a Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Jan 2026 09:34:24 -0800 Subject: [PATCH 4/5] Add comments about character comparisons versus state transitions. --- stl/inc/regex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/stl/inc/regex b/stl/inc/regex index f288629e545..932cddeefe4 100644 --- a/stl/inc/regex +++ b/stl/inc/regex @@ -3839,7 +3839,7 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N while (_Nx) { do { // match current node - _Increase_complexity_count(_Iter_diff_t<_It>{1}); + _Increase_complexity_count(_Iter_diff_t<_It>{1}); // one state transition _Node_base* _Next = _Nx->_Next; switch (_Nx->_Kind) { // handle current node's type case _N_nop: @@ -3901,6 +3901,8 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N } else { _Failed = true; } + + // divide by 64 because character comparisons are cheaper than state transitions _Increase_complexity_count(static_cast<_Iter_diff_t<_It>>(_Count / 64)); break; @@ -4045,6 +4047,8 @@ bool _Matcher3<_BidIt, _Elem, _RxTraits, _It, _Alloc>::_Match_pat(_Node_base* _N } else { _Tgt_state._Cur = _Res0; } + + // divide by 64 because character comparisons are cheaper than state transitions _Increase_complexity_count(static_cast<_Iter_diff_t<_It>>(_Count / 64)); } else if (_Sflags & (regex_constants::basic | regex_constants::grep)) { _Failed = true; From 7b67ccd6a5ea398b42005e577ade685f5ac05ca0 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 13 Jan 2026 09:44:17 -0800 Subject: [PATCH 5/5] Catch by const reference. --- tests/std/tests/VSO_0000000_regex_use/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/std/tests/VSO_0000000_regex_use/test.cpp b/tests/std/tests/VSO_0000000_regex_use/test.cpp index a2459c98edd..cce150a1f88 100644 --- a/tests/std/tests/VSO_0000000_regex_use/test.cpp +++ b/tests/std/tests/VSO_0000000_regex_use/test.cpp @@ -2403,7 +2403,7 @@ void test_gh_5944() { regex re("a*[^b]*a*[^b]*a*[^b]*a*[^b]*a*[^b]*a*[^b]*"); (void) regex_match("aaaaaaaaaaaaaaaaaaaaaaaaaaaaab", re); assert(false); - } catch (regex_error& ex) { + } catch (const regex_error& ex) { assert(ex.code() == error_complexity); } }