From e0e2f573addf786b5daaa99eddaa20db092874a7 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 13 Aug 2025 14:28:25 -0700 Subject: [PATCH 1/3] Avoid overflow in stable_sort. --- stl/inc/algorithm | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index e8afb54e353..15d9bec7ab1 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -9223,10 +9223,14 @@ void _Buffered_merge_sort_unchecked(const _BidIt _First, const _BidIt _Last, con _Chunk <<= 1; _STD _Chunked_merge_unchecked(_Temp_ptr, _Temp_ptr + _Count, _First, static_cast(_Chunk), static_cast(_Count), _Pred); - _Chunk <<= 1; - if (_Count <= _Chunk) { // if the input would be a single chunk, it's already sorted and we're done - return; + + // In general, this is equivalent to `_Chunk <<= 1;` followed by testing `_Count <= _Chunk`, + // except that it doesn't risk overflowing. `(_Count - 1) / 2` correctly handles even/odd _Count. + // We have an early return for small _Count above, so `_Count - 1` is safe to form. + if ((_Count - 1) / 2 < _Chunk) { + return; // if the input would be a single chunk, it's already sorted and we're done } + _Chunk <<= 1; // more merges necessary; merge to temporary buffer _STD _Chunked_merge_unchecked(_First, _Last, _Temp_ptr, _Chunk, _Count, _Pred); @@ -9394,10 +9398,14 @@ namespace ranges { // unconditionally merge elements back into the source buffer _Chunk_size <<= 1; _Chunked_merge_common(_Temp_ptr, _Temp_ptr + _Count, _First, _Chunk_size, _Count, _Pred, _Proj); - _Chunk_size <<= 1; - if (_Count <= _Chunk_size) { // if the input would be a single chunk, it's already sorted and we're done - return; + + // In general, this is equivalent to `_Chunk_size <<= 1;` followed by testing `_Count <= _Chunk_size`, + // except that it doesn't risk overflowing. `(_Count - 1) / 2` correctly handles even/odd _Count. + // We have an early return for small _Count above, so `_Count - 1` is safe to form. + if ((_Count - 1) / 2 < _Chunk_size) { + return; // if the input would be a single chunk, it's already sorted and we're done } + _Chunk_size <<= 1; // more merges necessary; merge to temporary buffer _Chunked_merge_common(_First, _Last, _Temp_ptr, _Chunk_size, _Count, _Pred, _Proj); From bf76a60af7ed7ab8ae209d598758325f02474081 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 14 Aug 2025 17:49:13 -0700 Subject: [PATCH 2/3] Expand comments. --- stl/inc/algorithm | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 15d9bec7ab1..113ac9c5db0 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -395,6 +395,7 @@ _STD_BEGIN static_assert(_Is_ranges_bidi_iter_v<_Iter>, "This algorithm requires bidirectional iterators or stronger.") _INLINE_VAR constexpr int _ISORT_MAX = 32; // maximum size for insertion sort +// If _ISORT_MAX is ever changed from 32 == 2^5, re-analyze the implementation of stable_sort for integer overflow. template constexpr _Iter_diff_t<_It> _Isort_max{_ISORT_MAX}; @@ -9220,12 +9221,19 @@ void _Buffered_merge_sort_unchecked(const _BidIt _First, const _BidIt _Last, con auto _Chunk = _Isort_max<_BidIt>; for (;;) { // unconditionally merge elements back into the source buffer + + // _Chunk starts at 2^5 and is doubled twice in this loop. + // The first doubling (to 2^6, 2^8, 2^10, ...) doesn't check for overflow as it doesn't pose a risk. + // The second doubling (to 2^7, 2^9, 2^11, ..., 2^15, ..., 2^31, ...) defends against overflow below. _Chunk <<= 1; _STD _Chunked_merge_unchecked(_Temp_ptr, _Temp_ptr + _Count, _First, static_cast(_Chunk), static_cast(_Count), _Pred); - // In general, this is equivalent to `_Chunk <<= 1;` followed by testing `_Count <= _Chunk`, - // except that it doesn't risk overflowing. `(_Count - 1) / 2` correctly handles even/odd _Count. + // This is equivalent to doubling _Chunk followed by returning when `_Count <= _Chunk`, + // except that it doesn't risk overflowing. + // Note that returning when `_Count / 2 <= _Chunk` before doubling _Chunk + // would behave differently for odd _Count due to truncating integer division. + // Returning when `(_Count - 1) / 2 < _Chunk` correctly handles both even and odd _Count. // We have an early return for small _Count above, so `_Count - 1` is safe to form. if ((_Count - 1) / 2 < _Chunk) { return; // if the input would be a single chunk, it's already sorted and we're done @@ -9396,11 +9404,18 @@ namespace ranges { ptrdiff_t _Chunk_size = _ISORT_MAX; for (;;) { // unconditionally merge elements back into the source buffer + + // _Chunk_size starts at 2^5 and is doubled twice in this loop. + // The first doubling (to 2^6, 2^8, 2^10, ...) doesn't check for overflow as it doesn't pose a risk. + // The second doubling (to 2^7, 2^9, 2^11, ..., 2^15, ..., 2^31, ...) defends against overflow below. _Chunk_size <<= 1; _Chunked_merge_common(_Temp_ptr, _Temp_ptr + _Count, _First, _Chunk_size, _Count, _Pred, _Proj); - // In general, this is equivalent to `_Chunk_size <<= 1;` followed by testing `_Count <= _Chunk_size`, - // except that it doesn't risk overflowing. `(_Count - 1) / 2` correctly handles even/odd _Count. + // This is equivalent to doubling _Chunk_size followed by returning when `_Count <= _Chunk_size`, + // except that it doesn't risk overflowing. + // Note that returning when `_Count / 2 <= _Chunk_size` before doubling _Chunk_size + // would behave differently for odd _Count due to truncating integer division. + // Returning when `(_Count - 1) / 2 < _Chunk_size` correctly handles both even and odd _Count. // We have an early return for small _Count above, so `_Count - 1` is safe to form. if ((_Count - 1) / 2 < _Chunk_size) { return; // if the input would be a single chunk, it's already sorted and we're done From 06e9b60a75883fe7b4fb0762cdd79346641050d0 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 14 Aug 2025 18:15:04 -0700 Subject: [PATCH 3/3] Avoid overflow in `_Partition_point_unchecked`. --- stl/inc/algorithm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 113ac9c5db0..10203201835 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -2275,8 +2275,8 @@ namespace ranges { _First = _STD move(_Mid); ++_First; - using _Uty = _Make_unsigned_like_t>; - if (static_cast<_Uty>(_Skip) <= (static_cast<_Uty>(-1) >> 1)) { + constexpr auto _Half_max = (numeric_limits>::max)() / 2; + if (_Skip <= _Half_max) { _Skip <<= 1; } }