diff --git a/src/coreclr/vm/gchelpers.cpp b/src/coreclr/vm/gchelpers.cpp index 95fec0f878a96..74412a9e06d65 100644 --- a/src/coreclr/vm/gchelpers.cpp +++ b/src/coreclr/vm/gchelpers.cpp @@ -342,13 +342,11 @@ void PublishObjectAndNotify(TObj* &orObject, GC_ALLOC_FLAGS flags) #endif // FEATURE_EVENT_TRACE } -inline SIZE_T MaxArrayLength(SIZE_T componentSize) +inline SIZE_T MaxArrayLength() { - // Impose limits on maximum array length in each dimension to allow efficient - // implementation of advanced range check elimination in future. We have to allow - // higher limit for array of bytes (or one byte structs) for backward compatibility. - // Keep in sync with Array.MaxArrayLength in BCL. - return (componentSize == 1) ? 0X7FFFFFC7 : 0X7FEFFFFF; + // Impose limits on maximum array length to prevent corner case integer overflow bugs + // Keep in sync with Array.MaxLength in BCL. + return 0X7FFFFFC7; } OBJECTREF AllocateSzArray(TypeHandle arrayType, INT32 cElements, GC_ALLOC_FLAGS flags) @@ -388,11 +386,11 @@ OBJECTREF AllocateSzArray(MethodTable* pArrayMT, INT32 cElements, GC_ALLOC_FLAGS if (cElements < 0) COMPlusThrow(kOverflowException); - SIZE_T componentSize = pArrayMT->GetComponentSize(); - if ((SIZE_T)cElements > MaxArrayLength(componentSize)) + if ((SIZE_T)cElements > MaxArrayLength()) ThrowOutOfMemoryDimensionsExceeded(); // Allocate the space from the GC heap + SIZE_T componentSize = pArrayMT->GetComponentSize(); #ifdef TARGET_64BIT // POSITIVE_INT32 * UINT16 + SMALL_CONST // this cannot overflow on 64bit @@ -568,7 +566,6 @@ OBJECTREF AllocateArrayEx(MethodTable *pArrayMT, INT32 *pArgs, DWORD dwNumArgs, // Calculate the total number of elements in the array UINT32 cElements; - SIZE_T componentSize = pArrayMT->GetComponentSize(); bool maxArrayDimensionLengthOverflow = false; bool providedLowerBounds = false; @@ -599,7 +596,7 @@ OBJECTREF AllocateArrayEx(MethodTable *pArrayMT, INT32 *pArgs, DWORD dwNumArgs, int length = pArgs[i]; if (length < 0) COMPlusThrow(kOverflowException); - if ((SIZE_T)length > MaxArrayLength(componentSize)) + if ((SIZE_T)length > MaxArrayLength()) maxArrayDimensionLengthOverflow = true; if ((length > 0) && (lowerBound + (length - 1) < lowerBound)) COMPlusThrow(kArgumentOutOfRangeException, W("ArgumentOutOfRange_ArrayLBAndLength")); @@ -615,7 +612,7 @@ OBJECTREF AllocateArrayEx(MethodTable *pArrayMT, INT32 *pArgs, DWORD dwNumArgs, int length = pArgs[0]; if (length < 0) COMPlusThrow(kOverflowException); - if ((SIZE_T)length > MaxArrayLength(componentSize)) + if ((SIZE_T)length > MaxArrayLength()) maxArrayDimensionLengthOverflow = true; cElements = length; } @@ -625,6 +622,7 @@ OBJECTREF AllocateArrayEx(MethodTable *pArrayMT, INT32 *pArgs, DWORD dwNumArgs, ThrowOutOfMemoryDimensionsExceeded(); // Allocate the space from the GC heap + SIZE_T componentSize = pArrayMT->GetComponentSize(); #ifdef TARGET_64BIT // POSITIVE_INT32 * UINT16 + SMALL_CONST // this cannot overflow on 64bit diff --git a/src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs b/src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs index 905e9f804abf6..10bc644aecba0 100644 --- a/src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs +++ b/src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs @@ -15,8 +15,8 @@ namespace System.Buffers #endif sealed class ArrayBufferWriter : IBufferWriter { - // Copy of Array.MaxArrayLength. For byte arrays the limit is slightly larger - private const int MaxArrayLength = 0X7FEFFFFF; + // Copy of Array.MaxLength. + private const int ArrayMaxLength = 0x7FFFFFC7; private const int DefaultInitialBufferSize = 256; @@ -184,16 +184,16 @@ private void CheckAndResizeBuffer(int sizeHint) if ((uint)newSize > int.MaxValue) { - // Attempt to grow to MaxArrayLength. + // Attempt to grow to ArrayMaxLength. uint needed = (uint)(currentLength - FreeCapacity + sizeHint); Debug.Assert(needed > currentLength); - if (needed > MaxArrayLength) + if (needed > ArrayMaxLength) { ThrowOutOfMemoryException(needed); } - newSize = MaxArrayLength; + newSize = ArrayMaxLength; } Array.Resize(ref _buffer, newSize); diff --git a/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs b/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs index cb543f13d8cb6..ec862f46d5c3f 100644 --- a/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs +++ b/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs @@ -12,7 +12,6 @@ namespace System.Collections.Generic internal struct ArrayBuilder { private const int DefaultCapacity = 4; - private const int MaxCoreClrArrayLength = 0x7fefffff; // For byte arrays the limit is slightly larger private T[]? _array; // Starts out null, initialized on first Add. private int _count; // Number of items into _array we're using. @@ -144,9 +143,9 @@ private void EnsureCapacity(int minimum) int capacity = Capacity; int nextCapacity = capacity == 0 ? DefaultCapacity : 2 * capacity; - if ((uint)nextCapacity > (uint)MaxCoreClrArrayLength) + if ((uint)nextCapacity > (uint)Array.MaxLength) { - nextCapacity = Math.Max(capacity + 1, MaxCoreClrArrayLength); + nextCapacity = Math.Max(capacity + 1, Array.MaxLength); } nextCapacity = Math.Max(nextCapacity, minimum); diff --git a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs index b5bebedb3af16..b8646e3e878a5 100644 --- a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs +++ b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.cs @@ -49,27 +49,16 @@ internal static T[] ToArray(IEnumerable source, out int length) { if (count == arr.Length) { - // MaxArrayLength is defined in Array.MaxArrayLength and in gchelpers in CoreCLR. - // It represents the maximum number of elements that can be in an array where - // the size of the element is greater than one byte; a separate, slightly larger constant, - // is used when the size of the element is one. - const int MaxArrayLength = 0x7FEFFFFF; - // This is the same growth logic as in List: // If the array is currently empty, we make it a default size. Otherwise, we attempt to // double the size of the array. Doubling will overflow once the size of the array reaches // 2^30, since doubling to 2^31 is 1 larger than Int32.MaxValue. In that case, we instead - // constrain the length to be MaxArrayLength (this overflow check works because of the - // cast to uint). Because a slightly larger constant is used when T is one byte in size, we - // could then end up in a situation where arr.Length is MaxArrayLength or slightly larger, such - // that we constrain newLength to be MaxArrayLength but the needed number of elements is actually - // larger than that. For that case, we then ensure that the newLength is large enough to hold - // the desired capacity. This does mean that in the very rare case where we've grown to such a - // large size, each new element added after MaxArrayLength will end up doing a resize. + // constrain the length to be Array.MaxLength (this overflow check works because of the + // cast to uint). int newLength = count << 1; - if ((uint)newLength > MaxArrayLength) + if ((uint)newLength > Array.MaxLength) { - newLength = MaxArrayLength <= count ? count + 1 : MaxArrayLength; + newLength = Array.MaxLength <= count ? count + 1 : Array.MaxLength; } Array.Resize(ref arr, newLength); diff --git a/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs b/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs index 66a6a7c1f9f96..af8f23a8254e3 100644 --- a/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs +++ b/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs @@ -1908,7 +1908,6 @@ private bool AreAllBucketsEmpty() /// private void GrowTable(Tables tables) { - const int MaxArrayLength = 0X7FEFFFFF; int locksAcquired = 0; try { @@ -1964,7 +1963,7 @@ private void GrowTable(Tables tables) Debug.Assert(newLength % 2 != 0); - if (newLength > MaxArrayLength) + if (newLength > Array.MaxLength) { maximizeTableSize = true; } @@ -1977,7 +1976,7 @@ private void GrowTable(Tables tables) if (maximizeTableSize) { - newLength = MaxArrayLength; + newLength = Array.MaxLength; // We want to make sure that GrowTable will not be called again, since table is at the maximum size. // To achieve that, we set the budget to int.MaxValue. diff --git a/src/libraries/System.Collections.NonGeneric/src/System/Collections/SortedList.cs b/src/libraries/System.Collections.NonGeneric/src/System/Collections/SortedList.cs index 709bf2d0c9d4c..db5465c70f7e6 100644 --- a/src/libraries/System.Collections.NonGeneric/src/System/Collections/SortedList.cs +++ b/src/libraries/System.Collections.NonGeneric/src/System/Collections/SortedList.cs @@ -70,9 +70,6 @@ public class SortedList : IDictionary, ICloneable private KeyList? keyList; // Do not rename (binary serialization) private ValueList? valueList; // Do not rename (binary serialization) - // Copy of Array.MaxArrayLength - internal const int MaxArrayLength = 0X7FEFFFFF; - // Constructs a new sorted list. The sorted list is initially empty and has // a capacity of zero. Upon adding the first element to the sorted list the // capacity is increased to 16, and then increased in multiples of two as @@ -378,7 +375,7 @@ private void EnsureCapacity(int min) int newCapacity = keys.Length == 0 ? 16 : keys.Length * 2; // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. // Note that this check works even when _items.Length overflowed thanks to the (uint) cast - if ((uint)newCapacity > MaxArrayLength) newCapacity = MaxArrayLength; + if ((uint)newCapacity > Array.MaxLength) newCapacity = Array.MaxLength; if (newCapacity < min) newCapacity = min; Capacity = newCapacity; } diff --git a/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs b/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs index aa7c8f9ec1b17..63959915e02b6 100644 --- a/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs +++ b/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs @@ -255,7 +255,7 @@ public void DebuggerAttribute_NullSortedList_ThrowsArgumentNullException() } [Fact] - public void EnsureCapacity_NewCapacityLessThanMin_CapsToMaxArrayLength() + public void EnsureCapacity_NewCapacityLessThanMin_CapsToArrayMaxLength() { // A situation like this occurs for very large lengths of SortedList. // To avoid allocating several GBs of memory and making this test run for a very diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs b/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs index ae4b3c34ff9d3..a9fbb3f8375ca 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs @@ -529,8 +529,6 @@ private void Grow(int minCapacity) { Debug.Assert(_nodes.Length < minCapacity); - // Array.MaxArrayLength is internal to S.P.CoreLib, replicate here. - const int MaxArrayLength = 0X7FEFFFFF; const int GrowFactor = 2; const int MinimumGrow = 4; @@ -538,13 +536,13 @@ private void Grow(int minCapacity) // Allow the queue to grow to maximum possible capacity (~2G elements) before encountering overflow. // Note that this check works even when _nodes.Length overflowed thanks to the (uint) cast - if ((uint)newcapacity > MaxArrayLength) newcapacity = MaxArrayLength; + if ((uint)newcapacity > Array.MaxLength) newcapacity = Array.MaxLength; // Ensure minimum growth is respected. newcapacity = Math.Max(newcapacity, _nodes.Length + MinimumGrow); // If the computed capacity is still less than specified, set to the original argument. - // Capacities exceeding MaxArrayLength will be surfaced as OutOfMemoryException by Array.Resize. + // Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize. if (newcapacity < minCapacity) newcapacity = minCapacity; Array.Resize(ref _nodes, newcapacity); diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/Queue.cs b/src/libraries/System.Collections/src/System/Collections/Generic/Queue.cs index c8c01ab965e2a..efde02aeea3b4 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/Queue.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/Queue.cs @@ -400,8 +400,6 @@ private void Grow(int capacity) { Debug.Assert(_array.Length < capacity); - // Array.MaxArrayLength is internal to S.P.CoreLib, replicate here. - const int MaxArrayLength = 0X7FEFFFFF; const int GrowFactor = 2; const int MinimumGrow = 4; @@ -409,13 +407,13 @@ private void Grow(int capacity) // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. // Note that this check works even when _items.Length overflowed thanks to the (uint) cast - if ((uint)newcapacity > MaxArrayLength) newcapacity = MaxArrayLength; + if ((uint)newcapacity > Array.MaxLength) newcapacity = Array.MaxLength; // Ensure minimum growth is respected. newcapacity = Math.Max(newcapacity, _array.Length + MinimumGrow); // If the computed capacity is still less than specified, set to the original argument. - // Capacities exceeding MaxArrayLength will be surfaced as OutOfMemoryException by Array.Resize. + // Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize. if (newcapacity < capacity) newcapacity = capacity; SetCapacity(newcapacity); diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/SortedList.cs b/src/libraries/System.Collections/src/System/Collections/Generic/SortedList.cs index 8a07c4188cb5f..a7168b5ae0384 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/SortedList.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/SortedList.cs @@ -522,8 +522,6 @@ void ICollection.CopyTo(Array array, int index) } } - private const int MaxArrayLength = 0X7FEFFFFF; - // Ensures that the capacity of this sorted list is at least the given // minimum value. The capacity is increased to twice the current capacity // or to min, whichever is larger. @@ -532,7 +530,7 @@ private void EnsureCapacity(int min) int newCapacity = keys.Length == 0 ? DefaultCapacity : keys.Length * 2; // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. // Note that this check works even when _items.Length overflowed thanks to the (uint) cast - if ((uint)newCapacity > MaxArrayLength) newCapacity = MaxArrayLength; + if ((uint)newCapacity > Array.MaxLength) newCapacity = Array.MaxLength; if (newCapacity < min) newCapacity = min; Capacity = newCapacity; } diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs b/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs index b2ba4755958d3..950c847e144af 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs @@ -315,17 +315,14 @@ private void Grow(int capacity) { Debug.Assert(_array.Length < capacity); - // Array.MaxArrayLength is internal to S.P.CoreLib, replicate here. - const int MaxArrayLength = 0X7FEFFFFF; - int newcapacity = _array.Length == 0 ? DefaultCapacity : 2 * _array.Length; // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. // Note that this check works even when _items.Length overflowed thanks to the (uint) cast. - if ((uint)newcapacity > MaxArrayLength) newcapacity = MaxArrayLength; + if ((uint)newcapacity > Array.MaxLength) newcapacity = Array.MaxLength; // If computed capacity is still less than specified, set to the original argument. - // Capacities exceeding MaxArrayLength will be surfaced as OutOfMemoryException by Array.Resize. + // Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize. if (newcapacity < capacity) newcapacity = capacity; Array.Resize(ref _array, newcapacity); diff --git a/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.EnsureCapacity.cs b/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.EnsureCapacity.cs index 7eebfe146d451..c643df16ef087 100644 --- a/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.EnsureCapacity.cs +++ b/src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.EnsureCapacity.cs @@ -41,12 +41,15 @@ public void EnsureCapacity_NegativeCapacityRequested_Throws() AssertExtensions.Throws("capacity", () => list.EnsureCapacity(-1)); } - const int MaxArraySize = 0X7FEFFFFF; + public static IEnumerable EnsureCapacity_LargeCapacity_Throws_MemberData() + { + yield return new object[] { 5, Array.MaxLength + 1 }; + yield return new object[] { 1, int.MaxValue }; + } [Theory] - [InlineData(5, MaxArraySize + 1)] - [InlineData(1, int.MaxValue)] - [SkipOnMono("mono forces no restrictions on array size.")] + [MemberData(nameof(EnsureCapacity_LargeCapacity_Throws_MemberData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/51411", TestRuntimes.Mono)] public void EnsureCapacity_LargeCapacity_Throws(int count, int requestCapacity) { List list = GenericListFactory(count); diff --git a/src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs index 2aa40a0549de3..802cfe4bd10b1 100644 --- a/src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs @@ -348,12 +348,15 @@ public void Queue_Generic_EnsureCapacity_NegativeCapacityRequested_Throws() AssertExtensions.Throws("capacity", () => queue.EnsureCapacity(-1)); } - const int MaxArraySize = 0X7FEFFFFF; + public static IEnumerable Queue_Generic_EnsureCapacity_LargeCapacityRequested_Throws_MemberData() + { + yield return new object[] { Array.MaxLength + 1 }; + yield return new object[] { int.MaxValue }; + } [Theory] - [InlineData(MaxArraySize + 1)] - [InlineData(int.MaxValue)] - [SkipOnMono("mono forces no restrictions on array size.")] + [MemberData(nameof(Queue_Generic_EnsureCapacity_LargeCapacityRequested_Throws_MemberData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/51411", TestRuntimes.Mono)] public void Queue_Generic_EnsureCapacity_LargeCapacityRequested_Throws(int requestedCapacity) { var queue = GenericQueueFactory(); diff --git a/src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs index 9f3f1f2dd2a7e..65532202ba887 100644 --- a/src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs @@ -314,12 +314,15 @@ public void Stack_Generic_EnsureCapacity_NegativeCapacityRequested_Throws() AssertExtensions.Throws("capacity", () => stack.EnsureCapacity(-1)); } - const int MaxArraySize = 0X7FEFFFFF; + public static IEnumerable Stack_Generic_EnsureCapacity_LargeCapacityRequested_Throws_MemberData() + { + yield return new object[] { Array.MaxLength + 1 }; + yield return new object[] { int.MaxValue }; + } [Theory] - [InlineData(MaxArraySize + 1)] - [InlineData(int.MaxValue)] - [SkipOnMono("mono forces no restrictions on array size.")] + [MemberData(nameof(Stack_Generic_EnsureCapacity_LargeCapacityRequested_Throws_MemberData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/51411", TestRuntimes.Mono)] public void Stack_Generic_EnsureCapacity_LargeCapacityRequested_Throws(int requestedCapacity) { var stack = GenericStackFactory(); diff --git a/src/libraries/System.Linq.Expressions/tests/Array/NewArrayBoundsTests.cs b/src/libraries/System.Linq.Expressions/tests/Array/NewArrayBoundsTests.cs index 738009f3897d5..2464423bcc9cf 100644 --- a/src/libraries/System.Linq.Expressions/tests/Array/NewArrayBoundsTests.cs +++ b/src/libraries/System.Linq.Expressions/tests/Array/NewArrayBoundsTests.cs @@ -10,8 +10,6 @@ namespace System.Linq.Expressions.Tests { public static class ArrayBoundsTests { - private const int MaxArraySize = 0X7FEFFFFF; - private class BogusCollection : IList { public T this[int index] @@ -168,7 +166,7 @@ private static void VerifyArrayGenerator(Func func, Type arrayType, long? { Assert.Throws(() => func()); } - else if (size > MaxArraySize) + else if (size > Array.MaxLength) { Assert.Throws(() => func()); } diff --git a/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs b/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs index c41ef5a54a136..af3945f659a92 100644 --- a/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs +++ b/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs @@ -98,7 +98,6 @@ public async Task WriteAndCopyToStreamAsync() [OuterLoop] public void GetMemory_ExceedMaximumBufferSize() { - const int MaxArrayLength = 0X7FEFFFFF; int initialCapacity = int.MaxValue / 2 + 1; try @@ -111,8 +110,8 @@ public void GetMemory_ExceedMaximumBufferSize() memory = output.GetMemory(1); // The buffer should grow more than the 1 byte requested otherwise performance will not be usable - // between 1GB and 2GB. The current implementation maxes out the buffer size to MaxArrayLength. - Assert.Equal(MaxArrayLength - initialCapacity, memory.Length); + // between 1GB and 2GB. The current implementation maxes out the buffer size to Array.MaxLength. + Assert.Equal(Array.MaxLength - initialCapacity, memory.Length); Assert.Throws(() => output.GetMemory(int.MaxValue)); } catch (OutOfMemoryException) diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 85d353343aa6f..ea0c6a71d74ea 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -18,14 +18,6 @@ namespace System [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] public abstract partial class Array : ICloneable, IList, IStructuralComparable, IStructuralEquatable { - // We impose limits on maximum array length in each dimension to allow efficient - // implementation of advanced range check elimination in future. - // Keep in sync with vm\gcscan.cpp and HashHelpers.MaxPrimeArrayLength. - // The constants are defined in this method: inline SIZE_T MaxArrayLength(SIZE_T componentSize) from gcscan - // We have different max sizes for arrays with elements of size 1 for backwards compatibility - internal const int MaxArrayLength = 0X7FEFFFFF; - internal const int MaxByteArrayLength = 0x7FFFFFC7; - // This is the threshold where Introspective sort switches to Insertion sort. // Empirically, 16 seems to speed up most cases without slowing down others, at least for integers. // Large value types may benefit from a smaller number. @@ -1872,6 +1864,17 @@ public static bool TrueForAll(T[] array, Predicate match) return true; } + /// Gets the maximum number of elements that may be contained in an array. + /// The maximum count of elements allowed in any array. + /// + /// This property represents a runtime limitation, the maximum number of elements (not bytes) + /// the runtime will allow in an array. There is no guarantee that an allocation under this length + /// will succeed, but all attempts to allocate a larger array will fail. + /// + public static int MaxLength => + // Keep in sync with `inline SIZE_T MaxArrayLength()` from gchelpers and HashHelpers.MaxPrimeArrayLength. + 0X7FFFFFC7; + // Private value type used by the Sort methods. private readonly struct SorterObjectArray { diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/ArrayList.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/ArrayList.cs index d20f81a798ebd..cfacf96b1f143 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/ArrayList.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/ArrayList.cs @@ -314,7 +314,7 @@ private void EnsureCapacity(int min) int newCapacity = _items.Length == 0 ? _defaultCapacity : _items.Length * 2; // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. // Note that this check works even when _items.Length overflowed thanks to the (uint) cast - if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if ((uint)newCapacity > Array.MaxLength) newCapacity = Array.MaxLength; if (newCapacity < min) newCapacity = min; Capacity = newCapacity; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs index 5bfc45d753b36..3dfd88fafc4d3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs @@ -425,10 +425,10 @@ private void Grow(int capacity) // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. // Note that this check works even when _items.Length overflowed thanks to the (uint) cast - if ((uint)newcapacity > Array.MaxArrayLength) newcapacity = Array.MaxArrayLength; + if ((uint)newcapacity > Array.MaxLength) newcapacity = Array.MaxLength; // If the computed capacity is still less than specified, set to the original argument. - // Capacities exceeding MaxArrayLength will be surfaced as OutOfMemoryException by Array.Resize. + // Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize. if (newcapacity < capacity) newcapacity = capacity; Capacity = newcapacity; diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/HashHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/HashHelpers.cs index 8bb7d8ff95dd6..a70b9bf5c0c89 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/HashHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/HashHelpers.cs @@ -10,8 +10,8 @@ internal static partial class HashHelpers { public const uint HashCollisionThreshold = 100; - // This is the maximum prime smaller than Array.MaxArrayLength - public const int MaxPrimeArrayLength = 0x7FEFFFFD; + // This is the maximum prime smaller than Array.MaxLength. + public const int MaxPrimeArrayLength = 0x7FFFFFC3; public const int HashPrime = 101; diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs index 8175eb9c74e23..9677f82426a84 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs @@ -155,11 +155,11 @@ private bool EnsureCapacity(int value) newCapacity = _capacity * 2; } - // We want to expand the array up to Array.MaxByteArrayLength + // We want to expand the array up to Array.MaxLength. // And we want to give the user the value that they asked for - if ((uint)(_capacity * 2) > Array.MaxByteArrayLength) + if ((uint)(_capacity * 2) > Array.MaxLength) { - newCapacity = Math.Max(value, Array.MaxByteArrayLength); + newCapacity = Math.Max(value, Array.MaxLength); } Capacity = newCapacity; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadLocal.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadLocal.cs index 0da194e8ac1fa..539ac4f9e9f5a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadLocal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadLocal.cs @@ -588,7 +588,7 @@ private static void GrowTable(ref LinkedSlotVolatile[] table, int minLength) /// private static int GetNewTableSize(int minSize) { - if ((uint)minSize > Array.MaxArrayLength) + if ((uint)minSize > Array.MaxLength) { // Intentionally return a value that will result in an OutOfMemoryException return int.MaxValue; @@ -623,9 +623,9 @@ private static int GetNewTableSize(int minSize) newSize++; // Don't set newSize to more than Array.MaxArrayLength - if ((uint)newSize > Array.MaxArrayLength) + if ((uint)newSize > Array.MaxLength) { - newSize = Array.MaxArrayLength; + newSize = Array.MaxLength; } return newSize; diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/ObjectToIdCache.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/ObjectToIdCache.cs index 38b9bc57e070f..5648bf4734ced 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/ObjectToIdCache.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/ObjectToIdCache.cs @@ -175,8 +175,10 @@ private static int GetPrime(int min) 3, 7, 17, 37, 89, 197, 431, 919, 1931, 4049, 8419, 17519, 36353, 75431, 156437, 324449, 672827, 1395263, 2893249, 5999471, 11998949, 23997907, 47995853, 95991737, 191983481, 383966977, 767933981, 1535867969, - 2146435069, 0X7FEFFFFF - // 0X7FEFFFFF is not prime, but it is the largest possible array size. There's nowhere to go from here. + 2146435069, 0x7FFFFFC7 + // 0x7FFFFFC7 == Array.MaxLength is not prime, but it is the largest possible array size. + // There's nowhere to go from here. Using a const rather than the MaxLength property + // so that the array contains only const values. }; } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index c418d29508a2e..5f9294e647aa4 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -305,6 +305,7 @@ internal Array() { } public bool IsSynchronized { get { throw null; } } public int Length { get { throw null; } } public long LongLength { get { throw null; } } + public static int MaxLength { get { throw null; } } public int Rank { get { throw null; } } public object SyncRoot { get { throw null; } } int System.Collections.ICollection.Count { get { throw null; } } diff --git a/src/libraries/System.Runtime/tests/System/ArrayTests.cs b/src/libraries/System.Runtime/tests/System/ArrayTests.cs index 7f97f95f4507c..9893cf2ffd501 100644 --- a/src/libraries/System.Runtime/tests/System/ArrayTests.cs +++ b/src/libraries/System.Runtime/tests/System/ArrayTests.cs @@ -4322,6 +4322,12 @@ public static void Reverse_NonSZArrayWithMinValueLowerBound() Reverse(array, int.MinValue, 2, new int[] { 2, 1, 3 }); } + [Fact] + public static void MaxSizes() + { + Assert.Equal(0x7FFFFFC7, Array.MaxLength); + } + private static void VerifyArray(Array array, Type elementType, int[] lengths, int[] lowerBounds, object repeatedValue) { VerifyArray(array, elementType, lengths, lowerBounds); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs index 2b4062658c192..a2a94f20607ce 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs @@ -12,9 +12,6 @@ namespace System.Text.Json { internal static partial class JsonHelpers { - // Copy of Array.MaxArrayLength. For byte arrays the limit is slightly larger - private const int MaxArrayLength = 0X7FEFFFFF; - // Members accessed by the serializer when deserializing. public const DynamicallyAccessedMemberTypes MembersAccessedOnRead = DynamicallyAccessedMemberTypes.PublicConstructors | @@ -157,7 +154,7 @@ public static bool IsValidNumberHandlingValue(JsonNumberHandling handling) => [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ValidateInt32MaxArrayLength(uint length) { - if (length > MaxArrayLength) + if (length > 0X7FEFFFFF) // prior to .NET 6, max array length for sizeof(T) != 1 (size == 1 is larger) { ThrowHelper.ThrowOutOfMemoryException(length); } diff --git a/src/tests/GC/Performance/Tests/Allocation.cs b/src/tests/GC/Performance/Tests/Allocation.cs index 8de894da81b60..8e7385589ed1e 100644 --- a/src/tests/GC/Performance/Tests/Allocation.cs +++ b/src/tests/GC/Performance/Tests/Allocation.cs @@ -55,11 +55,12 @@ static void Main(string[] args) UInt64 objCount = MaxBytes / (UInt64)byteArraySize; Console.WriteLine("Creating a list of {0} objects", objCount); - if (objCount > 0X7FEFFFFF) + UInt64 maxArrayLength = (UInt64)Array.MaxLength; + if (objCount > maxArrayLength) { Console.WriteLine("Exceeded the max number of objects in a list"); - Console.WriteLine("Creating a list with {0} objects", 0X7FEFFFFF); - objCount = 0X7FEFFFFF; + Console.WriteLine("Creating a list with {0} objects", maxArrayLength); + objCount = maxArrayLength; } Console.WriteLine("Byte array size is " + byteArraySize);