diff --git a/libraries/stdlib/jvm/src/kotlin/collections/builders/ListBuilder.kt b/libraries/stdlib/jvm/src/kotlin/collections/builders/ListBuilder.kt index 5932439a663..d0161fe8562 100644 --- a/libraries/stdlib/jvm/src/kotlin/collections/builders/ListBuilder.kt +++ b/libraries/stdlib/jvm/src/kotlin/collections/builders/ListBuilder.kt @@ -183,7 +183,7 @@ internal class ListBuilder private constructor( if (backing != null) throw IllegalStateException() // just in case somebody casts subList to ListBuilder if (minCapacity < 0) throw OutOfMemoryError() // overflow if (minCapacity > array.size) { - val newSize = ArrayDeque.newCapacity(array.size, minCapacity) + val newSize = AbstractList.newCapacity(array.size, minCapacity) array = array.copyOfUninitializedElements(newSize) } } diff --git a/libraries/stdlib/native-wasm/src/kotlin/collections/ArrayList.kt b/libraries/stdlib/native-wasm/src/kotlin/collections/ArrayList.kt index afa3fc0c5f4..3fd7b13104d 100644 --- a/libraries/stdlib/native-wasm/src/kotlin/collections/ArrayList.kt +++ b/libraries/stdlib/native-wasm/src/kotlin/collections/ArrayList.kt @@ -148,7 +148,7 @@ actual class ArrayList private constructor( if (backingList != null) throw IllegalStateException() // just in case somebody casts subList to ArrayList if (minCapacity < 0) throw OutOfMemoryError() // overflow if (minCapacity > backingArray.size) { - val newSize = ArrayDeque.newCapacity(backingArray.size, minCapacity) + val newSize = AbstractList.newCapacity(backingArray.size, minCapacity) backingArray = backingArray.copyOfUninitializedElements(newSize) } } diff --git a/libraries/stdlib/src/kotlin/collections/AbstractList.kt b/libraries/stdlib/src/kotlin/collections/AbstractList.kt index 49c178211fa..f5341782e8f 100644 --- a/libraries/stdlib/src/kotlin/collections/AbstractList.kt +++ b/libraries/stdlib/src/kotlin/collections/AbstractList.kt @@ -133,6 +133,19 @@ public abstract class AbstractList protected constructor() : AbstractColl } } + private const val maxArraySize = Int.MAX_VALUE - 8 + + /** [oldCapacity] and [minCapacity] must be non-negative. */ + internal fun newCapacity(oldCapacity: Int, minCapacity: Int): Int { + // overflow-conscious + var newCapacity = oldCapacity + (oldCapacity shr 1) + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity + if (newCapacity - maxArraySize > 0) + newCapacity = if (minCapacity > maxArraySize) Int.MAX_VALUE else maxArraySize + return newCapacity + } + internal fun orderedHashCode(c: Collection<*>): Int { var hashCode = 1 for (e in c) { diff --git a/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt b/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt index d41e585b923..d0a2cf695bc 100644 --- a/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt +++ b/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt @@ -63,7 +63,7 @@ public class ArrayDeque : AbstractMutableList { return } - val newCapacity = newCapacity(elementData.size, minCapacity) + val newCapacity = AbstractList.newCapacity(elementData.size, minCapacity) copyElements(newCapacity) } @@ -560,18 +560,7 @@ public class ArrayDeque : AbstractMutableList { internal companion object { private val emptyElementData = emptyArray() - private const val maxArraySize = Int.MAX_VALUE - 8 private const val defaultMinCapacity = 10 - - internal fun newCapacity(oldCapacity: Int, minCapacity: Int): Int { - // overflow-conscious - var newCapacity = oldCapacity + (oldCapacity shr 1) - if (newCapacity - minCapacity < 0) - newCapacity = minCapacity - if (newCapacity - maxArraySize > 0) - newCapacity = if (minCapacity > maxArraySize) Int.MAX_VALUE else maxArraySize - return newCapacity - } } // For testing only diff --git a/libraries/stdlib/test/collections/AbstractListTest.kt b/libraries/stdlib/test/collections/AbstractListTest.kt new file mode 100644 index 00000000000..8b1d5af3198 --- /dev/null +++ b/libraries/stdlib/test/collections/AbstractListTest.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package test.collections + +import kotlin.random.Random +import kotlin.random.nextInt +import kotlin.test.* + +class AbstractListTest { + @Suppress("INVISIBLE_MEMBER") + @Test + fun newCapacity() { + // oldCapacity < minCapacity < newCapacity + repeat(100) { + val oldCapacity = Random.nextInt(1 shl 30) + val newCapacity = oldCapacity + (oldCapacity shr 1) + val minCapacity = Random.nextInt(oldCapacity + 1 until newCapacity) + + assertEquals(newCapacity, AbstractList.newCapacity(oldCapacity, minCapacity)) + } + + // oldCapacity < newCapacity < minCapacity + repeat(100) { + val oldCapacity = Random.nextInt(1 shl 30) + val newCapacity = oldCapacity + (oldCapacity shr 1) + val minCapacity = Random.nextInt(newCapacity..Int.MAX_VALUE) + + assertEquals(minCapacity, AbstractList.newCapacity(oldCapacity, minCapacity)) + } + + // newCapacity overflow, oldCapacity < minCapacity <= maxArraySize + val maxArraySize = Int.MAX_VALUE - 8 + repeat(100) { + val oldCapacity = Random.nextInt((1 shl 30) + (1 shl 29) until maxArraySize) + val minCapacity = Random.nextInt(oldCapacity..maxArraySize) + + assertEquals(maxArraySize, AbstractList.newCapacity(oldCapacity, minCapacity)) + } + + // newCapacity overflow, minCapacity > maxArraySize + repeat(100) { + val oldCapacity = Random.nextInt((1 shl 30) + (1 shl 29)..maxArraySize) + val minCapacity = Random.nextInt(maxArraySize + 1..Int.MAX_VALUE) + + assertEquals(Int.MAX_VALUE, AbstractList.newCapacity(oldCapacity, minCapacity)) + } + } +} \ No newline at end of file diff --git a/libraries/stdlib/test/collections/ArrayDequeTest.kt b/libraries/stdlib/test/collections/ArrayDequeTest.kt index 665a2852254..64dabd64b92 100644 --- a/libraries/stdlib/test/collections/ArrayDequeTest.kt +++ b/libraries/stdlib/test/collections/ArrayDequeTest.kt @@ -608,45 +608,6 @@ class ArrayDequeTest { } } - @Suppress("INVISIBLE_MEMBER") - @Test - fun newCapacity() { - // oldCapacity < minCapacity < newCapacity - repeat(100) { - val oldCapacity = Random.nextInt(1 shl 30) - val newCapacity = oldCapacity + (oldCapacity shr 1) - val minCapacity = Random.nextInt(oldCapacity + 1 until newCapacity) - - assertEquals(newCapacity, ArrayDeque.newCapacity(oldCapacity, minCapacity)) - } - - // oldCapacity < newCapacity < minCapacity - repeat(100) { - val oldCapacity = Random.nextInt(1 shl 30) - val newCapacity = oldCapacity + (oldCapacity shr 1) - val minCapacity = Random.nextInt(newCapacity..Int.MAX_VALUE) - - assertEquals(minCapacity, ArrayDeque.newCapacity(oldCapacity, minCapacity)) - } - - // newCapacity overflow, oldCapacity < minCapacity <= maxArraySize - val maxArraySize = Int.MAX_VALUE - 8 - repeat(100) { - val oldCapacity = Random.nextInt((1 shl 30) + (1 shl 29) until maxArraySize) - val minCapacity = Random.nextInt(oldCapacity..maxArraySize) - - assertEquals(maxArraySize, ArrayDeque.newCapacity(oldCapacity, minCapacity)) - } - - // newCapacity overflow, minCapacity > maxArraySize - repeat(100) { - val oldCapacity = Random.nextInt((1 shl 30) + (1 shl 29)..maxArraySize) - val minCapacity = Random.nextInt(maxArraySize + 1..Int.MAX_VALUE) - - assertEquals(Int.MAX_VALUE, ArrayDeque.newCapacity(oldCapacity, minCapacity)) - } - } - @Suppress("INVISIBLE_MEMBER") @Test fun toArray() {