Skip to content

Commit

Permalink
Test string edge cases for Kotlin and Swift (#1602)
Browse files Browse the repository at this point in the history
* Test string edge cases for Kotlin and Swift

* Kotlin: Throw `CharacterCodingException` on invalid UTF-16 `String`

This exception is produced using
`CharsetEncoder.onUnmappableCharacter(CodingErrorAction.REPORT)`.
  • Loading branch information
heinrich5991 authored Jul 24, 2023
1 parent 826ea99 commit d146cf6
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 7 deletions.
16 changes: 16 additions & 0 deletions fixtures/type-limits/tests/bindings/test_type_limits.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import uniffi.uniffi_type_limits.*;

// test_strings
try {
takeString("\ud800")
throw RuntimeException("Should have thrown an CharacterCodingException exception!")
} catch (e: java.nio.charset.CharacterCodingException) {
// It's okay!
}
assert(takeString("") == "")
assert(takeString("") == "")
assert(takeString("💖") == "💖")
14 changes: 14 additions & 0 deletions fixtures/type-limits/tests/bindings/test_type_limits.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import Foundation
import uniffi_type_limits

// test_strings
do {
// strings cannot contain surrogates, "\u{d800}" gives an error.
assert(takeString(v: "") == "")
assert(takeString(v: "") == "")
assert(takeString(v: "💖") == "💖")
}
2 changes: 2 additions & 0 deletions fixtures/type-limits/tests/test_generated_bindings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
uniffi::build_foreign_language_testcases!(
"tests/bindings/test_type_limits.kts",
"tests/bindings/test_type_limits.py",
"tests/bindings/test_type_limits.rb",
"tests/bindings/test_type_limits.swift",
);
22 changes: 15 additions & 7 deletions uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,25 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
return byteArr.toString(Charsets.UTF_8)
}

fun toUtf8(value: String): ByteBuffer {
// Make sure we don't have invalid UTF-16, check for lone surrogates.
return Charsets.UTF_8.newEncoder().run {
onMalformedInput(CodingErrorAction.REPORT)
encode(CharBuffer.wrap(value))
}
}

override fun lower(value: String): RustBuffer.ByValue {
val byteArr = value.toByteArray(Charsets.UTF_8)
val byteBuf = toUtf8(value)
// Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us
// to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`.
val rbuf = RustBuffer.alloc(byteArr.size)
rbuf.asByteBuffer()!!.put(byteArr)
val rbuf = RustBuffer.alloc(byteBuf.limit())
rbuf.asByteBuffer()!!.put(byteBuf)
return rbuf
}

// We aren't sure exactly how many bytes our string will be once it's UTF-8
// encoded. Allocate 3 bytes per unicode codepoint which will always be
// encoded. Allocate 3 bytes per UTF-16 code unit which will always be
// enough.
override fun allocationSize(value: String): Int {
val sizeForLength = 4
Expand All @@ -38,8 +46,8 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
}

override fun write(value: String, buf: ByteBuffer) {
val byteArr = value.toByteArray(Charsets.UTF_8)
buf.putInt(byteArr.size)
buf.put(byteArr)
val byteBuf = toUtf8(value)
buf.putInt(byteBuf.limit())
buf.put(byteBuf)
}
}
2 changes: 2 additions & 0 deletions uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import com.sun.jna.Callback
import com.sun.jna.ptr.*
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.CharBuffer
import java.nio.charset.CodingErrorAction
import java.util.concurrent.ConcurrentHashMap

{%- for req in self.imports() %}
Expand Down

0 comments on commit d146cf6

Please sign in to comment.