[jnigen] Fix encoding bug (https://github.com/dart-lang/jnigen/issues/279)
* Use UTF-16 in strings
diff --git a/pkgs/jni/example/integration_test/on_device_jni_test.dart b/pkgs/jni/example/integration_test/on_device_jni_test.dart
index f9ae286..cdc7296 100644
--- a/pkgs/jni/example/integration_test/on_device_jni_test.dart
+++ b/pkgs/jni/example/integration_test/on_device_jni_test.dart
@@ -9,6 +9,7 @@
import '../../test/jlist_test.dart' as jlist_test;
import '../../test/jmap_test.dart' as jmap_test;
import '../../test/jobject_test.dart' as jobject_test;
+import '../../test/jstring_test.dart' as jstring_test;
import '../../test/jset_test.dart' as jset_test;
import '../../test/jarray_test.dart' as jarray_test;
import '../../test/boxed_test.dart' as boxed_test;
@@ -26,6 +27,7 @@
jlist_test.run,
jmap_test.run,
jobject_test.run,
+ jstring_test.run,
jset_test.run,
jarray_test.run,
boxed_test.run,
diff --git a/pkgs/jni/example/lib/main.dart b/pkgs/jni/example/lib/main.dart
index 50c3c75..b2ca2b7 100644
--- a/pkgs/jni/example/lib/main.dart
+++ b/pkgs/jni/example/lib/main.dart
@@ -55,6 +55,12 @@
);
}
+String backAndForth() {
+ final jstring = '🪓'.toJString();
+ final dartString = jstring.toDartString(deleteOriginal: true);
+ return dartString;
+}
+
void quit() {
JObject.fromRef(Jni.getCurrentActivity())
.use((ac) => ac.callMethodByName<void>("finish", "()V", []));
@@ -94,6 +100,7 @@
if (Platform.isAndroid) ...[
Example("Minutes of usage since reboot",
() => (uptime() / (60 * 1000)).floor()),
+ Example("Back and forth string conversion", () => backAndForth()),
Example(
"Device name",
() => Jni.retrieveStaticField<String>(
diff --git a/pkgs/jni/lib/src/lang/jstring.dart b/pkgs/jni/lib/src/lang/jstring.dart
index 1361b53..fff169c 100644
--- a/pkgs/jni/lib/src/lang/jstring.dart
+++ b/pkgs/jni/lib/src/lang/jstring.dart
@@ -49,8 +49,8 @@
JString.fromRef(JStringPtr reference) : super.fromRef(reference);
static JStringPtr _toJavaString(String s) => using((arena) {
- final chars = s.toNativeUtf8(allocator: arena).cast<Char>();
- final jstr = Jni.env.NewStringUTF(chars);
+ final chars = s.toNativeUtf16(allocator: arena).cast<Uint16>();
+ final jstr = Jni.env.NewString(chars, s.length);
if (jstr == nullptr) {
throw 'Fatal: cannot convert string to Java string: $s';
}
@@ -72,9 +72,10 @@
if (reference == nullptr) {
throw NullJStringException();
}
- final chars = Jni.env.GetStringUTFChars(reference, nullptr);
- final result = chars.cast<Utf8>().toDartString();
- Jni.env.ReleaseStringUTFChars(reference, chars);
+ final length = Jni.env.GetStringLength(reference);
+ final chars = Jni.env.GetStringChars(reference, nullptr);
+ final result = chars.cast<Utf16>().toDartString(length: length);
+ Jni.env.ReleaseStringChars(reference, chars);
if (deleteOriginal) {
delete();
}
diff --git a/pkgs/jni/src/third_party/global_jni_env.c b/pkgs/jni/src/third_party/global_jni_env.c
index c5be7b0..7ddc164 100644
--- a/pkgs/jni/src/third_party/global_jni_env.c
+++ b/pkgs/jni/src/third_party/global_jni_env.c
@@ -2181,10 +2181,6 @@
JniPointerResult globalEnv_GetStringCritical(jstring str, jboolean* isCopy) {
attach_thread();
const jchar* _result = (*jniEnv)->GetStringCritical(jniEnv, str, isCopy);
- jthrowable _exception = check_exception();
- if (_exception != NULL) {
- return (JniPointerResult){.value = NULL, .exception = _exception};
- }
return (JniPointerResult){.value = _result, .exception = NULL};
}
diff --git a/pkgs/jni/test/jstring_test.dart b/pkgs/jni/test/jstring_test.dart
new file mode 100644
index 0000000..1bc6436
--- /dev/null
+++ b/pkgs/jni/test/jstring_test.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jni/jni.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+void main() {
+ // Don't forget to initialize JNI.
+ if (!Platform.isAndroid) {
+ checkDylibIsUpToDate();
+ Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+ }
+ run(testRunner: test);
+}
+
+void testStringBackAndForth(String str) {
+ final jstring = str.toJString();
+ final dartString = jstring.toDartString(deleteOriginal: true);
+ expect(dartString, str);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+ group("String encoding tests", () {
+ testRunner('Long string back-and-forth', () {
+ testStringBackAndForth('1' * 8096);
+ });
+
+ testRunner('#278 UTF-8 bug', () {
+ testStringBackAndForth('🐬');
+ });
+
+ testRunner('String containing null character', () {
+ final str = 'A${String.fromCharCode(0)}B';
+ testStringBackAndForth(str);
+ });
+
+ testRunner('Zero length string', () {
+ testStringBackAndForth('');
+ });
+ });
+}
diff --git a/pkgs/jni/tool/wrapper_generators/generate_c_extensions.dart b/pkgs/jni/tool/wrapper_generators/generate_c_extensions.dart
index cde47c9..f1e15b2 100644
--- a/pkgs/jni/tool/wrapper_generators/generate_c_extensions.dart
+++ b/pkgs/jni/tool/wrapper_generators/generate_c_extensions.dart
@@ -206,6 +206,7 @@
/// Methods which do not throw exceptions, and thus not need to be checked
const _noCheckException = {
'GetVersion',
+ 'GetStringCritical',
'ExceptionClear',
'ExceptionDescribe',
};