|  | // Copyright (c) 2023, 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. | 
|  |  | 
|  | #include "vm/ffi_callback_metadata.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <thread>  // NOLINT(build/c++11) | 
|  | #include <unordered_set> | 
|  | #include <vector> | 
|  |  | 
|  | #include "include/dart_api.h" | 
|  | #include "platform/assert.h" | 
|  | #include "vm/class_finalizer.h" | 
|  | #include "vm/compiler/ffi/callback.h" | 
|  | #include "vm/compiler/jit/compiler.h" | 
|  | #include "vm/message_handler.h" | 
|  | #include "vm/object.h" | 
|  | #include "vm/port.h" | 
|  | #include "vm/symbols.h" | 
|  | #include "vm/unit_test.h" | 
|  |  | 
|  | namespace dart { | 
|  |  | 
|  | FunctionPtr CreateTestFunction(FfiCallbackKind kind) { | 
|  | const auto& ffi_lib = Library::Handle(Library::FfiLibrary()); | 
|  | const auto& ffi_void = Class::Handle(ffi_lib.LookupClass(Symbols::FfiVoid())); | 
|  | const auto& ffi_void_type = | 
|  | Type::Handle(Type::NewNonParameterizedType(ffi_void)); | 
|  |  | 
|  | auto* thread = Thread::Current(); | 
|  | const char* kScriptChars = | 
|  | R"( | 
|  | void testFunction() { | 
|  | } | 
|  | )"; | 
|  | Dart_Handle library; | 
|  | { | 
|  | TransitionVMToNative transition(thread); | 
|  | library = TestCase::LoadTestScript(kScriptChars, nullptr); | 
|  | EXPECT_VALID(library); | 
|  | } | 
|  |  | 
|  | const auto& lib = | 
|  | Library::Handle(Library::RawCast(Api::UnwrapHandle(library))); | 
|  | EXPECT(ClassFinalizer::ProcessPendingClasses()); | 
|  | const auto& cls = Class::Handle(lib.toplevel_class()); | 
|  | EXPECT(!cls.IsNull()); | 
|  | const auto& error = cls.EnsureIsFinalized(thread); | 
|  | EXPECT(error == Error::null()); | 
|  |  | 
|  | auto& function_name = String::Handle(String::New("testFunction")); | 
|  | const auto& func = Function::Handle(cls.LookupStaticFunction(function_name)); | 
|  | EXPECT(!func.IsNull()); | 
|  |  | 
|  | FunctionType& signature = FunctionType::Handle(FunctionType::New()); | 
|  | signature.set_result_type(ffi_void_type); | 
|  | signature.SetIsFinalized(); | 
|  | signature ^= signature.Canonicalize(thread); | 
|  |  | 
|  | const auto& callback = Function::Handle(compiler::ffi::NativeCallbackFunction( | 
|  | signature, func, Instance::Handle(Instance::null()), kind)); | 
|  |  | 
|  | const auto& result = Object::Handle( | 
|  | thread->zone(), Compiler::CompileFunction(thread, callback)); | 
|  | EXPECT(!result.IsError()); | 
|  |  | 
|  | return callback.ptr(); | 
|  | } | 
|  |  | 
|  | class FakeMessageHandler : public MessageHandler { | 
|  | public: | 
|  | MessageStatus HandleMessage(std::unique_ptr<Message> message) override { | 
|  | return MessageHandler::kOK; | 
|  | } | 
|  | }; | 
|  |  | 
|  | VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateSyncFfiCallback) { | 
|  | auto* fcm = FfiCallbackMetadata::Instance(); | 
|  | FfiCallbackMetadata::Trampoline tramp1 = 0; | 
|  | FfiCallbackMetadata::Trampoline tramp2 = 0; | 
|  |  | 
|  | { | 
|  | TestIsolateScope isolate_scope; | 
|  | Thread* thread = Thread::Current(); | 
|  | Isolate* isolate = thread->isolate(); | 
|  | ASSERT(isolate == isolate_scope.isolate()); | 
|  | TransitionNativeToVM transition(thread); | 
|  | StackZone stack_zone(thread); | 
|  | HandleScope handle_scope(thread); | 
|  |  | 
|  | auto* zone = thread->zone(); | 
|  |  | 
|  | const auto& func = Function::Handle( | 
|  | CreateTestFunction(FfiCallbackKind::kIsolateLocalStaticCallback)); | 
|  | const auto& code = Code::Handle(func.EnsureHasCode()); | 
|  | EXPECT(!code.IsNull()); | 
|  |  | 
|  | tramp1 = isolate->CreateIsolateLocalFfiCallback( | 
|  | zone, func, Closure::Handle(Closure::null()), false); | 
|  | EXPECT_NE(tramp1, 0u); | 
|  |  | 
|  | { | 
|  | FfiCallbackMetadata::Metadata m1 = | 
|  | fcm->LookupMetadataForTrampoline(tramp1); | 
|  | EXPECT(m1.IsLive()); | 
|  | EXPECT_EQ(m1.target_isolate(), isolate); | 
|  | EXPECT_EQ(m1.target_entry_point(), code.EntryPoint()); | 
|  | EXPECT_EQ(m1.closure_handle(), nullptr); | 
|  | EXPECT_EQ(static_cast<int>(m1.trampoline_type()), | 
|  | static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync)); | 
|  |  | 
|  | // head -> tramp1 | 
|  | auto* e1 = fcm->MetadataOfTrampoline(tramp1); | 
|  | EXPECT_EQ(isolate->ffi_callback_list_head(), e1); | 
|  | EXPECT_EQ(e1->list_prev(), nullptr); | 
|  | EXPECT_EQ(e1->list_next(), nullptr); | 
|  | } | 
|  |  | 
|  | tramp2 = isolate->CreateIsolateLocalFfiCallback( | 
|  | zone, func, Closure::Handle(Closure::null()), false); | 
|  | EXPECT_NE(tramp2, 0u); | 
|  | EXPECT_NE(tramp2, tramp1); | 
|  |  | 
|  | { | 
|  | FfiCallbackMetadata::Metadata m2 = | 
|  | fcm->LookupMetadataForTrampoline(tramp2); | 
|  | EXPECT(m2.IsLive()); | 
|  | EXPECT_EQ(m2.target_isolate(), isolate); | 
|  | EXPECT_EQ(m2.target_entry_point(), code.EntryPoint()); | 
|  | EXPECT_EQ(m2.closure_handle(), nullptr); | 
|  | EXPECT_EQ(static_cast<int>(m2.trampoline_type()), | 
|  | static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync)); | 
|  | } | 
|  |  | 
|  | { | 
|  | // head -> tramp2 -> tramp1 | 
|  | auto* e1 = fcm->MetadataOfTrampoline(tramp1); | 
|  | auto* e2 = fcm->MetadataOfTrampoline(tramp2); | 
|  | EXPECT_EQ(isolate->ffi_callback_list_head(), e2); | 
|  | EXPECT_EQ(e2->list_prev(), nullptr); | 
|  | EXPECT_EQ(e2->list_next(), e1); | 
|  | EXPECT_EQ(e1->list_prev(), e2); | 
|  | EXPECT_EQ(e1->list_next(), nullptr); | 
|  | } | 
|  |  | 
|  | { | 
|  | isolate->DeleteFfiCallback(tramp1); | 
|  | FfiCallbackMetadata::Metadata m1 = | 
|  | fcm->LookupMetadataForTrampoline(tramp1); | 
|  | EXPECT(!m1.IsLive()); | 
|  |  | 
|  | // head -> tramp2 | 
|  | auto* e2 = fcm->MetadataOfTrampoline(tramp2); | 
|  | EXPECT_EQ(isolate->ffi_callback_list_head(), e2); | 
|  | EXPECT_EQ(e2->list_prev(), nullptr); | 
|  | EXPECT_EQ(e2->list_next(), nullptr); | 
|  | } | 
|  | } | 
|  |  | 
|  | { | 
|  | // Isolate has shut down, so all callbacks should be deleted. | 
|  | FfiCallbackMetadata::Metadata m1 = fcm->LookupMetadataForTrampoline(tramp1); | 
|  | EXPECT(!m1.IsLive()); | 
|  |  | 
|  | FfiCallbackMetadata::Metadata m2 = fcm->LookupMetadataForTrampoline(tramp2); | 
|  | EXPECT(!m2.IsLive()); | 
|  | } | 
|  | } | 
|  |  | 
|  | VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateAsyncFfiCallback) { | 
|  | auto* fcm = FfiCallbackMetadata::Instance(); | 
|  | FfiCallbackMetadata::Trampoline tramp1 = 0; | 
|  | FfiCallbackMetadata::Trampoline tramp2 = 0; | 
|  |  | 
|  | { | 
|  | TestIsolateScope isolate_scope; | 
|  | Thread* thread = Thread::Current(); | 
|  | Isolate* isolate = thread->isolate(); | 
|  | ASSERT(thread->isolate() == isolate_scope.isolate()); | 
|  | TransitionNativeToVM transition(thread); | 
|  | StackZone stack_zone(thread); | 
|  | HandleScope handle_scope(thread); | 
|  |  | 
|  | auto* zone = thread->zone(); | 
|  |  | 
|  | const Function& func = | 
|  | Function::Handle(CreateTestFunction(FfiCallbackKind::kAsyncCallback)); | 
|  | const Code& code = Code::Handle(func.EnsureHasCode()); | 
|  | EXPECT(!code.IsNull()); | 
|  |  | 
|  | EXPECT_EQ(isolate->ffi_callback_list_head(), nullptr); | 
|  |  | 
|  | auto port1 = PortMap::CreatePort(new FakeMessageHandler()); | 
|  | tramp1 = isolate->CreateAsyncFfiCallback(zone, func, port1); | 
|  | EXPECT_NE(tramp1, 0u); | 
|  |  | 
|  | { | 
|  | FfiCallbackMetadata::Metadata m1 = | 
|  | fcm->LookupMetadataForTrampoline(tramp1); | 
|  | EXPECT(m1.IsLive()); | 
|  | EXPECT_EQ(m1.target_isolate(), isolate); | 
|  | EXPECT_EQ(m1.target_entry_point(), code.EntryPoint()); | 
|  | EXPECT_EQ(m1.send_port(), port1); | 
|  | EXPECT_EQ(static_cast<int>(m1.trampoline_type()), | 
|  | static_cast<int>(FfiCallbackMetadata::TrampolineType::kAsync)); | 
|  |  | 
|  | // head -> tramp1 | 
|  | auto* e1 = fcm->MetadataOfTrampoline(tramp1); | 
|  | EXPECT_EQ(isolate->ffi_callback_list_head(), e1); | 
|  | EXPECT_EQ(e1->list_prev(), nullptr); | 
|  | EXPECT_EQ(e1->list_next(), nullptr); | 
|  | } | 
|  |  | 
|  | auto port2 = PortMap::CreatePort(new FakeMessageHandler()); | 
|  | tramp2 = isolate->CreateAsyncFfiCallback(zone, func, port2); | 
|  | EXPECT_NE(tramp2, 0u); | 
|  | EXPECT_NE(tramp2, tramp1); | 
|  |  | 
|  | { | 
|  | FfiCallbackMetadata::Metadata m2 = | 
|  | fcm->LookupMetadataForTrampoline(tramp2); | 
|  | EXPECT(m2.IsLive()); | 
|  | EXPECT_EQ(m2.target_isolate(), isolate); | 
|  | EXPECT_EQ(m2.target_entry_point(), code.EntryPoint()); | 
|  | EXPECT_EQ(m2.send_port(), port2); | 
|  | EXPECT_EQ(static_cast<int>(m2.trampoline_type()), | 
|  | static_cast<int>(FfiCallbackMetadata::TrampolineType::kAsync)); | 
|  | } | 
|  |  | 
|  | { | 
|  | // head -> tramp2 -> tramp1 | 
|  | auto* e1 = fcm->MetadataOfTrampoline(tramp1); | 
|  | auto* e2 = fcm->MetadataOfTrampoline(tramp2); | 
|  | EXPECT_EQ(isolate->ffi_callback_list_head(), e2); | 
|  | EXPECT_EQ(e2->list_prev(), nullptr); | 
|  | EXPECT_EQ(e2->list_next(), e1); | 
|  | EXPECT_EQ(e1->list_prev(), e2); | 
|  | EXPECT_EQ(e1->list_next(), nullptr); | 
|  | } | 
|  |  | 
|  | { | 
|  | isolate->DeleteFfiCallback(tramp2); | 
|  | FfiCallbackMetadata::Metadata m2 = | 
|  | fcm->LookupMetadataForTrampoline(tramp2); | 
|  | EXPECT(!m2.IsLive()); | 
|  |  | 
|  | // head -> tramp1 | 
|  | auto* e1 = fcm->MetadataOfTrampoline(tramp1); | 
|  | EXPECT_EQ(isolate->ffi_callback_list_head(), e1); | 
|  | EXPECT_EQ(e1->list_prev(), nullptr); | 
|  | EXPECT_EQ(e1->list_next(), nullptr); | 
|  | } | 
|  | } | 
|  |  | 
|  | { | 
|  | // Isolate has shut down, so all callbacks should be deleted. | 
|  | FfiCallbackMetadata::Metadata m1 = fcm->LookupMetadataForTrampoline(tramp1); | 
|  | EXPECT(!m1.IsLive()); | 
|  |  | 
|  | FfiCallbackMetadata::Metadata m2 = fcm->LookupMetadataForTrampoline(tramp2); | 
|  | EXPECT(!m2.IsLive()); | 
|  | } | 
|  | } | 
|  |  | 
|  | VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateIsolateLocalFfiCallback) { | 
|  | auto* fcm = FfiCallbackMetadata::Instance(); | 
|  | FfiCallbackMetadata::Trampoline tramp1 = 0; | 
|  | FfiCallbackMetadata::Trampoline tramp2 = 0; | 
|  |  | 
|  | { | 
|  | TestIsolateScope isolate_scope; | 
|  | Thread* thread = Thread::Current(); | 
|  | Isolate* isolate = thread->isolate(); | 
|  | ASSERT(thread->isolate() == isolate_scope.isolate()); | 
|  | TransitionNativeToVM transition(thread); | 
|  | StackZone stack_zone(thread); | 
|  | HandleScope handle_scope(thread); | 
|  |  | 
|  | auto* zone = thread->zone(); | 
|  |  | 
|  | const Function& func = Function::Handle( | 
|  | CreateTestFunction(FfiCallbackKind::kIsolateLocalClosureCallback)); | 
|  | const Code& code = Code::Handle(func.EnsureHasCode()); | 
|  | EXPECT(!code.IsNull()); | 
|  |  | 
|  | // Using a FfiCallbackKind::kSync function as a dummy closure. | 
|  | const Function& closure_func = Function::Handle( | 
|  | CreateTestFunction(FfiCallbackKind::kIsolateLocalStaticCallback)); | 
|  | const Context& context = Context::Handle(Context::null()); | 
|  | const Closure& closure1 = Closure::Handle( | 
|  | Closure::New(Object::null_type_arguments(), | 
|  | Object::null_type_arguments(), closure_func, context)); | 
|  |  | 
|  | EXPECT_EQ(isolate->ffi_callback_list_head(), nullptr); | 
|  |  | 
|  | tramp1 = isolate->CreateIsolateLocalFfiCallback(zone, func, closure1, true); | 
|  | EXPECT_NE(tramp1, 0u); | 
|  |  | 
|  | { | 
|  | FfiCallbackMetadata::Metadata m1 = | 
|  | fcm->LookupMetadataForTrampoline(tramp1); | 
|  | EXPECT(m1.IsLive()); | 
|  | EXPECT_EQ(m1.target_isolate(), isolate); | 
|  | EXPECT_EQ(m1.target_entry_point(), code.EntryPoint()); | 
|  | EXPECT_EQ(m1.closure_handle()->ptr(), closure1.ptr()); | 
|  | EXPECT_EQ(static_cast<int>(m1.trampoline_type()), | 
|  | static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync)); | 
|  |  | 
|  | // head -> tramp1 | 
|  | auto* e1 = fcm->MetadataOfTrampoline(tramp1); | 
|  | EXPECT_EQ(isolate->ffi_callback_list_head(), e1); | 
|  | EXPECT_EQ(e1->list_prev(), nullptr); | 
|  | EXPECT_EQ(e1->list_next(), nullptr); | 
|  | } | 
|  |  | 
|  | const Closure& closure2 = Closure::Handle( | 
|  | Closure::New(Object::null_type_arguments(), | 
|  | Object::null_type_arguments(), closure_func, context)); | 
|  | tramp2 = isolate->CreateIsolateLocalFfiCallback(zone, func, closure2, true); | 
|  | EXPECT_NE(tramp2, 0u); | 
|  | EXPECT_NE(tramp2, tramp1); | 
|  |  | 
|  | { | 
|  | FfiCallbackMetadata::Metadata m2 = | 
|  | fcm->LookupMetadataForTrampoline(tramp2); | 
|  | EXPECT(m2.IsLive()); | 
|  | EXPECT_EQ(m2.target_isolate(), isolate); | 
|  | EXPECT_EQ(m2.target_entry_point(), code.EntryPoint()); | 
|  | EXPECT_EQ(m2.closure_handle()->ptr(), closure2.ptr()); | 
|  | EXPECT_EQ(static_cast<int>(m2.trampoline_type()), | 
|  | static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync)); | 
|  | } | 
|  |  | 
|  | { | 
|  | // head -> tramp2 -> tramp1 | 
|  | auto* e1 = fcm->MetadataOfTrampoline(tramp1); | 
|  | auto* e2 = fcm->MetadataOfTrampoline(tramp2); | 
|  | EXPECT_EQ(isolate->ffi_callback_list_head(), e2); | 
|  | EXPECT_EQ(e2->list_prev(), nullptr); | 
|  | EXPECT_EQ(e2->list_next(), e1); | 
|  | EXPECT_EQ(e1->list_prev(), e2); | 
|  | EXPECT_EQ(e1->list_next(), nullptr); | 
|  | } | 
|  |  | 
|  | { | 
|  | isolate->DeleteFfiCallback(tramp2); | 
|  | FfiCallbackMetadata::Metadata m2 = | 
|  | fcm->LookupMetadataForTrampoline(tramp2); | 
|  | EXPECT(!m2.IsLive()); | 
|  |  | 
|  | // head -> tramp1 | 
|  | auto* e1 = fcm->MetadataOfTrampoline(tramp1); | 
|  | EXPECT_EQ(isolate->ffi_callback_list_head(), e1); | 
|  | EXPECT_EQ(e1->list_prev(), nullptr); | 
|  | EXPECT_EQ(e1->list_next(), nullptr); | 
|  | } | 
|  | } | 
|  |  | 
|  | { | 
|  | // Isolate has shut down, so all callbacks should be deleted. | 
|  | FfiCallbackMetadata::Metadata m1 = fcm->LookupMetadataForTrampoline(tramp1); | 
|  | EXPECT(!m1.IsLive()); | 
|  |  | 
|  | FfiCallbackMetadata::Metadata m2 = fcm->LookupMetadataForTrampoline(tramp2); | 
|  | EXPECT(!m2.IsLive()); | 
|  | } | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(FfiCallbackMetadata_TrampolineRecycling) { | 
|  | Isolate* isolate = thread->isolate(); | 
|  | auto* zone = thread->zone(); | 
|  | auto* fcm = FfiCallbackMetadata::Instance(); | 
|  |  | 
|  | const Function& func = | 
|  | Function::Handle(CreateTestFunction(FfiCallbackKind::kAsyncCallback)); | 
|  | const Code& code = Code::Handle(func.EnsureHasCode()); | 
|  | EXPECT(!code.IsNull()); | 
|  |  | 
|  | auto port = PortMap::CreatePort(new FakeMessageHandler()); | 
|  | FfiCallbackMetadata::Metadata* list_head = nullptr; | 
|  |  | 
|  | // Allocate and free one callback at a time, and verify that we don't reuse | 
|  | // them. Allocate enough that the whole page fills up with dead trampolines. | 
|  | std::vector<FfiCallbackMetadata::Trampoline> allocation_order; | 
|  | std::unordered_set<FfiCallbackMetadata::Trampoline> allocated; | 
|  | const intptr_t trampolines_per_page = | 
|  | FfiCallbackMetadata::NumCallbackTrampolinesPerPage(); | 
|  | for (intptr_t i = 0; i < trampolines_per_page; ++i) { | 
|  | auto tramp = | 
|  | fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head); | 
|  | EXPECT_EQ(allocated.count(tramp), 0u); | 
|  | allocation_order.push_back(tramp); | 
|  | allocated.insert(tramp); | 
|  | fcm->DeleteCallback(tramp, &list_head); | 
|  | } | 
|  |  | 
|  | // Now  as we continue allocating and freeing, we start reusing them, in the | 
|  | // same allocation order as before. | 
|  | for (intptr_t i = 0; i < trampolines_per_page; ++i) { | 
|  | auto tramp = | 
|  | fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head); | 
|  | EXPECT_EQ(allocated.count(tramp), 1u); | 
|  | EXPECT_EQ(allocation_order[i], tramp); | 
|  | fcm->DeleteCallback(tramp, &list_head); | 
|  | } | 
|  |  | 
|  | // Now allocate enough to fill the page without freeing them. Again they | 
|  | // should come out in the same order. | 
|  | for (intptr_t i = 0; i < trampolines_per_page; ++i) { | 
|  | auto tramp = | 
|  | fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head); | 
|  | EXPECT_EQ(allocated.count(tramp), 1u); | 
|  | EXPECT_EQ(allocation_order[i], tramp); | 
|  | } | 
|  |  | 
|  | // Now that the page is full, we should allocate a new page and see new | 
|  | // trampolines we haven't seen before. | 
|  | for (intptr_t i = 0; i < 3 * trampolines_per_page; ++i) { | 
|  | auto tramp = | 
|  | fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head); | 
|  | EXPECT_EQ(allocated.count(tramp), 0u); | 
|  | } | 
|  | } | 
|  |  | 
|  | VM_UNIT_TEST_CASE(FfiCallbackMetadata_DeleteTrampolines) { | 
|  | static constexpr int kCreations = 1000; | 
|  | static constexpr int kDeletions = 100; | 
|  |  | 
|  | TestIsolateScope isolate_scope; | 
|  | Thread* thread = Thread::Current(); | 
|  | Isolate* isolate = thread->isolate(); | 
|  | ASSERT(isolate == isolate_scope.isolate()); | 
|  | TransitionNativeToVM transition(thread); | 
|  | StackZone stack_zone(thread); | 
|  | HandleScope handle_scope(thread); | 
|  |  | 
|  | auto* fcm = FfiCallbackMetadata::Instance(); | 
|  | std::unordered_set<FfiCallbackMetadata::Trampoline> tramps; | 
|  | FfiCallbackMetadata::Metadata* list_head = nullptr; | 
|  |  | 
|  | const auto& sync_func = Function::Handle( | 
|  | CreateTestFunction(FfiCallbackKind::kIsolateLocalStaticCallback)); | 
|  | const auto& sync_code = Code::Handle(sync_func.EnsureHasCode()); | 
|  | EXPECT(!sync_code.IsNull()); | 
|  |  | 
|  | // Create some callbacks. | 
|  | for (int itr = 0; itr < kCreations; ++itr) { | 
|  | tramps.insert(fcm->CreateIsolateLocalFfiCallback( | 
|  | isolate, thread->zone(), sync_func, Closure::Handle(Closure::null()), | 
|  | &list_head)); | 
|  | } | 
|  |  | 
|  | // Delete some of the callbacks. | 
|  | for (int itr = 0; itr < kDeletions; ++itr) { | 
|  | auto tramp = *tramps.begin(); | 
|  | fcm->DeleteCallback(tramp, &list_head); | 
|  | tramps.erase(tramp); | 
|  | } | 
|  |  | 
|  | // Verify all the callbacks. | 
|  | for (FfiCallbackMetadata::Trampoline tramp : tramps) { | 
|  | auto metadata = fcm->LookupMetadataForTrampoline(tramp); | 
|  | EXPECT(metadata.IsLive()); | 
|  | EXPECT_EQ(metadata.target_isolate(), isolate); | 
|  | EXPECT_EQ(static_cast<int>(metadata.trampoline_type()), | 
|  | static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync)); | 
|  | EXPECT_EQ(metadata.target_entry_point(), sync_code.EntryPoint()); | 
|  | } | 
|  |  | 
|  | // Verify the list of callbacks. | 
|  | uword list_length = 0; | 
|  | for (FfiCallbackMetadata::Metadata* m = list_head; m != nullptr;) { | 
|  | ++list_length; | 
|  | auto tramp = fcm->TrampolineOfMetadata(m); | 
|  | EXPECT(m->IsLive()); | 
|  | EXPECT_EQ(m->target_isolate(), isolate); | 
|  | EXPECT_EQ(tramps.count(tramp), 1u); | 
|  | auto* next = m->list_next(); | 
|  | auto* prev = m->list_prev(); | 
|  | if (prev != nullptr) { | 
|  | EXPECT_EQ(prev->list_next(), m); | 
|  | } else { | 
|  | EXPECT_EQ(list_head, m); | 
|  | } | 
|  | if (next != nullptr) { | 
|  | EXPECT_EQ(next->list_prev(), m); | 
|  | } | 
|  | m = m->list_next(); | 
|  | } | 
|  | EXPECT_EQ(list_length, tramps.size()); | 
|  |  | 
|  | // Delete all callbacks and verify they're destroyed. | 
|  | fcm->DeleteAllCallbacks(&list_head); | 
|  | EXPECT_EQ(list_head, nullptr); | 
|  | for (FfiCallbackMetadata::Trampoline tramp : tramps) { | 
|  | EXPECT(!fcm->LookupMetadataForTrampoline(tramp).IsLive()); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void RunBigRandomMultithreadedTest(uint64_t seed) { | 
|  | static constexpr int kIterations = 1000; | 
|  |  | 
|  | TestIsolateScope isolate_scope; | 
|  | Thread* thread = Thread::Current(); | 
|  | Isolate* isolate = thread->isolate(); | 
|  | ASSERT(isolate == isolate_scope.isolate()); | 
|  | TransitionNativeToVM transition(thread); | 
|  | StackZone stack_zone(thread); | 
|  | HandleScope handle_scope(thread); | 
|  |  | 
|  | struct TrampolineWithPort { | 
|  | FfiCallbackMetadata::Trampoline tramp; | 
|  | Dart_Port port; | 
|  | }; | 
|  |  | 
|  | auto* fcm = FfiCallbackMetadata::Instance(); | 
|  | Random random(seed); | 
|  | std::vector<TrampolineWithPort> tramps; | 
|  | std::unordered_set<FfiCallbackMetadata::Trampoline> tramp_set; | 
|  | FfiCallbackMetadata::Metadata* list_head = nullptr; | 
|  |  | 
|  | const Function& async_func = | 
|  | Function::Handle(CreateTestFunction(FfiCallbackKind::kAsyncCallback)); | 
|  | const Code& async_code = Code::Handle(async_func.EnsureHasCode()); | 
|  | EXPECT(!async_code.IsNull()); | 
|  | const Function& sync_func = Function::Handle( | 
|  | CreateTestFunction(FfiCallbackKind::kIsolateLocalStaticCallback)); | 
|  | const auto& sync_code = Code::Handle(sync_func.EnsureHasCode()); | 
|  | EXPECT(!sync_code.IsNull()); | 
|  |  | 
|  | for (int itr = 0; itr < kIterations; ++itr) { | 
|  | // Do a random action: | 
|  | //  - Allocate a sync callback | 
|  | //  - Allocate an async callback | 
|  | //  - Delete a callback | 
|  | //  - Delete all the sync callbacks for an isolate | 
|  |  | 
|  | if ((random.NextUInt32() % 100) == 0) { | 
|  | // 1% chance of deleting all the callbacks on the thread. | 
|  | fcm->DeleteAllCallbacks(&list_head); | 
|  |  | 
|  | // It would be nice to verify that all the trampolines have been deleted, | 
|  | // but this is flaky because other threads can recycle these trampolines | 
|  | // before we finish checking all of them. | 
|  | tramps.clear(); | 
|  | tramp_set.clear(); | 
|  | EXPECT_EQ(list_head, nullptr); | 
|  | } else if (tramps.size() > 0 && (random.NextUInt32() % 4) == 0) { | 
|  | // 25% chance of deleting a callback. | 
|  | uint32_t r = random.NextUInt32() % tramps.size(); | 
|  | auto tramp = tramps[r].tramp; | 
|  | fcm->DeleteCallback(tramp, &list_head); | 
|  | tramps[r] = tramps[tramps.size() - 1]; | 
|  | tramps.pop_back(); | 
|  | tramp_set.erase(tramp); | 
|  | } else { | 
|  | TrampolineWithPort tramp; | 
|  | if ((random.NextUInt32() % 2) == 0) { | 
|  | // 50% chance of creating a sync callback. | 
|  | tramp.port = ILLEGAL_PORT; | 
|  | tramp.tramp = fcm->CreateIsolateLocalFfiCallback( | 
|  | isolate, thread->zone(), sync_func, | 
|  | Closure::Handle(Closure::null()), &list_head); | 
|  | } else { | 
|  | // 50% chance of creating an async callback. | 
|  | tramp.port = PortMap::CreatePort(new FakeMessageHandler()); | 
|  | tramp.tramp = fcm->CreateAsyncFfiCallback( | 
|  | isolate, thread->zone(), async_func, tramp.port, &list_head); | 
|  | } | 
|  | tramps.push_back(tramp); | 
|  | tramp_set.insert(tramp.tramp); | 
|  | } | 
|  |  | 
|  | // Verify all the callbacks. | 
|  | for (const auto& tramp : tramps) { | 
|  | auto metadata = fcm->LookupMetadataForTrampoline(tramp.tramp); | 
|  | EXPECT(metadata.IsLive()); | 
|  | EXPECT_EQ(metadata.target_isolate(), isolate); | 
|  | if (metadata.trampoline_type() == | 
|  | FfiCallbackMetadata::TrampolineType::kSync) { | 
|  | EXPECT_EQ(metadata.closure_handle(), nullptr); | 
|  | EXPECT_EQ(metadata.target_entry_point(), sync_code.EntryPoint()); | 
|  | } else { | 
|  | EXPECT_EQ(metadata.send_port(), tramp.port); | 
|  | EXPECT_EQ(metadata.target_entry_point(), async_code.EntryPoint()); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Verify the isolate's list of callbacks. | 
|  | uword list_length = 0; | 
|  | for (FfiCallbackMetadata::Metadata* m = list_head; m != nullptr;) { | 
|  | ++list_length; | 
|  | auto tramp = fcm->TrampolineOfMetadata(m); | 
|  | EXPECT(m->IsLive()); | 
|  | EXPECT_EQ(m->target_isolate(), isolate); | 
|  | EXPECT_EQ(tramp_set.count(tramp), 1u); | 
|  | m = m->list_next(); | 
|  | } | 
|  | EXPECT_EQ(list_length, tramps.size()); | 
|  | EXPECT_EQ(list_length, tramp_set.size()); | 
|  | } | 
|  |  | 
|  | // Delete all remaining callbacks. | 
|  | fcm->DeleteAllCallbacks(&list_head); | 
|  | EXPECT_EQ(list_head, nullptr); | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(FfiCallbackMetadata_BigRandomMultithreadedTest) { | 
|  | static constexpr int kThreads = 5; | 
|  |  | 
|  | std::vector<std::thread> threads; | 
|  |  | 
|  | Random random; | 
|  | for (int i = 0; i < kThreads; ++i) { | 
|  | threads.push_back( | 
|  | std::thread(RunBigRandomMultithreadedTest, random.NextUInt64())); | 
|  | } | 
|  |  | 
|  | for (auto& thread : threads) { | 
|  | thread.join(); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace dart |