[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>
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart index 301adb7..e50a82b 100644 --- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart +++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -514,6 +514,7 @@ } if (!_validateCompatibleNativeType( + _FfiTypeCheckDirection.nativeToDart, type, ffiSignature, // Functions are not allowed in native fields, but allowing them in the @@ -631,8 +632,9 @@ ); 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 @@ 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 @@ /// 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 @@ } // 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 @@ 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 @@ } 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 @@ 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 @@ 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 @@ 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 @@ // 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 @@ } 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 @@ 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 @@ 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 @@ 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 @@ } 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 @@ } } } 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 @@ } } +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,
diff --git a/pkg/analyzer/test/src/diagnostics/ffi_async_callback_test.dart b/pkg/analyzer/test/src/diagnostics/ffi_async_callback_test.dart index 0d7bf94..9f2c276 100644 --- a/pkg/analyzer/test/src/diagnostics/ffi_async_callback_test.dart +++ b/pkg/analyzer/test/src/diagnostics/ffi_async_callback_test.dart
@@ -133,6 +133,16 @@ ''', []); } + 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'; @@ -202,4 +212,14 @@ } ''', []); } + + 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); +} +''', []); + } }
diff --git a/pkg/analyzer/test/src/diagnostics/must_be_a_subtype_test.dart b/pkg/analyzer/test/src/diagnostics/must_be_a_subtype_test.dart index 551c7ec..c60c7fb 100644 --- a/pkg/analyzer/test/src/diagnostics/must_be_a_subtype_test.dart +++ b/pkg/analyzer/test/src/diagnostics/must_be_a_subtype_test.dart
@@ -63,6 +63,17 @@ '''); } + 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';
diff --git a/pkg/vm/lib/transformations/ffi/common.dart b/pkg/vm/lib/transformations/ffi/common.dart index 5f93772..037ecd6 100644 --- a/pkg/vm/lib/transformations/ffi/common.dart +++ b/pkg/vm/lib/transformations/ffi/common.dart
@@ -137,6 +137,14 @@ 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 @@ } } - void ensureNativeTypeToDartType( + DartType ensureNativeTypeMatch( + FfiTypeCheckDirection direction, DartType nativeType, DartType dartType, TreeNode reportErrorOn, { @@ -1411,23 +1420,31 @@ 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,
diff --git a/pkg/vm/lib/transformations/ffi/native.dart b/pkg/vm/lib/transformations/ffi/native.dart index 3762b42..e32025f 100644 --- a/pkg/vm/lib/transformations/ffi/native.dart +++ b/pkg/vm/lib/transformations/ffi/native.dart
@@ -25,7 +25,8 @@ 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 @@ 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 @@ try { ensureNativeTypeValid(nativeType, node); - ensureNativeTypeToDartType( + ensureNativeTypeMatch( + FfiTypeCheckDirection.nativeToDart, nativeType, wrappedDartFunctionType, node,
diff --git a/pkg/vm/lib/transformations/ffi/use_sites.dart b/pkg/vm/lib/transformations/ffi/use_sites.dart index 08cb503..f63fb21 100644 --- a/pkg/vm/lib/transformations/ffi/use_sites.dart +++ b/pkg/vm/lib/transformations/ffi/use_sites.dart
@@ -32,7 +32,8 @@ 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 @@ _ensureIsLeafIsConst(node); final isLeaf = getIsLeafBoolean(node) ?? false; ensureNativeTypeValid(nativeType, node); - ensureNativeTypeToDartType( + ensureNativeTypeMatch( + FfiTypeCheckDirection.nativeToDart, nativeType, dartType, node, @@ -423,7 +425,8 @@ final isLeaf = getIsLeafBoolean(node) ?? false; ensureNativeTypeValid(nativeType, node); - ensureNativeTypeToDartType( + ensureNativeTypeMatch( + FfiTypeCheckDirection.nativeToDart, nativeType, dartType, node, @@ -457,15 +460,18 @@ 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 @@ } ensureNativeTypeValid(nativeType, node); - ensureNativeTypeToDartType( + final ffiFuncType = ensureNativeTypeMatch( + FfiTypeCheckDirection.dartToNative, nativeType, dartType, node, - ); + ) as FunctionType; final funcType = dartType as FunctionType; @@ -875,7 +882,7 @@ if (hasExceptionalReturn) { diagnosticReporter.report( templateFfiExpectedNoExceptionalReturn.withArguments( - funcType.returnType, currentLibrary.isNonNullableByDefault), + ffiFuncType.returnType, currentLibrary.isNonNullableByDefault), node.fileOffset, 1, node.location?.file); @@ -886,7 +893,7 @@ if (!hasExceptionalReturn) { diagnosticReporter.report( templateFfiExpectedExceptionalReturn.withArguments( - funcType.returnType, currentLibrary.isNonNullableByDefault), + ffiFuncType.returnType, currentLibrary.isNonNullableByDefault), node.fileOffset, 1, node.location?.file); @@ -1307,8 +1314,8 @@ 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(
diff --git a/runtime/bin/ffi_test/ffi_test_functions.cc b/runtime/bin/ffi_test/ffi_test_functions.cc index d2e3f7a..9a84fea 100644 --- a/runtime/bin/ffi_test/ffi_test_functions.cc +++ b/runtime/bin/ffi_test/ffi_test_functions.cc
@@ -1307,10 +1307,28 @@ //////////////////////////////////////////////////////////////////////////////// // 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
diff --git a/tests/ffi/async_void_function_callbacks_test.dart b/tests/ffi/async_void_function_callbacks_test.dart index 37db221..d58bb6f 100644 --- a/tests/ffi/async_void_function_callbacks_test.dart +++ b/tests/ffi/async_void_function_callbacks_test.dart
@@ -189,11 +189,10 @@ 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);
diff --git a/tests/ffi/function_callbacks_subtype_test.dart b/tests/ffi/function_callbacks_subtype_test.dart new file mode 100644 index 0000000..b812087 --- /dev/null +++ b/tests/ffi/function_callbacks_subtype_test.dart
@@ -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))); +}
diff --git a/tests/ffi/isolate_local_function_callbacks_test.dart b/tests/ffi/isolate_local_function_callbacks_test.dart index ad4d5c0..0ea8563 100644 --- a/tests/ffi/isolate_local_function_callbacks_test.dart +++ b/tests/ffi/isolate_local_function_callbacks_test.dart
@@ -61,11 +61,9 @@ 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 @@ 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 @@ } 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> 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(
diff --git a/tests/ffi/variance_function_test.dart b/tests/ffi/variance_function_test.dart index bd3f0d1..704019f 100644 --- a/tests/ffi/variance_function_test.dart +++ b/tests/ffi/variance_function_test.dart
@@ -198,9 +198,9 @@ } 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); }
diff --git a/tests/ffi/vmspecific_variance_function_checks_test.dart b/tests/ffi/vmspecific_variance_function_checks_test.dart index ad1f7e5..964ab1f 100644 --- a/tests/ffi/vmspecific_variance_function_checks_test.dart +++ b/tests/ffi/vmspecific_variance_function_checks_test.dart
@@ -14,6 +14,7 @@ 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 @@ // 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() {}