From cb59df7acfef63f1e124d9124c19c0d48ceed345 Mon Sep 17 00:00:00 2001 From: Ryan Macnak Date: Wed, 19 Feb 2025 09:40:34 -0800 Subject: [PATCH] [vm, ffi] Better handle errors that are not unhandled exceptions during FFI callbacks. Before this change, an error reaching an FFI callback would attempt to execute the normal invocation stub from the beginning in the FFI callback's frame, which quickly crashes. After this change, the runtime recognizes this marker use of the invocation stub and returns to the FFI callback function instead. TEST=ffi/unwind Bug: https://github.com/dart-lang/sdk/issues/39487 Change-Id: I477cfcfc236e6cf518ebfe52860ba49e466ebf8b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/409562 Reviewed-by: Daco Harkes Commit-Queue: Ryan Macnak --- runtime/bin/ffi_test/ffi_test_functions.cc | 7 ++ .../ffi_test/ffi_test_functions_vmspecific.cc | 5 + runtime/vm/compiler/stub_code_compiler_arm.cc | 20 +++- .../vm/compiler/stub_code_compiler_arm64.cc | 20 +++- .../vm/compiler/stub_code_compiler_ia32.cc | 22 +++- .../vm/compiler/stub_code_compiler_riscv.cc | 20 +++- runtime/vm/compiler/stub_code_compiler_x64.cc | 22 +++- runtime/vm/exceptions.cc | 105 ++++++++++-------- runtime/vm/object.cc | 7 ++ runtime/vm/object.h | 1 + runtime/vm/stub_code_list.h | 1 + runtime/vm/thread.cc | 5 + runtime/vm/thread.h | 2 + tests/ffi/callback_unwind_error_test.dart | 32 ++++-- ...back_unwind_error_through_handle_test.dart | 51 +++++++++ 15 files changed, 260 insertions(+), 60 deletions(-) create mode 100644 tests/ffi/vmspecific_callback_unwind_error_through_handle_test.dart diff --git a/runtime/bin/ffi_test/ffi_test_functions.cc b/runtime/bin/ffi_test/ffi_test_functions.cc index ba0fd4b0914..93d3040e4b2 100644 --- a/runtime/bin/ffi_test/ffi_test_functions.cc +++ b/runtime/bin/ffi_test/ffi_test_functions.cc @@ -928,6 +928,13 @@ DART_EXPORT intptr_t TestSimpleAddition(intptr_t (*add)(int, int)) { return 0; } +DART_EXPORT intptr_t TestUnwindError(intptr_t (*add)(int, int)) { + const intptr_t result = add(10, 20); + printf("result %" PRIdPTR "\n", result); + CHECK_EQ(result, 42); + return 0; +} + //// Following tests are copied from above, with the role of Dart and C++ code //// reversed. diff --git a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc index 7722169dad6..76d4454b522 100644 --- a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc +++ b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc @@ -1469,4 +1469,9 @@ DART_EXPORT void ManyHandles(Dart_Handle o0, } #undef CHECK_STRING +DART_EXPORT Dart_Handle TestUnwindErrorThroughHandle(Dart_Handle (*add)(int, + int)) { + return add(10, 20); +} + } // namespace dart diff --git a/runtime/vm/compiler/stub_code_compiler_arm.cc b/runtime/vm/compiler/stub_code_compiler_arm.cc index 5f07a49ac72..4d83975c794 100644 --- a/runtime/vm/compiler/stub_code_compiler_arm.cc +++ b/runtime/vm/compiler/stub_code_compiler_arm.cc @@ -3108,7 +3108,8 @@ void StubCodeCompiler::GenerateJumpToFrameStub() { // // The arguments are stored in the Thread object. // Does not return. -void StubCodeCompiler::GenerateRunExceptionHandlerStub() { +static void GenerateRunExceptionHandler(Assembler* assembler, + bool unbox_exception) { WRITES_RETURN_ADDRESS_TO_LR( __ LoadFromOffset(LR, THR, target::Thread::resume_pc_offset())); @@ -3120,6 +3121,15 @@ void StubCodeCompiler::GenerateRunExceptionHandlerStub() { // Exception object. __ LoadFromOffset(R0, THR, target::Thread::active_exception_offset()); __ StoreToOffset(R2, THR, target::Thread::active_exception_offset()); + if (unbox_exception) { + compiler::Label not_smi, done; + __ BranchIfNotSmi(R0, ¬_smi); + __ SmiUntag(R0); + __ Jump(&done); + __ Bind(¬_smi); + __ ldr(R0, FieldAddress(R0, Mint::value_offset())); + __ Bind(&done); + } // StackTrace object. __ LoadFromOffset(R1, THR, target::Thread::active_stacktrace_offset()); @@ -3129,6 +3139,14 @@ void StubCodeCompiler::GenerateRunExceptionHandlerStub() { __ bx(LR)); // Jump to the exception handler code. } +void StubCodeCompiler::GenerateRunExceptionHandlerStub() { + GenerateRunExceptionHandler(assembler, false); +} + +void StubCodeCompiler::GenerateRunExceptionHandlerUnboxStub() { + GenerateRunExceptionHandler(assembler, true); +} + // Deoptimize a frame on the call stack before rewinding. // The arguments are stored in the Thread object. // No result. diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc index e333d6da84a..713b49b1fb7 100644 --- a/runtime/vm/compiler/stub_code_compiler_arm64.cc +++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc @@ -3494,7 +3494,8 @@ void StubCodeCompiler::GenerateJumpToFrameStub() { // // The arguments are stored in the Thread object. // Does not return. -void StubCodeCompiler::GenerateRunExceptionHandlerStub() { +static void GenerateRunExceptionHandler(Assembler* assembler, + bool unbox_exception) { WRITES_RETURN_ADDRESS_TO_LR( __ LoadFromOffset(LR, THR, target::Thread::resume_pc_offset())); @@ -3506,6 +3507,15 @@ void StubCodeCompiler::GenerateRunExceptionHandlerStub() { // Exception object. __ LoadFromOffset(R0, THR, target::Thread::active_exception_offset()); __ StoreToOffset(R2, THR, target::Thread::active_exception_offset()); + if (unbox_exception) { + compiler::Label not_smi, done; + __ BranchIfNotSmi(R0, ¬_smi); + __ SmiUntag(R0); + __ Jump(&done); + __ Bind(¬_smi); + __ ldr(R0, FieldAddress(R0, Mint::value_offset())); + __ Bind(&done); + } // StackTrace object. __ LoadFromOffset(R1, THR, target::Thread::active_stacktrace_offset()); @@ -3514,6 +3524,14 @@ void StubCodeCompiler::GenerateRunExceptionHandlerStub() { __ ret(); // Jump to the exception handler code. } +void StubCodeCompiler::GenerateRunExceptionHandlerStub() { + GenerateRunExceptionHandler(assembler, false); +} + +void StubCodeCompiler::GenerateRunExceptionHandlerUnboxStub() { + GenerateRunExceptionHandler(assembler, true); +} + // Deoptimize a frame on the call stack before rewinding. // The arguments are stored in the Thread object. // No result. diff --git a/runtime/vm/compiler/stub_code_compiler_ia32.cc b/runtime/vm/compiler/stub_code_compiler_ia32.cc index 405389d6999..4f92391d01a 100644 --- a/runtime/vm/compiler/stub_code_compiler_ia32.cc +++ b/runtime/vm/compiler/stub_code_compiler_ia32.cc @@ -2987,7 +2987,8 @@ void StubCodeCompiler::GenerateJumpToFrameStub() { // // The arguments are stored in the Thread object. // No result. -void StubCodeCompiler::GenerateRunExceptionHandlerStub() { +static void GenerateRunExceptionHandler(Assembler* assembler, + bool unbox_exception) { ASSERT(kExceptionObjectReg == EAX); ASSERT(kStackTraceObjectReg == EDX); __ movl(EBX, Address(THR, target::Thread::resume_pc_offset())); @@ -2999,6 +3000,17 @@ void StubCodeCompiler::GenerateRunExceptionHandlerStub() { Address exception_addr(THR, target::Thread::active_exception_offset()); __ movl(kExceptionObjectReg, exception_addr); __ movl(exception_addr, ECX); + if (unbox_exception) { + compiler::Label not_smi, done; + __ BranchIfNotSmi(kExceptionObjectReg, ¬_smi, + compiler::Assembler::kNearJump); + __ SmiUntag(kExceptionObjectReg); + __ jmp(&done, compiler::Assembler::kNearJump); + __ Bind(¬_smi); + __ movl(kExceptionObjectReg, + compiler::FieldAddress(kExceptionObjectReg, Mint::value_offset())); + __ Bind(&done); + } // Load the stacktrace from the current thread. Address stacktrace_addr(THR, target::Thread::active_stacktrace_offset()); @@ -3008,6 +3020,14 @@ void StubCodeCompiler::GenerateRunExceptionHandlerStub() { __ jmp(EBX); // Jump to continuation point. } +void StubCodeCompiler::GenerateRunExceptionHandlerStub() { + GenerateRunExceptionHandler(assembler, false); +} + +void StubCodeCompiler::GenerateRunExceptionHandlerUnboxStub() { + GenerateRunExceptionHandler(assembler, true); +} + // Deoptimize a frame on the call stack before rewinding. // The arguments are stored in the Thread object. // No result. diff --git a/runtime/vm/compiler/stub_code_compiler_riscv.cc b/runtime/vm/compiler/stub_code_compiler_riscv.cc index e540b85b4ac..dbd557df457 100644 --- a/runtime/vm/compiler/stub_code_compiler_riscv.cc +++ b/runtime/vm/compiler/stub_code_compiler_riscv.cc @@ -2967,11 +2967,21 @@ void StubCodeCompiler::GenerateJumpToFrameStub() { // // The arguments are stored in the Thread object. // Does not return. -void StubCodeCompiler::GenerateRunExceptionHandlerStub() { +static void GenerateRunExceptionHandler(Assembler* assembler, + bool unbox_exception) { // Exception object. ASSERT(kExceptionObjectReg == A0); __ LoadFromOffset(A0, THR, target::Thread::active_exception_offset()); __ StoreToOffset(NULL_REG, THR, target::Thread::active_exception_offset()); + if (unbox_exception) { + compiler::Label not_smi, done; + __ BranchIfNotSmi(A0, ¬_smi); + __ SmiUntag(A0); + __ Jump(&done); + __ Bind(¬_smi); + __ lx(A0, FieldAddress(A0, Mint::value_offset())); + __ Bind(&done); + } // StackTrace object. ASSERT(kStackTraceObjectReg == A1); @@ -2982,6 +2992,14 @@ void StubCodeCompiler::GenerateRunExceptionHandlerStub() { __ ret(); // Jump to the exception handler code. } +void StubCodeCompiler::GenerateRunExceptionHandlerStub() { + GenerateRunExceptionHandler(assembler, false); +} + +void StubCodeCompiler::GenerateRunExceptionHandlerUnboxStub() { + GenerateRunExceptionHandler(assembler, true); +} + // Deoptimize a frame on the call stack before rewinding. // The arguments are stored in the Thread object. // No result. diff --git a/runtime/vm/compiler/stub_code_compiler_x64.cc b/runtime/vm/compiler/stub_code_compiler_x64.cc index cbcb0665e7f..b397e955197 100644 --- a/runtime/vm/compiler/stub_code_compiler_x64.cc +++ b/runtime/vm/compiler/stub_code_compiler_x64.cc @@ -3392,7 +3392,8 @@ void StubCodeCompiler::GenerateJumpToFrameStub() { // // The arguments are stored in the Thread object. // No result. -void StubCodeCompiler::GenerateRunExceptionHandlerStub() { +static void GenerateRunExceptionHandler(Assembler* assembler, + bool unbox_exception) { ASSERT(kExceptionObjectReg == RAX); ASSERT(kStackTraceObjectReg == RDX); __ movq(CallingConventions::kArg1Reg, @@ -3407,6 +3408,17 @@ void StubCodeCompiler::GenerateRunExceptionHandlerStub() { Address exception_addr(THR, target::Thread::active_exception_offset()); __ movq(kExceptionObjectReg, exception_addr); __ movq(exception_addr, TMP); + if (unbox_exception) { + compiler::Label not_smi, done; + __ BranchIfNotSmi(kExceptionObjectReg, ¬_smi, + compiler::Assembler::kNearJump); + __ SmiUntagAndSignExtend(kExceptionObjectReg); + __ jmp(&done, compiler::Assembler::kNearJump); + __ Bind(¬_smi); + __ movq(kExceptionObjectReg, + compiler::FieldAddress(kExceptionObjectReg, Mint::value_offset())); + __ Bind(&done); + } // Load the stacktrace from the current thread. Address stacktrace_addr(THR, target::Thread::active_stacktrace_offset()); @@ -3416,6 +3428,14 @@ void StubCodeCompiler::GenerateRunExceptionHandlerStub() { __ jmp(CallingConventions::kArg1Reg); // Jump to continuation point. } +void StubCodeCompiler::GenerateRunExceptionHandlerStub() { + GenerateRunExceptionHandler(assembler, false); +} + +void StubCodeCompiler::GenerateRunExceptionHandlerUnboxStub() { + GenerateRunExceptionHandler(assembler, true); +} + // Deoptimize a frame on the call stack before rewinding. // The arguments are stored in the Thread object. // No result. diff --git a/runtime/vm/exceptions.cc b/runtime/vm/exceptions.cc index 16d77403211..41533d7b539 100644 --- a/runtime/vm/exceptions.cc +++ b/runtime/vm/exceptions.cc @@ -12,6 +12,7 @@ #include "lib/stacktrace.h" #include "vm/dart_api_impl.h" +#include "vm/dart_api_state.h" #include "vm/dart_entry.h" #include "vm/datastream.h" #include "vm/debugger.h" @@ -538,24 +539,6 @@ CatchEntryMoves* CatchEntryMovesMapReader::ReadCompressedCatchEntryMovesSuffix( return moves; } -static void FindErrorHandler(uword* handler_pc, - uword* handler_sp, - uword* handler_fp) { - StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, - Thread::Current(), - StackFrameIterator::kNoCrossThreadIteration); - StackFrame* frame = frames.NextFrame(); - ASSERT(frame != nullptr); - while (!frame->IsEntryFrame()) { - frame = frames.NextFrame(); - ASSERT(frame != nullptr); - } - ASSERT(frame->IsEntryFrame()); - *handler_pc = frame->pc(); - *handler_sp = frame->sp(); - *handler_fp = frame->fp(); -} - static void ClearLazyDeopts(Thread* thread, uword frame_pointer) { if (thread->pending_deopts().HasPendingDeopts()) { // We may be jumping over frames scheduled for lazy deopt. Remove these @@ -590,19 +573,40 @@ static void ClearLazyDeopts(Thread* thread, uword frame_pointer) { } } +enum ExceptionType { kPassObject, kPassHandle, kPassUnboxed }; + static void JumpToExceptionHandler(Thread* thread, uword program_counter, uword stack_pointer, uword frame_pointer, const Object& exception_object, - const Object& stacktrace_object) { + const Object& stacktrace_object, + ExceptionType type = kPassObject) { bool clear_deopt = false; uword remapped_pc = thread->pending_deopts().RemapExceptionPCForDeopt( program_counter, frame_pointer, &clear_deopt); - thread->set_active_exception(exception_object); + uword run_exception_pc = StubCode::RunExceptionHandler().EntryPoint(); + switch (type) { + case kPassObject: + thread->set_active_exception(exception_object); + break; + case kPassHandle: { + LocalHandle* handle = + thread->api_top_scope()->local_handles()->AllocateHandle(); + handle->set_ptr(exception_object.ptr()); + thread->set_active_exception(handle); + break; + } + case kPassUnboxed: { + thread->set_active_exception(exception_object); + run_exception_pc = StubCode::RunExceptionHandlerUnbox().EntryPoint(); + break; + } + default: + UNREACHABLE(); + } thread->set_active_stacktrace(stacktrace_object); thread->set_resume_pc(remapped_pc); - uword run_exception_pc = StubCode::RunExceptionHandler().EntryPoint(); Exceptions::JumpToFrame(thread, run_exception_pc, stack_pointer, frame_pointer, clear_deopt); } @@ -1012,39 +1016,46 @@ void Exceptions::PropagateError(const Error& error) { const Instance& stk = Instance::Handle(zone, uhe.stacktrace()); Exceptions::ReThrow(thread, exc, stk); } else { + const Instance& stk = StackTrace::Handle(zone); // Null stacktrace. // Return to the invocation stub and return this error object. The // C++ code which invoked this dart sequence can check and do the // appropriate thing. - uword handler_pc = 0; - uword handler_sp = 0; - uword handler_fp = 0; - FindErrorHandler(&handler_pc, &handler_sp, &handler_fp); - JumpToExceptionHandler(thread, handler_pc, handler_sp, handler_fp, error, - StackTrace::Handle(zone)); // Null stacktrace. + StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, thread, + StackFrameIterator::kNoCrossThreadIteration); + StackFrame* frame = frames.NextFrame(); + StackFrame* prev = frame; + ASSERT(frame != nullptr); + while (!frame->IsEntryFrame()) { + prev = frame; + frame = frames.NextFrame(); + ASSERT(frame != nullptr); + } + if (frame->pc() == StubCode::InvokeDartCode().EntryPoint()) { + // This is an FFI callback using the invocation stub as a marker. Real use + // of invocation stub would be in the middle, not the entry point. Use the + // callback's exceptional return value instead of the error unless the + // return type is Dart_Handle. + ASSERT(prev->IsDartFrame()); + frame = prev; + const Function& func = + Function::Handle(zone, frame->LookupDartFunction()); + ASSERT(func.IsFfiCallbackTrampoline()); + if (func.FfiCSignatureReturnsHandle()) { + JumpToExceptionHandler(thread, frame->pc(), frame->sp(), frame->fp(), + error, stk, kPassHandle); + } else { + const Instance& val = + Instance::Handle(zone, func.FfiCallbackExceptionalReturn()); + JumpToExceptionHandler(thread, frame->pc(), frame->sp(), frame->fp(), + val, stk, kPassUnboxed); + } + } + JumpToExceptionHandler(thread, frame->pc(), frame->sp(), frame->fp(), error, + stk); } UNREACHABLE(); } -void Exceptions::PropagateToEntry(const Error& error) { - Thread* thread = Thread::Current(); - Zone* zone = thread->zone(); - ASSERT(thread->top_exit_frame_info() != 0); - Instance& stacktrace = Instance::Handle(zone); - if (error.IsUnhandledException()) { - const UnhandledException& uhe = UnhandledException::Cast(error); - stacktrace = uhe.stacktrace(); - } else { - stacktrace = Exceptions::CurrentStackTrace(); - } - uword handler_pc = 0; - uword handler_sp = 0; - uword handler_fp = 0; - FindErrorHandler(&handler_pc, &handler_sp, &handler_fp); - JumpToExceptionHandler(thread, handler_pc, handler_sp, handler_fp, error, - stacktrace); - UNREACHABLE(); -} - void Exceptions::ThrowByType(ExceptionType type, const Array& arguments) { Thread* thread = Thread::Current(); const Object& result = diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index b4d7ed55310..a68a91702c4 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -8786,6 +8786,13 @@ bool Function::FfiCSignatureReturnsStruct() const { return true; } +bool Function::FfiCSignatureReturnsHandle() const { + ASSERT(IsFfiCallbackTrampoline()); + const auto& c_signature = FunctionType::Handle(FfiCSignature()); + const auto& type = AbstractType::Handle(c_signature.result_type()); + return type.type_class_id() == kFfiHandleCid; +} + int32_t Function::FfiCallbackId() const { ASSERT(IsFfiCallbackTrampoline()); diff --git a/runtime/vm/object.h b/runtime/vm/object.h index ff92974ffe5..0fb0044eca1 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -3024,6 +3024,7 @@ class Function : public Object { bool FfiCSignatureContainsHandles() const; bool FfiCSignatureReturnsStruct() const; + bool FfiCSignatureReturnsHandle() const; // Can only be called on FFI trampolines. int32_t FfiCallbackId() const; diff --git a/runtime/vm/stub_code_list.h b/runtime/vm/stub_code_list.h index 7c14e756b2e..b8dc64766f0 100644 --- a/runtime/vm/stub_code_list.h +++ b/runtime/vm/stub_code_list.h @@ -32,6 +32,7 @@ namespace dart { V(GetCStackPointer) \ V(JumpToFrame) \ V(RunExceptionHandler) \ + V(RunExceptionHandlerUnbox) \ V(DeoptForRewind) \ V(WriteBarrier) \ V(WriteBarrierWrappers) \ diff --git a/runtime/vm/thread.cc b/runtime/vm/thread.cc index ee99e7d4b96..5b37119122e 100644 --- a/runtime/vm/thread.cc +++ b/runtime/vm/thread.cc @@ -229,6 +229,11 @@ void Thread::set_active_exception(const Object& value) { active_exception_ = value.ptr(); } +void Thread::set_active_exception(LocalHandle* value) { + active_exception_ = ObjectPtr(reinterpret_cast(value)); + ASSERT(active_exception_.IsImmediateObject()); // GC won't try to visit this. +} + void Thread::set_active_stacktrace(const Object& value) { active_stacktrace_ = value.ptr(); } diff --git a/runtime/vm/thread.h b/runtime/vm/thread.h index 45e86fff909..93079bb773d 100644 --- a/runtime/vm/thread.h +++ b/runtime/vm/thread.h @@ -54,6 +54,7 @@ class Interpreter; class Isolate; class IsolateGroup; class Library; +class LocalHandle; class Object; class OSThread; class JSONObject; @@ -874,6 +875,7 @@ class Thread : public ThreadState { ObjectPtr active_exception() const { return active_exception_; } void set_active_exception(const Object& value); + void set_active_exception(LocalHandle* value); static intptr_t active_exception_offset() { return OFFSET_OF(Thread, active_exception_); } diff --git a/tests/ffi/callback_unwind_error_test.dart b/tests/ffi/callback_unwind_error_test.dart index e8813a19e6b..98163a40633 100644 --- a/tests/ffi/callback_unwind_error_test.dart +++ b/tests/ffi/callback_unwind_error_test.dart @@ -9,21 +9,37 @@ import "dart:isolate"; import "callback_tests_utils.dart"; -typedef SimpleAdditionType = Int32 Function(Int32, Int32); -int simpleAddition(int x, int y) { - print("simpleAddition($x, $y)"); +typedef Type = Int32 Function(Int32, Int32); +int unwindError(int x, int y) { + print("unwindError($x, $y)"); Isolate.current.kill(priority: Isolate.immediate); return x + y; } final testcases = [ - CallbackTest( - "SimpleAddition", - Pointer.fromFunction(simpleAddition, 0), - ), + CallbackTest("UnwindError", Pointer.fromFunction(unwindError, 42)), ]; -void main() { +void child(_) { testcases.forEach((t) => t.run()); throw "Should not be reached"; } + +void main() { + var onExit = new RawReceivePort(); + var onError = new RawReceivePort(); + onExit.handler = ((msg) { + print("Child exited"); + onExit.close(); + onError.close(); + }); + onError.handler = ((msg) { + throw "Child error: $msg"; + }); + Isolate.spawn( + child, + null, + onError: onError.sendPort, + onExit: onExit.sendPort, + ); +} diff --git a/tests/ffi/vmspecific_callback_unwind_error_through_handle_test.dart b/tests/ffi/vmspecific_callback_unwind_error_through_handle_test.dart new file mode 100644 index 00000000000..c67d0bce08f --- /dev/null +++ b/tests/ffi/vmspecific_callback_unwind_error_through_handle_test.dart @@ -0,0 +1,51 @@ +// Copyright (c) 2025, 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 + +import "dart:ffi"; +import "dart:isolate"; + +import "dylib_utils.dart"; + +final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions"); + +Object unwindErrorThroughHandle(int x, int y) { + print("unwindErrorThroughHandle($x, $y)"); + Isolate.current.kill(priority: Isolate.immediate); + return x + y; +} + +typedef CallbackType = Handle Function(Int32, Int32); +typedef CalloutCType = Handle Function(Pointer); +typedef CalloutDartType = Object Function(Pointer); + +void child(_) { + var callout = ffiTestFunctions.lookupFunction( + "TestUnwindErrorThroughHandle", + isLeaf: false, + ); + var callback = Pointer.fromFunction(unwindErrorThroughHandle); + var result = callout(callback); + throw "Should not be reached"; +} + +void main() { + var onExit = new RawReceivePort(); + var onError = new RawReceivePort(); + onExit.handler = ((msg) { + print("Child exited"); + onExit.close(); + onError.close(); + }); + onError.handler = ((msg) { + throw "Child error: $msg"; + }); + Isolate.spawn( + child, + null, + onError: onError.sendPort, + onExit: onExit.sendPort, + ); +}