Skip to content

[jnigen] Fix crash on Kotlin wildcards #1881

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pkgs/jnigen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.13.1-wip

- Fixed a bug where Kotlin wildcards would crash the code generation.

## 0.13.0

- **Breaking Change**([#1516](https://github.com/dart-lang/native/issues/1516)):
Expand Down
10 changes: 7 additions & 3 deletions pkgs/jnigen/lib/src/bindings/kotlin_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -254,16 +254,20 @@ class _KotlinTypeProcessor extends TypeVisitor<void> {
@override
void visitDeclaredType(DeclaredType node) {
for (var i = 0; i < node.params.length; ++i) {
node.params[i].accept(_KotlinTypeProcessor(kotlinType.arguments[i].type));
if (kotlinType.arguments[i] case final KotlinTypeProjection projection) {
node.params[i].accept(_KotlinTypeProcessor(projection.type));
}
}
super.visitDeclaredType(node);
}

@override
void visitArrayType(ArrayType node) {
if (kotlinType.arguments.isNotEmpty) {
node.elementType
.accept(_KotlinTypeProcessor(kotlinType.arguments.first.type));
if (kotlinType.arguments.first
case final KotlinTypeProjection projection) {
node.elementType.accept(_KotlinTypeProcessor(projection.type));
}
}
super.visitArrayType(node);
}
Expand Down
35 changes: 24 additions & 11 deletions pkgs/jnigen/lib/src/elements/elements.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class ClassDecl with ClassMember, Annotated implements Element<ClassDecl> {
this.kotlinPackage,
});

@JsonKey(includeFromJson: false)
bool isExcluded;

@override
Expand Down Expand Up @@ -625,6 +626,7 @@ class Method with ClassMember, Annotated implements Element<Method> {
required this.returnType,
});

@JsonKey(includeFromJson: false)
bool isExcluded;

@override
Expand Down Expand Up @@ -728,6 +730,7 @@ class Field with ClassMember, Annotated implements Element<Field> {
this.defaultValue,
});

@JsonKey(includeFromJson: false)
bool isExcluded;

@override
Expand Down Expand Up @@ -1120,7 +1123,7 @@ class KotlinType implements Element<KotlinType> {
final String kind;
final String? name;
final int id;
final List<KotlinTypeProjection> arguments;
final List<KotlinTypeArgument> arguments;
final bool isNullable;

factory KotlinType.fromJson(Map<String, dynamic> json) =>
Expand Down Expand Up @@ -1190,21 +1193,31 @@ class KotlinValueParameter implements Element<KotlinValueParameter> {
}
}

@JsonSerializable(createToJson: false)
class KotlinTypeProjection implements Element<KotlinTypeProjection> {
sealed class KotlinTypeArgument implements Element<KotlinTypeArgument> {
KotlinTypeArgument();

factory KotlinTypeArgument.fromJson(Map<String, dynamic> json) =>
json['type'] == null
? KotlinWildcard()
: KotlinTypeProjection(
type: KotlinType.fromJson(json['type'] as Map<String, dynamic>),
variance: $enumDecode(_$KmVarianceEnumMap, json['variance']),
);

@override
R accept<R>(Visitor<KotlinTypeArgument, R> v) {
return v.visit(this);
}
}

class KotlinWildcard extends KotlinTypeArgument {}

class KotlinTypeProjection extends KotlinTypeArgument {
KotlinTypeProjection({
required this.type,
required this.variance,
});

final KotlinType type;
final KmVariance variance;

factory KotlinTypeProjection.fromJson(Map<String, dynamic> json) =>
_$KotlinTypeProjectionFromJson(json);

@override
R accept<R>(Visitor<KotlinTypeProjection, R> v) {
return v.visit(this);
}
}
11 changes: 2 additions & 9 deletions pkgs/jnigen/lib/src/elements/elements.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkgs/jnigen/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

name: jnigen
description: A Dart bindings generator for Java and Kotlin that uses JNI under the hood to interop with Java virtual machine.
version: 0.13.0
version: 0.13.1-wip
repository: https://github.com/dart-lang/native/tree/main/pkgs/jnigen
issue_tracker: https://github.com/dart-lang/native/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ajnigen

Expand Down
25 changes: 25 additions & 0 deletions pkgs/jnigen/test/kotlin_test/bindings/kotlin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,31 @@ class Nullability<$T extends jni$_.JObject?, $U extends jni$_.JObject>
.object<jni$_.JString?>(const jni$_.JStringNullableType());
}

static final _id_list = _class.instanceMethodId(
r'list',
r'()Ljava/util/List;',
);

static final _list = jni$_.ProtectedJniExtensions.lookup<
jni$_.NativeFunction<
jni$_.JniResult Function(
jni$_.Pointer<jni$_.Void>,
jni$_.JMethodIDPtr,
)>>('globalEnv_CallObjectMethod')
.asFunction<
jni$_.JniResult Function(
jni$_.Pointer<jni$_.Void>,
jni$_.JMethodIDPtr,
)>();

/// from: `public final java.util.List list()`
/// The returned object must be released after use, by calling the [release] method.
jni$_.JList<jni$_.JObject?> list() {
return _list(reference.pointer, _id_list as jni$_.JMethodIDPtr)
.object<jni$_.JList<jni$_.JObject?>>(
const jni$_.JListType<jni$_.JObject?>(jni$_.JObjectNullableType()));
}

static final _id_methodGenericEcho = _class.instanceMethodId(
r'methodGenericEcho',
r'(Ljava/lang/Object;)Ljava/lang/Object;',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ public class Nullability<T, U: Any>(val t: T, val u: U, var nullableU: U?) {
return if (returnNull) null else "hello"
}

public fun list(): List<*> {
return listOf("hello", 42)
}

public fun <V: Any> methodGenericEcho(v: V): V {
return v
}
Expand Down
8 changes: 8 additions & 0 deletions pkgs/jnigen/test/kotlin_test/runtime_test_registrant.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ void registerTests(String groupName, TestRunnerCallback test) {
test('Methods', () {
using((arena) {
final obj = testObject(arena);
expect(
obj
.list()
.first!
.as(JString.type, releaseOriginal: true)
.toDartString(releaseOriginal: true),
'hello',
);
expect(obj.hello().toDartString(releaseOriginal: true), 'hello');
expect(
obj.nullableHello(false)!.toDartString(releaseOriginal: true),
Expand Down
Loading