// Copyright (c) 2020, 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. // // SharedObjects=ffi_test_functions // VMOptions= // VMOptions=--use_slow_path import 'dart:ffi'; import 'package:expect/expect.dart'; import 'dylib_utils.dart'; void main() { testHandle(); testHandleInReturn(); testHandleWithInteger(); testReadField(); testTrueHandle(); testClosureCallback(); testReturnHandleInCallback(); testPropagateError(); testCallbackReturnException(); testDeepException(); testDeepException2(); testNull(); testDeepRecursive(); testNoHandlePropagateError(); testThrowOnReturnOfError(); } void testHandle() { print("testHandle"); final s = SomeClass(123); print("passObjectToC($s)"); final result = passObjectToC(s); print("result = $result"); Expect.isTrue(identical(s, result)); } void testHandleInReturn() { final string = 'foo'; Expect.identical(string, stringFromString(string)); Expect.identical(string, stringFromObject(string)); Expect.identical(42, intFromInt(42)); Expect.identical(42, intFromObject(42)); // TODO(https://dartbug.com/49518): Uncomment the lines below when the // runtime checks are added. // // Expect.throws(() => stringFromObject(Object())); // Expect.throws(() => stringFromObject(42)); // Expect.throws(() => intFromObject(Object())); // Expect.throws(() => intFromObject(string)); } void testHandleWithInteger() { print("testHandleWithInteger"); final s = 1337; print("passObjectToC($s)"); final result = passObjectToC(s); print("result = $result"); Expect.isTrue(identical(s, result)); } void testReadField() { final s = SomeClass(123); final result = handleReadFieldValue(s); Expect.equals(s.a, result); } void testTrueHandle() { final result = trueHandle(); Expect.isTrue(result); } int globalCounter = 0; void increaseCounter() { print("increaseCounter"); globalCounter++; } void doClosureCallback(Object callback) { print("doClosureCallback"); print(callback.runtimeType); print(callback); final callback_as_function = callback as void Function(); callback_as_function(); } final closureCallbackPointer = Pointer.fromFunction( doClosureCallback, ); void testClosureCallback() { print("testClosureCallback $closureCallbackPointer"); Expect.equals(0, globalCounter); closureCallbackThroughHandle(closureCallbackPointer, increaseCounter); Expect.equals(1, globalCounter); closureCallbackThroughHandle(closureCallbackPointer, increaseCounter); Expect.equals(2, globalCounter); } final someObject = SomeClass(12356789); Object returnHandleCallback() { print("returnHandleCallback returning $someObject"); return someObject; } final returnHandleCallbackPointer = Pointer.fromFunction( returnHandleCallback, ); void testReturnHandleInCallback() { print("testReturnHandleInCallback"); final result = returnHandleInCallback(returnHandleCallbackPointer); Expect.isTrue(identical(someObject, result)); } class SomeClass { // We use this getter in the native api, don't tree shake it. @pragma("vm:entry-point") final int a; SomeClass(this.a); } void testPropagateError() { final s = SomeOtherClass(123); Expect.throws(() => handleReadFieldValue(s)); } class SomeOtherClass { final int notA; SomeOtherClass(this.notA); } final someException = Exception("exceptionHandleCallback exception"); Object exceptionHandleCallback() { print("exceptionHandleCallback throwing ($someException)"); throw someException; } final exceptionHandleCallbackPointer = Pointer.fromFunction( exceptionHandleCallback, ); void testCallbackReturnException() { print("testCallbackReturnException"); bool throws = false; try { final result = returnHandleInCallback(exceptionHandleCallbackPointer); print(result); } catch (e) { throws = true; print("caught ($e)"); Expect.isTrue(identical(someException, e)); } Expect.isTrue(throws); } Object callCAgainFromCallback() { print("callCAgainFromCallback"); final s = SomeOtherClass(123); Expect.throws(() => handleReadFieldValue(s)); return someObject; } final callCAgainFromCallbackPointer = Pointer.fromFunction( callCAgainFromCallback, ); void testDeepException() { print("testDeepException"); final result = returnHandleInCallback(callCAgainFromCallbackPointer); Expect.isTrue(identical(someObject, result)); } Object callCAgainFromCallback2() { print("callCAgainFromCallback2"); final s = SomeOtherClass(123); handleReadFieldValue(s); // throws. return someObject; } final callCAgainFromCallbackPointer2 = Pointer.fromFunction( callCAgainFromCallback2, ); void testDeepException2() { print("testDeepException2"); Expect.throws(() => returnHandleInCallback(callCAgainFromCallbackPointer2)); } Object? returnNullHandleCallback() { print("returnHandleCallback returning null"); return null; } final returnNullHandleCallbackPointer = Pointer.fromFunction( returnNullHandleCallback, ); void testNull() { print("testNull"); final result = passObjectToC(null); Expect.isNull(result); final result2 = returnHandleInCallback(returnNullHandleCallbackPointer); Expect.isNull(result2); } Object recurseAbove0(int i) { print("recurseAbove0($i)"); if (i == 0) { print("throwing"); throw someException; } if (i < 0) { print("returning"); return someObject; } final result = handleRecursion( SomeClassWithMethod(), recurseAbove0Pointer, i - 1, ); print("return $i"); return result; } final recurseAbove0Pointer = Pointer.fromFunction( recurseAbove0, ); class SomeClassWithMethod { // We use this method in the native api, don't tree shake it. @pragma("vm:entry-point") Object a(int i) => recurseAbove0(i); } void testDeepRecursive() { // works on arm. Expect.throws(() { handleRecursion(123, recurseAbove0Pointer, 1); }); Expect.throws(() { handleRecursion(SomeClassWithMethod(), recurseAbove0Pointer, 1); }); Expect.throws(() { recurseAbove0(100); }); final result = recurseAbove0(101); Expect.isTrue(identical(someObject, result)); } void testNoHandlePropagateError() { bool throws = false; try { final result = propagateErrorWithoutHandle(exceptionHandleCallbackPointer); print(result.runtimeType); print(result); } catch (e) { throws = true; print("caught ($e, ${e.runtimeType})"); Expect.isTrue(identical(someException, e)); } Expect.isTrue(throws); } void testThrowOnReturnOfError() { bool throws = false; try { final result = autoPropagateErrorInHandle(exceptionHandleCallbackPointer); print(result.runtimeType); print(result); } catch (e) { throws = true; print("caught ($e)"); Expect.isTrue(identical(someException, e)); } Expect.isTrue(throws); } final testLibrary = dlopenPlatformSpecific("ffi_test_functions"); final passObjectToC = testLibrary .lookupFunction( "PassObjectToC", ); final stringFromString = testLibrary .lookupFunction( "PassObjectToC", ); final stringFromObject = testLibrary .lookupFunction( "PassObjectToC", ); final intFromInt = testLibrary .lookupFunction( "PassObjectToC", ); final intFromObject = testLibrary .lookupFunction( "PassObjectToC", ); final handleReadFieldValue = testLibrary .lookupFunction( "HandleReadFieldValue", ); final trueHandle = testLibrary .lookupFunction("TrueHandle"); final closureCallbackThroughHandle = testLibrary .lookupFunction< Void Function(Pointer>, Handle), void Function(Pointer>, Object) >("ClosureCallbackThroughHandle"); final returnHandleInCallback = testLibrary .lookupFunction< Handle Function(Pointer>), Object? Function(Pointer>) >("ReturnHandleInCallback"); final handleRecursion = testLibrary .lookupFunction< Handle Function( Handle, Pointer>, Int64, ), Object Function( Object, Pointer>, int, ) >("HandleRecursion"); final propagateErrorWithoutHandle = testLibrary .lookupFunction< Int64 Function(Pointer>), int Function(Pointer>) >("PropagateErrorWithoutHandle"); final autoPropagateErrorInHandle = testLibrary .lookupFunction< Handle Function(Pointer>), Object Function(Pointer>) >("ThrowOnReturnOfError");