[jnigen] Fix buffer overflow issue when converting strings from JNI to Dart (https://github.com/dart-lang/jnigen/issues/416)
diff --git a/pkgs/jni/CHANGELOG.md b/pkgs/jni/CHANGELOG.md index e4f919c..2065bb1 100644 --- a/pkgs/jni/CHANGELOG.md +++ b/pkgs/jni/CHANGELOG.md
@@ -19,6 +19,9 @@ - **Breaking Change**: `JArray.filled` now uses the generated type class of the `fill` object and not its Java runtime type. +## 0.7.2 +- Fixed a bug where reading non-null terminated strings would overflow. + ## 0.7.1 - Removed macOS Flutter plugin until package:jni supports it ([#41](https://github.com/dart-lang/jnigen/issues/41)).
diff --git a/pkgs/jni/lib/src/jni.dart b/pkgs/jni/lib/src/jni.dart index 2554041..4993aee 100644 --- a/pkgs/jni/lib/src/jni.dart +++ b/pkgs/jni/lib/src/jni.dart
@@ -330,8 +330,7 @@ } extension AdditionalEnvMethods on GlobalJniEnv { - /// Convenience method for converting a [JStringPtr] - /// to dart string. + /// Convenience method for converting a [JStringPtr] to dart string. /// if [releaseOriginal] is specified, jstring passed will be deleted using /// DeleteGlobalRef. String toDartString(JStringPtr jstringPtr, {bool releaseOriginal = false}) { @@ -342,7 +341,8 @@ if (chars == nullptr) { throw ArgumentError('Not a valid jstring pointer.'); } - final result = chars.cast<Utf16>().toDartString(); + final length = GetStringLength(jstringPtr); + final result = chars.cast<Utf16>().toDartString(length: length); ReleaseStringChars(jstringPtr, chars); if (releaseOriginal) { DeleteGlobalRef(jstringPtr); @@ -377,7 +377,7 @@ extension CharPtrMethodsForJni on Pointer<Char> { /// Same as calling `cast<Utf8>` followed by `toDartString`. - String toDartString() { - return cast<Utf8>().toDartString(); + String toDartString({int? length}) { + return cast<Utf8>().toDartString(length: length); } }
diff --git a/pkgs/jni/lib/src/lang/jstring.dart b/pkgs/jni/lib/src/lang/jstring.dart index 9e542ff..9b8fbbb 100644 --- a/pkgs/jni/lib/src/lang/jstring.dart +++ b/pkgs/jni/lib/src/lang/jstring.dart
@@ -4,7 +4,6 @@ import 'dart:ffi'; -import 'package:ffi/ffi.dart'; import 'package:jni/src/jreference.dart'; import '../jni.dart'; @@ -59,10 +58,7 @@ /// after conversion and this object will be marked as released. String toDartString({bool releaseOriginal = false}) { ensureNotNull(); - 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); + final result = Jni.env.toDartString(reference); if (releaseOriginal) { release(); }
diff --git a/pkgs/jni/test/global_env_test.dart b/pkgs/jni/test/global_env_test.dart index 9b6a8b2..a5588cd 100644 --- a/pkgs/jni/test/global_env_test.dart +++ b/pkgs/jni/test/global_env_test.dart
@@ -4,6 +4,7 @@ import 'dart:io'; +import 'package:ffi/ffi.dart'; import 'package:jni/jni.dart'; import 'package:jni/src/jvalues.dart'; import 'package:test/test.dart'; @@ -118,19 +119,32 @@ })); testRunner( - "Convert back & forth between Dart & Java strings", + "Convert back & forth between Dart & Java strings (UTF-8)", () => using((arena) { const str = "ABCD EFGH"; - // This is what asJString and asDartString do internally final jstr = env.NewStringUTF(str.toNativeChars(arena)); final jchars = env.GetStringUTFChars(jstr, nullptr); - final dstr = jchars.toDartString(); + final jlen = env.GetStringUTFLength(jstr); + final dstr = jchars.toDartString(length: jlen); env.ReleaseStringUTFChars(jstr, jchars); expect(str, equals(dstr)); env.DeleteGlobalRef(jstr); })); testRunner( + "Convert back & forth between Dart & Java strings (UTF-16)", + () => using((arena) { + const str = "ABCD EFGH"; + final jstr = env.NewString(str.toNativeUtf16().cast(), str.length); + final jchars = env.GetStringChars(jstr, nullptr); + final jlen = env.GetStringLength(jstr); + final dstr = jchars.cast<Utf16>().toDartString(length: jlen); + env.ReleaseStringChars(jstr, jchars); + expect(str, equals(dstr)); + env.DeleteGlobalRef(jstr); + })); + + testRunner( "Print something from Java", () => using((arena) { final system =