diff --git a/libraries/stdlib/common/src/generated/_Arrays.kt b/libraries/stdlib/common/src/generated/_Arrays.kt index 9aafae332b447..6d97161832a1a 100644 --- a/libraries/stdlib/common/src/generated/_Arrays.kt +++ b/libraries/stdlib/common/src/generated/_Arrays.kt @@ -4806,14 +4806,7 @@ public fun Array.take(n: Int): List { if (n == 0) return emptyList() if (n >= size) return toList() if (n == 1) return listOf(this[0]) - var count = 0 - val list = ArrayList(n) - for (item in this) { - list.add(item) - if (++count == n) - break - } - return list + return copyOfRange(0, n).asList() } /** @@ -5005,10 +4998,7 @@ public fun Array.takeLast(n: Int): List { val size = size if (n >= size) return toList() if (n == 1) return listOf(this[size - 1]) - val list = ArrayList(n) - for (index in size - n until size) - list.add(this[index]) - return list + return copyOfRange(size - n, size).asList() } /** @@ -5295,13 +5285,11 @@ public inline fun CharArray.takeLastWhile(predicate: (Char) -> Boolean): List Array.takeWhile(predicate: (T) -> Boolean): List { - val list = ArrayList() - for (item in this) { - if (!predicate(item)) - break - list.add(item) - } - return list + var i = 0 + while (i < size && predicate(this[i])) i++ + return if (i == 0) emptyList() + else if (i == 1) listOf(this[0]) + else copyOfRange(0, i).asList() } /** @@ -9838,7 +9826,7 @@ public fun Array.toList(): List { return when (size) { 0 -> emptyList() 1 -> listOf(this[0]) - else -> this.toMutableList() + else -> copyOf().asList() } } diff --git a/libraries/stdlib/src/kotlin/collections/Arrays.kt b/libraries/stdlib/src/kotlin/collections/Arrays.kt index f789c74b42b19..b6c627ef49596 100644 --- a/libraries/stdlib/src/kotlin/collections/Arrays.kt +++ b/libraries/stdlib/src/kotlin/collections/Arrays.kt @@ -17,11 +17,23 @@ import kotlin.contracts.* * @sample samples.collections.Arrays.Transformations.flattenArray */ public fun Array>.flatten(): List { - val result = ArrayList(sumOf { it.size }) - for (element in this) { - result.addAll(element) + if (isEmpty()) return emptyList() + + val totalSizeLong = sumOf { it.size.toLong() } + if (totalSizeLong == 0L) return emptyList() + require(totalSizeLong <= Int.MAX_VALUE.toLong()) { + "Sum of all arrays overflow maximum array capacity (of Int.MAX_VALUE)" + } + + val outputArray = arrayOfNulls(totalSizeLong.toInt()) + var offset = 0 + for (innerArray in this) { + innerArray.copyInto(outputArray, offset) + offset += innerArray.size } - return result + + @Suppress("UNCHECKED_CAST") + return outputArray.asList() as List } /** diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Filtering.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Filtering.kt index 840191c5f86cb..9e333e1b94af0 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Filtering.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Filtering.kt @@ -192,7 +192,7 @@ object Filtering : TemplateGroupBase() { """ } - body(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned) { + body(ArraysOfPrimitives, ArraysOfUnsigned) { """ require(n >= 0) { "Requested element count $n is less than zero." } if (n == 0) return emptyList() @@ -208,6 +208,17 @@ object Filtering : TemplateGroupBase() { return list """ } + + // For object arrays, ensure a single array copy instead of copying using a loop (see KT-75801) + body(ArraysOfObjects) { + """ + require(n >= 0) { "Requested element count $n is less than zero." } + if (n == 0) return emptyList() + if (n >= size) return toList() + if (n == 1) return listOf(this[0]) + return copyOfRange(0, n).asList() + """ + } } val f_dropLast = fn("dropLast(n: Int)") { @@ -270,7 +281,7 @@ object Filtering : TemplateGroupBase() { """ } - body(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned) { + body(ArraysOfPrimitives, ArraysOfUnsigned) { """ require(n >= 0) { "Requested element count $n is less than zero." } if (n == 0) return emptyList() @@ -284,6 +295,19 @@ object Filtering : TemplateGroupBase() { return list """ } + + // For object arrays, ensure a single array copy instead of copying using a loop (see KT-75801) + body(ArraysOfObjects) { + """ + require(n >= 0) { "Requested element count $n is less than zero." } + if (n == 0) return emptyList() + val size = size + if (n >= size) return toList() + if (n == 1) return listOf(this[size - 1]) + return copyOfRange(size - n, size).asList() + """ + } + body(Lists) { """ require(n >= 0) { "Requested element count $n is less than zero." } @@ -413,6 +437,17 @@ object Filtering : TemplateGroupBase() { returns("Sequence") body { """return TakeWhileSequence(this, predicate)""" } } + + // For object arrays, ensure a single array copy instead of copying using a loop (see KT-75801) + body(ArraysOfObjects) { + """ + var i = 0 + while (i < ${f.code.size} && predicate(this[i])) i++ + return if (i == 0) emptyList() + else if (i == 1) listOf(this[0]) + else copyOfRange(0, i).asList() + """ + } } val f_dropLastWhile = fn("dropLastWhile(predicate: (T) -> Boolean)") { diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Snapshots.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Snapshots.kt index 3f58410a32659..621f0ec6b1694 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Snapshots.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Snapshots.kt @@ -173,7 +173,7 @@ object Snapshots : TemplateGroupBase() { return this.toMutableList().optimizeReadOnlyList() """ } - body(CharSequences, ArraysOfPrimitives, ArraysOfObjects) { + body(CharSequences, ArraysOfPrimitives) { """ return when (${f.code.size}) { 0 -> emptyList() @@ -182,6 +182,16 @@ object Snapshots : TemplateGroupBase() { } """ } + // For object array, ensure a single array copy instead of delegating to `toMutableList`, which can cause two copies (see KT-75801) + body(ArraysOfObjects) { + """ + return when (${f.code.size}) { + 0 -> emptyList() + 1 -> listOf(this[0]) + else -> copyOf().asList() + } + """ + } body(Sequences) { optimizedSequenceToCollection("emptyList", "listOf", "ArrayList") } specialFor(Maps) { doc { "Returns a [List] containing all key-value pairs." }