From df6da138702c76f19299627b1ee89837d16ed219 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 17 Apr 2021 20:08:56 +0800 Subject: [PATCH] Add Array.GetMaxLength (#43301) * Add Array.GetMaxLength implementation. * Implement Array.MaxLength and fix all remaining references * Apply suggestions from code review Co-authored-by: Jan Kotas * Apply suggestions from code review Co-authored-by: Jan Kotas * Update src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs Co-authored-by: Jan Kotas Co-authored-by: Stephen Toub Co-authored-by: Jan Kotas --- src/coreclr/vm/gchelpers.cpp | 20 +++++++++---------- .../src/System/Buffers/ArrayBufferWriter.cs | 10 +++++----- .../Collections/Generic/ArrayBuilder.cs | 5 ++--- .../Collections/Generic/EnumerableHelpers.cs | 19 ++++-------------- .../Concurrent/ConcurrentDictionary.cs | 5 ++--- .../src/System/Collections/SortedList.cs | 5 +---- .../tests/SortedListTests.cs | 2 +- .../Collections/Generic/PriorityQueue.cs | 6 ++---- .../src/System/Collections/Generic/Queue.cs | 6 ++---- .../System/Collections/Generic/SortedList.cs | 4 +--- .../src/System/Collections/Generic/Stack.cs | 7 ++----- .../List/List.Generic.Tests.EnsureCapacity.cs | 11 ++++++---- .../Generic/Queue/Queue.Generic.Tests.cs | 11 ++++++---- .../Generic/Stack/Stack.Generic.Tests.cs | 11 ++++++---- .../tests/Array/NewArrayBoundsTests.cs | 4 +--- .../ArrayBufferWriterTests.Byte.cs | 5 ++--- .../src/System/Array.cs | 19 ++++++++++-------- .../src/System/Collections/ArrayList.cs | 2 +- .../src/System/Collections/Generic/List.cs | 4 ++-- .../src/System/Collections/HashHelpers.cs | 4 ++-- .../src/System/IO/MemoryStream.cs | 6 +++--- .../src/System/Threading/ThreadLocal.cs | 6 +++--- .../Runtime/Serialization/ObjectToIdCache.cs | 6 ++++-- .../System.Runtime/ref/System.Runtime.cs | 1 + .../System.Runtime/tests/System/ArrayTests.cs | 6 ++++++ .../src/System/Text/Json/JsonHelpers.cs | 5 +---- src/tests/GC/Performance/Tests/Allocation.cs | 7 ++++--- 27 files changed, 93 insertions(+), 104 deletions(-) diff --git a/src/coreclr/vm/gchelpers.cpp b/src/coreclr/vm/gchelpers.cpp index 95fec0f878a962..74412a9e06d658 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 905e9f804abf6a..10bc644aecba0d 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 cb543f13d8cb6d..ec862f46d5c3f1 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 b5bebedb3af169..b8646e3e878a5d 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 66a6a7c1f9f968..af8f23a8254e33 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 709bf2d0c9d4cc..db5465c70f7e64 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 aa7c8f9ec1b17e..63959915e02b6b 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 ae4b3c34ff9d3a..a9fbb3f8375cae 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 c8c01ab965e2aa..efde02aeea3b4a 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 8a07c4188cb5f2..a7168b5ae0384c 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 b2ba4755958d3a..950c847e144af0 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 7eebfe146d4510..c643df16ef087b 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 2aa40a0549de35..802cfe4bd10b10 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 9f3f1f2dd2a7ed..65532202ba8878 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 738009f3897d5e..2464423bcc9cfc 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 c41ef5a54a136b..af3945f659a92c 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 85d353343aa6f4..ea0c6a71d74eaf 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 d20f81a798ebd1..cfacf96b1f1436 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 5bfc45d753b369..3dfd88fafc4d37 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 8bb7d8ff95dd67..a70b9bf5c0c897 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 8175eb9c74e23d..9677f82426a842 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 0da194e8ac1fa2..539ac4f9e9f5ab 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 38b9bc57e070f2..5648bf4734cedd 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 c418d29508a2ec..5f9294e647aa42 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 7f97f95f4507c7..9893cf2ffd501b 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 2b4062658c1926..a2a94f20607ce6 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 8de894da81b602..8e7385589ed1e4 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);