Skip to content

Commit 253bcdc

Browse files
ensure same algorithm is used for all resize operations
1 parent 05aca89 commit 253bcdc

File tree

6 files changed

+75
-64
lines changed

6 files changed

+75
-64
lines changed

src/libraries/System.Collections/src/System/Collections/Generic/Queue.cs

+15-25
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,7 @@ public void Enqueue(T item)
183183
{
184184
if (_size == _array.Length)
185185
{
186-
int newcapacity = (int)(_array.Length * (long)GrowFactor / 100);
187-
if (newcapacity < _array.Length + MinimumGrow)
188-
{
189-
newcapacity = _array.Length + MinimumGrow;
190-
}
191-
SetCapacity(newcapacity);
186+
EnsureCapacityCore(_size + 1);
192187
}
193188

194189
_array[_tail] = item;
@@ -388,7 +383,7 @@ public void TrimExcess()
388383
/// <summary>
389384
/// Ensures that the capacity of this Queue is at least the specified <paramref name="capacity"/>.
390385
/// </summary>
391-
/// <param name="capacity">The minimum capacity to ensure</param>
386+
/// <param name="capacity">The minimum capacity to ensure.</param>
392387
public int EnsureCapacity(int capacity)
393388
{
394389
if (capacity < 0)
@@ -398,28 +393,23 @@ public int EnsureCapacity(int capacity)
398393

399394
if (_array.Length < capacity)
400395
{
401-
long newCapacity = _array.Length == 0 ? MinimumGrow : _array.Length * (long)GrowFactor / 100;
396+
EnsureCapacityCore(capacity);
397+
}
402398

403-
while (newCapacity < capacity)
404-
{
405-
newCapacity = newCapacity * GrowFactor / 100;
406-
}
399+
return _array.Length;
400+
}
407401

408-
// MaxArrayLength is defined in Array.MaxArrayLength and in gchelpers in CoreCLR.
409-
// It represents the maximum number of elements that can be in an array where
410-
// the size of the element is greater than one byte; a separate, slightly larger constant,
411-
// is used when the size of the element is one.
412-
const int MaxArrayLength = 0x7FEFFFFF;
413-
if (newCapacity > MaxArrayLength) // Exceeds MaxArrayLength which includes possibility of integer overflow during extending capacity
414-
{
415-
newCapacity = MaxArrayLength;
416-
if (newCapacity < capacity) newCapacity = capacity;
417-
}
402+
private void EnsureCapacityCore(int capacity)
403+
{
404+
Debug.Assert(capacity > _array.Length);
418405

419-
SetCapacity((int)newCapacity);
420-
}
406+
int newcapacity = (int)((long)_array.Length * GrowFactor / 100);
421407

422-
return _array.Length;
408+
// Ensure minimum growth and account for arithmetic overflow.
409+
if (newcapacity < _array.Length + MinimumGrow) newcapacity = _array.Length + MinimumGrow;
410+
if (newcapacity < capacity) newcapacity = capacity;
411+
412+
SetCapacity(newcapacity);
423413
}
424414

425415
// Implements an enumerator for a Queue. The enumerator uses the

src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs

+15-20
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,8 @@ public void Push(T item)
282282
[MethodImpl(MethodImplOptions.NoInlining)]
283283
private void PushWithResize(T item)
284284
{
285-
Array.Resize(ref _array, (_array.Length == 0) ? DefaultCapacity : 2 * _array.Length);
285+
Debug.Assert(_size == _array.Length);
286+
EnsureCapacityCore(_size + 1);
286287
_array[_size] = item;
287288
_version++;
288289
_size++;
@@ -293,7 +294,7 @@ private void PushWithResize(T item)
293294
/// If the current capacity of the Stack is less than specified <paramref name="capacity"/>,
294295
/// the capacity is increased by continuously twice current capacity until it is at least the specified <paramref name="capacity"/>.
295296
/// </summary>
296-
/// <param name="capacity">The minimum capacity to ensure</param>
297+
/// <param name="capacity">The minimum capacity to ensure.</param>
297298
public int EnsureCapacity(int capacity)
298299
{
299300
if (capacity < 0)
@@ -303,30 +304,24 @@ public int EnsureCapacity(int capacity)
303304

304305
if (_array.Length < capacity)
305306
{
306-
int newCapacity = _array.Length == 0 ? DefaultCapacity : _array.Length << 1;
307-
while ((uint)newCapacity < capacity)
308-
{
309-
newCapacity <<= 1;
310-
}
311-
312-
// MaxArrayLength is defined in Array.MaxArrayLength and in gchelpers in CoreCLR.
313-
// It represents the maximum number of elements that can be in an array where
314-
// the size of the element is greater than one byte; a separate, slightly larger constant,
315-
// is used when the size of the element is one.
316-
const int MaxArrayLength = 0x7FEFFFFF;
317-
if ((uint)newCapacity > MaxArrayLength)
318-
{
319-
newCapacity = MaxArrayLength;
320-
if (newCapacity < capacity) newCapacity = capacity;
321-
}
322-
323-
Array.Resize(ref _array, newCapacity);
307+
EnsureCapacityCore(capacity);
324308
_version++;
325309
}
326310

327311
return _array.Length;
328312
}
329313

314+
private void EnsureCapacityCore(int capacity)
315+
{
316+
Debug.Assert(_array.Length < capacity);
317+
318+
int newcapacity = _array.Length == 0 ? DefaultCapacity : 2 * _array.Length;
319+
// ensure min capacity is respected and account for arithmetic overflow
320+
if (newcapacity < capacity) newcapacity = capacity;
321+
322+
Array.Resize(ref _array, newcapacity);
323+
}
324+
330325
// Copies the Stack to an array, in the same order Pop would return the items.
331326
public T[] ToArray()
332327
{

src/libraries/System.Collections/tests/Generic/List/List.Generic.Tests.EnsureCapacity.cs

+11
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ public void EnsureCapacity_NegativeCapacityRequested_Throws()
4141
AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => list.EnsureCapacity(-1));
4242
}
4343

