diff --git a/libraries/stdlib/common/src/kotlin/TextH.kt b/libraries/stdlib/common/src/kotlin/TextH.kt index b5a4009e5d4..2d0a6305d64 100644 --- a/libraries/stdlib/common/src/kotlin/TextH.kt +++ b/libraries/stdlib/common/src/kotlin/TextH.kt @@ -100,6 +100,9 @@ public expect fun String(chars: CharArray): String /** * Converts the characters from a portion of the specified array to a string. + * + * @throws IndexOutOfBoundsException if either [offset] or [length] are less than zero + * or `offset + length` is out of [chars] array bounds. */ @SinceKotlin("1.2") public expect fun String(chars: CharArray, offset: Int, length: Int): String diff --git a/libraries/stdlib/js/src/kotlin/string.kt b/libraries/stdlib/js/src/kotlin/string.kt index 1f20bf6d13c..74c8a52dd06 100644 --- a/libraries/stdlib/js/src/kotlin/string.kt +++ b/libraries/stdlib/js/src/kotlin/string.kt @@ -11,17 +11,29 @@ import kotlin.js.RegExp * Converts the characters in the specified array to a string. */ @SinceKotlin("1.2") -@kotlin.internal.InlineOnly -public actual inline fun String(chars: CharArray): String { - return js("String.fromCharCode").apply(null, chars) +public actual fun String(chars: CharArray): String { + var result = "" + for (char in chars) { + result += char + } + return result } /** * Converts the characters from a portion of the specified array to a string. + * + * @throws IndexOutOfBoundsException if either [offset] or [length] are less than zero + * or `offset + length` is out of [chars] array bounds. */ @SinceKotlin("1.2") public actual fun String(chars: CharArray, offset: Int, length: Int): String { - return String(chars.copyOfRange(offset, offset + length)) + if (offset < 0 || length < 0 || chars.size - offset < length) + throw IndexOutOfBoundsException("size: ${chars.size}; offset: $offset; length: $length") + var result = "" + for (index in offset until offset + length) { + result += chars[index] + } + return result } /** diff --git a/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt b/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt index b2bd93fece7..0a9d375780f 100644 --- a/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt @@ -272,6 +272,9 @@ public actual inline fun String(chars: CharArray): String = /** * Converts the characters from a portion of the specified array to a string. + * + * @throws IndexOutOfBoundsException if either [offset] or [length] are less than zero + * or `offset + length` is out of [chars] array bounds. */ @kotlin.internal.InlineOnly public actual inline fun String(chars: CharArray, offset: Int, length: Int): String = diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index a163296af67..ded4bc92967 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -53,11 +53,21 @@ class StringTest { @Test fun stringFromCharArraySlice() { val chars: CharArray = charArrayOf('K', 'o', 't', 'l', 'i', 'n', ' ', 'r', 'u', 'l', 'e', 's') assertEquals("rule", String(chars, 7, 4)) + + val longChars = CharArray(200_000) { 'k' } + val longString = String(longChars, 1000, 190_000) + assertEquals(190_000, longString.length) + assertTrue(longString.all { it == 'k' }) } @Test fun stringFromCharArray() { val chars: CharArray = charArrayOf('K', 'o', 't', 'l', 'i', 'n') assertEquals("Kotlin", String(chars)) + + val longChars = CharArray(200_000) { 'k' } + val longString = String(longChars) + assertEquals(200_000, longString.length) + assertTrue(longString.all { it == 'k' }) } @Test fun stringFromCharArrayUnicodeSurrogatePairs() { @@ -67,6 +77,16 @@ class StringTest { assertEquals("Ŭᎍ🀺", String(chars, 3, 4)) } + @Test fun stringFromCharArrayOutOfBounds() { + fun test(chars: CharArray) { + assertFailsWith { String(chars, -1, 1) } + assertFailsWith { String(chars, 1, -1) } + assertFailsWith { String(chars, chars.size - 1, 2) } + } + test(CharArray(16) { 'k' }) + test(CharArray(160_000) { 'k' }) + } + @Test fun isEmptyAndBlank() = withOneCharSequenceArg { arg1 -> class Case(val value: String?, val isNull: Boolean = false, val isEmpty: Boolean = false, val isBlank: Boolean = false)