diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenHashTable.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenHashTable.cs index 2bda7b2c76e70a..533d3bde2ad0bf 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenHashTable.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenHashTable.cs @@ -56,6 +56,16 @@ public static FrozenHashTable Create(Span hashCodes, bool hashCodesAreUniqu // - bucketStarts: initially filled with all -1s, the ith element stores the index // into hashCodes of the head element of that bucket's chain. // - nexts: the ith element stores the index of the next item in the chain. + // Use long to check for overflow before allocating - very large collections can overflow int. +#if NET + if ((long)numBuckets + hashCodes.Length > Array.MaxLength) +#else + if ((long)numBuckets + hashCodes.Length > 0x7FFFFFC7) +#endif + { + throw new OutOfMemoryException(); + } + int[] arrayPoolBuckets = ArrayPool.Shared.Rent(numBuckets + hashCodes.Length); Span bucketStarts = arrayPoolBuckets.AsSpan(0, numBuckets); Span nexts = arrayPoolBuckets.AsSpan(numBuckets, hashCodes.Length); @@ -174,7 +184,20 @@ private static int CalcNumBuckets(ReadOnlySpan hashCodes, bool hashCodesAre // Based on our observations, in more than 99.5% of cases the number of buckets that meets our criteria is // at least twice as big as the number of unique hash codes. - int minNumBuckets = uniqueCodesCount * 2; + // Use long to avoid integer overflow when uniqueCodesCount is large (> ~1 billion). + long minNumBuckets = (long)uniqueCodesCount * 2; + + // If the minimum bucket count combined with hash codes exceeds array length limits, + // skip the expensive collision-counting loop below — any bucket count it finds + // would cause Create to fail. Fall back to the next prime above uniqueCodesCount. +#if NET + if (minNumBuckets + hashCodes.Length > Array.MaxLength) +#else + if (minNumBuckets + hashCodes.Length > 0x7FFFFFC7) +#endif + { + return HashHelpers.GetPrime(uniqueCodesCount); + } // In our precomputed primes table, find the index of the smallest prime that's at least as large as our number of // hash codes. If there are more codes than in our precomputed primes table, which accommodates millions of values, diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenDictionaryTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenDictionaryTests.cs index 14c967ce573098..7b1c22d9f2f7b7 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenDictionaryTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenDictionaryTests.cs @@ -516,7 +516,7 @@ protected static long NextLong(Random random) public class FrozenDictionary_Generic_Tests_int_int : FrozenDictionary_Generic_Tests_base_for_numbers { - protected override int Next(Random random) => random.Next(); + protected override int Next(Random random) => random.Next(); } public class FrozenDictionary_Generic_Tests_uint_uint : FrozenDictionary_Generic_Tests_base_for_numbers diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs index a021dc2353b285..1768b55d9aab08 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs @@ -434,6 +434,14 @@ protected override ulong CreateT(int seed) ulong lo = unchecked((ulong)rand.Next()); return (hi << 32) | lo; } + + [OuterLoop("Takes several seconds")] + [Theory] + [InlineData(8_000_000)] + public void CreateHugeSet_Success(int largeCount) + { + GenericISetFactory(largeCount); + } } public class FrozenSet_Generic_Tests_int : FrozenSet_Generic_Tests