44+
const int MaxArraySize = 0X7FEFFFFF;
45+
46+
[Theory]
47+
[InlineData(5, MaxArraySize + 1)]
48+
[InlineData(1, int.MaxValue)]
49+
public void EnsureCapacity_LargeCapacity_Throws(int count, int requestCapacity)
50+
{
51+
List<T> list = GenericListFactory(count);
52+
Assert.Throws<OutOfMemoryException>(() => list.EnsureCapacity(requestCapacity));
53+
}
54+
4455
[Theory]
4556
[InlineData(5)]
4657
public void EnsureCapacity_RequestedCapacitySmallerThanOrEqualToCurrent_CapacityUnchanged(int currentCapacity)

src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs

+11
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,17 @@ public void Queue_Generic_EnsureCapacity_NegativeCapacityRequested_Throws()
348348
AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => queue.EnsureCapacity(-1));
349349
}
350350

351+
const int MaxArraySize = 0X7FEFFFFF;
352+
353+
[Theory]
354+
[InlineData(MaxArraySize + 1)]
355+
[InlineData(int.MaxValue)]
356+
public void Queue_Generic_EnsureCapacity_LargeCapacityRequested_Throws(int requestedCapacity)
357+
{
358+
var queue = GenericQueueFactory();
359+
AssertExtensions.Throws<OutOfMemoryException>(() => queue.EnsureCapacity(requestedCapacity));
360+
}
361+
351362
[Theory]
352363
[InlineData(5)]
353364
public void Queue_Generic_EnsureCapacity_RequestedCapacitySmallerThanOrEqualToCurrent_CapacityUnchanged(int currentCapacity)

src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs

+11
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,17 @@ public void Stack_Generic_EnsureCapacity_NegativeCapacityRequested_Throws()
314314
AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => stack.EnsureCapacity(-1));
315315
}
316316

317+
const int MaxArraySize = 0X7FEFFFFF;
318+
319+
[Theory]
320+
[InlineData(MaxArraySize + 1)]
321+
[InlineData(int.MaxValue)]
322+
public void Stack_Generic_EnsureCapacity_LargeCapacityRequested_Throws(int requestedCapacity)
323+
{
324+
var stack = GenericStackFactory();
325+
AssertExtensions.Throws<OutOfMemoryException>(() => stack.EnsureCapacity(requestedCapacity));
326+
}
327+
317328
[Theory]
318329
[InlineData(5)]
319330
public void Stack_Generic_EnsureCapacity_RequestedCapacitySmallerThanOrEqualToCurrent_CapacityUnchanged(int currentCapacity)

src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs

+12-19
Original file line numberDiff line numberDiff line change
@@ -413,28 +413,21 @@ public int EnsureCapacity(int capacity)
413413
}
414414

415415
/// <summary>
416-
/// Increase the capacity of this list to at least the specified <paramref name="min"/> by continuously twice current capacity.
416+
/// Increase the capacity of this list to at least the specified <paramref name="capacity"/> by continuously twice current capacity.
417417
/// </summary>
418-
/// <param name="min">The minimum capacity to ensure</param>
419-
private void EnsureCapacityCore(int min)
418+
/// <param name="capacity">The minimum capacity to ensure.</param>
419+
private void EnsureCapacityCore(int capacity)
420420
{
421-
if (_items.Length < min)
422-
{
423-
int newCapacity = _items.Length == 0 ? DefaultCapacity : _items.Length << 1;
424-
while ((uint)newCapacity < min)
425-
{
426-
newCapacity <<= 1;
427-
}
421+
Debug.Assert(_items.Length < capacity);
428422

429-
// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
430-
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
431-
if ((uint)newCapacity > Array.MaxArrayLength)
432-
{
433-
newCapacity = Array.MaxArrayLength;
434-
if (newCapacity < min) newCapacity = min;
435-
}
436-
Capacity = newCapacity;
437-
}
423+
int newCapacity = _items.Length == 0 ? DefaultCapacity : 2 * _items.Length;
424+
425+
// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
426+
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
427+
if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
428+
if (newCapacity < capacity) newCapacity = capacity;
429+
430+
Capacity = newCapacity;
438431
}
439432

440433
public bool Exists(Predicate<T> match)

0 commit comments

Comments
 (0)