From 30c769c19a8afb5117771354e3afe4251312704e Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Sat, 29 Dec 2018 05:25:19 +0300 Subject: [PATCH] Avoid hitting max argument limit in String(chars) Rewrite CharArray to String conversions to appending chars one by one. Refine parameter checking in String(chars, offset, length) to adhere to the common exception contract and document it. #KT-29003 --- libraries/stdlib/common/src/kotlin/TextH.kt | 3 +++ libraries/stdlib/js/src/kotlin/string.kt | 20 +++++++++++++++---- .../stdlib/jvm/src/kotlin/text/StringsJVM.kt | 3 +++ libraries/stdlib/test/text/StringTest.kt | 20 +++++++++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) 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)