dart-sdk/tests/ffi_2/vmspecific_handle_test.dart
Daco Harkes b113fffb65 Reland "[vm/ffi] Throw on returning Error in Handle"
Fix: Check handle contents for Smi.
Closes: https://github.com/flutter/flutter/issues/112726

Orignal CL description:

Makes `Dart_Handle` FFI returns behave as the following snippet:

```
Dart_Handle ExampleSnippet() {
  Dart_Handle result = ...;
  if (Dart_IsError(result)) {
    Dart_PropagateError(result);
  }
  return result;
}
```

Also makes FFI consistent with Dart_NativeFunctions, which will
automatically throw upon return if Dart_SetReturnValue set the result
to an error.

`UnhandledExceptions` cannot flow out into Dart generated code. So,
the implementation needs to be in `FfiCallInstr::EmitNativeCode`.

Using `Dart_IsError` is slow compared to a machine code class id
check. So, we should do the handle unwrapping and class id check in
machine code.

Unwrapping Handles in machine code is only safe when the GC is
guaranteed to not run: Either (1) in `kThreadInGenerated`, or (2) in
`kThreadInNative`, but only when transitioned into safepoint. So, the
handle cannot be unwrapped immediately after the FFI call in machine code. We first need to transition back to generated.

This means we need to transition again to native to do the actual
`Dart_PropagateError` call. We can do so without the stub in JIT
because we never return with normal control flow.

Performance impact of this change is within benchmark noise in both
JIT and AOT.
Size impact is 42 bytes on x64, which is 10% in AOT and 12% in JIT.

For more numbers see: go/dart-ffi-handle-error

TEST=runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
TEST=tests/ffi/vmspecific_handle_test.dart

Closes: https://github.com/dart-lang/sdk/issues/49936
Change-Id: Id8edfd841a7d6246438386007d83747868a0a151
Cq-Include-Trybots: luci.dart.try:vm-canary-linux-debug-try,vm-ffi-android-debug-arm64c-try,vm-ffi-android-debug-arm-try,vm-kernel-gcc-linux-try,vm-kernel-linux-debug-x64-try,vm-kernel-linux-debug-x64c-try,vm-kernel-msvc-windows-try,vm-kernel-optcounter-threshold-linux-release-x64-try,vm-kernel-precomp-asan-linux-release-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-reload-linux-debug-x64-try,vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-tsan-linux-release-x64-try,vm-kernel-win-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-precomp-ffi-qemu-linux-release-arm-try,vm-precomp-ffi-qemu-linux-release-riscv64-try,vm-kernel-linux-debug-ia32-try,vm-kernel-mac-release-arm64-try,vm-kernel-precomp-win-debug-x64c-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/262342
Reviewed-by: Martin Kustermann <kustermann@google.com>
Auto-Submit: Daco Harkes <dacoharkes@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
2022-10-03 12:25:08 +00:00

280 lines
7.3 KiB
Dart

// 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
// @dart = 2.9
import 'dart:ffi';
import 'package:expect/expect.dart';
import 'dylib_utils.dart';
void main() {
testHandle();
testHandleWithInteger();
testReadField();
testTrueHandle();
testClosureCallback();
testReturnHandleInCallback();
testPropagateError();
testCallbackReturnException();
testDeepException();
testDeepException2();
testNull();
testDeepRecursive();
testNoHandlePropagateError();
}
void testHandle() {
print("testHandle");
final s = SomeClass(123);
print("passObjectToC($s)");
final result = passObjectToC(s);
print("result = $result");
Expect.isTrue(identical(s, result));
}
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<Void Function(Handle)>(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<Handle Function()>(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<Handle Function()>(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<Handle Function()>(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<Handle Function()>(callCAgainFromCallback2);
void testDeepException2() {
print("testDeepException2");
Expect.throws(() => returnHandleInCallback(callCAgainFromCallbackPointer2));
}
Object returnNullHandleCallback() {
print("returnHandleCallback returning null");
return null;
}
final returnNullHandleCallbackPointer =
Pointer.fromFunction<Handle Function()>(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<Handle Function(Int64)>(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);
} 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<Handle Function(Handle),
Object Function(Object)>("PassObjectToC");
final handleReadFieldValue =
testLibrary.lookupFunction<Int64 Function(Handle), int Function(Object)>(
"HandleReadFieldValue");
final trueHandle = testLibrary
.lookupFunction<Handle Function(), Object Function()>("TrueHandle");
final closureCallbackThroughHandle = testLibrary.lookupFunction<
Void Function(Pointer<NativeFunction<Void Function(Handle)>>, Handle),
void Function(Pointer<NativeFunction<Void Function(Handle)>>,
Object)>("ClosureCallbackThroughHandle");
final returnHandleInCallback = testLibrary.lookupFunction<
Handle Function(Pointer<NativeFunction<Handle Function()>>),
Object Function(
Pointer<NativeFunction<Handle Function()>>)>("ReturnHandleInCallback");
final handleRecursion = testLibrary.lookupFunction<
Handle Function(
Handle, Pointer<NativeFunction<Handle Function(Int64)>>, Int64),
Object Function(Object, Pointer<NativeFunction<Handle Function(Int64)>>,
int)>("HandleRecursion");
final propagateErrorWithoutHandle = testLibrary.lookupFunction<
Int64 Function(Pointer<NativeFunction<Handle Function()>>),
int Function(Pointer<NativeFunction<Handle Function()>>)>(
"PropagateErrorWithoutHandle");