Skip to content

Commit ece9bdc

Browse files
[jnigen] Convert incorrect exceptions into errors (#397)
Closes #394.
1 parent 91e0768 commit ece9bdc

12 files changed

+244
-176
lines changed

pkgs/jni/CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
## 0.8.0-wip
2+
3+
- **Breaking Change** ([#394](https://github.com/dart-lang/jnigen/issues/394)):
4+
Converted various `Exception`s into `Error`s:
5+
- `UseAfterReleaseException` -> `UseAfterReleaseError`
6+
- `DoubleReleaseException` -> `DoubleReleaseError`
7+
- `SpawnException` -> `JniError` (It's now a `sealed class`)
8+
- `JNullException` -> `JNullError`
9+
- `InvalidCallTypeException` -> `InvalidCallTypeError`
10+
- `HelperNotFoundException` -> `HelperNotFoundError`
11+
- `JvmExistsException` -> `JniVmExistsError`
12+
- `NoJvmInstanceException` -> `NoJvmInstanceError`
13+
- **Breaking Change**: Removed `InvalidJStringException`.
14+
- **Breaking Change**: The default return `callType` of type parameter `int` for
15+
methods such as `JObject.callMethodByName<int>` is now Java's `long` instead
16+
of `int` to be consistent with the way arguments work.
17+
118
## 0.7.0
219

320
- **Breaking Change** ([#387](https://github.com/dart-lang/jnigen/issues/387)):

pkgs/jni/lib/jni.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,21 @@
6060
/// This library provides classes and functions for JNI interop from Dart.
6161
library jni;
6262

63-
export 'src/third_party/generated_bindings.dart'
64-
hide JniBindings, JniEnv, JniEnv1, JniExceptionDetails;
63+
export 'src/errors.dart';
6564
export 'src/jni.dart' hide ProtectedJniExtensions;
6665
export 'src/jvalues.dart' hide JValueArgs, toJValues;
6766
export 'src/types.dart';
6867
export 'src/jarray.dart';
69-
export 'src/jexceptions.dart';
7068
export 'src/jobject.dart';
7169
export 'src/jprimitives.dart';
7270
export 'src/jreference.dart' show JReferenceUseExtension;
71+
7372
export 'src/lang/lang.dart';
7473
export 'src/nio/nio.dart';
7574
export 'src/util/util.dart';
7675

76+
export 'src/third_party/generated_bindings.dart'
77+
hide JniBindings, JniEnv, JniEnv1, JniExceptionDetails;
78+
7779
export 'package:ffi/ffi.dart' show using, Arena;
7880
export 'dart:ffi' show nullptr;

pkgs/jni/lib/src/accessors.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'package:ffi/ffi.dart' show using;
77

88
import 'package:jni/src/jvalues.dart';
99

10-
import 'jexceptions.dart';
10+
import 'errors.dart';
1111
import 'third_party/generated_bindings.dart';
1212
import 'jni.dart';
1313

pkgs/jni/lib/src/errors.dart

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:jni/src/third_party/generated_bindings.dart';
6+
7+
// TODO(#393): Add the fact that [JException] is now a [JObject] to the
8+
// CHANGELOG.
9+
10+
final class UseAfterReleaseError extends Error {
11+
@override
12+
String toString() {
13+
return 'Use after release error';
14+
}
15+
}
16+
17+
// TODO(#393): Use NullPointerError once it's available.
18+
final class JNullError extends Error {
19+
@override
20+
String toString() => 'The reference was null';
21+
}
22+
23+
final class DoubleReleaseError extends Error {
24+
@override
25+
String toString() {
26+
return 'Double release error';
27+
}
28+
}
29+
30+
/// Represents JNI errors that might be returned by methods like
31+
/// `JNI_CreateJavaVM`.
32+
sealed class JniError extends Error {
33+
static const _errors = {
34+
JniErrorCode.JNI_ERR: JniGenericError.new,
35+
JniErrorCode.JNI_EDETACHED: JniThreadDetachedError.new,
36+
JniErrorCode.JNI_EVERSION: JniVersionError.new,
37+
JniErrorCode.JNI_ENOMEM: JniOutOfMemoryError.new,
38+
JniErrorCode.JNI_EEXIST: JniVmExistsError.new,
39+
JniErrorCode.JNI_EINVAL: JniArgumentError.new,
40+
};
41+
42+
final String message;
43+
44+
JniError(this.message);
45+
46+
factory JniError.of(int status) {
47+
if (!_errors.containsKey(status)) {
48+
status = JniErrorCode.JNI_ERR;
49+
}
50+
return _errors[status]!();
51+
}
52+
53+
@override
54+
String toString() {
55+
return 'JniError: $message';
56+
}
57+
}
58+
59+
final class JniGenericError extends JniError {
60+
JniGenericError() : super('Generic JNI error');
61+
}
62+
63+
final class JniThreadDetachedError extends JniError {
64+
JniThreadDetachedError() : super('Thread detached from VM');
65+
}
66+
67+
final class JniVersionError extends JniError {
68+
JniVersionError() : super('JNI version error');
69+
}
70+
71+
final class JniOutOfMemoryError extends JniError {
72+
JniOutOfMemoryError() : super('Out of memory');
73+
}
74+
75+
final class JniVmExistsError extends JniError {
76+
JniVmExistsError() : super('VM Already created');
77+
}
78+
79+
final class JniArgumentError extends JniError {
80+
JniArgumentError() : super('Invalid arguments');
81+
}
82+
83+
final class NoJvmInstanceError extends Error {
84+
@override
85+
String toString() => 'No JNI instance is available';
86+
}
87+
88+
// TODO(#395): Remove this when calltypes are removed.
89+
extension on int {
90+
static const _names = {
91+
JniCallType.booleanType: 'bool',
92+
JniCallType.byteType: 'byte',
93+
JniCallType.shortType: 'short',
94+
JniCallType.charType: 'char',
95+
JniCallType.intType: 'int',
96+
JniCallType.longType: 'long',
97+
JniCallType.floatType: 'float',
98+
JniCallType.doubleType: 'double',
99+
JniCallType.objectType: 'object',
100+
JniCallType.voidType: 'void',
101+
};
102+
String str() => _names[this]!;
103+
}
104+
105+
// TODO(#395): Remove this when `JniCallType`s are removed.
106+
final class InvalidCallTypeError extends Error {
107+
final int type;
108+
final Set<int> allowed;
109+
110+
InvalidCallTypeError(this.type, this.allowed);
111+
112+
@override
113+
String toString() => 'Invalid type for call ${type.str()}. '
114+
'Allowed types are ${allowed.map((t) => t.str()).toSet()}';
115+
}
116+
117+
// TODO(#393): Remove this class in favor of `JThrowable`.
118+
class JniException implements Exception {
119+
/// Error message from Java exception.
120+
final String message;
121+
122+
/// Stack trace from Java.
123+
final String stackTrace;
124+
125+
JniException(this.message, this.stackTrace);
126+
127+
@override
128+
String toString() => 'Exception in Java code called through JNI: '
129+
'$message\n\n$stackTrace\n';
130+
}
131+
132+
final class HelperNotFoundError extends Error {
133+
final String path;
134+
135+
HelperNotFoundError(this.path);
136+
137+
@override
138+
String toString() => '''
139+
Lookup for helper library $path failed.
140+
Please ensure that `dartjni` shared library is built.
141+
Provided jni:setup script can be used to build the shared library.
142+
If the library is already built, ensure that the JVM libraries can be
143+
loaded from Dart.''';
144+
}

pkgs/jni/lib/src/jexceptions.dart

Lines changed: 0 additions & 123 deletions
This file was deleted.

pkgs/jni/lib/src/jni.dart

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import 'dart:isolate';
99
import 'package:ffi/ffi.dart';
1010
import 'package:path/path.dart';
1111

12-
import 'jexceptions.dart';
12+
import 'errors.dart';
1313
import 'jobject.dart';
1414
import 'third_party/generated_bindings.dart';
1515
import 'jvalues.dart';
@@ -38,7 +38,7 @@ DynamicLibrary _loadDartJniLibrary({String? dir, String baseName = "dartjni"}) {
3838
final dylib = DynamicLibrary.open(libPath);
3939
return dylib;
4040
} on Error {
41-
throw HelperNotFoundException(libPath);
41+
throw HelperNotFoundError(libPath);
4242
}
4343
}
4444

@@ -97,12 +97,12 @@ abstract class Jni {
9797
jniVersion: jniVersion,
9898
);
9999
if (status == false) {
100-
throw JvmExistsException();
100+
throw JniVmExistsError();
101101
}
102102
}
103103

104104
/// Same as [spawn] but if a JVM exists, returns silently instead of
105-
/// throwing [JvmExistsException].
105+
/// throwing [JvmExistsError].
106106
///
107107
/// If the options are different than that of existing VM, the existing VM's
108108
/// options will remain in effect.
@@ -129,7 +129,7 @@ abstract class Jni {
129129
} else if (status == DART_JNI_SINGLETON_EXISTS) {
130130
return false;
131131
} else {
132-
throw SpawnException.of(status);
132+
throw JniError.of(status);
133133
}
134134
});
135135

@@ -176,12 +176,12 @@ abstract class Jni {
176176
return _bindings.GetJavaVM();
177177
}
178178

179-
/// Returns the instance of [GlobalJniEnvStruct], which is an abstraction over JNIEnv
180-
/// without the same-thread restriction.
179+
/// Returns the instance of [GlobalJniEnvStruct], which is an abstraction over
180+
/// JNIEnv without the same-thread restriction.
181181
static Pointer<GlobalJniEnvStruct> _fetchGlobalEnv() {
182182
final env = _bindings.GetGlobalEnv();
183183
if (env == nullptr) {
184-
throw NoJvmInstanceException();
184+
throw NoJvmInstanceError();
185185
}
186186
return env;
187187
}
@@ -336,11 +336,11 @@ extension AdditionalEnvMethods on GlobalJniEnv {
336336
/// DeleteGlobalRef.
337337
String toDartString(JStringPtr jstringPtr, {bool releaseOriginal = false}) {
338338
if (jstringPtr == nullptr) {
339-
throw const JNullException();
339+
throw JNullError();
340340
}
341341
final chars = GetStringChars(jstringPtr, nullptr);
342342
if (chars == nullptr) {
343-
throw InvalidJStringException(jstringPtr);
343+
throw ArgumentError('Not a valid jstring pointer.');
344344
}
345345
final result = chars.cast<Utf16>().toDartString();
346346
ReleaseStringChars(jstringPtr, chars);

0 commit comments

Comments
 (0)