From 85af1f5d38bd35281462bd2c244ab144172c4f8d Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Sat, 4 Aug 2018 22:07:06 +0300 Subject: [PATCH] Improve argument validation in copyOfRange Make copyOfRange implementation non-inline in order not to expose copyOfRangeToIndexCheck implementation detail. It will be possible to make the function non-inline itself later without that JvmName trick, when apiVersion 1.2 support is discontinued. #KT-19489 --- .../stdlib/common/src/generated/_Arrays.kt | 45 ++++- .../stdlib/js/src/generated/_ArraysJs.kt | 73 +++++-- .../stdlib/jvm/src/generated/_ArraysJvm.kt | 187 ++++++++++++++++-- .../jvm/src/kotlin/collections/ArraysJVM.kt | 5 + .../stdlib/test/collections/ArraysTest.kt | 35 ++++ .../test/collections/ListSpecificTest.kt | 10 + .../kotlin-stdlib-runtime-merged.txt | 9 + .../kotlin-stdlib-gen/src/templates/Arrays.kt | 42 +++- 8 files changed, 355 insertions(+), 51 deletions(-) diff --git a/libraries/stdlib/common/src/generated/_Arrays.kt b/libraries/stdlib/common/src/generated/_Arrays.kt index 3b19ecbd564..07914e25f73 100644 --- a/libraries/stdlib/common/src/generated/_Arrays.kt +++ b/libraries/stdlib/common/src/generated/_Arrays.kt @@ -5737,48 +5737,75 @@ public expect fun CharArray.copyOf(newSize: Int): CharArray public expect fun Array.copyOf(newSize: Int): Array /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ @Suppress("NO_ACTUAL_FOR_EXPECT") public expect fun Array.copyOfRange(fromIndex: Int, toIndex: Int): Array /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ public expect fun ByteArray.copyOfRange(fromIndex: Int, toIndex: Int): ByteArray /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ public expect fun ShortArray.copyOfRange(fromIndex: Int, toIndex: Int): ShortArray /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ public expect fun IntArray.copyOfRange(fromIndex: Int, toIndex: Int): IntArray /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ public expect fun LongArray.copyOfRange(fromIndex: Int, toIndex: Int): LongArray /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ public expect fun FloatArray.copyOfRange(fromIndex: Int, toIndex: Int): FloatArray /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ public expect fun DoubleArray.copyOfRange(fromIndex: Int, toIndex: Int): DoubleArray /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ public expect fun BooleanArray.copyOfRange(fromIndex: Int, toIndex: Int): BooleanArray /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ public expect fun CharArray.copyOfRange(fromIndex: Int, toIndex: Int): CharArray diff --git a/libraries/stdlib/js/src/generated/_ArraysJs.kt b/libraries/stdlib/js/src/generated/_ArraysJs.kt index 6e23c5d96cb..22370d52ca0 100644 --- a/libraries/stdlib/js/src/generated/_ArraysJs.kt +++ b/libraries/stdlib/js/src/generated/_ArraysJs.kt @@ -622,71 +622,102 @@ public actual fun Array.copyOf(newSize: Int): Array { } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ -@Suppress("ACTUAL_WITHOUT_EXPECT", "NOTHING_TO_INLINE") -public actual inline fun Array.copyOfRange(fromIndex: Int, toIndex: Int): Array { +@Suppress("ACTUAL_WITHOUT_EXPECT") +public actual fun Array.copyOfRange(fromIndex: Int, toIndex: Int): Array { + AbstractList.checkRangeIndexes(fromIndex, toIndex, size) return this.asDynamic().slice(fromIndex, toIndex) } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ -@Suppress("NOTHING_TO_INLINE") -public actual inline fun ByteArray.copyOfRange(fromIndex: Int, toIndex: Int): ByteArray { +public actual fun ByteArray.copyOfRange(fromIndex: Int, toIndex: Int): ByteArray { + AbstractList.checkRangeIndexes(fromIndex, toIndex, size) return this.asDynamic().slice(fromIndex, toIndex) } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ -@Suppress("NOTHING_TO_INLINE") -public actual inline fun ShortArray.copyOfRange(fromIndex: Int, toIndex: Int): ShortArray { +public actual fun ShortArray.copyOfRange(fromIndex: Int, toIndex: Int): ShortArray { + AbstractList.checkRangeIndexes(fromIndex, toIndex, size) return this.asDynamic().slice(fromIndex, toIndex) } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ -@Suppress("NOTHING_TO_INLINE") -public actual inline fun IntArray.copyOfRange(fromIndex: Int, toIndex: Int): IntArray { +public actual fun IntArray.copyOfRange(fromIndex: Int, toIndex: Int): IntArray { + AbstractList.checkRangeIndexes(fromIndex, toIndex, size) return this.asDynamic().slice(fromIndex, toIndex) } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ public actual fun LongArray.copyOfRange(fromIndex: Int, toIndex: Int): LongArray { + AbstractList.checkRangeIndexes(fromIndex, toIndex, size) return withType("LongArray", this.asDynamic().slice(fromIndex, toIndex)) } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ -@Suppress("NOTHING_TO_INLINE") -public actual inline fun FloatArray.copyOfRange(fromIndex: Int, toIndex: Int): FloatArray { +public actual fun FloatArray.copyOfRange(fromIndex: Int, toIndex: Int): FloatArray { + AbstractList.checkRangeIndexes(fromIndex, toIndex, size) return this.asDynamic().slice(fromIndex, toIndex) } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ -@Suppress("NOTHING_TO_INLINE") -public actual inline fun DoubleArray.copyOfRange(fromIndex: Int, toIndex: Int): DoubleArray { +public actual fun DoubleArray.copyOfRange(fromIndex: Int, toIndex: Int): DoubleArray { + AbstractList.checkRangeIndexes(fromIndex, toIndex, size) return this.asDynamic().slice(fromIndex, toIndex) } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ public actual fun BooleanArray.copyOfRange(fromIndex: Int, toIndex: Int): BooleanArray { + AbstractList.checkRangeIndexes(fromIndex, toIndex, size) return withType("BooleanArray", this.asDynamic().slice(fromIndex, toIndex)) } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ public actual fun CharArray.copyOfRange(fromIndex: Int, toIndex: Int): CharArray { + AbstractList.checkRangeIndexes(fromIndex, toIndex, size) return withType("CharArray", this.asDynamic().slice(fromIndex, toIndex)) } diff --git a/libraries/stdlib/jvm/src/generated/_ArraysJvm.kt b/libraries/stdlib/jvm/src/generated/_ArraysJvm.kt index a1a4f9c6ee4..50d0ab423f4 100644 --- a/libraries/stdlib/jvm/src/generated/_ArraysJvm.kt +++ b/libraries/stdlib/jvm/src/generated/_ArraysJvm.kt @@ -814,74 +814,227 @@ public actual inline fun Array.copyOf(newSize: Int): Array { } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ +@JvmName("copyOfRangeInline") @kotlin.internal.InlineOnly public actual inline fun Array.copyOfRange(fromIndex: Int, toIndex: Int): Array { - return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + return if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) { + copyOfRangeImpl(fromIndex, toIndex) + } else { + if (toIndex > size) throw IndexOutOfBoundsException("toIndex: $toIndex, size: $size") + java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + } } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ +@JvmName("copyOfRangeInline") @kotlin.internal.InlineOnly public actual inline fun ByteArray.copyOfRange(fromIndex: Int, toIndex: Int): ByteArray { - return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + return if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) { + copyOfRangeImpl(fromIndex, toIndex) + } else { + if (toIndex > size) throw IndexOutOfBoundsException("toIndex: $toIndex, size: $size") + java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + } } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ +@JvmName("copyOfRangeInline") @kotlin.internal.InlineOnly public actual inline fun ShortArray.copyOfRange(fromIndex: Int, toIndex: Int): ShortArray { - return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + return if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) { + copyOfRangeImpl(fromIndex, toIndex) + } else { + if (toIndex > size) throw IndexOutOfBoundsException("toIndex: $toIndex, size: $size") + java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + } } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ +@JvmName("copyOfRangeInline") @kotlin.internal.InlineOnly public actual inline fun IntArray.copyOfRange(fromIndex: Int, toIndex: Int): IntArray { - return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + return if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) { + copyOfRangeImpl(fromIndex, toIndex) + } else { + if (toIndex > size) throw IndexOutOfBoundsException("toIndex: $toIndex, size: $size") + java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + } } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ +@JvmName("copyOfRangeInline") @kotlin.internal.InlineOnly public actual inline fun LongArray.copyOfRange(fromIndex: Int, toIndex: Int): LongArray { - return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + return if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) { + copyOfRangeImpl(fromIndex, toIndex) + } else { + if (toIndex > size) throw IndexOutOfBoundsException("toIndex: $toIndex, size: $size") + java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + } } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ +@JvmName("copyOfRangeInline") @kotlin.internal.InlineOnly public actual inline fun FloatArray.copyOfRange(fromIndex: Int, toIndex: Int): FloatArray { - return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + return if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) { + copyOfRangeImpl(fromIndex, toIndex) + } else { + if (toIndex > size) throw IndexOutOfBoundsException("toIndex: $toIndex, size: $size") + java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + } } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ +@JvmName("copyOfRangeInline") @kotlin.internal.InlineOnly public actual inline fun DoubleArray.copyOfRange(fromIndex: Int, toIndex: Int): DoubleArray { - return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + return if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) { + copyOfRangeImpl(fromIndex, toIndex) + } else { + if (toIndex > size) throw IndexOutOfBoundsException("toIndex: $toIndex, size: $size") + java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + } } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ +@JvmName("copyOfRangeInline") @kotlin.internal.InlineOnly public actual inline fun BooleanArray.copyOfRange(fromIndex: Int, toIndex: Int): BooleanArray { - return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + return if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) { + copyOfRangeImpl(fromIndex, toIndex) + } else { + if (toIndex > size) throw IndexOutOfBoundsException("toIndex: $toIndex, size: $size") + java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + } } /** - * Returns new array which is a copy of range of original array. + * Returns a new array which is a copy of the specified range of the original array. + * + * @param fromIndex the start of the range (inclusive), must be in `0..array.size` + * @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` */ +@JvmName("copyOfRangeInline") @kotlin.internal.InlineOnly public actual inline fun CharArray.copyOfRange(fromIndex: Int, toIndex: Int): CharArray { + return if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) { + copyOfRangeImpl(fromIndex, toIndex) + } else { + if (toIndex > size) throw IndexOutOfBoundsException("toIndex: $toIndex, size: $size") + java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + } +} + +@SinceKotlin("1.3") +@PublishedApi +@JvmName("copyOfRange") +internal fun Array.copyOfRangeImpl(fromIndex: Int, toIndex: Int): Array { + copyOfRangeToIndexCheck(toIndex, size) + return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) +} + +@SinceKotlin("1.3") +@PublishedApi +@JvmName("copyOfRange") +internal fun ByteArray.copyOfRangeImpl(fromIndex: Int, toIndex: Int): ByteArray { + copyOfRangeToIndexCheck(toIndex, size) + return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) +} + +@SinceKotlin("1.3") +@PublishedApi +@JvmName("copyOfRange") +internal fun ShortArray.copyOfRangeImpl(fromIndex: Int, toIndex: Int): ShortArray { + copyOfRangeToIndexCheck(toIndex, size) + return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) +} + +@SinceKotlin("1.3") +@PublishedApi +@JvmName("copyOfRange") +internal fun IntArray.copyOfRangeImpl(fromIndex: Int, toIndex: Int): IntArray { + copyOfRangeToIndexCheck(toIndex, size) + return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) +} + +@SinceKotlin("1.3") +@PublishedApi +@JvmName("copyOfRange") +internal fun LongArray.copyOfRangeImpl(fromIndex: Int, toIndex: Int): LongArray { + copyOfRangeToIndexCheck(toIndex, size) + return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) +} + +@SinceKotlin("1.3") +@PublishedApi +@JvmName("copyOfRange") +internal fun FloatArray.copyOfRangeImpl(fromIndex: Int, toIndex: Int): FloatArray { + copyOfRangeToIndexCheck(toIndex, size) + return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) +} + +@SinceKotlin("1.3") +@PublishedApi +@JvmName("copyOfRange") +internal fun DoubleArray.copyOfRangeImpl(fromIndex: Int, toIndex: Int): DoubleArray { + copyOfRangeToIndexCheck(toIndex, size) + return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) +} + +@SinceKotlin("1.3") +@PublishedApi +@JvmName("copyOfRange") +internal fun BooleanArray.copyOfRangeImpl(fromIndex: Int, toIndex: Int): BooleanArray { + copyOfRangeToIndexCheck(toIndex, size) + return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) +} + +@SinceKotlin("1.3") +@PublishedApi +@JvmName("copyOfRange") +internal fun CharArray.copyOfRangeImpl(fromIndex: Int, toIndex: Int): CharArray { + copyOfRangeToIndexCheck(toIndex, size) return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) } diff --git a/libraries/stdlib/jvm/src/kotlin/collections/ArraysJVM.kt b/libraries/stdlib/jvm/src/kotlin/collections/ArraysJVM.kt index f6ad8c6b2ff..f74ed16b78a 100644 --- a/libraries/stdlib/jvm/src/kotlin/collections/ArraysJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/collections/ArraysJVM.kt @@ -43,3 +43,8 @@ internal actual fun arrayOfNulls(reference: Array, size: Int): Array { @Suppress("UNCHECKED_CAST") return java.lang.reflect.Array.newInstance(reference.javaClass.componentType, size) as Array } + +@SinceKotlin("1.3") +internal fun copyOfRangeToIndexCheck(toIndex: Int, size: Int) { + if (toIndex > size) throw IndexOutOfBoundsException("toIndex ($toIndex) is greater than size ($size).") +} \ No newline at end of file diff --git a/libraries/stdlib/test/collections/ArraysTest.kt b/libraries/stdlib/test/collections/ArraysTest.kt index 81f1b8d4058..acbcd2b63ee 100644 --- a/libraries/stdlib/test/collections/ArraysTest.kt +++ b/libraries/stdlib/test/collections/ArraysTest.kt @@ -606,6 +606,15 @@ class ArraysTest { assertEquals(listOf(200, 100), shortArrayOf(50, 100, 200).slice(2 downTo 1)) assertEquals(listOf(100L, 200L, 30L), longArrayOf(50L, 100L, 200L, 30L).slice(1..3)) assertEquals(listOf(true, false, true), booleanArrayOf(true, false, true, true).slice(iter)) + + for (range in listOf(-1 until 0, 0 until 2, 2..2)) { + val bounds = "range: $range" + val exClass = IndexOutOfBoundsException::class + assertFailsWith(exClass, bounds) { arrayOf("x").slice(range) } + assertFailsWith(exClass, bounds) { intArrayOf(1).slice(range) } + assertFailsWith(exClass, bounds) { longArrayOf(1L).slice(range) } + assertFailsWith(exClass, bounds) { charArrayOf('C').slice(range) } + } } @Test fun sliceArray() { @@ -625,6 +634,15 @@ class ArraysTest { // assertArrayNotSameButEquals(shortArrayOf(200, 100), shortArrayOf(50, 100, 200).sliceArray(2 downTo 1)) assertArrayNotSameButEquals(longArrayOf(100L, 200L, 30L), longArrayOf(50L, 100L, 200L, 30L).sliceArray(1..3)) assertArrayNotSameButEquals(booleanArrayOf(true, false, true), booleanArrayOf(true, false, true, true).sliceArray(coll)) + + for (range in listOf(-1 until 0, 0 until 2, 2..2)) { + val bounds = "range: $range" + val exClass = IndexOutOfBoundsException::class + assertFailsWith(exClass, bounds) { arrayOf("x").sliceArray(range) } + assertFailsWith(exClass, bounds) { intArrayOf(1).sliceArray(range) } + assertFailsWith(exClass, bounds) { longArrayOf(1L).sliceArray(range) } + assertFailsWith(exClass, bounds) { charArrayOf('C').sliceArray(range) } + } } @Test fun iterators() { @@ -738,6 +756,7 @@ class ArraysTest { } @Test fun copyOfRange() { + assertArrayNotSameButEquals(arrayOf("b", "c"), arrayOf("a", "b", "c").copyOfRange(1, 3)) assertArrayNotSameButEquals(booleanArrayOf(true, false, true), booleanArrayOf(true, false, true, true).copyOfRange(0, 3)) assertArrayNotSameButEquals(byteArrayOf(0, 1, 2), byteArrayOf(0, 1, 2, 3, 4, 5).copyOfRange(0, 3)) assertArrayNotSameButEquals(shortArrayOf(0, 1, 2), shortArrayOf(0, 1, 2, 3, 4, 5).copyOfRange(0, 3)) @@ -746,6 +765,22 @@ class ArraysTest { assertArrayNotSameButEquals(floatArrayOf(0F, 1F, 2F), floatArrayOf(0F, 1F, 2F, 3F, 4F, 5F).copyOfRange(0, 3)) assertArrayNotSameButEquals(doubleArrayOf(0.0, 1.0, 2.0), doubleArrayOf(0.0, 1.0, 2.0, 3.0, 4.0, 5.0).copyOfRange(0, 3)) assertArrayNotSameButEquals(charArrayOf('0', '1', '2'), charArrayOf('0', '1', '2', '3', '4', '5').copyOfRange(0, 3)) + + for (pos in 0..3) { + assertArrayNotSameButEquals(emptyArray(), arrayOf("a", "b", "c").copyOfRange(pos, pos)) + assertArrayNotSameButEquals(intArrayOf(), intArrayOf(1, 2, 3).copyOfRange(pos, pos)) + assertArrayNotSameButEquals(charArrayOf(), charArrayOf('a', 'b', 'c').copyOfRange(pos, pos)) + assertArrayNotSameButEquals(longArrayOf(), LongArray(3) { it.toLong() }.copyOfRange(pos, pos)) + } + + for ((start, end) in listOf(-1 to 0, 0 to 2, 2 to 2, 1 to 0)) { + val bounds = "start: $start, end: $end" + val exClass = if (start > end) IllegalArgumentException::class else IndexOutOfBoundsException::class + assertFailsWith(exClass, bounds) { arrayOf("x").copyOfRange(start, end) } + assertFailsWith(exClass, bounds) { intArrayOf(1).copyOfRange(start, end) } + assertFailsWith(exClass, bounds) { longArrayOf(1L).copyOfRange(start, end) } + assertFailsWith(exClass, bounds) { charArrayOf('C').copyOfRange(start, end) } + } } diff --git a/libraries/stdlib/test/collections/ListSpecificTest.kt b/libraries/stdlib/test/collections/ListSpecificTest.kt index b661ef147b3..2c942dd46f3 100644 --- a/libraries/stdlib/test/collections/ListSpecificTest.kt +++ b/libraries/stdlib/test/collections/ListSpecificTest.kt @@ -26,6 +26,9 @@ class ListSpecificTest { @Test fun slice() { val list = listOf('A', 'B', 'C', 'D') + + assertEquals(emptyList(), list.slice(IntRange.EMPTY)) + // ABCD // 0123 assertEquals(listOf('B', 'C', 'D'), list.slice(1..3)) @@ -33,6 +36,13 @@ class ListSpecificTest { val iter = listOf(2, 0, 3) assertEquals(listOf('C', 'A', 'D'), list.slice(iter)) + + for (range in listOf(-1 until 0, 0 until 2, 2..2)) { + val bounds = "range: $range" + val exClass = IndexOutOfBoundsException::class + assertFailsWith(exClass, bounds) { listOf("x").slice(range) } + assertFailsWith(exClass, bounds) { listOf("x").slice(range.asIterable()) } + } } @Test diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 03ed489c92a..2d6bf5b283d 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -1142,6 +1142,15 @@ public final class kotlin/collections/ArraysKt { public static final fun contains ([Ljava/lang/Object;Ljava/lang/Object;)Z public static final fun contains ([SS)Z public static final fun contains ([ZZ)Z + public static final fun copyOfRange ([BII)[B + public static final fun copyOfRange ([CII)[C + public static final fun copyOfRange ([DII)[D + public static final fun copyOfRange ([FII)[F + public static final fun copyOfRange ([III)[I + public static final fun copyOfRange ([JII)[J + public static final fun copyOfRange ([Ljava/lang/Object;II)[Ljava/lang/Object; + public static final fun copyOfRange ([SII)[S + public static final fun copyOfRange ([ZII)[Z public static final fun count ([BLkotlin/jvm/functions/Function1;)I public static final fun count ([CLkotlin/jvm/functions/Function1;)I public static final fun count ([DLkotlin/jvm/functions/Function1;)I diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Arrays.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Arrays.kt index f667458c2f4..9f720baf9f2 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Arrays.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Arrays.kt @@ -370,11 +370,34 @@ object ArrayOps : TemplateGroupBase() { } } + val f_copyOfRangeJvmImpl = fn("copyOfRangeImpl(fromIndex: Int, toIndex: Int)") { + include(InvariantArraysOfObjects, ArraysOfPrimitives) + platforms(Platform.JVM) + } builderWith { primitive -> + since("1.3") + visibility("internal") + annotation("@PublishedApi") + annotation("""@JvmName("copyOfRange")""") + returns("SELF") + body { + """ + copyOfRangeToIndexCheck(toIndex, size) + return java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + """ + } + } val f_copyOfRange = fn("copyOfRange(fromIndex: Int, toIndex: Int)") { include(InvariantArraysOfObjects, ArraysOfPrimitives) } builderWith { primitive -> - doc { "Returns new array which is a copy of range of original array." } + doc { + """ + Returns a new array which is a copy of the specified range of the original array. + + @param fromIndex the start of the range (inclusive), must be in `0..array.size` + @param toIndex the end of the range (exclusive), must be in `fromIndex..array.size` + """ + } returns("SELF") on(Platform.JS) { @@ -383,18 +406,29 @@ object ArrayOps : TemplateGroupBase() { suppress("ACTUAL_WITHOUT_EXPECT") // TODO: KT-21937 returns("Array") } - when(primitive) { + val rangeCheck = "AbstractList.checkRangeIndexes(fromIndex, toIndex, size)" + when (primitive) { PrimitiveType.Char, PrimitiveType.Boolean, PrimitiveType.Long -> body { "return withType(\"${primitive}Array\", this.asDynamic().slice(fromIndex, toIndex))" } else -> { - inline(suppressWarning = true) body { "return this.asDynamic().slice(fromIndex, toIndex)" } } } + body { rangeCheck + "\n" + body } } on(Platform.JVM) { + annotation("""@JvmName("copyOfRangeInline")""") inlineOnly() - body { "return java.util.Arrays.copyOfRange(this, fromIndex, toIndex)" } + body { + """ + return if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) { + copyOfRangeImpl(fromIndex, toIndex) + } else { + if (toIndex > size) throw IndexOutOfBoundsException("toIndex: ${'$'}toIndex, size: ${'$'}size") + java.util.Arrays.copyOfRange(this, fromIndex, toIndex) + } + """ + } } on(Platform.Common) { specialFor(InvariantArraysOfObjects) {