[ffi] Fix callback subtyping test

Previously we were reusing the ensureNativeTypeToDartType function to
check that Dart function passed as a callback matches the native type.
This works if the types exactly match, but the subtyping test is
backwards, so it doesn't allow certain cases that should be allowed. The
main case is that when the native function type returns void, the Dart
function should be allowed to return anything.

So I added ensureDartTypeToNativeType, which reverses the subtype test.

As well as making the return types more permissive, this has also
changed what parameters are allowed to be passed to callbacks. For
example, in tests/ffi/vmspecific_static_checks_typeddata_test.dart:80,
passing a Handle to a function expecting an Int8List used to work, but
is now a compile error. I think this change is an improvement, because
previously it would have been possible to pass any type of object to
that callback. So this change turns some potential runtime type errors
into compile errors. But technically I think this is a breaking change.

Fixes: https://github.com/dart-lang/sdk/issues/53659
Bug: https://github.com/dart-lang/sdk/issues/53659
Change-Id: I6846a59fc309ec897cba8f985d7dd0a63b912b42
TEST=tests/ffi/function_callbacks_subtype_test.dart and others
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/346440
Commit-Queue: Liam Appelbe <liama@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Liam Appelbe 2024-02-05 03:19:01 +00:00 committed by Commit Queue
parent 6f83936c23
commit dff831dbfc
12 changed files with 329 additions and 99 deletions

View file

