Fix utf8 conversion (#1121)

This commit is contained in:
ilmat192
2017-12-12 17:30:50 +07:00
committed by Nikolay Igotti
parent 8fa8e2ebc8
commit cba7319e98
21 changed files with 709 additions and 43 deletions
@@ -18,9 +18,9 @@ package kotlinx.cinterop
import konan.internal.Intrinsic
internal fun decodeFromUtf8(bytes: ByteArray): String = kotlin.text.fromUtf8Array(bytes, 0, bytes.size)
internal fun decodeFromUtf8(bytes: ByteArray): String = bytes.stringFromUtf8()
fun encodeToUtf8(str: String): ByteArray = kotlin.text.toUtf8Array(str, 0, str.length)
fun encodeToUtf8(str: String): ByteArray = str.toUtf8()
@Intrinsic
external fun bitsToFloat(bits: Int): Float
+7
View File
@@ -1520,6 +1520,13 @@ task chars0(type: RunKonanTest) {
expectedExitStatus = 0
}
task utf8(type: RunKonanTest) {
expectedFail = (project.testTarget == 'wasm32') // Uses exceptions.
goldValue = "Hello\nПривет\n\uD800\uDC00\n\n\uFFFD\uFFFD\n\uFFFD12\n\uFFFD12\n12\uFFFD\n\uD83D\uDE25\n"
source = "runtime/text/utf8.kt"
}
task catch1(type: RunKonanTest) {
expectedFail = (project.testTarget == 'wasm32') // Uses exceptions.
goldValue = "Before\nCaught Throwable\nDone\n"
+369
View File
@@ -0,0 +1,369 @@
package runtime.text.utf8
import kotlin.test.*
import kotlin.reflect.KClass
// region Util
fun assertEquals(expected: ByteArray, actual: ByteArray, message: String) =
assertTrue(expected.contentEquals(actual), message)
fun checkUtf16to8(string: String, expected: IntArray, conversion: String.() -> ByteArray) {
expected.forEach {
assertTrue(it in Byte.MIN_VALUE..Byte.MAX_VALUE, "Expected array contains illegal values")
}
val expectedBytes = ByteArray(expected.size) { i -> expected[i].toByte() }
val actual = string.conversion()
assertEquals(expectedBytes, actual, """
Assert failed for string: $string
Expected: ${expected.joinToString()}
Actual: ${actual.joinToString()}
""".trimIndent())
}
fun checkUtf16to8Replacing(string: String, expected: IntArray) = checkUtf16to8(string, expected) { toUtf8() }
fun checkUtf16to8Throwing(string: String, expected: IntArray) = checkUtf16to8(string, expected) { toUtf8OrThrow() }
fun checkUtf16to8Replacing(string: String, expected: IntArray, start: Int, size: Int) =
checkUtf16to8(string, expected) { toUtf8(start, size) }
fun checkUtf16to8Throwing(string: String, expected: IntArray, start: Int, size: Int) =
checkUtf16to8(string, expected) { toUtf8OrThrow(start, size) }
fun checkUtf8to16(expected: String, array: IntArray, conversion: ByteArray.() -> String) {
array.forEach {
assertTrue(it in Byte.MIN_VALUE..Byte.MAX_VALUE, "Expected array contains illegal values")
}
val arrayBytes = ByteArray(array.size) { i -> array[i].toByte() }
val actual = arrayBytes.conversion()
assertEquals(expected, actual, """
Assert failed for string: $expected
Expected: $expected
Actual: $actual
""".trimIndent())
}
fun checkUtf8to16Replacing(expected: String, array: IntArray) = checkUtf8to16(expected, array) { stringFromUtf8() }
fun checkUtf8to16Throwing(expected: String, array: IntArray) = checkUtf8to16(expected, array) { stringFromUtf8OrThrow() }
fun checkUtf8to16Replacing(expected: String, array: IntArray, start: Int, size: Int) =
checkUtf8to16(expected, array) { stringFromUtf8(start, size) }
fun checkUtf8to16Throwing(expected: String, array: IntArray, start: Int, size: Int) =
checkUtf8to16(expected, array) { stringFromUtf8OrThrow(start, size) }
fun <T: Any> checkThrows(e: KClass<T>, string: String, action: () -> Unit) {
var exception: Throwable? = null
try {
action()
} catch (e: Throwable) {
exception = e
}
assertNotNull(exception, "No excpetion was thrown for string: $string")
assertTrue(e.isInstance(exception),"""
Wrong exception was thrown for string: $string
Expected: ${e.qualifiedName}
Actual: ${exception!!::class.qualifiedName}
""".trimIndent())
}
fun checkUtf16to8Throws(string: String) = checkThrows(IllegalCharacterConversionException::class, string) {
string.toUtf8OrThrow()
}
fun checkUtf16to8Throws(string: String, start: Int, size: Int) =
checkThrows(IllegalCharacterConversionException::class, string) {
string.toUtf8OrThrow(start, size)
}
fun checkUtf8to16Throws(string: String, array: IntArray)
= checkThrows(IllegalCharacterConversionException::class, string) {
array.forEach {
assertTrue(it in Byte.MIN_VALUE..Byte.MAX_VALUE, "Expected array contains illegal values")
}
val arrayBytes = ByteArray(array.size) { i -> array[i].toByte() }
arrayBytes.stringFromUtf8OrThrow()
}
fun checkUtf8to16Throws(string: String, array: IntArray, start: Int, size: Int)
= checkThrows(IllegalCharacterConversionException::class, string) {
array.forEach {
assertTrue(it in Byte.MIN_VALUE..Byte.MAX_VALUE, "Expected array contains illegal values")
}
val arrayBytes = ByteArray(array.size) { i -> array[i].toByte() }
arrayBytes.stringFromUtf8OrThrow(start, size)
}
fun checkDobuleConversionThrows(string: String) = checkThrows(NumberFormatException::class, string) {
string.toDouble()
}
fun checkFloatConversionThrows(string: String) = checkThrows(NumberFormatException::class, string) {
string.toFloat()
}
// endregion
// region UTF-16 -> UTF-8 conversion
fun test16to8() {
// Test manual conversion with replacement.
// Valid strings.
checkUtf16to8Replacing("Hello", intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()))
checkUtf16to8Replacing("Привет", intArrayOf(-48, -97, -47, -128, -48, -72, -48, -78, -48, -75, -47, -126))
checkUtf16to8Replacing("\uD800\uDC00", intArrayOf(-16, -112, -128, -128))
checkUtf16to8Replacing("", intArrayOf())
// Illegal surrogate pair -> replace with default
checkUtf16to8Replacing("\uDC00\uD800", intArrayOf(-17, -65, -67, -17, -65, -67))
// Different kinds of input
checkUtf16to8Replacing("\uD800\uDC001\uDC00\uD800",
intArrayOf(-16, -112, -128, -128, '1'.toInt(), -17, -65, -67, -17, -65, -67))
// Lone surrogate - replace with default
checkUtf16to8Replacing("\uD80012", intArrayOf(-17, -65, -67, '1'.toInt(), '2'.toInt()))
checkUtf16to8Replacing("\uDC0012", intArrayOf(-17, -65, -67, '1'.toInt(), '2'.toInt()))
checkUtf16to8Replacing("12\uD800", intArrayOf('1'.toInt(), '2'.toInt(), -17, -65, -67))
// Test manual conversion with an exception if an input is invalid.
// Valid strings.
checkUtf16to8Throwing("Hello", intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()))
checkUtf16to8Throwing("Привет", intArrayOf(-48, -97, -47, -128, -48, -72, -48, -78, -48, -75, -47, -126))
checkUtf16to8Throwing("\uD800\uDC00", intArrayOf(-16, -112, -128, -128))
checkUtf16to8Replacing("", intArrayOf())
// Illegal surrogate pair -> throw
checkUtf16to8Throws("\uDC00\uD800")
// Different kinds of input (including illegal one) -> throw
checkUtf16to8Throws("\uD800\uDC001\uDC00\uD800")
// Lone surrogate - throw
checkUtf16to8Throws("\uD80012")
checkUtf16to8Throws("\uDC0012")
checkUtf16to8Throws("12\uD800")
// Test double parsing.
assertEquals(4.2, "4.2".toDouble())
// Illegal surrogate pair -> throw
checkDobuleConversionThrows("\uDC00\uD800")
// Different kinds of input (including illegal one) -> throw
checkDobuleConversionThrows("\uD800\uDC001\uDC00\uD800")
// Lone surrogate - throw
checkDobuleConversionThrows("\uD80012")
checkDobuleConversionThrows("\uDC0012")
checkDobuleConversionThrows("12\uD800")
// Test float parsing.
assertEquals(4.2F, "4.2".toFloat())
// Illegal surrogate pair -> throw
checkFloatConversionThrows("\uDC00\uD800")
// Different kinds of input (including illegal one) -> throw
checkFloatConversionThrows("\uD800\uDC001\uDC00\uD800")
// Lone surrogate - throw
checkFloatConversionThrows("\uD80012")
checkFloatConversionThrows("\uDC0012")
checkFloatConversionThrows("12\uD800")
}
fun test16to8CustomBorders() {
// Test manual conversion with replacement and custom borders.
// Valid strings.
checkUtf16to8Replacing("Hello!", intArrayOf('H'.toInt(), 'e'.toInt()), 0, 2)
checkUtf16to8Replacing("Hello!", intArrayOf('e'.toInt(), 'l'.toInt()), 1, 2)
checkUtf16to8Replacing("Hello!", intArrayOf('o'.toInt(), '!'.toInt()), 4, 2)
checkUtf16to8Replacing("Hello!", intArrayOf(), 0, 0)
checkUtf16to8Replacing("Hello!", intArrayOf(), 10, 0)
checkUtf16to8Replacing("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 0, 4)
checkUtf16to8Replacing("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 2, 4)
checkUtf16to8Replacing("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 4, 4)
// Illegal surrogate pair -> replace with default
checkUtf16to8Replacing("\uDC00\uD80012",
intArrayOf(-17, -65, -67, -17, -65, -67, '1'.toInt()), 0, 3)
checkUtf16to8Replacing("1\uDC00\uD8002",
intArrayOf(-17, -65, -67, -17, -65, -67, '2'.toInt()), 1, 3)
checkUtf16to8Replacing("12\uDC00\uD800",
intArrayOf('2'.toInt(), -17, -65, -67, -17, -65, -67), 1, 3)
// Lone surrogate - replace with default
checkUtf16to8Replacing("1\uD800\uDC002", intArrayOf('1'.toInt(), -17, -65, -67), 0, 2)
checkUtf16to8Replacing("1\uD800\uDC002", intArrayOf(-17, -65, -67, '2'.toInt()), 2, 2)
// Index out of bound
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8(-1, 4)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8(5, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8(2, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8(3, -2)}
// Test manual conversion with an exception if an input is invalid and custom borders.
// Valid strings.
checkUtf16to8Throwing("Hello!", intArrayOf('H'.toInt(), 'e'.toInt()), 0, 2)
checkUtf16to8Throwing("Hello!", intArrayOf('e'.toInt(), 'l'.toInt()), 1, 2)
checkUtf16to8Throwing("Hello!", intArrayOf('o'.toInt(), '!'.toInt()), 4, 2)
checkUtf16to8Throwing("Hello!", intArrayOf(), 0, 0)
checkUtf16to8Throwing("Hello!", intArrayOf(), 10, 0)
checkUtf16to8Throwing("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 0, 4)
checkUtf16to8Throwing("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 2, 4)
checkUtf16to8Throwing("\uD800\uDC00\uD800\uDC00\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128), 4, 4)
// Illegal surrogate pair -> throw
checkUtf16to8Throws("\uDC00\uD80012", 0, 3)
checkUtf16to8Throws("1\uDC00\uD8002", 1, 3)
checkUtf16to8Throws("12\uDC00\uD800", 1, 3)
// Lone surrogate -> throw
checkUtf16to8Throws("1\uD800\uDC002", 0, 2)
checkUtf16to8Throws("1\uD800\uDC002", 2, 2)
// Index out of bound
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8OrThrow(-1, 4)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8OrThrow(5, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8OrThrow(2, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { "Hello".toUtf8OrThrow(3, -2)}
}
fun testPrint() {
// Valid strings.
println("Hello")
println("Привет")
println("\uD800\uDC00")
println("")
// Illegal surrogate pair -> default output
println("\uDC00\uD800")
// Lone surrogate -> default output
println("\uD80012")
println("\uDC0012")
println("12\uD800")
// https://github.com/JetBrains/kotlin-native/issues/1091
val array = byteArrayOf(0xF0.toByte(), 0x9F.toByte(), 0x98.toByte(), 0xA5.toByte())
val badStr = array.stringFromUtf8()
assertEquals(2, badStr.length)
println(badStr)
}
// endregion
// region UTF-8 -> UTF-16 conversion
fun test8to16() {
// Test manual conversion with replacement.
// Valid strings.
checkUtf8to16Replacing("Hello", intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()))
checkUtf8to16Replacing("Привет", intArrayOf(-48, -97, -47, -128, -48, -72, -48, -78, -48, -75, -47, -126))
checkUtf8to16Replacing("\uD800\uDC00", intArrayOf(-16, -112, -128, -128))
checkUtf8to16Replacing("", intArrayOf())
// Incorrect UTF-8 lead character.
checkUtf8to16Replacing("\uFFFD1", intArrayOf(-1, '1'.toInt()))
// Incomplete codepoint.
checkUtf8to16Replacing("\uFFFD1", intArrayOf(-16, -97, -104, '1'.toInt()))
checkUtf8to16Replacing("\uFFFD1\uFFFD", intArrayOf(-16, -97, -104, '1'.toInt(), -16, -97, -104))
// Test manual conversion with exception throwing
// Valid strings.
checkUtf8to16Throwing("Hello", intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()))
checkUtf8to16Throwing("Привет", intArrayOf(-48, -97, -47, -128, -48, -72, -48, -78, -48, -75, -47, -126))
checkUtf8to16Throwing("\uD800\uDC00", intArrayOf(-16, -112, -128, -128))
checkUtf8to16Throwing("", intArrayOf())
// Incorrect UTF-8 lead character -> throw.
checkUtf8to16Throws("\uFFFD1", intArrayOf(-1, '1'.toInt()))
// Incomplete codepoint -> throw.
checkUtf8to16Throws("\uFFFD1", intArrayOf(-16, -97, -104, '1'.toInt()))
checkUtf8to16Throws("\uFFFD1\uFFFD", intArrayOf(-16, -97, -104, '1'.toInt(), -16, -97, -104))
}
fun test8to16CustomBorders() {
// Conversion with replacement.
checkUtf8to16Replacing("He",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()),0, 2)
checkUtf8to16Replacing("ll",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 2, 2)
checkUtf8to16Replacing("lo",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 3, 2)
checkUtf8to16Replacing("",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 0, 0)
checkUtf8to16Replacing("",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 10, 0)
checkUtf8to16Replacing("\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128),
0, 8)
checkUtf8to16Replacing("\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128),
4, 8)
checkUtf8to16Replacing("\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128),
8, 8)
// Incorrect UTF-8 lead character.
checkUtf8to16Replacing("\uFFFD1", intArrayOf(-1, '1'.toInt(), '2'.toInt()), 0, 2)
checkUtf8to16Replacing("\uFFFD2", intArrayOf('1'.toInt(), -1, '2'.toInt(), '3'.toInt()), 1, 2)
checkUtf8to16Replacing("2\uFFFD", intArrayOf('1'.toInt(), '2'.toInt(), -1), 1, 2)
// Incomplete codepoint.
checkUtf8to16Replacing("\uFFFD1", intArrayOf(-16, -97, -104, '1'.toInt(), '2'.toInt()), 0, 4)
checkUtf8to16Replacing("\uFFFD2", intArrayOf('1'.toInt(), -16, -97, -104, '2'.toInt(), '3'.toInt()), 1, 4)
checkUtf8to16Replacing("2\uFFFD", intArrayOf('1'.toInt(), '2'.toInt(), -16, -97, -104), 1, 4)
// Index out of bound
val helloArray = byteArrayOf('H'.toByte(), 'e'.toByte(), 'l'.toByte(), 'l'.toByte(), 'o'.toByte())
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8(-1, 4)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8(5, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8(2, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8(3, -2)}
// Conversion with throwing
checkUtf8to16Throwing("He",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()),0, 2)
checkUtf8to16Throwing("ll",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 2, 2)
checkUtf8to16Throwing("lo",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 3, 2)
checkUtf8to16Throwing("",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 0, 0)
checkUtf8to16Throwing("",
intArrayOf('H'.toInt(), 'e'.toInt(), 'l'.toInt(), 'l'.toInt(), 'o'.toInt()), 10, 0)
checkUtf8to16Throwing("\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128),
0, 8)
checkUtf8to16Throwing("\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128),
4, 8)
checkUtf8to16Throwing("\uD800\uDC00\uD800\uDC00",
intArrayOf(-16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128, -16, -112, -128, -128),
8, 8)
// Incorrect UTF-8 lead character.
checkUtf8to16Throws("\uFFFD1", intArrayOf(-1, '1'.toInt(), '2'.toInt()), 0, 2)
checkUtf8to16Throws("\uFFFD2", intArrayOf('1'.toInt(), -1, '2'.toInt(), '3'.toInt()), 1, 2)
checkUtf8to16Throws("2\uFFFD", intArrayOf('1'.toInt(), '2'.toInt(), -1), 1, 2)
// Incomplete codepoint.
checkUtf8to16Throws("\uFFFD1", intArrayOf(-16, -97, -104, '1'.toInt(), '2'.toInt()), 0, 4)
checkUtf8to16Throws("\uFFFD2", intArrayOf('1'.toInt(), -16, -97, -104, '2'.toInt(), '3'.toInt()), 1, 4)
checkUtf8to16Throws("2\uFFFD", intArrayOf('1'.toInt(), '2'.toInt(), -16, -97, -104), 1, 4)
// Index out of bound
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8OrThrow(-1, 4)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8OrThrow(5, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8OrThrow(2, 10)}
checkThrows(IndexOutOfBoundsException::class, "Hello") { helloArray.stringFromUtf8OrThrow(3, -2)}
}
// endregion
@Test fun runTest() {
test16to8()
test16to8CustomBorders()
test8to16()
test8to16CustomBorders()
testPrint()
}
+2 -1
View File
@@ -30,7 +30,8 @@ void Kotlin_io_Console_print(KString message) {
// TODO: system stdout must be aware about UTF-8.
const KChar* utf16 = CharArrayAddressOfElementAt(message, 0);
KStdString utf8;
utf8::unchecked::utf16to8(utf16, utf16 + message->count_, back_inserter(utf8));
// Replace incorrect sequences with a default codepoint (see utf8::with_replacement::default_replacement)
utf8::with_replacement::utf16to8(utf16, utf16 + message->count_, back_inserter(utf8));
konan::consoleWriteUtf8(utf8.c_str(), utf8.size());
}
+2
View File
@@ -48,6 +48,8 @@ void ThrowNumberFormatException();
void ThrowOutOfMemoryError();
// Throws not implemented error.
void ThrowNotImplementedError();
// Throws illegal character conversion exception (used in UTF8/UTF16 conversions).
void ThrowIllegalCharacterConversionException();
// Prints out mesage of Throwable.
void PrintThrowable(KRef);
+65 -21
View File
@@ -37,16 +37,55 @@
namespace {
OBJ_GETTER(utf8ToUtf16, const char* rawString, size_t rawStringLength) {
uint32_t charCount = utf8::unchecked::distance(rawString, rawString + rawStringLength);
ArrayHeader* result = AllocArrayInstance(
theStringTypeInfo, charCount, OBJ_RESULT)->array();
typedef std::back_insert_iterator<KStdString> KStdStringInserter;
typedef KChar* utf8to16(const char*, const char*, KChar*);
typedef KStdStringInserter utf16to8(const KChar*,const KChar*, KStdStringInserter);
KStdStringInserter utf16toUtf8OrThrow(const KChar* start, const KChar* end, KStdStringInserter result) {
TRY_CATCH(result = utf8::utf16to8(start, end, result),
result = utf8::unchecked::utf16to8(start, end, result),
ThrowIllegalCharacterConversionException());
return result;
}
template<utf8to16 conversion>
OBJ_GETTER(utf8ToUtf16Impl, const char* rawString, const char* end, uint32_t charCount) {
ArrayHeader* result = AllocArrayInstance(theStringTypeInfo, charCount, OBJ_RESULT)->array();
KChar* rawResult = CharArrayAddressOfElementAt(result, 0);
auto convertResult =
utf8::unchecked::utf8to16(rawString, rawString + rawStringLength, rawResult);
auto convertResult = conversion(rawString, end, rawResult);
RETURN_OBJ(result->obj());
}
template<utf16to8 conversion>
OBJ_GETTER(utf16ToUtf8Impl, KString thiz, KInt start, KInt size) {
RuntimeAssert(thiz->type_info() == theStringTypeInfo, "Must use String");
if (start < 0 || size < 0 || size > thiz->count_ - start) {
ThrowArrayIndexOutOfBoundsException();
}
const KChar* utf16 = CharArrayAddressOfElementAt(thiz, start);
KStdString utf8;
conversion(utf16, utf16 + size, back_inserter(utf8));
ArrayHeader* result = AllocArrayInstance(theByteArrayTypeInfo, utf8.size(), OBJ_RESULT)->array();
::memcpy(ByteArrayAddressOfElementAt(result, 0), utf8.c_str(), utf8.size());
RETURN_OBJ(result->obj());
}
OBJ_GETTER(utf8ToUtf16OrThrow, const char* rawString, size_t rawStringLength) {
const char* end = rawString + rawStringLength;
uint32_t charCount;
TRY_CATCH(charCount = utf8::utf16_length(rawString, end),
charCount = utf8::unchecked::utf16_length(rawString, end),
ThrowIllegalCharacterConversionException());
RETURN_RESULT_OF(utf8ToUtf16Impl<utf8::unchecked::utf8to16>, rawString, end, charCount);
}
OBJ_GETTER(utf8ToUtf16, const char* rawString, size_t rawStringLength) {
const char* end = rawString + rawStringLength;
uint32_t charCount = utf8::with_replacement::utf16_length(rawString, end);
RETURN_RESULT_OF(utf8ToUtf16Impl<utf8::with_replacement::utf8to16>, rawString, end, charCount);
}
// Case conversion is derived work from Apache Harmony.
// Unicode 3.0.1 (same as Unicode 3.0.0)
enum CharacterClass {
@@ -731,32 +770,37 @@ KInt Kotlin_String_getStringLength(KString thiz) {
return thiz->count_;
}
OBJ_GETTER(Kotlin_String_fromUtf8Array, KConstRef thiz, KInt start, KInt size) {
const char* byteArrayAsCString(KConstRef thiz, KInt start, KInt size) {
const ArrayHeader* array = thiz->array();
RuntimeAssert(array->type_info() == theByteArrayTypeInfo, "Must use a byte array");
if (start < 0 || size < 0 || size > array->count_ - start) {
ThrowArrayIndexOutOfBoundsException();
}
return reinterpret_cast<const char*>(ByteArrayAddressOfElementAt(array, start));
}
OBJ_GETTER(Kotlin_ByteArray_stringFromUtf8OrThrow, KConstRef thiz, KInt start, KInt size) {
const char* rawString = byteArrayAsCString(thiz, start, size);
if (size == 0) {
RETURN_RESULT_OF0(TheEmptyString);
}
RETURN_RESULT_OF(utf8ToUtf16OrThrow, rawString, size);
}
OBJ_GETTER(Kotlin_ByteArray_stringFromUtf8, KConstRef thiz, KInt start, KInt size) {
const char* rawString = byteArrayAsCString(thiz, start, size);
if (size == 0) {
RETURN_RESULT_OF0(TheEmptyString);
}
const char* rawString =
reinterpret_cast<const char*>(ByteArrayAddressOfElementAt(array, start));
RETURN_RESULT_OF(utf8ToUtf16, rawString, size);
}
OBJ_GETTER(Kotlin_String_toUtf8Array, KString thiz, KInt start, KInt size) {
RuntimeAssert(thiz->type_info() == theStringTypeInfo, "Must use String");
if (start < 0 || size < 0 || size > thiz->count_ - start) {
ThrowArrayIndexOutOfBoundsException();
}
const KChar* utf16 = CharArrayAddressOfElementAt(thiz, start);
KStdString utf8;
utf8::unchecked::utf16to8(utf16, utf16 + size, back_inserter(utf8));
ArrayHeader* result = AllocArrayInstance(
theByteArrayTypeInfo, utf8.size(), OBJ_RESULT)->array();
::memcpy(ByteArrayAddressOfElementAt(result, 0), utf8.c_str(), utf8.size());
RETURN_OBJ(result->obj());
OBJ_GETTER(Kotlin_String_toUtf8, KString thiz, KInt start, KInt size) {
RETURN_RESULT_OF(utf16ToUtf8Impl<utf8::with_replacement::utf16to8>, thiz, start, size);
}
OBJ_GETTER(Kotlin_String_toUtf8OrThrow, KString thiz, KInt start, KInt size) {
RETURN_RESULT_OF(utf16ToUtf8Impl<utf16toUtf8OrThrow>, thiz, start, size);
}
OBJ_GETTER(Kotlin_String_fromCharArray, KConstRef thiz, KInt start, KInt size) {
+1 -1
View File
@@ -27,7 +27,7 @@ extern "C" {
#endif
OBJ_GETTER(CreateStringFromCString, const char* cstring);
OBJ_GETTER(CreateStringFromUtf8, const char* utf8, uint32_t size);
OBJ_GETTER(CreateStringFromUtf8, const char* utf8, uint32_t lengthBytes);
char* CreateCStringFromString(KConstRef kstring);
void DisposeCString(char* cstring);
+10
View File
@@ -52,6 +52,16 @@ uint64_t getTimeMillis();
uint64_t getTimeMicros();
uint64_t getTimeNanos();
#if KONAN_NO_EXCEPTIONS
#define TRY_CATCH(tryAction, actionWithoutExceptions, catchAction) actionWithoutExceptions;
#else
#define TRY_CATCH(tryAction, actionWithoutExceptions, catchAction) \
do { \
try { tryAction; } \
catch(...) { catchAction; } \
} while(0)
#endif
} // namespace konan
#endif // RUNTIME_PORTING_H
+3 -1
View File
@@ -642,7 +642,9 @@ KDouble Konan_FloatingPointParser_parseDoubleImpl (KString s, KInt e)
{
const KChar* utf16 = CharArrayAddressOfElementAt(s, 0);
KStdString utf8;
utf8::unchecked::utf16to8(utf16, utf16 + s->count_, back_inserter(utf8));
TRY_CATCH(utf8::utf16to8(utf16, utf16 + s->count_, back_inserter(utf8)),
utf8::unchecked::utf16to8(utf16, utf16 + s->count_, back_inserter(utf8)),
/* Illegal UTF-16 string. */ ThrowNumberFormatException());
const char *str = utf8.c_str();
auto dbl = createDouble (str, e);
+3 -1
View File
@@ -542,7 +542,9 @@ Konan_FloatingPointParser_parseFloatImpl(KString s, KInt e)
{
const KChar* utf16 = CharArrayAddressOfElementAt(s, 0);
KStdString utf8;
utf8::unchecked::utf16to8(utf16, utf16 + s->count_, back_inserter(utf8));
TRY_CATCH(utf8::utf16to8(utf16, utf16 + s->count_, back_inserter(utf8)),
utf8::unchecked::utf16to8(utf16, utf16 + s->count_, back_inserter(utf8)),
/* Illegal UTF-16 string. */ ThrowNumberFormatException());
const char *str = utf8.c_str();
auto flt = createFloat(str, e);
+5
View File
@@ -29,5 +29,10 @@ DEALINGS IN THE SOFTWARE.
#define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731
#include "utf8/unchecked.h"
#include "utf8/with_replacement.h"
#if !KONAN_NO_EXCEPTIONS
#include "utf8/checked.h"
#endif
#endif // header guard
+14
View File
@@ -193,6 +193,20 @@ namespace utf8
utf8::next(it, end);
}
/**
* Calculates a count of characters needed to represent the string from first to last in UTF-16
* taking into account surrogate symbols. Throws an exception if the input is invalid.
*/
template<typename octet_iterator>
uint32_t utf16_length(octet_iterator first, octet_iterator last) {
uint32_t dist = 0;
while(first < last) {
uint32_t cp = utf8::next(first, last);
dist += (cp > 0xffff) ? 2 : 1;
}
return dist;
}
template <typename octet_iterator>
typename std::iterator_traits<octet_iterator>::difference_type
distance (octet_iterator first, octet_iterator last)
+5 -5
View File
@@ -150,7 +150,7 @@ namespace internal
/// get_sequence_x functions decode utf-8 sequences of the length x
template <typename octet_iterator>
utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point)
utf_error get_sequence_1(octet_iterator& it, const octet_iterator end, uint32_t& code_point)
{
if (it == end)
return NOT_ENOUGH_ROOM;
@@ -161,7 +161,7 @@ namespace internal
}
template <typename octet_iterator>
utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point)
utf_error get_sequence_2(octet_iterator& it, const octet_iterator end, uint32_t& code_point)
{
if (it == end)
return NOT_ENOUGH_ROOM;
@@ -176,7 +176,7 @@ namespace internal
}
template <typename octet_iterator>
utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point)
utf_error get_sequence_3(octet_iterator& it, const octet_iterator end, uint32_t& code_point)
{
if (it == end)
return NOT_ENOUGH_ROOM;
@@ -195,7 +195,7 @@ namespace internal
}
template <typename octet_iterator>
utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point)
utf_error get_sequence_4(octet_iterator& it, const octet_iterator end, uint32_t& code_point)
{
if (it == end)
return NOT_ENOUGH_ROOM;
@@ -220,7 +220,7 @@ namespace internal
#undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR
template <typename octet_iterator>
utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point)
utf_error validate_next(octet_iterator& it, const octet_iterator end, uint32_t& code_point)
{
// Save the original value of it so we can go back in case of failure
// Of course, it does not make much sense with i.e. stream iterators
+16 -2
View File
@@ -116,6 +116,20 @@ namespace utf8
utf8::unchecked::next(it);
}
/**
* Calculates a count of characters needed to represent the string from first to last in UTF-16
* taking into account surrogate symbols. Doesn't validate the input.
*/
template<typename octet_iterator>
uint32_t utf16_length(octet_iterator first, const octet_iterator last) {
uint32_t dist = 0;
while (first < last) {
uint32_t cp = utf8::unchecked::next(first);
dist += (cp > 0xffff) ? 2 : 1;
}
return dist;
}
template <typename octet_iterator>
typename std::iterator_traits<octet_iterator>::difference_type
distance (octet_iterator first, octet_iterator last)
@@ -127,7 +141,7 @@ namespace utf8
}
template <typename u16bit_iterator, typename octet_iterator>
octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result)
octet_iterator utf16to8 (u16bit_iterator start, const u16bit_iterator end, octet_iterator result)
{
while (start != end) {
uint32_t cp = utf8::internal::mask16(*start++);
@@ -142,7 +156,7 @@ namespace utf8
}
template <typename u16bit_iterator, typename octet_iterator>
u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result)
u16bit_iterator utf8to16 (octet_iterator start, const octet_iterator end, u16bit_iterator result)
{
while (start < end) {
uint32_t cp = utf8::unchecked::next(start);
@@ -0,0 +1,148 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UTF_FOR_CPP_WITH_REPLACEMENT_H
#define UTF_FOR_CPP_WITH_REPLACEMENT_H
#include "core.h"
#include "unchecked.h"
namespace utf8 {
namespace with_replacement {
constexpr uint32_t default_replacement = 0xfffd;
/*
* Returns the next codepoint replacing any invalid sequence with the replacement codepoint.
*/
template<typename octet_iterator>
uint32_t next(octet_iterator &it, const octet_iterator end, uint32_t replacement) {
uint32_t cp = 0;
internal::utf_error err_code = utf8::internal::validate_next(it, end, cp);
switch (err_code) {
case internal::UTF8_OK :
return cp;
case internal::INVALID_LEAD :
it++;
return replacement;
case internal::NOT_ENOUGH_ROOM :
case internal::INCOMPLETE_SEQUENCE :
case internal::OVERLONG_SEQUENCE :
case internal::INVALID_CODE_POINT :
// The whole invalid sequence is replaced with one replacement codepoint.
for (it++; it < end && utf8::internal::is_trail(*it); it++);
return replacement;
}
}
// Library API
/**
* Calculates a count of characters needed to represent the string from first to last in UTF-16
* taking into account surrogate symbols and invalid sequences.
* Assumes that all invalid sequences in the input will be replaced with `replacement`
* so each invalid sequence increases the result by 1 or 2 depending on `replacement`.
*/
template<typename octet_iterator>
uint32_t utf16_length(octet_iterator first,
const octet_iterator last,
const uint32_t replacement = default_replacement) {
uint32_t dist = 0;
while (first < last) {
uint32_t cp = next(first, last, replacement);
dist += (cp > 0xffff) ? 2 : 1;
}
return dist;
}
template<typename octet_iterator>
typename std::iterator_traits<octet_iterator>::difference_type
distance(octet_iterator first, const octet_iterator last) {
typename std::iterator_traits<octet_iterator>::difference_type dist;
uint32_t unused = 0;
for (dist = 0; first < last; dist++) {
next(first, last, unused);
}
return dist;
}
template<typename u16bit_iterator, typename octet_iterator>
octet_iterator utf16to8(u16bit_iterator start,
const u16bit_iterator end,
octet_iterator result,
const uint32_t replacement) {
while (start != end) {
uint32_t cp = utf8::internal::mask16(*start++);
// Process surrogates.
if (utf8::internal::is_lead_surrogate(cp)) {
if (start != end) {
uint32_t trail_surrogate = utf8::internal::mask16(*start);
if (utf8::internal::is_trail_surrogate(trail_surrogate)) {
// Valid surrogate pair.
cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET;
start++;
} else {
cp = replacement; // Invalid input: lone lead surrogate.
}
} else {
cp = replacement; // Invalid input: lone lead surrogate at the end of input.
}
} else if (utf8::internal::is_trail_surrogate(cp)) {
cp = replacement; // Invalid input: lone trail surrogate
}
result = utf8::unchecked::append(cp, result);
}
return result;
}
template<typename u16bit_iterator, typename octet_iterator>
inline octet_iterator utf16to8(u16bit_iterator start,
const u16bit_iterator end,
octet_iterator result) {
return utf16to8(start, end, result, default_replacement);
}
template<typename u16bit_iterator, typename octet_iterator>
u16bit_iterator utf8to16(octet_iterator start,
const octet_iterator end,
u16bit_iterator result,
const uint32_t replacement) {
while (start != end) {
// The `next` method takes care about replacing invalid sequences.
uint32_t cp = next(start, end, replacement);
if (cp > 0xffff) { //make a surrogate pair
*result++ = static_cast<uint16_t>((cp >> 10) + internal::LEAD_OFFSET);
*result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);
} else
*result++ = static_cast<uint16_t>(cp);
}
return result;
}
template<typename u16bit_iterator, typename octet_iterator>
inline u16bit_iterator utf8to16(octet_iterator start,
const octet_iterator end,
u16bit_iterator result) {
return utf8to16(start, end, result, default_replacement);
}
} // namespace with_replacement
} // namespace utf8
#endif // UTF_FOR_CPP_WITH_REPLACEMENT_H
@@ -17,7 +17,6 @@
package konan.internal
import kotlin.internal.getProgressionLastElement
import kotlin.text.toUtf8Array
@ExportForCppRuntime
fun ThrowNullPointerException(): Nothing {
@@ -66,6 +65,11 @@ internal fun ThrowNotImplementedError(): Nothing {
throw NotImplementedError("An operation is not implemented.")
}
@ExportForCppRuntime
internal fun ThrowIllegalCharacterConversionException(): Nothing {
throw IllegalCharacterConversionException()
}
@ExportForCppRuntime
fun PrintThrowable(throwable: Throwable) {
println(throwable)
@@ -140,5 +144,5 @@ fun KonanObjectToUtf8Array(value: Any?): ByteArray {
is DoubleArray -> value.contentToString()
else -> value.toString()
}
return toUtf8Array(string, 0, string.length)
return string.toUtf8()
}
@@ -216,4 +216,9 @@ public open class NumberFormatException : IllegalArgumentException {
}
constructor(s: String) : super(s) {}
}
public open class IllegalCharacterConversionException : IllegalArgumentException {
constructor(): super()
constructor(s: String) : super(s)
}
@@ -16,11 +16,50 @@
package kotlin.text
@SymbolName("Kotlin_String_fromUtf8Array")
external fun fromUtf8Array(array: ByteArray, start: Int, size: Int) : String
// ByteArray -> String (UTF-8 -> UTF-16)
@SymbolName("Kotlin_String_toUtf8Array")
external fun toUtf8Array(string: String, start: Int, size: Int) : ByteArray
@Deprecated("Use ByteArray.stringFromUtf8()", ReplaceWith("array.stringFromUtf8(start, end)"))
fun fromUtf8Array(array: ByteArray, start: Int, size: Int) = array.stringFromUtf8Impl(start, size)
@Deprecated("Use String.toUtf8()", ReplaceWith("string.toUtf8(start, end)"))
fun toUtf8Array(string: String, start: Int, size: Int) : ByteArray = string.toUtf8(start, size)
/**
* Converts an UTF-8 array into a [String]. Replaces invalid input sequences with a default character.
*/
fun ByteArray.stringFromUtf8(start: Int = 0, size: Int = this.size) : String =
stringFromUtf8Impl(start, size)
@SymbolName("Kotlin_ByteArray_stringFromUtf8")
private external fun ByteArray.stringFromUtf8Impl(start: Int, size: Int) : String
/**
* Converts an UTF-8 array into a [String]. Throws [IllegalCharacterConversionException] if the input is invalid.
*/
fun ByteArray.stringFromUtf8OrThrow(start: Int = 0, size: Int = this.size) : String =
stringFromUtf8OrThrowImpl(start, size)
@SymbolName("Kotlin_ByteArray_stringFromUtf8OrThrow")
private external fun ByteArray.stringFromUtf8OrThrowImpl(start: Int, size: Int) : String
// String -> ByteArray (UTF-16 -> UTF-8)
/**
* Converts a [String] into an UTF-8 array. Replaces invalid input sequences with a default character.
*/
fun String.toUtf8(start: Int = 0, size: Int = this.length) : ByteArray =
toUtf8Impl(start, size)
@SymbolName("Kotlin_String_toUtf8")
private external fun String.toUtf8Impl(start: Int, size: Int) : ByteArray
/**
* Converts a [String] into an UTF-8 array. Throws [IllegalCharacterConversionException] if the input is invalid.
*/
fun String.toUtf8OrThrow(start: Int = 0, size: Int = this.length) : ByteArray =
toUtf8OrThrowImpl(start, size)
@SymbolName("Kotlin_String_toUtf8OrThrow")
private external fun String.toUtf8OrThrowImpl(start: Int, size: Int) : ByteArray
// TODO: make it somewhat private?
@SymbolName("Kotlin_String_fromCharArray")
@@ -33,7 +33,7 @@ class CUrl(val url: String) {
fun CPointer<ByteVar>.toKString(length: Int): String {
val bytes = this.readBytes(length)
return kotlin.text.fromUtf8Array(bytes, 0, bytes.size)
return bytes.stringFromUtf8()
}
fun header_callback(buffer: CPointer<ByteVar>?, size: size_t, nitems: size_t, userdata: COpaquePointer?): size_t {
+1 -1
View File
@@ -31,7 +31,7 @@ fun main(args: Array<String>) {
memScoped {
val buffer = ByteArray(1024)
val prefixBuffer = kotlin.text.toUtf8Array("echo: ", 0, 6)
val prefixBuffer = "echo: ".toUtf8()
val serverAddr = alloc<sockaddr_in>()
val listenFd = socket(AF_INET, SOCK_STREAM, 0)
@@ -43,7 +43,7 @@ private fun Int.checkError() {
if (this != 0) {
val buffer = ByteArray(1024)
av_strerror(this, buffer.refTo(0), buffer.size.signExtend())
throw Error("AVError: ${kotlin.text.fromUtf8Array(buffer, 0, buffer.size)}")
throw Error("AVError: ${buffer.stringFromUtf8()}")
}
}