From 8d2e863e0eb654cd413433fa8273eb41eb2adab9 Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Sun, 16 Nov 2025 09:11:53 +0900 Subject: [PATCH 1/4] Fix overflow in LengthBuckets for large strings LengthBuckets.CreateLengthBucketsArrayIfAppropriate caused an integer overflow when calculating bucket array size for very large strings, resulting in a negative value being passed to ArrayPool.Shared.Rent. This triggered an ArgumentOutOfRangeException when creating a FrozenDictionary with large strings approaching Array.MaxLength. Add an overflow check to prevent creating buckets when the calculation would overflow. --- .../src/System/Collections/Frozen/String/LengthBuckets.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/LengthBuckets.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/LengthBuckets.cs index 626b21a8bf1212..ac2f17bca8372e 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/LengthBuckets.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/LengthBuckets.cs @@ -27,11 +27,11 @@ internal static class LengthBuckets return null; } - int arraySize = spread * MaxPerLength; + long expectedArraySize = (long)spread * MaxPerLength; #if NET - if (arraySize > Array.MaxLength) + if (expectedArraySize > Array.MaxLength) #else - if (arraySize > 0X7FFFFFC7) + if (expectedArraySize > 0X7FFFFFC7) #endif { // In the future we may lower the value, as it may be quite unlikely @@ -39,6 +39,8 @@ internal static class LengthBuckets return null; } + int arraySize = (int)expectedArraySize; + // Instead of creating a dictionary of lists or a multi-dimensional array // we rent a single dimension array, where every bucket has five slots. // The bucket starts at (key.Length - minLength) * 5. From d2e4ed9a77c4073f350c924385c9c6848365fd5a Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:05:11 +0900 Subject: [PATCH 2/4] Add test case for large strings exceeding length bucket boundaries --- .../tests/Frozen/FrozenFromKnownValuesTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs index dc4e2b9e89dbee..b07ef1c6e5d0bd 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs @@ -143,6 +143,9 @@ public static IEnumerable StringStringData() => "X-MSEdge-Ref", "X-Powered-By", "X-Request-ID", "X-UA-Compatible", "X-XSS-Protection", }, + // Test case with extremely large strings that exceed length bucket boundaries. + new[] {"", new string('a', 0X7FFFFFC7 / 4) }, + // exercise left/right justified ordinal comparers Enumerable.Range(0, 10).Select(i => $"{i}ABCDEFGH").ToArray(), // left justified single char ascii Enumerable.Range(0, 10).Select(i => $"ABCDEFGH{i}").ToArray(), // right justified single char ascii From 4be871c5b6e5a330effbe2cdfa6895b6c91f982e Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Wed, 19 Nov 2025 00:52:02 +0900 Subject: [PATCH 3/4] Add test for extremely large strings exceeding length bucket boundaries in FrozenDictionary and FrozenSet --- .../tests/Frozen/FrozenDictionaryTests.cs | 13 +++++++++++++ .../tests/Frozen/FrozenFromKnownValuesTests.cs | 3 --- .../tests/Frozen/FrozenSetTests.cs | 12 ++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenDictionaryTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenDictionaryTests.cs index 61b2bea331f857..14c967ce573098 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenDictionaryTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenDictionaryTests.cs @@ -419,6 +419,19 @@ public void ContainsKey_WithNonAscii(int percentageWithNonAscii) Assert.All(expected, kvp => actual.ContainsKey(kvp.Key)); } + + [Fact] + [OuterLoop] + public void ToFrozenDictionary_WithExtremelyLargeStrings() + { + // Test case with extremely large strings that exceed length bucket boundaries. + var keys = new[] { "", new string('a', 0X7FFFFFC7 / 4) }; + var frozen = keys.ToFrozenDictionary(s => s, GetKeyIEqualityComparer()); + Assert.Equal(keys.Length, frozen.Count); + Assert.Equal(keys.Length, frozen.Keys.Length); + Assert.Equal(keys.Length, frozen.Values.Length); + Assert.All(keys, key => frozen.ContainsKey(key)); + } } public class FrozenDictionary_Generic_Tests_string_string_Default : FrozenDictionary_Generic_Tests_string_string diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs index b07ef1c6e5d0bd..dc4e2b9e89dbee 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs @@ -143,9 +143,6 @@ public static IEnumerable StringStringData() => "X-MSEdge-Ref", "X-Powered-By", "X-Request-ID", "X-UA-Compatible", "X-XSS-Protection", }, - // Test case with extremely large strings that exceed length bucket boundaries. - new[] {"", new string('a', 0X7FFFFFC7 / 4) }, - // exercise left/right justified ordinal comparers Enumerable.Range(0, 10).Select(i => $"{i}ABCDEFGH").ToArray(), // left justified single char ascii Enumerable.Range(0, 10).Select(i => $"ABCDEFGH{i}").ToArray(), // right justified single char ascii diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs index 989292fe7b4f2d..644afae06b66d9 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs @@ -376,6 +376,18 @@ public void Contains_WithNonAscii(int percentageWithNonAscii) Assert.All(expected, s => actual.Contains(s)); } + + + [Fact] + [OuterLoop] + public void ToFrozenSet_WithExtremelyLargeStrings() + { + // Test case with extremely large strings that exceed length bucket boundaries. + var keys = new[] { "", new string('a', 0X7FFFFFC7 / 4) }; + var frozen = keys.ToFrozenSet(GetIEqualityComparer()); + Assert.Equal(2, frozen.Count); + Assert.All(keys, key => frozen.Contains(key)); + } } public class FrozenSet_Generic_Tests_string_Default : FrozenSet_Generic_Tests_string From ded2b748449fb85a881fee6a27d141d003c23b20 Mon Sep 17 00:00:00 2001 From: prozolic <42107886+prozolic@users.noreply.github.com> Date: Wed, 19 Nov 2025 01:00:28 +0900 Subject: [PATCH 4/4] Remove unused code and minor adjustments --- .../tests/Frozen/FrozenSetTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs index 644afae06b66d9..a021dc2353b285 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs @@ -377,7 +377,6 @@ public void Contains_WithNonAscii(int percentageWithNonAscii) Assert.All(expected, s => actual.Contains(s)); } - [Fact] [OuterLoop] public void ToFrozenSet_WithExtremelyLargeStrings() @@ -385,7 +384,7 @@ public void ToFrozenSet_WithExtremelyLargeStrings() // Test case with extremely large strings that exceed length bucket boundaries. var keys = new[] { "", new string('a', 0X7FFFFFC7 / 4) }; var frozen = keys.ToFrozenSet(GetIEqualityComparer()); - Assert.Equal(2, frozen.Count); + Assert.Equal(keys.Length, frozen.Count); Assert.All(keys, key => frozen.Contains(key)); } }