Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Array.GetMaxLength<T> #43301

Merged
merged 5 commits into from
Apr 17, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions src/coreclr/vm/gchelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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"));
Expand All @@ -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;
}
Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ namespace System.Buffers
#endif
sealed class ArrayBufferWriter<T> : IBufferWriter<T>
{
// 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;

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ namespace System.Collections.Generic
internal struct ArrayBuilder<T>
{
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.
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,27 +49,16 @@ internal static T[] ToArray<T>(IEnumerable<T> 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<T>:
// 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1908,7 +1908,6 @@ private bool AreAllBucketsEmpty()
/// </summary>
private void GrowTable(Tables tables)
{
const int MaxArrayLength = 0X7FEFFFFF;
int locksAcquired = 0;
try
{
Expand Down Expand Up @@ -1964,7 +1963,7 @@ private void GrowTable(Tables tables)

Debug.Assert(newLength % 2 != 0);

if (newLength > MaxArrayLength)
if (newLength > Array.MaxLength)
{
maximizeTableSize = true;
}
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -529,22 +529,20 @@ 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;

int newcapacity = GrowFactor * _nodes.Length;

// 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,22 +400,20 @@ 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;

int newcapacity = GrowFactor * _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;

// 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@ public void EnsureCapacity_NegativeCapacityRequested_Throws()
AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => list.EnsureCapacity(-1));
}

const int MaxArraySize = 0X7FEFFFFF;
public static IEnumerable<object[]> 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)]
[MemberData(nameof(EnsureCapacity_LargeCapacity_Throws_MemberData))]
[SkipOnMono("mono forces no restrictions on array size.")]
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
public void EnsureCapacity_LargeCapacity_Throws(int count, int requestCapacity)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,11 +348,14 @@ public void Queue_Generic_EnsureCapacity_NegativeCapacityRequested_Throws()
AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => queue.EnsureCapacity(-1));
}

const int MaxArraySize = 0X7FEFFFFF;
public static IEnumerable<object[]> 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)]
[MemberData(nameof(Queue_Generic_EnsureCapacity_LargeCapacityRequested_Throws_MemberData))]
[SkipOnMono("mono forces no restrictions on array size.")]
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
public void Queue_Generic_EnsureCapacity_LargeCapacityRequested_Throws(int requestedCapacity)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,11 +314,14 @@ public void Stack_Generic_EnsureCapacity_NegativeCapacityRequested_Throws()
AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => stack.EnsureCapacity(-1));
}

const int MaxArraySize = 0X7FEFFFFF;
public static IEnumerable<object[]> 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)]
[MemberData(nameof(Stack_Generic_EnsureCapacity_LargeCapacityRequested_Throws_MemberData))]
[SkipOnMono("mono forces no restrictions on array size.")]
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
public void Stack_Generic_EnsureCapacity_LargeCapacityRequested_Throws(int requestedCapacity)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ namespace System.Linq.Expressions.Tests
{
public static class ArrayBoundsTests
{
private const int MaxArraySize = 0X7FEFFFFF;

private class BogusCollection<T> : IList<T>
{
public T this[int index]
Expand Down Expand Up @@ -168,7 +166,7 @@ private static void VerifyArrayGenerator(Func<Array> func, Type arrayType, long?
{
Assert.Throws<OverflowException>(() => func());
}
else if (size > MaxArraySize)
else if (size > Array.MaxLength)
{
Assert.Throws<OutOfMemoryException>(() => func());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ public async Task WriteAndCopyToStreamAsync()
[OuterLoop]
public void GetMemory_ExceedMaximumBufferSize()
{
const int MaxArrayLength = 0X7FEFFFFF;
int initialCapacity = int.MaxValue / 2 + 1;

try
Expand All @@ -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<OutOfMemoryException>(() => output.GetMemory(int.MaxValue));
}
catch (OutOfMemoryException)
Expand Down
Loading