Skip to content

Commit

Permalink
ensure same algorithm is used for all resize operations
Browse files Browse the repository at this point in the history
  • Loading branch information
eiriktsarpalis committed Feb 18, 2021
1 parent 05aca89 commit 253bcdc
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,7 @@ public void Enqueue(T item)
{
if (_size == _array.Length)
{
int newcapacity = (int)(_array.Length * (long)GrowFactor / 100);
if (newcapacity < _array.Length + MinimumGrow)
{
newcapacity = _array.Length + MinimumGrow;
}
SetCapacity(newcapacity);
EnsureCapacityCore(_size + 1);
}

_array[_tail] = item;
Expand Down Expand Up @@ -388,7 +383,7 @@ public void TrimExcess()
/// <summary>
/// Ensures that the capacity of this Queue is at least the specified <paramref name="capacity"/>.
/// </summary>
/// <param name="capacity">The minimum capacity to ensure</param>
/// <param name="capacity">The minimum capacity to ensure.</param>
public int EnsureCapacity(int capacity)
{
if (capacity < 0)
Expand All @@ -398,28 +393,23 @@ public int EnsureCapacity(int capacity)

if (_array.Length < capacity)
{
long newCapacity = _array.Length == 0 ? MinimumGrow : _array.Length * (long)GrowFactor / 100;
EnsureCapacityCore(capacity);
}

while (newCapacity < capacity)
{
newCapacity = newCapacity * GrowFactor / 100;
}
return _array.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;
if (newCapacity > MaxArrayLength) // Exceeds MaxArrayLength which includes possibility of integer overflow during extending capacity
{
newCapacity = MaxArrayLength;
if (newCapacity < capacity) newCapacity = capacity;
}
private void EnsureCapacityCore(int capacity)
{
Debug.Assert(capacity > _array.Length);

SetCapacity((int)newCapacity);
}
int newcapacity = (int)((long)_array.Length * GrowFactor / 100);

return _array.Length;
// Ensure minimum growth and account for arithmetic overflow.
if (newcapacity < _array.Length + MinimumGrow) newcapacity = _array.Length + MinimumGrow;
if (newcapacity < capacity) newcapacity = capacity;

SetCapacity(newcapacity);
}

// Implements an enumerator for a Queue. The enumerator uses the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ public void Push(T item)
[MethodImpl(MethodImplOptions.NoInlining)]
private void PushWithResize(T item)
{
Array.Resize(ref _array, (_array.Length == 0) ? DefaultCapacity : 2 * _array.Length);
Debug.Assert(_size == _array.Length);
EnsureCapacityCore(_size + 1);
_array[_size] = item;
_version++;
_size++;
Expand All @@ -293,7 +294,7 @@ private void PushWithResize(T item)
/// If the current capacity of the Stack is less than specified <paramref name="capacity"/>,
/// the capacity is increased by continuously twice current capacity until it is at least the specified <paramref name="capacity"/>.
/// </summary>
/// <param name="capacity">The minimum capacity to ensure</param>
/// <param name="capacity">The minimum capacity to ensure.</param>
public int EnsureCapacity(int capacity)
{
if (capacity < 0)
Expand All @@ -303,30 +304,24 @@ public int EnsureCapacity(int capacity)

if (_array.Length < capacity)
{
int newCapacity = _array.Length == 0 ? DefaultCapacity : _array.Length << 1;
while ((uint)newCapacity < capacity)
{
newCapacity <<= 1;
}

// 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;
if ((uint)newCapacity > MaxArrayLength)
{
newCapacity = MaxArrayLength;
if (newCapacity < capacity) newCapacity = capacity;
}

Array.Resize(ref _array, newCapacity);
EnsureCapacityCore(capacity);
_version++;
}

return _array.Length;
}

private void EnsureCapacityCore(int capacity)
{
Debug.Assert(_array.Length < capacity);

int newcapacity = _array.Length == 0 ? DefaultCapacity : 2 * _array.Length;
// ensure min capacity is respected and account for arithmetic overflow
if (newcapacity < capacity) newcapacity = capacity;

Array.Resize(ref _array, newcapacity);
}

// Copies the Stack to an array, in the same order Pop would return the items.
public T[] ToArray()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ public void EnsureCapacity_NegativeCapacityRequested_Throws()
AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => list.EnsureCapacity(-1));
}

const int MaxArraySize = 0X7FEFFFFF;

[Theory]
[InlineData(5, MaxArraySize + 1)]
[InlineData(1, int.MaxValue)]
public void EnsureCapacity_LargeCapacity_Throws(int count, int requestCapacity)
{
List<T> list = GenericListFactory(count);
Assert.Throws<OutOfMemoryException>(() => list.EnsureCapacity(requestCapacity));
}

[Theory]
[InlineData(5)]
public void EnsureCapacity_RequestedCapacitySmallerThanOrEqualToCurrent_CapacityUnchanged(int currentCapacity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,17 @@ public void Queue_Generic_EnsureCapacity_NegativeCapacityRequested_Throws()
AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => queue.EnsureCapacity(-1));
}

const int MaxArraySize = 0X7FEFFFFF;

[Theory]
[InlineData(MaxArraySize + 1)]
[InlineData(int.MaxValue)]
public void Queue_Generic_EnsureCapacity_LargeCapacityRequested_Throws(int requestedCapacity)
{
var queue = GenericQueueFactory();
AssertExtensions.Throws<OutOfMemoryException>(() => queue.EnsureCapacity(requestedCapacity));
}

[Theory]
[InlineData(5)]
public void Queue_Generic_EnsureCapacity_RequestedCapacitySmallerThanOrEqualToCurrent_CapacityUnchanged(int currentCapacity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,17 @@ public void Stack_Generic_EnsureCapacity_NegativeCapacityRequested_Throws()
AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => stack.EnsureCapacity(-1));
}

const int MaxArraySize = 0X7FEFFFFF;

[Theory]
[InlineData(MaxArraySize + 1)]
[InlineData(int.MaxValue)]
public void Stack_Generic_EnsureCapacity_LargeCapacityRequested_Throws(int requestedCapacity)
{
var stack = GenericStackFactory();
AssertExtensions.Throws<OutOfMemoryException>(() => stack.EnsureCapacity(requestedCapacity));
}

[Theory]
[InlineData(5)]
public void Stack_Generic_EnsureCapacity_RequestedCapacitySmallerThanOrEqualToCurrent_CapacityUnchanged(int currentCapacity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,28 +413,21 @@ public int EnsureCapacity(int capacity)
}

/// <summary>
/// Increase the capacity of this list to at least the specified <paramref name="min"/> by continuously twice current capacity.
/// Increase the capacity of this list to at least the specified <paramref name="capacity"/> by continuously twice current capacity.
/// </summary>
/// <param name="min">The minimum capacity to ensure</param>
private void EnsureCapacityCore(int min)
/// <param name="capacity">The minimum capacity to ensure.</param>
private void EnsureCapacityCore(int capacity)
{
if (_items.Length < min)
{
int newCapacity = _items.Length == 0 ? DefaultCapacity : _items.Length << 1;
while ((uint)newCapacity < min)
{
newCapacity <<= 1;
}
Debug.Assert(_items.Length < 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 (newCapacity < min) newCapacity = min;
}
Capacity = newCapacity;
}
int newCapacity = _items.Length == 0 ? DefaultCapacity : 2 * _items.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 > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
if (newCapacity < capacity) newCapacity = capacity;

Capacity = newCapacity;
}

public bool Exists(Predicate<T> match)
Expand Down

0 comments on commit 253bcdc

Please sign in to comment.