mirror of
https://github.com/dart-lang/sdk
synced 2024-11-05 18:22:09 +00:00
b113fffb65
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>
279 lines
7.3 KiB
Dart
279 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");
|