From 357c5be4fb69f40f75f622d588101098f072aec1 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 9 Aug 2018 23:31:31 +0300 Subject: [PATCH] Make sure index and count do not overflow for long sequences Throw an exception immediately before an overflow becomes observable. Place check to prevent negative index from indexOf, indexOfFirst. Do not insert overflow checks for arrays, lists, maps and char sequences. #KT-16097 --- .../stdlib/common/src/generated/_Arrays.kt | 42 +++---- .../common/src/generated/_Collections.kt | 28 +++-- .../stdlib/common/src/generated/_Maps.kt | 2 +- .../stdlib/common/src/generated/_Sequences.kt | 28 +++-- .../stdlib/common/src/generated/_Strings.kt | 2 +- libraries/stdlib/js/src/kotlin/collections.kt | 20 ++++ .../src/kotlin/collections/CollectionsJVM.kt | 30 +++++ .../test/collections/IndexOverflowJVMTest.kt | 106 ++++++++++++++++++ .../src/kotlin/collections/Collections.kt | 17 +++ .../src/kotlin/collections/Iterators.kt | 2 +- .../src/kotlin/collections/Sequences.kt | 4 +- .../kotlin-stdlib-runtime-merged.txt | 2 + .../src/templates/Aggregates.kt | 15 ++- .../src/templates/Elements.kt | 6 +- .../src/templates/Mapping.kt | 3 +- .../src/templates/Numeric.kt | 3 +- 16 files changed, 252 insertions(+), 58 deletions(-) diff --git a/libraries/stdlib/common/src/generated/_Arrays.kt b/libraries/stdlib/common/src/generated/_Arrays.kt index 07914e25f73..10cd0c70d33 100644 --- a/libraries/stdlib/common/src/generated/_Arrays.kt +++ b/libraries/stdlib/common/src/generated/_Arrays.kt @@ -9701,7 +9701,7 @@ public inline fun CharArray.count(): Int { */ public inline fun Array.count(predicate: (T) -> Boolean): Int { var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) ++count return count } @@ -9710,7 +9710,7 @@ public inline fun Array.count(predicate: (T) -> Boolean): Int { */ public inline fun ByteArray.count(predicate: (Byte) -> Boolean): Int { var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) ++count return count } @@ -9719,7 +9719,7 @@ public inline fun ByteArray.count(predicate: (Byte) -> Boolean): Int { */ public inline fun ShortArray.count(predicate: (Short) -> Boolean): Int { var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) ++count return count } @@ -9728,7 +9728,7 @@ public inline fun ShortArray.count(predicate: (Short) -> Boolean): Int { */ public inline fun IntArray.count(predicate: (Int) -> Boolean): Int { var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) ++count return count } @@ -9737,7 +9737,7 @@ public inline fun IntArray.count(predicate: (Int) -> Boolean): Int { */ public inline fun LongArray.count(predicate: (Long) -> Boolean): Int { var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) ++count return count } @@ -9746,7 +9746,7 @@ public inline fun LongArray.count(predicate: (Long) -> Boolean): Int { */ public inline fun FloatArray.count(predicate: (Float) -> Boolean): Int { var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) ++count return count } @@ -9755,7 +9755,7 @@ public inline fun FloatArray.count(predicate: (Float) -> Boolean): Int { */ public inline fun DoubleArray.count(predicate: (Double) -> Boolean): Int { var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) ++count return count } @@ -9764,7 +9764,7 @@ public inline fun DoubleArray.count(predicate: (Double) -> Boolean): Int { */ public inline fun BooleanArray.count(predicate: (Boolean) -> Boolean): Int { var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) ++count return count } @@ -9773,7 +9773,7 @@ public inline fun BooleanArray.count(predicate: (Boolean) -> Boolean): Int { */ public inline fun CharArray.count(predicate: (Char) -> Boolean): Int { var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) ++count return count } @@ -13504,7 +13504,7 @@ public fun Array.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + ++count } return if (count == 0) Double.NaN else sum / count } @@ -13518,7 +13518,7 @@ public fun Array.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + ++count } return if (count == 0) Double.NaN else sum / count } @@ -13532,7 +13532,7 @@ public fun Array.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + ++count } return if (count == 0) Double.NaN else sum / count } @@ -13546,7 +13546,7 @@ public fun Array.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + ++count } return if (count == 0) Double.NaN else sum / count } @@ -13560,7 +13560,7 @@ public fun Array.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + ++count } return if (count == 0) Double.NaN else sum / count } @@ -13574,7 +13574,7 @@ public fun Array.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + ++count } return if (count == 0) Double.NaN else sum / count } @@ -13587,7 +13587,7 @@ public fun ByteArray.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + ++count } return if (count == 0) Double.NaN else sum / count } @@ -13600,7 +13600,7 @@ public fun ShortArray.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + ++count } return if (count == 0) Double.NaN else sum / count } @@ -13613,7 +13613,7 @@ public fun IntArray.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + ++count } return if (count == 0) Double.NaN else sum / count } @@ -13626,7 +13626,7 @@ public fun LongArray.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + ++count } return if (count == 0) Double.NaN else sum / count } @@ -13639,7 +13639,7 @@ public fun FloatArray.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + ++count } return if (count == 0) Double.NaN else sum / count } @@ -13652,7 +13652,7 @@ public fun DoubleArray.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + ++count } return if (count == 0) Double.NaN else sum / count } diff --git a/libraries/stdlib/common/src/generated/_Collections.kt b/libraries/stdlib/common/src/generated/_Collections.kt index 1e8258bb541..c6fcc5f34ad 100644 --- a/libraries/stdlib/common/src/generated/_Collections.kt +++ b/libraries/stdlib/common/src/generated/_Collections.kt @@ -251,6 +251,7 @@ public fun <@kotlin.internal.OnlyInputTypes T> Iterable.indexOf(element: T): if (this is List) return this.indexOf(element) var index = 0 for (item in this) { + checkIndexOverflow(index) if (element == item) return index index++ @@ -272,6 +273,7 @@ public fun <@kotlin.internal.OnlyInputTypes T> List.indexOf(element: T): Int public inline fun Iterable.indexOfFirst(predicate: (T) -> Boolean): Int { var index = 0 for (item in this) { + checkIndexOverflow(index) if (predicate(item)) return index index++ @@ -299,6 +301,7 @@ public inline fun Iterable.indexOfLast(predicate: (T) -> Boolean): Int { var lastIndex = -1 var index = 0 for (item in this) { + checkIndexOverflow(index) if (predicate(item)) lastIndex = index index++ @@ -387,6 +390,7 @@ public fun <@kotlin.internal.OnlyInputTypes T> Iterable.lastIndexOf(element: var lastIndex = -1 var index = 0 for (item in this) { + checkIndexOverflow(index) if (element == item) lastIndex = index index++ @@ -1258,7 +1262,7 @@ public inline fun > Iterable.mapIndex public inline fun > Iterable.mapIndexedTo(destination: C, transform: (index: Int, T) -> R): C { var index = 0 for (item in this) - destination.add(transform(index++, item)) + destination.add(transform(checkIndexOverflow(index++), item)) return destination } @@ -1407,7 +1411,7 @@ public inline fun Iterable.any(predicate: (T) -> Boolean): Boolean { public fun Iterable.count(): Int { if (this is Collection) return size var count = 0 - for (element in this) count++ + for (element in this) checkCountOverflow(++count) return count } @@ -1425,7 +1429,7 @@ public inline fun Collection.count(): Int { public inline fun Iterable.count(predicate: (T) -> Boolean): Int { if (this is Collection && isEmpty()) return 0 var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) checkCountOverflow(++count) return count } @@ -1447,7 +1451,7 @@ public inline fun Iterable.fold(initial: R, operation: (acc: R, T) -> public inline fun Iterable.foldIndexed(initial: R, operation: (index: Int, acc: R, T) -> R): R { var index = 0 var accumulator = initial - for (element in this) accumulator = operation(index++, accumulator, element) + for (element in this) accumulator = operation(checkIndexOverflow(index++), accumulator, element) return accumulator } @@ -1498,7 +1502,7 @@ public inline fun Iterable.forEach(action: (T) -> Unit): Unit { */ public inline fun Iterable.forEachIndexed(action: (index: Int, T) -> Unit): Unit { var index = 0 - for (item in this) action(index++, item) + for (item in this) action(checkIndexOverflow(index++), item) } /** @@ -1725,7 +1729,7 @@ public inline fun Iterable.reduceIndexed(operation: (index: Int, a var index = 1 var accumulator: S = iterator.next() while (iterator.hasNext()) { - accumulator = operation(index++, accumulator, iterator.next()) + accumulator = operation(checkIndexOverflow(index++), accumulator, iterator.next()) } return accumulator } @@ -2238,7 +2242,7 @@ public fun Iterable.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + checkCountOverflow(++count) } return if (count == 0) Double.NaN else sum / count } @@ -2252,7 +2256,7 @@ public fun Iterable.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + checkCountOverflow(++count) } return if (count == 0) Double.NaN else sum / count } @@ -2266,7 +2270,7 @@ public fun Iterable.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + checkCountOverflow(++count) } return if (count == 0) Double.NaN else sum / count } @@ -2280,7 +2284,7 @@ public fun Iterable.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + checkCountOverflow(++count) } return if (count == 0) Double.NaN else sum / count } @@ -2294,7 +2298,7 @@ public fun Iterable.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + checkCountOverflow(++count) } return if (count == 0) Double.NaN else sum / count } @@ -2308,7 +2312,7 @@ public fun Iterable.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + checkCountOverflow(++count) } return if (count == 0) Double.NaN else sum / count } diff --git a/libraries/stdlib/common/src/generated/_Maps.kt b/libraries/stdlib/common/src/generated/_Maps.kt index 667076e802b..1009ba3ba0c 100644 --- a/libraries/stdlib/common/src/generated/_Maps.kt +++ b/libraries/stdlib/common/src/generated/_Maps.kt @@ -135,7 +135,7 @@ public inline fun Map.count(): Int { public inline fun Map.count(predicate: (Map.Entry) -> Boolean): Int { if (isEmpty()) return 0 var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) ++count return count } diff --git a/libraries/stdlib/common/src/generated/_Sequences.kt b/libraries/stdlib/common/src/generated/_Sequences.kt index 1c720363db3..89770b5b7f9 100644 --- a/libraries/stdlib/common/src/generated/_Sequences.kt +++ b/libraries/stdlib/common/src/generated/_Sequences.kt @@ -146,6 +146,7 @@ public inline fun Sequence.firstOrNull(predicate: (T) -> Boolean): T? { public fun <@kotlin.internal.OnlyInputTypes T> Sequence.indexOf(element: T): Int { var index = 0 for (item in this) { + checkIndexOverflow(index) if (element == item) return index index++ @@ -161,6 +162,7 @@ public fun <@kotlin.internal.OnlyInputTypes T> Sequence.indexOf(element: T): public inline fun Sequence.indexOfFirst(predicate: (T) -> Boolean): Int { var index = 0 for (item in this) { + checkIndexOverflow(index) if (predicate(item)) return index index++ @@ -177,6 +179,7 @@ public inline fun Sequence.indexOfLast(predicate: (T) -> Boolean): Int { var lastIndex = -1 var index = 0 for (item in this) { + checkIndexOverflow(index) if (predicate(item)) lastIndex = index index++ @@ -229,6 +232,7 @@ public fun <@kotlin.internal.OnlyInputTypes T> Sequence.lastIndexOf(element: var lastIndex = -1 var index = 0 for (item in this) { + checkIndexOverflow(index) if (element == item) lastIndex = index index++ @@ -852,7 +856,7 @@ public inline fun > Sequence.mapIndex public inline fun > Sequence.mapIndexedTo(destination: C, transform: (index: Int, T) -> R): C { var index = 0 for (item in this) - destination.add(transform(index++, item)) + destination.add(transform(checkIndexOverflow(index++), item)) return destination } @@ -976,7 +980,7 @@ public inline fun Sequence.any(predicate: (T) -> Boolean): Boolean { */ public fun Sequence.count(): Int { var count = 0 - for (element in this) count++ + for (element in this) checkCountOverflow(++count) return count } @@ -987,7 +991,7 @@ public fun Sequence.count(): Int { */ public inline fun Sequence.count(predicate: (T) -> Boolean): Int { var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) checkCountOverflow(++count) return count } @@ -1013,7 +1017,7 @@ public inline fun Sequence.fold(initial: R, operation: (acc: R, T) -> public inline fun Sequence.foldIndexed(initial: R, operation: (index: Int, acc: R, T) -> R): R { var index = 0 var accumulator = initial - for (element in this) accumulator = operation(index++, accumulator, element) + for (element in this) accumulator = operation(checkIndexOverflow(index++), accumulator, element) return accumulator } @@ -1035,7 +1039,7 @@ public inline fun Sequence.forEach(action: (T) -> Unit): Unit { */ public inline fun Sequence.forEachIndexed(action: (index: Int, T) -> Unit): Unit { var index = 0 - for (item in this) action(index++, item) + for (item in this) action(checkIndexOverflow(index++), item) } /** @@ -1293,7 +1297,7 @@ public inline fun Sequence.reduceIndexed(operation: (index: Int, a var index = 1 var accumulator: S = iterator.next() while (iterator.hasNext()) { - accumulator = operation(index++, accumulator, iterator.next()) + accumulator = operation(checkIndexOverflow(index++), accumulator, iterator.next()) } return accumulator } @@ -1696,7 +1700,7 @@ public fun Sequence.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + checkCountOverflow(++count) } return if (count == 0) Double.NaN else sum / count } @@ -1712,7 +1716,7 @@ public fun Sequence.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + checkCountOverflow(++count) } return if (count == 0) Double.NaN else sum / count } @@ -1728,7 +1732,7 @@ public fun Sequence.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + checkCountOverflow(++count) } return if (count == 0) Double.NaN else sum / count } @@ -1744,7 +1748,7 @@ public fun Sequence.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + checkCountOverflow(++count) } return if (count == 0) Double.NaN else sum / count } @@ -1760,7 +1764,7 @@ public fun Sequence.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + checkCountOverflow(++count) } return if (count == 0) Double.NaN else sum / count } @@ -1776,7 +1780,7 @@ public fun Sequence.average(): Double { var count: Int = 0 for (element in this) { sum += element - count += 1 + checkCountOverflow(++count) } return if (count == 0) Double.NaN else sum / count } diff --git a/libraries/stdlib/common/src/generated/_Strings.kt b/libraries/stdlib/common/src/generated/_Strings.kt index 8e32c35d2c1..c9adef6aa0f 100644 --- a/libraries/stdlib/common/src/generated/_Strings.kt +++ b/libraries/stdlib/common/src/generated/_Strings.kt @@ -883,7 +883,7 @@ public inline fun CharSequence.count(): Int { */ public inline fun CharSequence.count(predicate: (Char) -> Boolean): Int { var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) ++count return count } diff --git a/libraries/stdlib/js/src/kotlin/collections.kt b/libraries/stdlib/js/src/kotlin/collections.kt index bfaed4a2194..68cc558bf40 100644 --- a/libraries/stdlib/js/src/kotlin/collections.kt +++ b/libraries/stdlib/js/src/kotlin/collections.kt @@ -6,6 +6,7 @@ package kotlin.collections import kotlin.comparisons.naturalOrder +import kotlin.internal.InlineOnly import kotlin.random.Random /** Returns the array if it's not `null`, or an empty array otherwise. */ @@ -140,3 +141,22 @@ internal actual inline fun Array.copyToArrayOfAny(isVarargs: Boolean) this else this.copyOf() + + + +@PublishedApi +internal actual fun checkIndexOverflow(index: Int): Int { + if (index < 0) { + throwIndexOverflow() + } + return index +} + +@PublishedApi +internal actual fun checkCountOverflow(count: Int): Int { + if (count < 0) { + throwCountOverflow() + } + return count +} + diff --git a/libraries/stdlib/jvm/src/kotlin/collections/CollectionsJVM.kt b/libraries/stdlib/jvm/src/kotlin/collections/CollectionsJVM.kt index 7df929aa279..632f5e770ce 100644 --- a/libraries/stdlib/jvm/src/kotlin/collections/CollectionsJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/collections/CollectionsJVM.kt @@ -9,6 +9,8 @@ package kotlin.collections import kotlin.* +import kotlin.internal.InlineOnly +import kotlin.internal.apiVersionIsAtLeast /** * Returns an immutable list containing only the specified object [element]. @@ -42,3 +44,31 @@ internal actual fun Array.copyToArrayOfAny(isVarargs: Boolean): Array @Suppress("UNCHECKED_CAST") (this as Array) else java.util.Arrays.copyOf(this, this.size, Array::class.java) + + +@PublishedApi +@SinceKotlin("1.3") +@InlineOnly +internal actual inline fun checkIndexOverflow(index: Int): Int { + if (index < 0) { + if (apiVersionIsAtLeast(1, 3, 0)) + throwIndexOverflow() + else + throw ArithmeticException("Index overflow has happened.") + } + return index +} + +@PublishedApi +@SinceKotlin("1.3") +@InlineOnly +internal actual inline fun checkCountOverflow(count: Int): Int { + if (count < 0) { + if (apiVersionIsAtLeast(1, 3, 0)) + throwCountOverflow() + else + throw ArithmeticException("Count overflow has happened.") + } + return count +} + diff --git a/libraries/stdlib/jvm/test/collections/IndexOverflowJVMTest.kt b/libraries/stdlib/jvm/test/collections/IndexOverflowJVMTest.kt index 94807b9826d..cd1eab7542d 100644 --- a/libraries/stdlib/jvm/test/collections/IndexOverflowJVMTest.kt +++ b/libraries/stdlib/jvm/test/collections/IndexOverflowJVMTest.kt @@ -37,8 +37,114 @@ class IndexOverflowJVMTest { override fun next(): Long = counter++ } } + + fun assertIndexOverflow(f: () -> Unit) { + val ex = assertFailsWith(block = f) + assertTrue(ex.message!!.contains("index", ignoreCase = true)) + } + + fun assertCountOverflow(f: () -> Unit) { + val ex = assertFailsWith(block = f) + assertTrue(ex.message!!.contains("count", ignoreCase = true)) + } + + fun checkIndexPositive(index: Int) { + if (index < 0) fail("Encountered negative index") + } } + @Test + fun indexOfOverflowSequence() { + assertIndexOverflow { maxIndexSequence.indexOf("j") } + assertIndexOverflow { maxIndexSequence.lastIndexOf("k") } + assertIndexOverflow { maxIndexSequence.indexOfFirst { false } } + assertIndexOverflow { maxIndexSequence.indexOfLast { false } } + } + + @Test + fun indexOfOverflowIterable() { + assertIndexOverflow { maxIndexIterable.indexOf("j") } + assertIndexOverflow { maxIndexIterable.lastIndexOf("k") } + assertIndexOverflow { maxIndexIterable.indexOfFirst { false } } + assertIndexOverflow { maxIndexIterable.indexOfLast { false } } + } + + + @Test + fun forEachIndexedOverflow() { + assertIndexOverflow { maxIndexSequence.forEachIndexed { index, _ -> checkIndexPositive(index) } } + assertIndexOverflow { maxIndexIterable.forEachIndexed { index, _ -> checkIndexPositive(index) } } + } + + @Test + fun withIndexOverflow() { + assertIndexOverflow { maxIndexSequence.withIndex().forEach { (index, _) -> checkIndexPositive(index) } } + assertIndexOverflow { maxIndexIterable.withIndex().forEach { (index, _) -> checkIndexPositive(index) } } + } + + + @Test + fun countOverflow() { + assertCountOverflow { repeatCounted("k").count() } + assertCountOverflow { repeatCounted("k").count { true } } + assertCountOverflow { repeatCounted("k").asIterable().count() } + assertCountOverflow { repeatCounted("k").asIterable().count { true } } + } + + @Test + fun averageCountOverflow() { + assertCountOverflow { repeatCounted(1.0).average() } + assertCountOverflow { repeatCounted(1L).asIterable().average() } + } + + + private class CountingCollection : AbstractMutableCollection() { + private var _size = 0 + + override fun add(element: T): Boolean { + if (_size < 0) error("Collection is too long") + _size++ + return true + } + + override val size: Int get() = _size + + override fun iterator(): MutableIterator = error("not implemented") + } + + @Test + fun mapIndexedOverflow() { + assertIndexOverflow { maxIndexSequence.mapIndexed { index, _ -> checkIndexPositive(index) }.forEach { } } + assertIndexOverflow { maxIndexSequence.mapIndexedTo(CountingCollection()) { index, _ -> checkIndexPositive(index) } } + assertIndexOverflow { maxIndexIterable.mapIndexedTo(CountingCollection()) { index, _ -> checkIndexPositive(index) } } + } + + @Test + fun mapNotNullIndexedOverflow() { + assertIndexOverflow { maxIndexSequence.mapIndexedNotNull { index, _ -> checkIndexPositive(index) }.forEach { } } + assertIndexOverflow { maxIndexSequence.mapIndexedNotNullTo(CountingCollection()) { index, _ -> checkIndexPositive(index) } } + assertIndexOverflow { maxIndexIterable.mapIndexedNotNullTo(CountingCollection()) { index, _ -> checkIndexPositive(index) } } + } + + @Test + fun filterIndexedOverflow() { + assertIndexOverflow { maxIndexSequence.filterIndexed { index, _ -> checkIndexPositive(index); true }.forEach { } } + assertIndexOverflow { maxIndexSequence.filterIndexedTo(CountingCollection()) { index, _ -> checkIndexPositive(index); true } } + assertIndexOverflow { maxIndexIterable.filterIndexedTo(CountingCollection()) { index, _ -> checkIndexPositive(index); true } } + } + + + @Test + fun foldIndexedOverflow() { + assertIndexOverflow { maxIndexSequence.foldIndexed("") { index, acc, s -> checkIndexPositive(index); s } } + assertIndexOverflow { maxIndexIterable.foldIndexed("") { index, acc, s -> checkIndexPositive(index); s } } + } + + @Test + fun reduceIndexedOverflow() { + assertIndexOverflow { maxIndexSequence.reduceIndexed { index, acc, s -> checkIndexPositive(index); s } } + assertIndexOverflow { maxIndexIterable.reduceIndexed { index, acc, s -> checkIndexPositive(index); s } } + } @Test diff --git a/libraries/stdlib/src/kotlin/collections/Collections.kt b/libraries/stdlib/src/kotlin/collections/Collections.kt index 7579ad77d3f..e21d0a18458 100644 --- a/libraries/stdlib/src/kotlin/collections/Collections.kt +++ b/libraries/stdlib/src/kotlin/collections/Collections.kt @@ -364,3 +364,20 @@ private fun rangeCheck(size: Int, fromIndex: Int, toIndex: Int) { } +@PublishedApi +@SinceKotlin("1.3") +internal expect fun checkIndexOverflow(index: Int): Int + +@PublishedApi +@SinceKotlin("1.3") +internal expect fun checkCountOverflow(count: Int): Int + + +@PublishedApi +@SinceKotlin("1.3") +internal fun throwIndexOverflow() { throw ArithmeticException("Index overflow has happened.") } + +@PublishedApi +@SinceKotlin("1.3") +internal fun throwCountOverflow() { throw ArithmeticException("Count overflow has happened.") } + diff --git a/libraries/stdlib/src/kotlin/collections/Iterators.kt b/libraries/stdlib/src/kotlin/collections/Iterators.kt index 7455ee2e959..5b08fb95137 100644 --- a/libraries/stdlib/src/kotlin/collections/Iterators.kt +++ b/libraries/stdlib/src/kotlin/collections/Iterators.kt @@ -37,5 +37,5 @@ public inline fun Iterator.forEach(operation: (T) -> Unit): Unit { internal class IndexingIterator(private val iterator: Iterator) : Iterator> { private var index = 0 final override fun hasNext(): Boolean = iterator.hasNext() - final override fun next(): IndexedValue = IndexedValue(index++, iterator.next()) + final override fun next(): IndexedValue = IndexedValue(checkIndexOverflow(index++), iterator.next()) } diff --git a/libraries/stdlib/src/kotlin/collections/Sequences.kt b/libraries/stdlib/src/kotlin/collections/Sequences.kt index f23d0d64bfa..7430d3ff881 100644 --- a/libraries/stdlib/src/kotlin/collections/Sequences.kt +++ b/libraries/stdlib/src/kotlin/collections/Sequences.kt @@ -178,7 +178,7 @@ constructor(private val sequence: Sequence, private val transformer: (Int, T) val iterator = sequence.iterator() var index = 0 override fun next(): R { - return transformer(index++, iterator.next()) + return transformer(checkIndexOverflow(index++), iterator.next()) } override fun hasNext(): Boolean { @@ -197,7 +197,7 @@ constructor(private val sequence: Sequence) : Sequence> { val iterator = sequence.iterator() var index = 0 override fun next(): IndexedValue { - return IndexedValue(index++, iterator.next()) + return IndexedValue(checkIndexOverflow(index++), iterator.next()) } override fun hasNext(): Boolean { 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 76a6ec47493..73b60dcee25 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 @@ -2263,6 +2263,8 @@ public final class kotlin/collections/CollectionsKt { public static final fun takeLast (Ljava/util/List;I)Ljava/util/List; public static final fun takeLastWhile (Ljava/util/List;Lkotlin/jvm/functions/Function1;)Ljava/util/List; public static final fun takeWhile (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Ljava/util/List; + public static final fun throwCountOverflow ()V + public static final fun throwIndexOverflow ()V public static final fun toBooleanArray (Ljava/util/Collection;)[Z public static final fun toByteArray (Ljava/util/Collection;)[B public static final fun toCharArray (Ljava/util/Collection;)[C diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt index 7ecaa483047..2343ccaa862 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt @@ -155,6 +155,7 @@ object Aggregates : TemplateGroupBase() { doc { "Returns the number of ${f.element.pluralize()} matching the given [predicate]." } returns("Int") body { + fun checkOverflow(value: String) = if (f == Sequences || f == Iterables) "checkCountOverflow($value)" else value """ ${when (f) { Iterables -> "if (this is Collection && isEmpty()) return 0" @@ -162,7 +163,7 @@ object Aggregates : TemplateGroupBase() { else -> "" }} var count = 0 - for (element in this) if (predicate(element)) count++ + for (element in this) if (predicate(element)) ${checkOverflow("++count")} return count """ } @@ -175,10 +176,11 @@ object Aggregates : TemplateGroupBase() { doc { "Returns the number of ${f.element.pluralize()} in this ${f.collection}." } returns("Int") body { + fun checkOverflow(value: String) = if (f == Sequences || f == Iterables) "checkCountOverflow($value)" else value """ ${if (f == Iterables) "if (this is Collection) return size" else ""} var count = 0 - for (element in this) count++ + for (element in this) ${checkOverflow("++count")} return count """ } @@ -474,10 +476,11 @@ object Aggregates : TemplateGroupBase() { typeParam("R") returns("R") body { + fun checkOverflow(value: String) = if (f == Sequences || f == Iterables) "checkIndexOverflow($value)" else value """ var index = 0 var accumulator = initial - for (element in this) accumulator = operation(index++, accumulator, element) + for (element in this) accumulator = operation(${checkOverflow("index++")}, accumulator, element) return accumulator """ } @@ -617,6 +620,7 @@ object Aggregates : TemplateGroupBase() { typeParam("T : S") returns("S") body { + fun checkOverflow(value: String) = if (f == Sequences || f == Iterables) "checkIndexOverflow($value)" else value """ val iterator = this.iterator() if (!iterator.hasNext()) throw UnsupportedOperationException("Empty ${f.doc.collection} can't be reduced.") @@ -624,7 +628,7 @@ object Aggregates : TemplateGroupBase() { var index = 1 var accumulator: S = iterator.next() while (iterator.hasNext()) { - accumulator = operation(index++, accumulator, iterator.next()) + accumulator = operation(${checkOverflow("index++")}, accumulator, iterator.next()) } return accumulator """ @@ -899,9 +903,10 @@ object Aggregates : TemplateGroupBase() { """ } returns("Unit") body { + fun checkOverflow(value: String) = if (f == Sequences || f == Iterables) "checkIndexOverflow($value)" else value """ var index = 0 - for (item in this) action(index++, item) + for (item in this) action(${checkOverflow("index++")}, item) """ } } diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Elements.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Elements.kt index a7b39a9e986..7df89e5301f 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Elements.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Elements.kt @@ -52,6 +52,7 @@ object Elements : TemplateGroupBase() { ${if (f == Iterables) "if (this is List) return this.indexOf(element)" else ""} var index = 0 for (item in this) { + checkIndexOverflow(index) if (element == item) return index index++ @@ -106,6 +107,7 @@ object Elements : TemplateGroupBase() { var lastIndex = -1 var index = 0 for (item in this) { + checkIndexOverflow(index) if (element == item) lastIndex = index index++ @@ -157,12 +159,13 @@ object Elements : TemplateGroupBase() { """ var index = 0 for (item in this) { + ${if (f != Lists) "checkIndexOverflow(index)" else ""} if (predicate(item)) return index index++ } return -1 - """ + """.lines().filterNot { it.isBlank() }.joinToString("\n") } body(CharSequences, ArraysOfPrimitives, ArraysOfObjects) { @@ -190,6 +193,7 @@ object Elements : TemplateGroupBase() { var lastIndex = -1 var index = 0 for (item in this) { + checkIndexOverflow(index) if (predicate(item)) lastIndex = index index++ diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Mapping.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Mapping.kt index 16dbd83b167..42ebd520bad 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Mapping.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Mapping.kt @@ -204,10 +204,11 @@ object Mapping : TemplateGroupBase() { returns("C") body { + fun checkOverflow(value: String) = if (f == Sequences || f == Iterables) "checkIndexOverflow($value)" else value """ var index = 0 for (item in this) - destination.add(transform(index++, item)) + destination.add(transform(${checkOverflow("index++")}, item)) return destination """ } diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Numeric.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Numeric.kt index e5ccba77372..16e5c040679 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Numeric.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Numeric.kt @@ -41,12 +41,13 @@ object Numeric : TemplateGroupBase() { returns("Double") platformName("averageOf") body { + fun checkOverflow(value: String) = if (f == Family.Sequences || f == Family.Iterables) "checkCountOverflow($value)" else value """ var sum: Double = 0.0 var count: Int = 0 for (element in this) { sum += element - count += 1 + ${checkOverflow("++count")} } return if (count == 0) Double.NaN else sum / count """