@ -514,6 +514,7 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
}
if (!_validateCompatibleNativeType(
_FfiTypeCheckDirection.nativeToDart,
type,
ffiSignature,
// Functions are not allowed in native fields, but allowing them in the
@ -631,8 +632,9 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
);
return;
}
if (!_validateCompatibleFunctionTypes(dartType, nativeType,
nativeFieldWrappersAsPointer: true, allowStricterReturn: true)) {
if (!_validateCompatibleFunctionTypes(
_FfiTypeCheckDirection.nativeToDart, dartType, nativeType,
nativeFieldWrappersAsPointer: true, permissiveReturnType: true)) {
_errorReporter.atNode(
errorNode,
FfiCode.MUST_BE_A_SUBTYPE,
@ -1084,7 +1086,8 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
final DartType TPrime = T.typeArguments[0];
final DartType F = node.typeArgumentTypes![0];
final isLeaf = _isLeaf(node.argumentList.arguments);
if (!_validateCompatibleFunctionTypes(F, TPrime)) {
if (!_validateCompatibleFunctionTypes(
_FfiTypeCheckDirection.nativeToDart, F, TPrime)) {
_errorReporter.atNode(
node,
FfiCode.MUST_BE_A_SUBTYPE,
@ -1100,11 +1103,14 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
/// Validates that the given [nativeType] is, when native types are converted
/// to their Dart equivalent, a subtype of [dartType].
/// [permissiveReturnType] means that the [direction] is ignored for return
/// types, and subtyping is allowed in either direction.
bool _validateCompatibleFunctionTypes(
_FfiTypeCheckDirection direction,
DartType dartType,
DartType nativeType, {
bool nativeFieldWrappersAsPointer = false,
bool allowStricterReturn = false,
bool permissiveReturnType = false,
}) {
// We require both to be valid function types.
if (dartType is! FunctionType ||
@ -1134,25 +1140,26 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
}
// Validate that the return types are compatible.
if (!_validateCompatibleNativeType(
dartType.returnType, nativeType.returnType)) {
// TODO(dacoharkes): Fix inconsistency between `FfiNative` and `asFunction`.
// http://dartbug.com/49518
if (!allowStricterReturn) {
return false;
} else if (!_validateCompatibleNativeType(
dartType.returnType, nativeType.returnType,
checkCovariance: true)) {
if (permissiveReturnType) {
// TODO(dacoharkes): Fix inconsistency between `FfiNative` and
// `asFunction`. http://dartbug.com/49518.
if (!(_validateCompatibleNativeType(_FfiTypeCheckDirection.nativeToDart,
dartType.returnType, nativeType.returnType) ||
_validateCompatibleNativeType(_FfiTypeCheckDirection.dartToNative,
dartType.returnType, nativeType.returnType))) {
return false;
}
} else if (!_validateCompatibleNativeType(
direction, dartType.returnType, nativeType.returnType)) {
return false;
}
// Validate that the parameter types are compatible.
for (int i = 0; i < parameterCount; ++i) {
if (!_validateCompatibleNativeType(
direction.reverse,
dartType.normalParameterTypes[i],
nativeTypeNormalParameterTypes[i],
checkCovariance: true,
nativeFieldWrappersAsPointer: nativeFieldWrappersAsPointer,
)) {
return false;
@ -1163,13 +1170,13 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
return true;
}
/// Validates that, if we convert [nativeType] to it's corresponding
/// [dartType] the latter is a subtype of the former if
/// [checkCovariance].
/// Validates that the [nativeType] can be converted to the [dartType] if
/// [direction] is [_FfiTypeCheckDirection.nativeToDart], or the reverse for
/// [_FfiTypeCheckDirection.dartToNative].
bool _validateCompatibleNativeType(
_FfiTypeCheckDirection direction,
DartType dartType,
DartType nativeType, {
bool checkCovariance = false,
bool nativeFieldWrappersAsPointer = false,
bool allowFunctions = false,
}) {
@ -1184,13 +1191,15 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
} else if (nativeReturnType == _PrimitiveDartType.bool) {
return dartType.isDartCoreBool;
} else if (nativeReturnType == _PrimitiveDartType.void_) {
return dartType is VoidType;
return direction == _FfiTypeCheckDirection.dartToNative
? true
: dartType is VoidType;
} else if (dartType is VoidType) {
// Don't allow other native subtypes if the Dart return type is void.
return nativeReturnType == _PrimitiveDartType.void_;
} else if (nativeReturnType == _PrimitiveDartType.handle) {
InterfaceType objectType = typeSystem.objectStar;
return checkCovariance
return direction == _FfiTypeCheckDirection.dartToNative
? /* everything is subtype of objectStar */ true
: typeSystem.isSubtypeOf(objectType, dartType);
} else if (dartType is InterfaceType && nativeType is InterfaceType) {
@ -1206,7 +1215,7 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
if (_isValidTypedData(nativeType, dartType)) {
return true;
}
return checkCovariance
return direction == _FfiTypeCheckDirection.dartToNative
? typeSystem.isSubtypeOf(dartType, nativeType)
: typeSystem.isSubtypeOf(nativeType, dartType);
} else if (dartType is FunctionType &&
@ -1214,7 +1223,8 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
nativeType is InterfaceType &&
nativeType.isNativeFunction) {
final nativeFunction = nativeType.typeArguments[0];
return _validateCompatibleFunctionTypes(dartType, nativeFunction,
return _validateCompatibleFunctionTypes(
direction, dartType, nativeFunction,
nativeFieldWrappersAsPointer: nativeFieldWrappersAsPointer);
} else {
// If the [nativeType] is not a primitive int/double type then it has to
@ -1382,7 +1392,8 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
Expression f = node.argumentList.arguments[0];
DartType FT = f.typeOrThrow;
if (!_validateCompatibleFunctionTypes(FT, T)) {
if (!_validateCompatibleFunctionTypes(
_FfiTypeCheckDirection.dartToNative, FT, T)) {
_errorReporter.atNode(
f,
FfiCode.MUST_BE_A_SUBTYPE,
@ -1393,7 +1404,7 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
// TODO(brianwilkerson): Validate that `f` is a top-level function.
final DartType R = (T as FunctionType).returnType;
if ((FT as FunctionType).returnType is VoidType ||
if (_primitiveNativeType(R) == _PrimitiveDartType.void_ ||
R.isPointer ||
R.isHandle ||
R.isCompoundSubtype) {
@ -1413,7 +1424,8 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
} else {
Expression e = node.argumentList.arguments[1];
var eType = e.typeOrThrow;
if (!_validateCompatibleNativeType(eType, R, checkCovariance: true)) {
if (!_validateCompatibleNativeType(
_FfiTypeCheckDirection.dartToNative, eType, R)) {
_errorReporter.atNode(
e,
FfiCode.MUST_BE_A_SUBTYPE,
@ -1474,7 +1486,8 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
return;
}
final isLeaf = _isLeaf(node.argumentList.arguments);
if (!_validateCompatibleFunctionTypes(F, S)) {
if (!_validateCompatibleFunctionTypes(
_FfiTypeCheckDirection.nativeToDart, F, S)) {
final AstNode errorNode = typeArguments[1];
_errorReporter.atNode(
errorNode,
@ -1605,7 +1618,8 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
var f = node.argumentList.arguments[0];
var funcType = f.typeOrThrow;
if (!_validateCompatibleFunctionTypes(funcType, typeArg)) {
if (!_validateCompatibleFunctionTypes(
_FfiTypeCheckDirection.dartToNative, funcType, typeArg)) {
_errorReporter.atNode(
f,
FfiCode.MUST_BE_A_SUBTYPE,
@ -1614,10 +1628,9 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
return;
}
var retType = (funcType as FunctionType).returnType;
var natRetType = (typeArg as FunctionType).returnType;
if (isolateLocal) {
if (retType is VoidType ||
if (_primitiveNativeType(natRetType) == _PrimitiveDartType.void_ ||
natRetType.isPointer ||
natRetType.isHandle ||
natRetType.isCompoundSubtype) {
@ -1637,8 +1650,8 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
} else {
var e = (node.argumentList.arguments[1] as NamedExpression).expression;
var eType = e.typeOrThrow;
if (!_validateCompatibleNativeType(eType, natRetType,
checkCovariance: true)) {
if (!_validateCompatibleNativeType(
_FfiTypeCheckDirection.dartToNative, eType, natRetType)) {
_errorReporter.atNode(
e,
FfiCode.MUST_BE_A_SUBTYPE,
@ -1654,11 +1667,11 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
}
}
} else {
if (retType is! VoidType) {
if (_primitiveNativeType(natRetType) != _PrimitiveDartType.void_) {
_errorReporter.atNode(
f,
FfiCode.MUST_RETURN_VOID,
arguments: [retType],
arguments: [natRetType],
);
}
}
@ -1847,6 +1860,25 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
}
}
enum _FfiTypeCheckDirection {
// Passing a value from native code to Dart code. For example, the return type
// of a loaded native function, or the arguments of a native callback.
nativeToDart,
// Passing a value from Dart code to native code. For example, the arguments
// of a loaded native function, or the return type of a native callback.
dartToNative;
_FfiTypeCheckDirection get reverse {
switch (this) {
case nativeToDart:
return dartToNative;
case dartToNative:
return nativeToDart;
}
}
}
enum _PrimitiveDartType {
double,
int,

View file

@ -133,6 +133,16 @@ void g() {
''', []);
}
test_NativeCallable_isolateLocal_voidReturnPermissive() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
int f(int i) => i * 2;
void g() {
NativeCallable<Void Function(Int32)>.isolateLocal(f);
}
''', []);
}
test_NativeCallable_listener_inferred() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
@ -200,6 +210,16 @@ void f(int i) => i * 2;
void g() {
NativeCallable<Void Function(Int32)>.listener(f);
}
''', []);
}
test_NativeCallable_listener_voidReturnPermissive() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
int f(int i) => i * 2;
void g() {
NativeCallable<Void Function(Int32)>.listener(f);
}
''', []);
}
}

View file

@ -63,6 +63,17 @@ void g() {
''');
}
test_fromFunction_valid_voidReturnPermissive() async {
await assertNoErrorsInCode(r'''
import 'dart:ffi';
typedef T = Void Function(Int8);
int f(int i) => i * 2;
void g() {
Pointer.fromFunction<T>(f);
}
''');
}
test_lookupFunction_F() async {
await assertErrorsInCode(r'''
import 'dart:ffi';

View file

@ -137,6 +137,14 @@ const List<NativeType> unalignedLoadsStores = [
NativeType.kDouble,
];
enum FfiTypeCheckDirection {
// Passing a value from native code to Dart code.
nativeToDart,
// Passing a value from Dart code to native code.
dartToNative,
}
/// [FfiTransformer] contains logic which is shared between
/// _FfiUseSiteTransformer and _FfiDefinitionTransformer.
class FfiTransformer extends Transformer {
@ -1396,7 +1404,8 @@ class FfiTransformer extends Transformer {
}
}
void ensureNativeTypeToDartType(
DartType ensureNativeTypeMatch(
FfiTypeCheckDirection direction,
DartType nativeType,
DartType dartType,
TreeNode reportErrorOn, {
@ -1411,23 +1420,31 @@ class FfiTransformer extends Transformer {
allowInlineArray: allowArray,
allowVoid: allowVoid,
)!;
if (dartType == correspondingDartType) return;
if (env.isSubtypeOf(correspondingDartType, dartType,
SubtypeCheckMode.ignoringNullabilities)) {
// If subtype, manually check the return type is not void.
if (correspondingDartType is FunctionType) {
if (dartType is FunctionType) {
if ((dartType.returnType is VoidType) ==
(correspondingDartType.returnType is VoidType)) {
return;
if (dartType == correspondingDartType) return correspondingDartType;
switch (direction) {
case FfiTypeCheckDirection.nativeToDart:
if (env.isSubtypeOf(correspondingDartType, dartType,
SubtypeCheckMode.ignoringNullabilities)) {
// If subtype, manually check the return type is not void.
if (correspondingDartType is FunctionType) {
if (dartType is FunctionType) {
if ((dartType.returnType is VoidType) ==
(correspondingDartType.returnType is VoidType)) {
return correspondingDartType;
}
// One of the return types is void, the other isn't, report error.
} else {
// One is a function type, the other isn't, report error.
}
} else {
return correspondingDartType;
}
// One of the return types is void, the other isn't, report error.
} else {
// One is a function type, the other isn't, report error.
}
} else {
return;
}
case FfiTypeCheckDirection.dartToNative:
if (env.isSubtypeOf(dartType, correspondingDartType,
SubtypeCheckMode.ignoringNullabilities)) {
return correspondingDartType;
}
}
diagnosticReporter.report(
templateFfiTypeMismatch.withArguments(dartType, correspondingDartType,

View file

@ -25,7 +25,8 @@ import 'package:kernel/reference_from_index.dart' show ReferenceFromIndex;
import 'package:kernel/target/targets.dart' show DiagnosticReporter;
import 'package:kernel/type_environment.dart';
import 'common.dart' show FfiStaticTypeError, FfiTransformer, NativeType;
import 'common.dart'
show FfiStaticTypeError, FfiTransformer, NativeType, FfiTypeCheckDirection;
import 'native_type_cfe.dart';
/// Transform @Native annotated functions into FFI native function pointer
@ -765,7 +766,8 @@ class FfiNativeTransformer extends FfiTransformer {
allowHandle: true,
allowInlineArray: true,
);
ensureNativeTypeToDartType(ffiType, dartType, node,
ensureNativeTypeMatch(
FfiTypeCheckDirection.nativeToDart, ffiType, dartType, node,
allowHandle: true, allowArray: true);
// Array types must have an @Array annotation denoting its size.
@ -955,7 +957,8 @@ class FfiNativeTransformer extends FfiTransformer {
try {
ensureNativeTypeValid(nativeType, node);
ensureNativeTypeToDartType(
ensureNativeTypeMatch(
FfiTypeCheckDirection.nativeToDart,
nativeType,
wrappedDartFunctionType,
node,

View file

@ -32,7 +32,8 @@ import 'package:kernel/type_environment.dart';
import 'definitions.dart' as definitions;
import 'native_type_cfe.dart';
import 'native.dart' as native;
import 'common.dart' show FfiStaticTypeError, FfiTransformer, NativeType;
import 'common.dart'
show FfiStaticTypeError, FfiTransformer, NativeType, FfiTypeCheckDirection;
import 'finalizable.dart';
/// Checks and replaces calls to dart:ffi compound fields and methods.
@ -402,7 +403,8 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
_ensureIsLeafIsConst(node);
final isLeaf = getIsLeafBoolean(node) ?? false;
ensureNativeTypeValid(nativeType, node);
ensureNativeTypeToDartType(
ensureNativeTypeMatch(
FfiTypeCheckDirection.nativeToDart,
nativeType,
dartType,
node,
@ -423,7 +425,8 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
final isLeaf = getIsLeafBoolean(node) ?? false;
ensureNativeTypeValid(nativeType, node);
ensureNativeTypeToDartType(
ensureNativeTypeMatch(
FfiTypeCheckDirection.nativeToDart,
nativeType,
dartType,
node,
@ -457,15 +460,18 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
final DartType dartType = func.getStaticType(staticTypeContext!);
ensureNativeTypeValid(nativeType, node);
ensureNativeTypeToDartType(nativeType, dartType, node);
final ffiFuncType = ensureNativeTypeMatch(
FfiTypeCheckDirection.dartToNative, nativeType, dartType, node)
as FunctionType;
final funcType = dartType as FunctionType;
// Check return type.
if (funcType.returnType != VoidType()) {
if (ffiFuncType.returnType != VoidType()) {
diagnosticReporter.report(
templateFfiNativeCallableListenerReturnVoid.withArguments(
funcType.returnType, currentLibrary.isNonNullableByDefault),
ffiFuncType.returnType,
currentLibrary.isNonNullableByDefault),
func.fileOffset,
1,
func.location?.file);
@ -839,11 +845,12 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
}
ensureNativeTypeValid(nativeType, node);
ensureNativeTypeToDartType(
final ffiFuncType = ensureNativeTypeMatch(
FfiTypeCheckDirection.dartToNative,
nativeType,
dartType,
node,
);
) as FunctionType;
final funcType = dartType as FunctionType;
@ -875,7 +882,7 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
if (hasExceptionalReturn) {
diagnosticReporter.report(
templateFfiExpectedNoExceptionalReturn.withArguments(
funcType.returnType, currentLibrary.isNonNullableByDefault),
ffiFuncType.returnType, currentLibrary.isNonNullableByDefault),
node.fileOffset,
1,
node.location?.file);
@ -886,7 +893,7 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
if (!hasExceptionalReturn) {
diagnosticReporter.report(
templateFfiExpectedExceptionalReturn.withArguments(
funcType.returnType, currentLibrary.isNonNullableByDefault),
ffiFuncType.returnType, currentLibrary.isNonNullableByDefault),
node.fileOffset,
1,
node.location?.file);
@ -1307,8 +1314,8 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
ensureNativeTypeValid(nativeType, node,
allowCompounds: true, allowInlineArray: true);
ensureNativeTypeToDartType(
nativeType, arg.getStaticType(staticTypeContext!), node,
ensureNativeTypeMatch(FfiTypeCheckDirection.nativeToDart, nativeType,
arg.getStaticType(staticTypeContext!), node,
allowArray: true);
return StaticInvocation(

View file

@ -1307,10 +1307,28 @@ DART_EXPORT void CallFunctionOnNewThreadNonBlocking(int64_t response_id,
////////////////////////////////////////////////////////////////////////////////
// Tests for isolate local callbacks.
DART_EXPORT void CallTwoIntFunction(int32_t (*fn)(int32_t, int32_t),
int32_t a,
int32_t b) {
DART_EXPORT int32_t CallTwoIntFunction(int32_t (*fn)(int32_t, int32_t),
int32_t a,
int32_t b) {
return fn(a, b);
}
DART_EXPORT void CallTwoIntVoidFunction(void (*fn)(int32_t, int32_t),
int32_t a,
int32_t b) {
fn(a, b);
}
DART_EXPORT void* CallTwoIntPointerFunction(void* (*fn)(int32_t, int32_t),
int32_t a,
int32_t b) {
return fn(a, b);
}
DART_EXPORT int32_t CallTwoPointerIntFunction(int32_t (*fn)(void*, void*),
void* a,
void* b) {
return fn(a, b);
}
} // namespace dart

View file

@ -189,11 +189,10 @@ testNativeCallableKeepAliveGetter() {
Future<void> testNativeCallableClosure() async {
final lib = NativeLibrary();
int c = 70000;
void foo(int a, int b) {
simpleFunctionResult.complete(a + b + c);
}
final callback = NativeCallable<CallbackNativeType>.listener(foo);
final callback = NativeCallable<CallbackNativeType>.listener((int a, int b) {
simpleFunctionResult.complete(a + b + c);
});
simpleFunctionResult = Completer<int>();
lib.callFunctionOnSameThread(1000, callback.nativeFunction);

View file

@ -0,0 +1,132 @@
// Copyright (c) 2024, 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.
// Dart test program for testing dart:ffi callbacks that take advantage of
// subtyping rules.
//
// VMOptions=
// VMOptions=--stacktrace-every=100
// VMOptions=--use-slow-path
// VMOptions=--use-slow-path --stacktrace-every=100
// VMOptions=--dwarf_stack_traces --no-retain_function_objects --no-retain_code_objects
// VMOptions=--test_il_serialization
// VMOptions=--profiler
// SharedObjects=ffi_test_functions
import 'dart:async';
import 'dart:ffi';
import "package:expect/expect.dart";
import 'dylib_utils.dart';
final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
typedef TwoIntVoidFnNativeType = Void Function(Pointer, Int32, Int32);
typedef TwoIntVoidFnType = void Function(Pointer, int, int);
final callTwoIntVoidFunction =
ffiTestFunctions.lookupFunction<TwoIntVoidFnNativeType, TwoIntVoidFnType>(
"CallTwoIntVoidFunction");
typedef TwoIntPointerFnNativeType = Pointer<NativeType> Function(
Pointer, Int32, Int32);
typedef TwoIntPointerFnType = Pointer<NativeType> Function(Pointer, int, int);
final callTwoIntPointerFunction = ffiTestFunctions.lookupFunction<
TwoIntPointerFnNativeType,
TwoIntPointerFnType>("CallTwoIntPointerFunction");
typedef TwoPointerIntFnNativeType = Int32 Function(
Pointer, Pointer<NativeType>, Pointer<NativeType>);
typedef TwoPointerIntFnType = int Function(
Pointer, Pointer<NativeType>, Pointer<NativeType>);
final callTwoPointerIntFunction = ffiTestFunctions.lookupFunction<
TwoPointerIntFnNativeType,
TwoPointerIntFnType>("CallTwoPointerIntFunction");
typedef VoidReturnFunction = Void Function(Int32, Int32);
int addVoidResult = 0;
int addVoid(int x, int y) {
print("addVoid($x, $y)");
addVoidResult = x + y;
return addVoidResult;
}
final addVoidAsyncResult = Completer<int>();
int addVoidAsync(int x, int y) {
print("addVoidAsync($x, $y)");
final result = x + y;
addVoidAsyncResult.complete(result);
return result;
}
typedef NaTyPtrReturnFunction = Pointer<NativeType> Function(Int32, Int32);
Pointer<Int64> addInt64PtrReturn(int x, int y) {
print("addInt64PtrReturn($x, $y)");
return Pointer<Int64>.fromAddress(x + y);
}
typedef Int64PtrParamFunction = Int32 Function(Pointer<Int64>, Pointer<Int64>);
int addNaTyPtrParam(Pointer<NativeType> x, Pointer<NativeType> y) {
print("addNaTyPtrParam($x, $y)");
return x.address + y.address;
}
Future<void> main() async {
await testReturnVoid();
testReturnSubtype();
testParamSubtype();
print("Done! :)");
}
Future<void> testReturnVoid() async {
// If the native function type returns void, the Dart function can return
// anything.
final legacyCallback = Pointer.fromFunction<VoidReturnFunction>(addVoid);
callTwoIntVoidFunction(legacyCallback, 100, 23);
Expect.equals(123, addVoidResult);
final isolateLocal = NativeCallable<VoidReturnFunction>.isolateLocal(addVoid)
..keepIsolateAlive = false;
callTwoIntVoidFunction(isolateLocal.nativeFunction, 400, 56);
Expect.equals(456, addVoidResult);
final listener = NativeCallable<VoidReturnFunction>.listener(addVoidAsync)
..keepIsolateAlive = false;
callTwoIntVoidFunction(listener.nativeFunction, 700, 89);
Expect.equals(789, await addVoidAsyncResult.future);
}
void testReturnSubtype() {
// The Dart function is allowed to return a subtype of the native return type.
final legacyCallback =
Pointer.fromFunction<NaTyPtrReturnFunction>(addInt64PtrReturn);
Expect.equals(
123, callTwoIntPointerFunction(legacyCallback, 100, 23).address);
final isolateLocal =
NativeCallable<NaTyPtrReturnFunction>.isolateLocal(addInt64PtrReturn)
..keepIsolateAlive = false;
Expect.equals(456,
callTwoIntPointerFunction(isolateLocal.nativeFunction, 400, 56).address);
}
void testParamSubtype() {
// The Dart function is allowed to accept params that are a supertype of the
// native type's params.
final legacyCallback =
Pointer.fromFunction<Int64PtrParamFunction>(addNaTyPtrParam, 0);
Expect.equals(
123,
callTwoPointerIntFunction(legacyCallback, Pointer<Int64>.fromAddress(100),
Pointer<Int64>.fromAddress(23)));
final isolateLocal = NativeCallable<Int64PtrParamFunction>.isolateLocal(
addNaTyPtrParam,
exceptionalReturn: 0)
..keepIsolateAlive = false;
Expect.equals(
456,
callTwoPointerIntFunction(isolateLocal.nativeFunction,
Pointer<Int64>.fromAddress(400), Pointer<Int64>.fromAddress(56)));
}

View file

@ -61,11 +61,9 @@ testNativeCallableStatic() {
testNativeCallableClosure() {
int c = 70000;
int closure(int a, int b) {
return a + b + c;
}
final callback = NativeCallable<CallbackNativeType>.isolateLocal(closure,
final callback = NativeCallable<CallbackNativeType>.isolateLocal(
(int a, int b) => a + b + c,
exceptionalReturn: 0);
Expect.equals(71234, callTwoIntFunction(callback.nativeFunction, 1000, 234));
@ -115,13 +113,10 @@ testNativeCallableNestedCloseCallStatic() {
testNativeCallableNestedCloseCallClosure() {
late NativeCallable callback;
int selfClosing(int a, int b) {
callback = NativeCallable<CallbackNativeType>.isolateLocal((int a, int b) {
callback.close();
return a + b;
}
callback = NativeCallable<CallbackNativeType>.isolateLocal(selfClosing,
exceptionalReturn: 0);
}, exceptionalReturn: 0);
Expect.equals(1234, callTwoIntFunction(callback.nativeFunction, 1000, 234));
@ -150,15 +145,13 @@ testNativeCallableExceptionalReturnStatic() {
}
testNativeCallableExceptionalReturnClosure() {
int thrower(int a, int b) {
final callback =
NativeCallable<CallbackNativeType>.isolateLocal((int a, int b) {
if (a != 1000) {
throw "Oh no!";
}
return a + b;
}
final callback = NativeCallable<CallbackNativeType>.isolateLocal(thrower,
exceptionalReturn: 5678);
}, exceptionalReturn: 5678);
Expect.equals(1234, callTwoIntFunction(callback.nativeFunction, 1000, 234));
Expect.equals(5678, callTwoIntFunction(callback.nativeFunction, 0, 0));
@ -182,13 +175,11 @@ Future<void> testNativeCallableDontKeepAliveStatic() async {
Future<void> testNativeCallableDontKeepAliveClosure() async {
int c = 70000;
int closure(int a, int b) {
return a + b + c;
}
final exitPort = ReceivePort();
await Isolate.spawn((_) async {
final callback = NativeCallable<CallbackNativeType>.isolateLocal(closure,
final callback = NativeCallable<CallbackNativeType>.isolateLocal(
(int a, int b) => a + b + c,
exceptionalReturn: 0);
Expect.equals(

View file

@ -198,9 +198,9 @@ void callbackParamImplicitDowncast1() {
}
void callbackParamSubtype1() {
final callback = ffiTestFunctions.lookupFunction<CallbackNaTyPointerParamOp,
CallbackNaTyPointerParamOpDart>(callbackParamOpName);
final fp = Pointer.fromFunction<NaTyPointerParamOp>(int64PointerParamOp);
final callback = ffiTestFunctions.lookupFunction<CallbackInt64PointerParamOp,
CallbackInt64PointerParamOpDart>(callbackParamOpName);
final fp = Pointer.fromFunction<Int64PointerParamOp>(naTyPointerParamOp);
callback(fp);
}

View file

@ -14,6 +14,7 @@ import 'dylib_utils.dart';
typedef Int64PointerParamOpDart = void Function(Pointer<Int64>);
typedef Int64PointerParamOp = Void Function(Pointer<Int64>);
typedef NaTyPointerParamOp = Void Function(Pointer<NativeType>);
typedef Int64PointerReturnOp = Pointer<Int64> Function();
typedef NaTyPointerReturnOp = Pointer<NativeType> Function();
@ -48,20 +49,19 @@ final f3 = p3 //# 10: compile-time error
// Test check on callbacks from native to Dart (fromFunction).
// ===========================================================
void naTyPointerParamOp(Pointer<NativeType> p) {
final Pointer<Int8> asInt8 = p.cast();
asInt8.value = 42;
void int64PointerParamOp(Pointer<Int64> p) {
p.value = 42;
}
Pointer<Int64> int64PointerReturnOp() {
Pointer<NativeType> naTyPointerReturnOp() {
return Pointer.fromAddress(0x13370000);
}
final implicitDowncast1 = //# 3: compile-time error
Pointer.fromFunction<Int64PointerParamOp>(//# 3: continued
Pointer.fromFunction<NaTyPointerParamOp>(//# 3: continued
naTyPointerParamOp); //# 3: continued
final implicitDowncast2 = //# 4: compile-time error
Pointer.fromFunction<NaTyPointerReturnOp>(//# 4: continued
int64PointerReturnOp); //# 4: continued
Pointer.fromFunction<Int64PointerReturnOp>(//# 4: continued
naTyPointerReturnOp); //# 4: continued
void main() {}