| // Copyright (c) 2012, 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 <memory> | |
| #include <utility> | |
| #include "include/dart_native_api.h" | |
| #include "platform/assert.h" | |
| #include "platform/unicode.h" | |
| #include "vm/bootstrap_natives.h" | |
| #include "vm/class_finalizer.h" | |
| #include "vm/dart.h" | |
| #include "vm/dart_api_impl.h" | |
| #include "vm/dart_api_message.h" | |
| #include "vm/dart_entry.h" | |
| #include "vm/exceptions.h" | |
| #include "vm/hash_table.h" | |
| #include "vm/lockers.h" | |
| #include "vm/longjump.h" | |
| #include "vm/message_handler.h" | |
| #include "vm/message_snapshot.h" | |
| #include "vm/object.h" | |
| #include "vm/object_graph_copy.h" | |
| #include "vm/object_store.h" | |
| #include "vm/port.h" | |
| #include "vm/resolver.h" | |
| #include "vm/service.h" | |
| #include "vm/snapshot.h" | |
| #include "vm/symbols.h" | |
| namespace dart { | |
| DEFINE_NATIVE_ENTRY(Capability_factory, 0, 0) { | |
| // Keep capability IDs less than 2^53 so web clients of the service | |
| // protocol can process it properly. | |
| // | |
| // See https://github.com/dart-lang/sdk/issues/53081. | |
| uint64_t id = thread->random()->NextJSInt(); | |
| return Capability::New(id); | |
| } | |
| DEFINE_NATIVE_ENTRY(Capability_equals, 0, 2) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(Capability, recv, arguments->NativeArgAt(0)); | |
| GET_NON_NULL_NATIVE_ARGUMENT(Capability, other, arguments->NativeArgAt(1)); | |
| return (recv.Id() == other.Id()) ? Bool::True().ptr() : Bool::False().ptr(); | |
| } | |
| DEFINE_NATIVE_ENTRY(Capability_get_hashcode, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(Capability, cap, arguments->NativeArgAt(0)); | |
| int64_t id = cap.Id(); | |
| int32_t hi = static_cast<int32_t>(id >> 32); | |
| int32_t lo = static_cast<int32_t>(id); | |
| int32_t hash = (hi ^ lo) & kSmiMax; | |
| return Smi::New(hash); | |
| } | |
| DEFINE_NATIVE_ENTRY(RawReceivePort_factory, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(String, debug_name, arguments->NativeArgAt(0)); | |
| if (isolate == nullptr) { | |
| ThrowCantRunWithoutIsolateError(); | |
| UNREACHABLE(); | |
| } | |
| return isolate->CreateReceivePort(debug_name); | |
| } | |
| DEFINE_NATIVE_ENTRY(RawReceivePort_get_id, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0)); | |
| return Integer::New(port.Id()); | |
| } | |
| DEFINE_NATIVE_ENTRY(RawReceivePort_closeInternal, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0)); | |
| Dart_Port id = port.Id(); | |
| isolate->CloseReceivePort(port); | |
| return Integer::New(id); | |
| } | |
| DEFINE_NATIVE_ENTRY(RawReceivePort_setActive, 0, 2) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0)); | |
| GET_NON_NULL_NATIVE_ARGUMENT(Bool, active, arguments->NativeArgAt(1)); | |
| isolate->SetReceivePortKeepAliveState(port, active.value()); | |
| return Object::null(); | |
| } | |
| DEFINE_NATIVE_ENTRY(RawReceivePort_getActive, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0)); | |
| return Bool::Get(port.keep_isolate_alive()).ptr(); | |
| } | |
| DEFINE_NATIVE_ENTRY(SendPort_get_id, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0)); | |
| return Integer::New(port.Id()); | |
| } | |
| DEFINE_NATIVE_ENTRY(SendPort_get_hashcode, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0)); | |
| int64_t id = port.Id(); | |
| int32_t hi = static_cast<int32_t>(id >> 32); | |
| int32_t lo = static_cast<int32_t>(id); | |
| int32_t hash = (hi ^ lo) & kSmiMax; | |
| return Smi::New(hash); | |
| } | |
| static bool InSameGroup(IsolateGroup* sender, const SendPort& receiver) { | |
| // Only allow arbitrary messages between isolates of the same IG. | |
| return sender->id() == receiver.origin_id(); | |
| } | |
| DEFINE_NATIVE_ENTRY(SendPort_sendInternal_, 0, 2) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0)); | |
| GET_NON_NULL_NATIVE_ARGUMENT(Instance, obj, arguments->NativeArgAt(1)); | |
| const Dart_Port destination_port_id = port.Id(); | |
| IsolateGroup* group = thread->isolate_group(); | |
| const bool same_group = InSameGroup(group, port); | |
| #if defined(DEBUG) | |
| if (same_group) { | |
| ASSERT(PortMap::IsReceiverInThisIsolateGroupOrClosed(destination_port_id, | |
| group)); | |
| } | |
| #endif | |
| // TODO(turnidge): Throw an exception when the return value is false? | |
| PortMap::PostMessage(WriteMessage(same_group, obj, destination_port_id, | |
| Message::kNormalPriority)); | |
| return Object::null(); | |
| } | |
| class UntaggedObjectPtrSetTraits { | |
| public: | |
| static bool ReportStats() { return false; } | |
| static const char* Name() { return "UntaggedObjectPtrSetTraits"; } | |
| static bool IsMatch(const ObjectPtr a, const ObjectPtr b) { return a == b; } | |
| static uword Hash(const ObjectPtr obj) { return static_cast<uword>(obj); } | |
| }; | |
| #if defined(HASH_IN_OBJECT_HEADER) | |
| // Written to avoid O(elements) pauses that block safepoints. Compare | |
| // object_graph_copy.cc's IdentityMap, which implements something similar | |
| // but with enough differents to make shared code less readable. | |
| class WorkSet { | |
| public: | |
| explicit WorkSet(Thread* thread) | |
| : thread_(thread), list_(GrowableObjectArray::Handle()) { | |
| hash_table_used_ = 0; | |
| hash_table_capacity_ = 32; | |
| hash_table_ = reinterpret_cast<uint32_t*>( | |
| malloc(hash_table_capacity_ * sizeof(uint32_t))); | |
| memset(hash_table_, 0, hash_table_capacity_ * sizeof(uint32_t)); | |
| list_ = GrowableObjectArray::New(256); | |
| list_.Add(Object::null_object()); // Id 0 is sentinel. | |
| cursor_ = 1; | |
| } | |
| ~WorkSet() { free(hash_table_); } | |
| void Push(const Object& obj) { | |
| intptr_t mask = hash_table_capacity_ - 1; | |
| intptr_t probe = GetHeaderHash(obj.ptr()) & mask; | |
| for (;;) { | |
| intptr_t index = hash_table_[probe]; | |
| if (index == 0) { | |
| intptr_t id = list_.Length(); | |
| list_.Add(obj); | |
| hash_table_[probe] = id; | |
| break; | |
| } | |
| if (list_.At(index) == obj.ptr()) { | |
| break; // Already present. | |
| } | |
| probe = (probe + 1) & mask; | |
| } | |
| hash_table_used_++; | |
| if (hash_table_used_ * 2 > hash_table_capacity_) { | |
| Rehash(hash_table_capacity_ * 2); | |
| } | |
| } | |
| bool Pop(Object* obj) { | |
| if (cursor_ < list_.Length()) { | |
| *obj = list_.At(cursor_); | |
| cursor_++; | |
| return true; | |
| } | |
| return false; | |
| } | |
| private: | |
| DART_FORCE_INLINE | |
| uint32_t GetHeaderHash(ObjectPtr object) { | |
| uint32_t hash = Object::GetCachedHash(object); | |
| if (hash == 0) { | |
| switch (object->GetClassIdOfHeapObject()) { | |
| case kMintCid: | |
| hash = Mint::Value(static_cast<MintPtr>(object)); | |
| // Don't write back: doesn't agree with dart:core's identityHash. | |
| break; | |
| case kDoubleCid: | |
| hash = | |
| bit_cast<uint64_t>(Double::Value(static_cast<DoublePtr>(object))); | |
| // Don't write back: doesn't agree with dart:core's identityHash. | |
| break; | |
| case kOneByteStringCid: | |
| case kTwoByteStringCid: | |
| hash = String::Hash(static_cast<StringPtr>(object)); | |
| hash = Object::SetCachedHashIfNotSet(object, hash); | |
| break; | |
| default: | |
| do { | |
| hash = thread_->random()->NextUInt32(); | |
| } while (hash == 0 || !Smi::IsValid(hash)); | |
| hash = Object::SetCachedHashIfNotSet(object, hash); | |
| break; | |
| } | |
| } | |
| return hash; | |
| } | |
| void Rehash(intptr_t new_capacity) { | |
| hash_table_capacity_ = new_capacity; | |
| hash_table_used_ = 0; | |
| free(hash_table_); | |
| hash_table_ = reinterpret_cast<uint32_t*>( | |
| malloc(hash_table_capacity_ * sizeof(uint32_t))); | |
| for (intptr_t i = 0; i < hash_table_capacity_; i++) { | |
| hash_table_[i] = 0; | |
| if (((i + 1) % kSlotsPerInterruptCheck) == 0) { | |
| thread_->CheckForSafepoint(); | |
| } | |
| } | |
| for (intptr_t id = 1; id < list_.Length(); id++) { | |
| ObjectPtr obj = list_.At(id); | |
| intptr_t mask = hash_table_capacity_ - 1; | |
| intptr_t probe = GetHeaderHash(obj) & mask; | |
| for (;;) { | |
| if (hash_table_[probe] == 0) { | |
| hash_table_[probe] = id; | |
| hash_table_used_++; | |
| break; | |
| } | |
| probe = (probe + 1) & mask; | |
| } | |
| if (((id + 1) % kSlotsPerInterruptCheck) == 0) { | |
| thread_->CheckForSafepoint(); | |
| } | |
| } | |
| } | |
| Thread* thread_; | |
| uint32_t* hash_table_; | |
| uint32_t hash_table_capacity_; | |
| uint32_t hash_table_used_; | |
| GrowableObjectArray& list_; | |
| intptr_t cursor_; | |
| }; | |
| #else // defined(HASH_IN_OBJECT_HEADER) | |
| class WorkSet { | |
| public: | |
| explicit WorkSet(Thread* thread) | |
| : thread_(thread), list_(GrowableObjectArray::Handle()) { | |
| thread_->set_forward_table_new(new WeakTable()); | |
| thread_->set_forward_table_old(new WeakTable()); | |
| list_ = GrowableObjectArray::New(256); | |
| cursor_ = 0; | |
| } | |
| ~WorkSet() { | |
| thread_->set_forward_table_new(nullptr); | |
| thread_->set_forward_table_old(nullptr); | |
| } | |
| void Push(const Object& obj) { | |
| ASSERT(WeakTable::kNoValue == 0); | |
| if (GetObjectId(obj.ptr()) == 0) { | |
| SetObjectId(obj.ptr(), 1); | |
| list_.Add(obj); | |
| } | |
| } | |
| bool Pop(Object* obj) { | |
| if (cursor_ < list_.Length()) { | |
| *obj = list_.At(cursor_); | |
| cursor_++; | |
| return true; | |
| } | |
| return false; | |
| } | |
| private: | |
| DART_FORCE_INLINE | |
| intptr_t GetObjectId(ObjectPtr object) { | |
| if (object->IsNewObject()) { | |
| return thread_->forward_table_new()->GetValueExclusive(object); | |
| } else { | |
| return thread_->forward_table_old()->GetValueExclusive(object); | |
| } | |
| } | |
| DART_FORCE_INLINE | |
| void SetObjectId(ObjectPtr object, intptr_t id) { | |
| if (object->IsNewObject()) { | |
| thread_->forward_table_new()->SetValueExclusive(object, id); | |
| } else { | |
| thread_->forward_table_old()->SetValueExclusive(object, id); | |
| } | |
| } | |
| Thread* thread_; | |
| GrowableObjectArray& list_; | |
| intptr_t cursor_; | |
| }; | |
| #endif // defined(HASH_IN_OBJECT_HEADER) | |
| class MessageValidator : private WorkSet { | |
| public: | |
| explicit MessageValidator(Thread* thread) | |
| : WorkSet(thread), | |
| value_(PassiveObject::Handle()), | |
| class_table_(thread->isolate()->group()->class_table()) {} | |
| ObjectPtr Validate(Thread* thread, const Object& root) { | |
| TIMELINE_DURATION(thread, Isolate, "ValidateMessageObject"); | |
| Visit(root.ptr()); | |
| Object& current = Object::Handle(); | |
| Class& klass = Class::Handle(); | |
| while (Pop(¤t)) { | |
| const intptr_t cid = current.GetClassId(); | |
| switch (cid) { | |
| case kArrayCid: | |
| case kImmutableArrayCid: | |
| VisitArrayPointers(thread, Array::Cast(current)); | |
| break; | |
| case kClosureCid: | |
| VisitClosurePointers(Closure::Cast(current)); | |
| break; | |
| case kContextCid: | |
| VisitContextPointers(Context::Cast(current)); | |
| break; | |
| case kGrowableObjectArrayCid: | |
| VisitGrowableObjectArrayPointers(GrowableObjectArray::Cast(current)); | |
| break; | |
| case kSetCid: | |
| case kConstSetCid: | |
| case kMapCid: | |
| case kConstMapCid: | |
| VisitLinkedHashBasePointers(LinkedHashBase::Cast(current)); | |
| break; | |
| case kRecordCid: | |
| VisitRecordPointers(Record::Cast(current)); | |
| break; | |
| case kWeakPropertyCid: | |
| VisitWeakPropertyPointers(WeakProperty::Cast(current)); | |
| break; | |
| case kWeakReferenceCid: | |
| VisitWeakReferencePointers(WeakReference::Cast(current)); | |
| break; | |
| #define MESSAGE_SNAPSHOT_ILLEGAL(type) \ | |
| case k##type##Cid: \ | |
| return Error(current, "is a " #type, root); | |
| MESSAGE_SNAPSHOT_ILLEGAL(DynamicLibrary) | |
| // TODO(http://dartbug.com/47777): Send and exit support: remove this. | |
| MESSAGE_SNAPSHOT_ILLEGAL(Finalizer) | |
| MESSAGE_SNAPSHOT_ILLEGAL(NativeFinalizer) | |
| MESSAGE_SNAPSHOT_ILLEGAL(MirrorReference) | |
| MESSAGE_SNAPSHOT_ILLEGAL(Pointer) | |
| MESSAGE_SNAPSHOT_ILLEGAL(ReceivePort) | |
| MESSAGE_SNAPSHOT_ILLEGAL(UserTag) | |
| MESSAGE_SNAPSHOT_ILLEGAL(SuspendState) | |
| default: | |
| klass = class_table_->At(cid); | |
| if (klass.is_isolate_unsendable()) { | |
| return Error( | |
| current, | |
| "is unsendable object (see restrictions listed at" | |
| "`SendPort.send()` documentation for more information)", | |
| root); | |
| } | |
| VisitInstancePointers(Instance::Cast(current), cid); | |
| } | |
| thread->CheckForSafepoint(); | |
| } | |
| return root.ptr(); | |
| } | |
| private: | |
| void VisitArrayPointers(Thread* thread, const Array& array) { | |
| for (intptr_t i = 0, n = array.Length(); i < n; i++) { | |
| Visit(array.At(i)); | |
| if (((i + 1) % kSlotsPerInterruptCheck) == 0) { | |
| thread->CheckForSafepoint(); | |
| } | |
| } | |
| } | |
| void VisitGrowableObjectArrayPointers(const GrowableObjectArray& list) { | |
| Visit(list.data()); | |
| } | |
| void VisitRecordPointers(const Record& record) { | |
| for (intptr_t i = 0, n = record.num_fields(); i < n; i++) { | |
| Visit(record.FieldAt(i)); | |
| } | |
| } | |
| void VisitContextPointers(const Context& context) { | |
| Visit(context.parent()); | |
| for (intptr_t i = 0, n = context.num_variables(); i < n; i++) { | |
| Visit(context.At(i)); | |
| } | |
| } | |
| void VisitLinkedHashBasePointers(const LinkedHashBase& hash) { | |
| Visit(hash.data()); | |
| } | |
| void VisitClosurePointers(const Closure& closure) { | |
| Visit(closure.RawContext()); | |
| } | |
| void VisitWeakPropertyPointers(const WeakProperty& weak) { | |
| Visit(weak.key()); | |
| Visit(weak.value()); | |
| } | |
| void VisitWeakReferencePointers(const WeakReference& weak) { | |
| Visit(weak.target()); | |
| } | |
| void VisitInstancePointers(const Instance& instance, intptr_t cid) { | |
| ASSERT(cid == kInstanceCid || cid >= kNumPredefinedCids); | |
| const auto bitmap = class_table_->GetUnboxedFieldsMapAt(cid); | |
| intptr_t size = instance.ptr()->untag()->HeapSize(); | |
| uword heap_base = instance.ptr()->heap_base(); | |
| intptr_t offset = kWordSize; | |
| intptr_t bit = offset >> kCompressedWordSizeLog2; | |
| for (; offset < size; offset += kCompressedWordSize) { | |
| if (!bitmap.Get(bit++)) { | |
| Visit(reinterpret_cast<CompressedObjectPtr*>( | |
| reinterpret_cast<uword>(instance.ptr()->untag()) + offset) | |
| ->Decompress(heap_base)); | |
| } | |
| } | |
| } | |
| void Visit(ObjectPtr obj) { | |
| if (!obj->IsHeapObject() || obj->untag()->IsCanonical() || | |
| obj->untag()->IsImmutable()) { | |
| return; | |
| } | |
| switch (obj->untag()->GetClassId()) { | |
| case kTransferableTypedDataCid: | |
| #define CASE(clazz) \ | |
| case kTypedData##clazz##Cid: \ | |
| case kTypedData##clazz##ViewCid: \ | |
| case kExternalTypedData##clazz##Cid: \ | |
| case kUnmodifiableTypedData##clazz##ViewCid: | |
| CLASS_LIST_TYPED_DATA(CASE) | |
| #undef CASE | |
| case kByteDataViewCid: | |
| case kUnmodifiableByteDataViewCid: | |
| case kByteBufferCid: | |
| return; | |
| } | |
| value_ = obj; | |
| Push(value_); | |
| } | |
| ObjectPtr Error(const Object& illegal_object, | |
| const char* exception_message, | |
| const Object& root) { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const Array& args = Array::Handle(zone, Array::New(3)); | |
| args.SetAt(0, illegal_object); | |
| args.SetAt(2, String::Handle( | |
| zone, String::NewFormatted( | |
| "%s%s", | |
| FindRetainingPath( | |
| zone, thread, root, illegal_object, | |
| TraversalRules::kInternalToIsolateGroup), | |
| exception_message))); | |
| const Object& exception = Object::Handle( | |
| zone, Exceptions::Create(Exceptions::kArgumentValue, args)); | |
| return UnhandledException::New(Instance::Cast(exception), | |
| StackTrace::Handle(zone)); | |
| } | |
| private: | |
| PassiveObject& value_; | |
| ClassTable* class_table_; | |
| }; | |
| DEFINE_NATIVE_ENTRY(Isolate_create_, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(String, debug_name, arguments->NativeArgAt(0)); | |
| const char* debug_name_cstr = | |
| !debug_name.IsNull() ? debug_name.ToCString() : nullptr; | |
| if (thread->isolate() != nullptr) { | |
| const auto& error = | |
| String::Handle(String::New("Should be invoked outside of an isolate")); | |
| Exceptions::ThrowStateError(error); | |
| UNREACHABLE(); | |
| } | |
| auto initialize_callback = Isolate::InitializeCallback(); | |
| char* error = nullptr; | |
| auto group = IsolateGroup::Current(); | |
| auto& created = Array::Handle(zone, Array::New(2)); | |
| const auto& capabilities = Array::Handle(zone, Array::New(2)); | |
| auto& capability = Capability::Handle(zone); | |
| auto& send_port = SendPort::Handle(zone); | |
| Thread::ExitIsolateGroupAsMutator(/*bypass_safepoint=*/false); | |
| Isolate* created_isolate = | |
| CreateWithinExistingIsolateGroup(group, debug_name_cstr, &error); | |
| RELEASE_ASSERT(created_isolate != nullptr); | |
| void* child_isolate_data = nullptr; | |
| const bool success = initialize_callback(&child_isolate_data, &error); | |
| if (!success) { | |
| const auto& error_str = String::Handle(String::New(error)); | |
| Exceptions::ThrowStateError(error_str); | |
| UNREACHABLE(); | |
| } | |
| created_isolate->set_init_callback_data(child_isolate_data); | |
| Dart_ExitIsolate(); | |
| Thread::EnterIsolateGroupAsMutator(group, /*bypass_safepoint=*/false, thread); | |
| auto current_thread = Thread::Current(); | |
| StackZone stack_zone(current_thread); | |
| capability = Capability::New(created_isolate->pause_capability()); | |
| capabilities.SetAt(0, capability); | |
| capability = Capability::New(created_isolate->terminate_capability()); | |
| capabilities.SetAt(1, capability); | |
| send_port = SendPort::New(created_isolate->main_port(), group->id()); | |
| created.SetAt(0, send_port); | |
| created.SetAt(1, capabilities); | |
| return created.ptr(); | |
| } | |
| namespace { | |
| // Scope that gives user an ability to acquire an isolate if it is available, | |
| // gain temporary ownership, which is released at the end of the scope. | |
| class IsolateAcquireScope : public ValueObject { | |
| public: | |
| IsolateAcquireScope(Thread* thread, Dart_Port receiver) : isolate_(nullptr) { | |
| Isolate* target_isolate = nullptr; | |
| acquire_result_ = | |
| PortMap::AcquireIsolateByControlPort(receiver, &target_isolate); | |
| if (target_isolate == nullptr) { | |
| // Isolate might have exited already. | |
| return; | |
| } | |
| Thread::EnterIsolate(target_isolate); | |
| isolate_ = target_isolate; | |
| } | |
| ~IsolateAcquireScope() { | |
| if (isolate_ == nullptr) { | |
| return; | |
| } | |
| ASSERT(Thread::Current()->isolate() == isolate_); | |
| Thread::ExitIsolate(); | |
| if (!isolate_->is_permanently_pinned()) { | |
| isolate_->ReleaseOwnership(); | |
| } | |
| } | |
| void Reset() { isolate_ = nullptr; } | |
| Isolate* isolate() { return isolate_; } | |
| IsolateAcquireResult acquire_result() { return acquire_result_; } | |
| const char* error_message() { | |
| switch (acquire_result_) { | |
| case IsolateAcquireResult::ISOLATE_NOT_AVAILABLE: | |
| return "Unable to enter the isolate as it's unavailable"; | |
| case IsolateAcquireResult::PINNED_TO_ANOTHER_THREAD: | |
| return "Isolate is pinned to a different thread already"; | |
| case IsolateAcquireResult::BUSY: | |
| return "Isolate is busy, running on a different thread"; | |
| case IsolateAcquireResult::HAS_MESSAGE_LOOP_OR_UNAVAILABLE: | |
| return "Isolate has a message loop running or otherwise unavailable."; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| private: | |
| Isolate* isolate_; | |
| IsolateAcquireResult acquire_result_; | |
| DISALLOW_COPY_AND_ASSIGN(IsolateAcquireScope); | |
| }; | |
| } // namespace | |
| DEFINE_NATIVE_ENTRY(Isolate_shutdownSync_, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(SendPort, isolate_control_port, | |
| arguments->NativeArgAt(0)); | |
| if (thread->isolate() != nullptr) { | |
| const auto& error = | |
| String::Handle(String::New("Should be invoked outside of an isolate")); | |
| Exceptions::ThrowStateError(error); | |
| UNREACHABLE(); | |
| } | |
| auto group = thread->isolate_group(); | |
| auto control_port_id = isolate_control_port.Id(); | |
| Thread::ExitIsolateGroupAsMutator(/*bypass_safepoint=*/false); | |
| { | |
| IsolateAcquireScope acquire_scope(thread, control_port_id); | |
| Isolate* target_isolate = acquire_scope.isolate(); | |
| if (target_isolate != nullptr) { | |
| Dart::ShutdownIsolate(Thread::Current()); | |
| acquire_scope.Reset(); | |
| } | |
| } | |
| Thread::EnterIsolateGroupAsMutator(group, /*bypass_safepoint=*/false, thread); | |
| return Object::null(); | |
| } | |
| DEFINE_NATIVE_ENTRY(Isolate_runEventLoopSync_, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(SendPort, isolate_control_port, | |
| arguments->NativeArgAt(0)); | |
| if (thread->isolate() != nullptr) { | |
| const auto& error = | |
| String::Handle(String::New("Should be invoked outside of an isolate")); | |
| Exceptions::ThrowStateError(error); | |
| UNREACHABLE(); | |
| } | |
| auto group = thread->isolate_group(); | |
| if (isolate_control_port.origin_id() != group->id()) { | |
| const auto& error = String::Handle(String::New( | |
| "Target isolate should be part of the same isolate group.")); | |
| Exceptions::ThrowStateError(error); | |
| UNREACHABLE(); | |
| } | |
| Dart_Port control_port_id = isolate_control_port.Id(); | |
| Error& result_error = Error::Handle(); | |
| Thread::ExitIsolateGroupAsMutator(/*bypass_safepoint=*/false); | |
| { | |
| // Take over isolate's event loop - block it's original message_handler. | |
| IsolateAcquireScope acquire_scope(thread, control_port_id); | |
| Isolate* target_isolate = acquire_scope.isolate(); | |
| if (target_isolate == nullptr) { | |
| // Reenter the group so we can report an error. | |
| Thread::EnterIsolateGroupAsMutator(group, /*bypass_safepoint=*/false, | |
| thread); | |
| const auto& message = | |
| String::Handle(String::New(acquire_scope.error_message())); | |
| Exceptions::ThrowStateError(message); | |
| UNREACHABLE(); | |
| } | |
| auto current_thread = Thread::Current(); | |
| { | |
| TransitionVMToNative transition(current_thread); | |
| Dart_EnterScope(); | |
| Dart_ExitIsolate(); | |
| target_isolate->message_handler()->RunSync(); | |
| Dart_EnterIsolate(reinterpret_cast<Dart_Isolate>(target_isolate)); | |
| if (target_isolate->sticky_error() != Object::null()) { | |
| result_error = target_isolate->StealStickyError(); | |
| } | |
| Dart_ExitScope(); | |
| } | |
| } | |
| Thread::EnterIsolateGroupAsMutator(group, /*bypass_safepoint=*/false, thread); | |
| if (!result_error.IsNull()) { | |
| Exceptions::PropagateError(result_error); | |
| UNREACHABLE(); | |
| } | |
| return Object::null(); | |
| } | |
| DEFINE_NATIVE_ENTRY(Isolate_runSync_, 1, 2) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(SendPort, isolate_control_port, | |
| arguments->NativeArgAt(0)); | |
| GET_NON_NULL_NATIVE_ARGUMENT(Closure, closure, arguments->NativeArgAt(1)); | |
| auto group = thread->isolate_group(); | |
| if (isolate_control_port.origin_id() != group->id()) { | |
| const auto& error = String::Handle(String::New( | |
| "Target isolate should be part of the same isolate group.")); | |
| Exceptions::ThrowStateError(error); | |
| UNREACHABLE(); | |
| } | |
| Dart_Port control_port_id = isolate_control_port.Id(); | |
| closure.EnsureDeeplyImmutable(zone); | |
| Array& args_desc = Array::Handle(zone, ArgumentsDescriptor::NewBoxed(0, 1)); | |
| Array& args = Array::Handle(zone, Array::New(1)); | |
| args.SetAt(0, closure); | |
| Object& result = Object::Handle(zone); | |
| if (isolate != nullptr && (isolate->main_port() == control_port_id)) { | |
| // Fast-path for when we are already running on the desired isolate. | |
| result = DartEntry::InvokeClosure(thread, args, args_desc); | |
| if (result.IsUnwindError()) { | |
| Exceptions::PropagateError(Error::Cast(result)); | |
| UNREACHABLE(); | |
| } | |
| } else { | |
| if (isolate != nullptr) { | |
| ASSERT(Thread::Current()->isolate() == isolate); | |
| Thread::ExitIsolate(/*isolate_shutdown=*/false); | |
| } else { | |
| Thread::ExitIsolateGroupAsMutator(/*bypass_safepoint=*/false); | |
| } | |
| { | |
| IsolateAcquireScope acquire_scope(thread, control_port_id); | |
| Isolate* target_isolate = acquire_scope.isolate(); | |
| if (target_isolate == nullptr) { | |
| // Re-enter the group or isolate so we can report an error. | |
| if (isolate != nullptr) { | |
| Thread::EnterIsolate(isolate); | |
| } else { | |
| Thread::EnterIsolateGroupAsMutator(group, /*bypass_safepoint=*/false, | |
| thread); | |
| } | |
| const char* message = acquire_scope.error_message(); | |
| Exceptions::ThrowStateError(String::Handle(String::New(message))); | |
| UNREACHABLE(); | |
| } | |
| auto current_thread = Thread::Current(); | |
| { | |
| StackZone stack_zone(current_thread); | |
| result = DartEntry::InvokeClosure(current_thread, args, args_desc); | |
| } | |
| } | |
| if (isolate != nullptr) { | |
| Thread::EnterIsolate(isolate); | |
| } else { | |
| Thread::EnterIsolateGroupAsMutator(group, /*bypass_safepoint=*/false, | |
| thread); | |
| } | |
| if (result.IsUnwindError()) { | |
| const auto& error = | |
| String::Handle(String::New("Isolate was forced to exit.")); | |
| Exceptions::ThrowStateError(error); | |
| UNREACHABLE(); | |
| } | |
| } | |
| if (result.IsUnhandledException()) { | |
| Exceptions::PropagateError(Error::Cast(result)); | |
| UNREACHABLE(); | |
| } | |
| result.EnsureDeeplyImmutable(zone); | |
| return result.ptr(); | |
| } | |
| DEFINE_NATIVE_ENTRY(Isolate_isPinnedToCurrentThread, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(SendPort, isolate_control_port, | |
| arguments->NativeArgAt(0)); | |
| auto control_port_id = isolate_control_port.Id(); | |
| return Bool::Get(Dart_GetCurrentThreadOwnsIsolate(control_port_id)).ptr(); | |
| } | |
| DEFINE_NATIVE_ENTRY(Isolate_pinToCurrentThread, 0, 0) { | |
| if (isolate == nullptr) { | |
| ThrowCantRunWithoutIsolateError(); | |
| UNREACHABLE(); | |
| } | |
| CHECK_ISOLATE(isolate); | |
| if (!isolate->SetOwnerThread(OSThread::kInvalidThreadId, | |
| OSThread::GetCurrentThreadId())) { | |
| // We might be running this method while running dart code | |
| // on this target isolate. | |
| // So first confirm that the isolate is not yet pinned yet. | |
| if (isolate->is_permanently_pinned()) { | |
| return Bool::False().ptr(); | |
| } | |
| // Allow pinning only if current owner is the current thread. | |
| if (isolate->GetOwnerThread(/*locker=*/nullptr) != | |
| OSThread::GetCurrentThreadId()) { | |
| return Bool::False().ptr(); | |
| } | |
| } | |
| isolate->set_is_permanently_pinned(); | |
| return Bool::True().ptr(); | |
| } | |
| // TODO(http://dartbug.com/47777): Add support for Finalizers. | |
| DEFINE_NATIVE_ENTRY(Isolate_exit_, 0, 2) { | |
| if (isolate == nullptr) { | |
| ThrowCantRunWithoutIsolateError(); | |
| UNREACHABLE(); | |
| } | |
| GET_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0)); | |
| if (!port.IsNull()) { | |
| GET_NATIVE_ARGUMENT(Instance, obj, arguments->NativeArgAt(1)); | |
| IsolateGroup* group = thread->isolate_group(); | |
| const bool same_group = InSameGroup(group, port); | |
| #if defined(DEBUG) | |
| if (same_group) { | |
| ASSERT(PortMap::IsReceiverInThisIsolateGroupOrClosed(port.Id(), group)); | |
| } | |
| #endif | |
| if (!same_group) { | |
| const auto& error = | |
| String::Handle(String::New("exit with final message is only allowed " | |
| "for isolates in one isolate group.")); | |
| Exceptions::ThrowArgumentError(error); | |
| UNREACHABLE(); | |
| } | |
| Object& validated_result = Object::Handle(zone); | |
| const Object& msg_obj = Object::Handle(zone, obj.ptr()); | |
| { | |
| MessageValidator validator(thread); | |
| validated_result = validator.Validate(thread, obj); | |
| } | |
| // msg_array = [ | |
| // <message>, | |
| // <collection-lib-objects-to-rehash>, | |
| // <core-lib-objects-to-rehash>, | |
| // ] | |
| const Array& msg_array = Array::Handle(zone, Array::New(3)); | |
| msg_array.SetAt(0, msg_obj); | |
| if (validated_result.IsUnhandledException()) { | |
| Exceptions::PropagateError(Error::Cast(validated_result)); | |
| UNREACHABLE(); | |
| } | |
| PersistentHandle* handle = group->api_state()->AllocatePersistentHandle(); | |
| handle->set_ptr(msg_array); | |
| isolate->bequeath(std::unique_ptr<Bequest>(new Bequest(handle, port.Id()))); | |
| } | |
| Thread::Current()->StartUnwindError(); | |
| const String& msg = | |
| String::Handle(String::New("isolate terminated by Isolate.exit")); | |
| const UnwindError& error = UnwindError::Handle(UnwindError::New(msg)); | |
| error.set_is_user_initiated(true); | |
| Exceptions::PropagateError(error); | |
| UNREACHABLE(); | |
| // We will never execute dart code again in this isolate. | |
| return Object::null(); | |
| } | |
| class IsolateSpawnState { | |
| public: | |
| IsolateSpawnState(Dart_Port parent_port, | |
| const char* script_url, | |
| PersistentHandle* closure_tuple_handle, | |
| SerializedObjectBuffer* message_buffer, | |
| const char* package_config, | |
| bool paused, | |
| bool errorsAreFatal, | |
| Dart_Port onExit, | |
| Dart_Port onError, | |
| const char* debug_name, | |
| IsolateGroup* group); | |
| IsolateSpawnState(Dart_Port parent_port, | |
| const char* script_url, | |
| const char* package_config, | |
| SerializedObjectBuffer* args_buffer, | |
| SerializedObjectBuffer* message_buffer, | |
| bool paused, | |
| bool errorsAreFatal, | |
| Dart_Port onExit, | |
| Dart_Port onError, | |
| const char* debug_name, | |
| IsolateGroup* group); | |
| ~IsolateSpawnState(); | |
| Dart_Port parent_port() const { return parent_port_; } | |
| Dart_Port on_exit_port() const { return on_exit_port_; } | |
| Dart_Port on_error_port() const { return on_error_port_; } | |
| const char* script_url() const { return script_url_; } | |
| const char* package_config() const { return package_config_; } | |
| const char* debug_name() const { return debug_name_; } | |
| bool is_spawn_uri() const { | |
| return closure_tuple_handle_ == nullptr; // No closure entrypoint. | |
| } | |
| bool paused() const { return paused_; } | |
| bool errors_are_fatal() const { return errors_are_fatal_; } | |
| Dart_IsolateFlags* isolate_flags() { return &isolate_flags_; } | |
| PersistentHandle* closure_tuple_handle() const { | |
| return closure_tuple_handle_; | |
| } | |
| ObjectPtr ResolveFunction(); | |
| ObjectPtr BuildArgs(Thread* thread); | |
| ObjectPtr BuildMessage(Thread* thread); | |
| IsolateGroup* isolate_group() const { return isolate_group_; } | |
| private: | |
| Dart_Port parent_port_; | |
| Dart_Port on_exit_port_; | |
| Dart_Port on_error_port_; | |
| const char* script_url_; | |
| const char* package_config_; | |
| const char* debug_name_; | |
| PersistentHandle* closure_tuple_handle_ = nullptr; | |
| IsolateGroup* isolate_group_; | |
| std::unique_ptr<Message> serialized_args_; | |
| std::unique_ptr<Message> serialized_message_; | |
| Dart_IsolateFlags isolate_flags_; | |
| bool paused_; | |
| bool errors_are_fatal_; | |
| }; | |
| static const char* NewConstChar(const char* chars) { | |
| size_t len = strlen(chars); | |
| char* mem = new char[len + 1]; | |
| memmove(mem, chars, len + 1); | |
| return mem; | |
| } | |
| IsolateSpawnState::IsolateSpawnState(Dart_Port parent_port, | |
| const char* script_url, | |
| PersistentHandle* closure_tuple_handle, | |
| SerializedObjectBuffer* message_buffer, | |
| const char* package_config, | |
| bool paused, | |
| bool errors_are_fatal, | |
| Dart_Port on_exit_port, | |
| Dart_Port on_error_port, | |
| const char* debug_name, | |
| IsolateGroup* isolate_group) | |
| : parent_port_(parent_port), | |
| on_exit_port_(on_exit_port), | |
| on_error_port_(on_error_port), | |
| script_url_(script_url), | |
| package_config_(package_config), | |
| debug_name_(debug_name), | |
| closure_tuple_handle_(closure_tuple_handle), | |
| isolate_group_(isolate_group), | |
| serialized_args_(nullptr), | |
| serialized_message_(message_buffer->StealMessage()), | |
| paused_(paused), | |
| errors_are_fatal_(errors_are_fatal) { | |
| ASSERT(closure_tuple_handle_ != nullptr); | |
| auto thread = Thread::Current(); | |
| auto isolate = thread->isolate(); | |
| // Inherit flags from spawning isolate. | |
| isolate->FlagsCopyTo(isolate_flags()); | |
| } | |
| IsolateSpawnState::IsolateSpawnState(Dart_Port parent_port, | |
| const char* script_url, | |
| const char* package_config, | |
| SerializedObjectBuffer* args_buffer, | |
| SerializedObjectBuffer* message_buffer, | |
| bool paused, | |
| bool errors_are_fatal, | |
| Dart_Port on_exit_port, | |
| Dart_Port on_error_port, | |
| const char* debug_name, | |
| IsolateGroup* isolate_group) | |
| : parent_port_(parent_port), | |
| on_exit_port_(on_exit_port), | |
| on_error_port_(on_error_port), | |
| script_url_(script_url), | |
| package_config_(package_config), | |
| debug_name_(debug_name), | |
| isolate_group_(isolate_group), | |
| serialized_args_(args_buffer->StealMessage()), | |
| serialized_message_(message_buffer->StealMessage()), | |
| isolate_flags_(), | |
| paused_(paused), | |
| errors_are_fatal_(errors_are_fatal) { | |
| if (debug_name_ == nullptr) { | |
| debug_name_ = NewConstChar("main"); | |
| } | |
| // By default inherit flags from spawning isolate. These can be overridden | |
| // from the calling code. | |
| Isolate::Current()->FlagsCopyTo(isolate_flags()); | |
| } | |
| IsolateSpawnState::~IsolateSpawnState() { | |
| delete[] script_url_; | |
| delete[] package_config_; | |
| delete[] debug_name_; | |
| } | |
| ObjectPtr IsolateSpawnState::ResolveFunction() { | |
| Thread* thread = Thread::Current(); | |
| auto IG = thread->isolate_group(); | |
| Zone* zone = thread->zone(); | |
| // Handle spawnUri lookup rules. | |
| // Check whether the root library defines a main function. | |
| const Library& lib = | |
| Library::Handle(zone, IG->object_store()->root_library()); | |
| const String& main = Symbols::main(); | |
| Function& func = Function::Handle(zone, lib.LookupFunctionAllowPrivate(main)); | |
| if (func.IsNull()) { | |
| const String& msg = String::Handle( | |
| zone, | |
| String::NewFormatted( | |
| "Unable to resolve function 'main' in script '%s'.", script_url())); | |
| return LanguageError::New(msg); | |
| } | |
| return func.ptr(); | |
| } | |
| static ObjectPtr DeserializeMessage(Thread* thread, Message* message) { | |
| if (message == nullptr) { | |
| return Object::null(); | |
| } | |
| if (message->IsRaw()) { | |
| return Object::RawCast(message->raw_obj()); | |
| } else { | |
| return ReadMessage(thread, message); | |
| } | |
| } | |
| ObjectPtr IsolateSpawnState::BuildArgs(Thread* thread) { | |
| const Object& result = | |
| Object::Handle(DeserializeMessage(thread, serialized_args_.get())); | |
| serialized_args_.reset(); | |
| return result.ptr(); | |
| } | |
| ObjectPtr IsolateSpawnState::BuildMessage(Thread* thread) { | |
| const Object& result = | |
| Object::Handle(DeserializeMessage(thread, serialized_message_.get())); | |
| serialized_message_.reset(); | |
| return result.ptr(); | |
| } | |
| static void ThrowIsolateSpawnException(const String& message) { | |
| const Array& args = Array::Handle(Array::New(1)); | |
| args.SetAt(0, message); | |
| Exceptions::ThrowByType(Exceptions::kIsolateSpawn, args); | |
| } | |
| class SpawnIsolateTask : public ThreadPool::Task { | |
| public: | |
| SpawnIsolateTask(Isolate* parent_isolate, | |
| std::unique_ptr<IsolateSpawnState> state) | |
| : parent_isolate_(parent_isolate), state_(std::move(state)) { | |
| parent_isolate->IncrementSpawnCount(); | |
| } | |
| ~SpawnIsolateTask() override { | |
| if (parent_isolate_ != nullptr) { | |
| parent_isolate_->DecrementSpawnCount(); | |
| } | |
| } | |
| void Run() override { | |
| const char* name = state_->debug_name(); | |
| ASSERT(name != nullptr); | |
| auto group = state_->isolate_group(); | |
| if (group == nullptr) { | |
| RunHeavyweight(name); | |
| } else { | |
| RunLightweight(name); | |
| } | |
| } | |
| void RunHeavyweight(const char* name) { | |
| // The create isolate group callback is mandatory. If not provided we | |
| // cannot spawn isolates. | |
| auto create_group_callback = Isolate::CreateGroupCallback(); | |
| if (create_group_callback == nullptr) { | |
| FailedSpawn("Isolate spawn is not supported by this Dart embedder\n"); | |
| return; | |
| } | |
| char* error = nullptr; | |
| // Make a copy of the state's isolate flags and hand it to the callback. | |
| Dart_IsolateFlags api_flags = *(state_->isolate_flags()); | |
| // Inherit the system isolate property to work around issues with | |
| // --pause-isolates-on-start and --pause-isolates-on-exit impacting macro | |
| // generation isolates which are spawned by the kernel-service | |
| // (see https://github.com/dart-lang/sdk/issues/54729 for details). | |
| // | |
| // This flag isn't inherited in the case that the main isolate is marked as | |
| // a system isolate in the standalone VM using the | |
| // --mark-main-isolate-as-system-isolate flag as it's currently used to | |
| // hide test runner implementation details and spawns isolates that should | |
| // be debuggable as non-system isolates. | |
| // | |
| // TODO(bkonyi): revisit this decision, see | |
| // https://github.com/dart-lang/sdk/issues/54736 for the tracking issue. | |
| const bool is_parent_main_isolate = | |
| strcmp(parent_isolate_->name(), "main") == 0; | |
| api_flags.is_system_isolate = | |
| api_flags.is_system_isolate && !is_parent_main_isolate; | |
| Dart_Isolate isolate = | |
| (create_group_callback)(state_->script_url(), name, nullptr, | |
| state_->package_config(), &api_flags, | |
| parent_isolate_->init_callback_data(), &error); | |
| parent_isolate_->DecrementSpawnCount(); | |
| parent_isolate_ = nullptr; | |
| if (isolate == nullptr) { | |
| FailedSpawn(error, /*has_current_isolate=*/false); | |
| free(error); | |
| return; | |
| } | |
| Dart_EnterIsolate(isolate); | |
| Run(reinterpret_cast<Isolate*>(isolate)); | |
| } | |
| void RunLightweight(const char* name) { | |
| // The create isolate initialize callback is mandatory. | |
| auto initialize_callback = Isolate::InitializeCallback(); | |
| if (initialize_callback == nullptr) { | |
| FailedSpawn( | |
| "Lightweight isolate spawn is not supported by this Dart embedder\n", | |
| /*has_current_isolate=*/false); | |
| return; | |
| } | |
| char* error = nullptr; | |
| auto group = state_->isolate_group(); | |
| Isolate* isolate = CreateWithinExistingIsolateGroup(group, name, &error); | |
| parent_isolate_->DecrementSpawnCount(); | |
| parent_isolate_ = nullptr; | |
| if (isolate == nullptr) { | |
| FailedSpawn(error, /*has_current_isolate=*/false); | |
| free(error); | |
| return; | |
| } | |
| void* child_isolate_data = nullptr; | |
| const bool success = initialize_callback(&child_isolate_data, &error); | |
| if (!success) { | |
| FailedSpawn(error); | |
| Dart_ShutdownIsolate(); | |
| free(error); | |
| return; | |
| } | |
| isolate->set_init_callback_data(child_isolate_data); | |
| Run(isolate); | |
| } | |
| private: | |
| void Run(Isolate* child) { | |
| if (!EnsureIsRunnable(child)) { | |
| Dart_ShutdownIsolate(); | |
| return; | |
| } | |
| bool errors_are_fatal = state_->errors_are_fatal(); | |
| Dart_Port on_error_port = state_->on_error_port(); | |
| Dart_Port on_exit_port = state_->on_exit_port(); | |
| bool success = true; | |
| { | |
| auto thread = Thread::Current(); | |
| TransitionNativeToVM transition(thread); | |
| StackZone zone(thread); | |
| success = EnqueueEntrypointInvocationAndNotifySpawner(thread); | |
| // Destruction of [IsolateSpawnState] may cause destruction of [Message] | |
| // which make need to delete persistent handles, so explicitly delete it | |
| // now while we are in the right safepoint state. | |
| state_ = nullptr; | |
| } | |
| if (!success) { | |
| Dart_ShutdownIsolate(); | |
| return; | |
| } | |
| // All preconditions are met for this to always succeed. | |
| char* error = nullptr; | |
| if (!Dart_RunLoopAsync(errors_are_fatal, on_error_port, on_exit_port, | |
| &error)) { | |
| FATAL("Dart_RunLoopAsync() failed: %s. Please file a Dart VM bug report.", | |
| error); | |
| } | |
| } | |
| bool EnsureIsRunnable(Isolate* child) { | |
| // We called out to the embedder to create/initialize a new isolate. The | |
| // embedder callback successfully did so. It is now our responsibility to | |
| // run the isolate. | |
| // If the isolate was not marked as runnable, we'll do so here and run it. | |
| if (!child->is_runnable()) { | |
| const char* error = child->MakeRunnable(); | |
| if (error != nullptr) { | |
| FailedSpawn(error); | |
| return false; | |
| } | |
| } | |
| ASSERT(child->is_runnable()); | |
| return true; | |
| } | |
| bool EnqueueEntrypointInvocationAndNotifySpawner(Thread* thread) { | |
| auto isolate = thread->isolate(); | |
| auto zone = thread->zone(); | |
| const bool is_spawn_uri = state_->is_spawn_uri(); | |
| // Step 1) Resolve the entrypoint function. | |
| auto& entrypoint_closure = Closure::Handle(zone); | |
| if (state_->closure_tuple_handle() != nullptr) { | |
| const auto& result = Object::Handle( | |
| zone, | |
| ReadObjectGraphCopyMessage(thread, state_->closure_tuple_handle())); | |
| if (result.IsError()) { | |
| ReportError( | |
| "Failed to deserialize the passed entrypoint to the new isolate."); | |
| return false; | |
| } | |
| entrypoint_closure = Closure::RawCast(result.ptr()); | |
| } else { | |
| const auto& result = Object::Handle(zone, state_->ResolveFunction()); | |
| if (result.IsError()) { | |
| ASSERT(is_spawn_uri); | |
| ReportError("Failed to resolve entrypoint function."); | |
| return false; | |
| } | |
| ASSERT(result.IsFunction()); | |
| auto& func = Function::Handle(zone, Function::Cast(result).ptr()); | |
| func = func.ImplicitClosureFunction(); | |
| entrypoint_closure = func.ImplicitStaticClosure(); | |
| } | |
| // Step 2) Enqueue delayed invocation of entrypoint callback. | |
| const auto& args_obj = Object::Handle(zone, state_->BuildArgs(thread)); | |
| if (args_obj.IsError()) { | |
| ReportError( | |
| "Failed to deserialize the passed arguments to the new isolate."); | |
| return false; | |
| } | |
| ASSERT(args_obj.IsNull() || args_obj.IsInstance()); | |
| const auto& message_obj = | |
| Object::Handle(zone, state_->BuildMessage(thread)); | |
| if (message_obj.IsError()) { | |
| ReportError( | |
| "Failed to deserialize the passed arguments to the new isolate."); | |
| return false; | |
| } | |
| ASSERT(message_obj.IsNull() || message_obj.IsInstance()); | |
| const Array& args = Array::Handle(zone, Array::New(4)); | |
| args.SetAt(0, entrypoint_closure); | |
| args.SetAt(1, args_obj); | |
| args.SetAt(2, message_obj); | |
| args.SetAt(3, is_spawn_uri ? Bool::True() : Bool::False()); | |
| const auto& lib = Library::Handle(zone, Library::IsolateLibrary()); | |
| const auto& entry_name = String::Handle(zone, String::New("_startIsolate")); | |
| const auto& entry_point = | |
| Function::Handle(zone, lib.LookupFunctionAllowPrivate(entry_name)); | |
| ASSERT(entry_point.IsFunction() && !entry_point.IsNull()); | |
| const auto& result = | |
| Object::Handle(zone, DartEntry::InvokeFunction(entry_point, args)); | |
| if (result.IsError()) { | |
| ReportError("Failed to enqueue delayed entrypoint invocation."); | |
| return false; | |
| } | |
| // Step 3) Pause the isolate if required & Notify parent isolate about | |
| // isolate creation. | |
| const auto& capabilities = Array::Handle(zone, Array::New(2)); | |
| auto& capability = Capability::Handle(zone); | |
| capability = Capability::New(isolate->pause_capability()); | |
| capabilities.SetAt(0, capability); | |
| capability = Capability::New(isolate->terminate_capability()); | |
| capabilities.SetAt(1, capability); | |
| const auto& send_port = SendPort::Handle( | |
| zone, SendPort::New(isolate->main_port(), isolate->group()->id())); | |
| const auto& message = Array::Handle(zone, Array::New(2)); | |
| message.SetAt(0, send_port); | |
| message.SetAt(1, capabilities); | |
| if (state_->paused()) { | |
| capability ^= capabilities.At(0); | |
| const bool added = isolate->AddResumeCapability(capability); | |
| ASSERT(added); | |
| isolate->message_handler()->increment_paused(); | |
| } | |
| { | |
| // If parent isolate died, we ignore the fact that we cannot notify it. | |
| PortMap::PostMessage(WriteMessage(/*same_group=*/false, message, | |
| state_->parent_port(), | |
| Message::kNormalPriority)); | |
| } | |
| return true; | |
| } | |
| void FailedSpawn(const char* error, bool has_current_isolate = true) { | |
| ReportError(error != nullptr | |
| ? error | |
| : "Unknown error occurred during Isolate spawning."); | |
| // Destruction of [IsolateSpawnState] may cause destruction of [Message] | |
| // which make need to delete persistent handles (which requires a current | |
| // isolate group). | |
| if (has_current_isolate) { | |
| ASSERT(IsolateGroup::Current() == state_->isolate_group()); | |
| TransitionNativeToVM transition(Thread::Current()); | |
| state_ = nullptr; | |
| } else if (state_->isolate_group() != nullptr) { | |
| ASSERT(IsolateGroup::Current() == nullptr); | |
| const bool kBypassSafepoint = false; | |
| Thread::EnterIsolateGroupAsHelper(state_->isolate_group(), | |
| Thread::kSpawnTask, kBypassSafepoint); | |
| state_ = nullptr; | |
| Thread::ExitIsolateGroupAsHelper(kBypassSafepoint); | |
| } else { | |
| // The state won't need a current isolate group, because it belongs to a | |
| // [Isolate.spawnUri] call. | |
| state_ = nullptr; | |
| } | |
| } | |
| void ReportError(const char* error) { | |
| Dart_CObject error_cobj; | |
| error_cobj.type = Dart_CObject_kString; | |
| error_cobj.value.as_string = const_cast<char*>(error); | |
| if (!Dart_PostCObject(state_->parent_port(), &error_cobj)) { | |
| // Perhaps the parent isolate died or closed the port before we | |
| // could report the error. Ignore. | |
| } | |
| } | |
| Isolate* parent_isolate_; | |
| std::unique_ptr<IsolateSpawnState> state_; | |
| DISALLOW_COPY_AND_ASSIGN(SpawnIsolateTask); | |
| }; | |
| static const char* String2UTF8(const String& str) { | |
| intptr_t len = Utf8::Length(str); | |
| char* result = new char[len + 1]; | |
| str.ToUTF8(reinterpret_cast<uint8_t*>(result), len); | |
| result[len] = 0; | |
| return result; | |
| } | |
| DEFINE_NATIVE_ENTRY(Isolate_spawnFunction, 0, 10) { | |
| if (isolate == nullptr) { | |
| ThrowCantRunWithoutIsolateError(); | |
| UNREACHABLE(); | |
| } | |
| GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0)); | |
| GET_NON_NULL_NATIVE_ARGUMENT(String, script_uri, arguments->NativeArgAt(1)); | |
| GET_NON_NULL_NATIVE_ARGUMENT(Closure, closure, arguments->NativeArgAt(2)); | |
| GET_NON_NULL_NATIVE_ARGUMENT(Instance, message, arguments->NativeArgAt(3)); | |
| GET_NON_NULL_NATIVE_ARGUMENT(Bool, paused, arguments->NativeArgAt(4)); | |
| GET_NATIVE_ARGUMENT(Bool, fatalErrors, arguments->NativeArgAt(5)); | |
| GET_NATIVE_ARGUMENT(SendPort, onExit, arguments->NativeArgAt(6)); | |
| GET_NATIVE_ARGUMENT(SendPort, onError, arguments->NativeArgAt(7)); | |
| GET_NATIVE_ARGUMENT(String, packageConfig, arguments->NativeArgAt(8)); | |
| GET_NATIVE_ARGUMENT(String, debugName, arguments->NativeArgAt(9)); | |
| PersistentHandle* closure_tuple_handle = nullptr; | |
| // We have a non-toplevel closure that we might need to copy. | |
| // Result will be [<closure-copy>, <objects-in-msg-to-rehash>] | |
| const auto& closure_copy_tuple = Object::Handle( | |
| zone, CopyMutableObjectGraph(closure)); // Throws if it fails. | |
| ASSERT(closure_copy_tuple.IsArray()); | |
| ASSERT( | |
| Object::Handle(zone, Array::Cast(closure_copy_tuple).At(0)).IsClosure()); | |
| auto isolate_group = thread->isolate_group(); | |
| closure_tuple_handle = isolate_group->api_state()->AllocatePersistentHandle(); | |
| closure_tuple_handle->set_ptr(closure_copy_tuple.ptr()); | |
| bool fatal_errors = fatalErrors.IsNull() ? true : fatalErrors.value(); | |
| Dart_Port on_exit_port = onExit.IsNull() ? ILLEGAL_PORT : onExit.Id(); | |
| Dart_Port on_error_port = onError.IsNull() ? ILLEGAL_PORT : onError.Id(); | |
| // We first try to serialize the message. In case the message is not | |
| // serializable this will throw an exception. | |
| SerializedObjectBuffer message_buffer; | |
| message_buffer.set_message(WriteMessage( | |
| /*same_group=*/true, message, ILLEGAL_PORT, Message::kNormalPriority)); | |
| const char* utf8_package_config = | |
| packageConfig.IsNull() ? nullptr : String2UTF8(packageConfig); | |
| const char* utf8_debug_name = | |
| debugName.IsNull() ? nullptr : String2UTF8(debugName); | |
| if (closure_tuple_handle != nullptr && utf8_debug_name == nullptr) { | |
| const auto& closure_function = Function::Handle(zone, closure.function()); | |
| utf8_debug_name = | |
| NewConstChar(closure_function.QualifiedUserVisibleNameCString()); | |
| } | |
| std::unique_ptr<IsolateSpawnState> state(new IsolateSpawnState( | |
| port.Id(), String2UTF8(script_uri), closure_tuple_handle, &message_buffer, | |
| utf8_package_config, paused.value(), fatal_errors, on_exit_port, | |
| on_error_port, utf8_debug_name, isolate_group)); | |
| isolate_group->thread_pool()->Run<SpawnIsolateTask>(isolate, | |
| std::move(state)); | |
| return Object::null(); | |
| } | |
| static const char* CanonicalizeUri(Thread* thread, | |
| const Library& library, | |
| const String& uri, | |
| char** error) { | |
| const char* result = nullptr; | |
| Zone* zone = thread->zone(); | |
| auto isolate_group = thread->isolate_group(); | |
| if (isolate_group->HasTagHandler()) { | |
| const Object& obj = Object::Handle( | |
| isolate_group->CallTagHandler(Dart_kCanonicalizeUrl, library, uri)); | |
| if (obj.IsString()) { | |
| result = String2UTF8(String::Cast(obj)); | |
| } else if (obj.IsError()) { | |
| Error& error_obj = Error::Handle(); | |
| error_obj ^= obj.ptr(); | |
| *error = zone->PrintToString("Unable to canonicalize uri '%s': %s", | |
| uri.ToCString(), error_obj.ToErrorCString()); | |
| } else { | |
| *error = zone->PrintToString( | |
| "Unable to canonicalize uri '%s': " | |
| "library tag handler returned wrong type", | |
| uri.ToCString()); | |
| } | |
| } else { | |
| *error = zone->PrintToString( | |
| "Unable to canonicalize uri '%s': no library tag handler found.", | |
| uri.ToCString()); | |
| } | |
| return result; | |
| } | |
| DEFINE_NATIVE_ENTRY(Isolate_spawnUri, 0, 12) { | |
| if (isolate == nullptr) { | |
| ThrowCantRunWithoutIsolateError(); | |
| UNREACHABLE(); | |
| } | |
| GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0)); | |
| GET_NON_NULL_NATIVE_ARGUMENT(String, uri, arguments->NativeArgAt(1)); | |
| GET_NON_NULL_NATIVE_ARGUMENT(Instance, args, arguments->NativeArgAt(2)); | |
| GET_NON_NULL_NATIVE_ARGUMENT(Instance, message, arguments->NativeArgAt(3)); | |
| GET_NON_NULL_NATIVE_ARGUMENT(Bool, paused, arguments->NativeArgAt(4)); | |
| GET_NATIVE_ARGUMENT(SendPort, onExit, arguments->NativeArgAt(5)); | |
| GET_NATIVE_ARGUMENT(SendPort, onError, arguments->NativeArgAt(6)); | |
| GET_NATIVE_ARGUMENT(Bool, fatalErrors, arguments->NativeArgAt(7)); | |
| GET_NATIVE_ARGUMENT(Bool, enableAsserts, arguments->NativeArgAt(8)); | |
| GET_NATIVE_ARGUMENT(Array, environment, arguments->NativeArgAt(9)); | |
| GET_NATIVE_ARGUMENT(String, packageConfig, arguments->NativeArgAt(10)); | |
| GET_NATIVE_ARGUMENT(String, debugName, arguments->NativeArgAt(11)); | |
| bool fatal_errors = fatalErrors.IsNull() ? true : fatalErrors.value(); | |
| Dart_Port on_exit_port = onExit.IsNull() ? ILLEGAL_PORT : onExit.Id(); | |
| Dart_Port on_error_port = onError.IsNull() ? ILLEGAL_PORT : onError.Id(); | |
| // We first try to serialize the arguments and the message. In case the | |
| // arguments or the message are not serializable this will throw an exception. | |
| SerializedObjectBuffer arguments_buffer; | |
| SerializedObjectBuffer message_buffer; | |
| { | |
| arguments_buffer.set_message(WriteMessage( | |
| /*same_group=*/false, args, ILLEGAL_PORT, Message::kNormalPriority)); | |
| } | |
| { | |
| message_buffer.set_message(WriteMessage( | |
| /*same_group=*/false, message, ILLEGAL_PORT, Message::kNormalPriority)); | |
| } | |
| // Canonicalize the uri with respect to the current isolate. | |
| auto isolate_group = thread->isolate_group(); | |
| const Library& root_lib = | |
| Library::Handle(isolate_group->object_store()->root_library()); | |
| char* error = nullptr; | |
| const char* canonical_uri = CanonicalizeUri(thread, root_lib, uri, &error); | |
| if (canonical_uri == nullptr) { | |
| const String& msg = String::Handle(String::New(error)); | |
| ThrowIsolateSpawnException(msg); | |
| } | |
| const char* utf8_package_config = | |
| packageConfig.IsNull() ? nullptr : String2UTF8(packageConfig); | |
| const char* utf8_debug_name = | |
| debugName.IsNull() ? nullptr : String2UTF8(debugName); | |
| std::unique_ptr<IsolateSpawnState> state(new IsolateSpawnState( | |
| port.Id(), canonical_uri, utf8_package_config, &arguments_buffer, | |
| &message_buffer, paused.value(), fatal_errors, on_exit_port, | |
| on_error_port, utf8_debug_name, /*group=*/nullptr)); | |
| // If we were passed a value then override the default flags state for | |
| // enableAsserts mode. The default state is the state of the current | |
| // isolate. | |
| if (!enableAsserts.IsNull()) { | |
| Dart_IsolateFlags* flags = state->isolate_flags(); | |
| flags->enable_asserts = enableAsserts.value(); | |
| } | |
| isolate_group->thread_pool()->Run<SpawnIsolateTask>(isolate, | |
| std::move(state)); | |
| return Object::null(); | |
| } | |
| DEFINE_NATIVE_ENTRY(Isolate_getDebugName, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0)); | |
| auto name = Isolate::LookupIsolateNameByPort(port.Id()); | |
| if (name == nullptr) { | |
| return String::null(); | |
| } | |
| return String::New(name.get()); | |
| } | |
| DEFINE_NATIVE_ENTRY(Isolate_getPortAndCapabilitiesOfCurrentIsolate, 0, 0) { | |
| const Array& result = Array::Handle(Array::New(3)); | |
| result.SetAt(0, SendPort::Handle(SendPort::New(isolate->main_port(), | |
| isolate->group()->id()))); | |
| result.SetAt( | |
| 1, Capability::Handle(Capability::New(isolate->pause_capability()))); | |
| result.SetAt( | |
| 2, Capability::Handle(Capability::New(isolate->terminate_capability()))); | |
| return result.ptr(); | |
| } | |
| DEFINE_NATIVE_ENTRY(Isolate_getCurrentRootUriStr, 0, 0) { | |
| const Library& root_lib = Library::Handle( | |
| zone, thread->isolate_group()->object_store()->root_library()); | |
| return root_lib.url(); | |
| } | |
| DEFINE_NATIVE_ENTRY(Isolate_sendOOB, 0, 2) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0)); | |
| GET_NON_NULL_NATIVE_ARGUMENT(Array, msg, arguments->NativeArgAt(1)); | |
| // Make sure to route this request to the isolate library OOB message handler. | |
| msg.SetAt(0, Smi::Handle(Smi::New(Message::kIsolateLibOOBMsg))); | |
| // Ensure message writer (and it's resources, e.g. forwarding tables) are | |
| // cleaned up before handling interrupts. | |
| { | |
| PortMap::PostMessage(WriteMessage(/*same_group=*/false, msg, port.Id(), | |
| Message::kOOBPriority)); | |
| } | |
| // Drain interrupts before running so any IMMEDIATE operations on the current | |
| // isolate happen synchronously. | |
| const Error& error = Error::Handle(thread->HandleInterrupts()); | |
| if (!error.IsNull()) { | |
| Exceptions::PropagateError(error); | |
| UNREACHABLE(); | |
| } | |
| return Object::null(); | |
| } | |
| static void EnsureThreadLocalsTableExistsAndBigEnough(Thread* thread, | |
| intptr_t id) { | |
| Array& locals = Array::Handle(thread->thread_locals()); | |
| if (id >= locals.Length()) { | |
| intptr_t new_length = id + 1; | |
| const Array& new_array = | |
| Array::Handle(Array::Grow(locals, new_length, Heap::kOld)); | |
| for (intptr_t i = locals.Length(); i < new_length; i++) { | |
| new_array.SetAt(i, Object::sentinel()); | |
| } | |
| thread->set_thread_locals(new_array); | |
| } | |
| } | |
| DEFINE_NATIVE_ENTRY(ThreadLocal_allocateId, 0, 0) { | |
| auto isolate_group = thread->isolate_group(); | |
| isolate_group->increment_thread_locals_count(); | |
| intptr_t new_index = isolate_group->thread_locals_count() - 1; | |
| EnsureThreadLocalsTableExistsAndBigEnough(thread, new_index); | |
| Array& locals = Array::Handle(thread->thread_locals()); | |
| locals.SetAt(new_index, Object::sentinel()); | |
| return Integer::New(new_index); | |
| } | |
| static void ValidateScopedThreadLocalId(Thread* thread, intptr_t id) { | |
| if (id < 0 || id >= thread->isolate_group()->thread_locals_count()) { | |
| const String& msg = String::Handle(String::New("Invalid local id.")); | |
| Exceptions::ThrowStateError(msg); | |
| UNREACHABLE(); | |
| } | |
| EnsureThreadLocalsTableExistsAndBigEnough(thread, id); | |
| } | |
| DEFINE_NATIVE_ENTRY(ThreadLocal_hasValue, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(Integer, id_obj, arguments->NativeArgAt(0)); | |
| intptr_t id = id_obj.Value(); | |
| ValidateScopedThreadLocalId(thread, id); | |
| Array& locals = Array::Handle(thread->thread_locals()); | |
| return locals.At(id) == Object::sentinel().ptr() ? Bool::False().ptr() | |
| : Bool::True().ptr(); | |
| } | |
| DEFINE_NATIVE_ENTRY(ThreadLocal_getValue, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(Integer, id_obj, arguments->NativeArgAt(0)); | |
| intptr_t id = id_obj.Value(); | |
| ValidateScopedThreadLocalId(thread, id); | |
| Array& locals = Array::Handle(thread->thread_locals()); | |
| return locals.At(id); | |
| } | |
| DEFINE_NATIVE_ENTRY(ThreadLocal_setValue, 0, 2) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(Integer, id_obj, arguments->NativeArgAt(0)); | |
| intptr_t id = id_obj.Value(); | |
| ValidateScopedThreadLocalId(thread, id); | |
| GET_NON_NULL_NATIVE_ARGUMENT(Instance, value, arguments->NativeArgAt(1)); | |
| Array& locals = Array::Handle(thread->thread_locals()); | |
| locals.SetAt(id, value); | |
| return Object::null(); | |
| } | |
| DEFINE_NATIVE_ENTRY(ThreadLocal_clearValue, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(Integer, id_obj, arguments->NativeArgAt(0)); | |
| intptr_t id = id_obj.Value(); | |
| ValidateScopedThreadLocalId(thread, id); | |
| Array& locals = Array::Handle(thread->thread_locals()); | |
| locals.SetAt(id, Object::sentinel()); | |
| return Object::null(); | |
| } | |
| static void ExternalTypedDataFinalizer(void* isolate_callback_data, | |
| void* peer) { | |
| free(peer); | |
| } | |
| static intptr_t GetTypedDataSizeOrThrow(const Instance& instance) { | |
| // From the Dart side we are guaranteed that the type of [instance] is a | |
| // subtype of TypedData. | |
| if (instance.IsTypedDataBase()) { | |
| return TypedDataBase::Cast(instance).LengthInBytes(); | |
| } | |
| // This can happen if [instance] is `null` or an instance of a 3rd party class | |
| // which implements [TypedData]. | |
| Exceptions::ThrowArgumentError(instance); | |
| } | |
| DEFINE_NATIVE_ENTRY(TransferableTypedData_factory, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(Instance, array_instance, | |
| arguments->NativeArgAt(0)); | |
| Array& array = Array::Handle(); | |
| intptr_t array_length; | |
| if (array_instance.IsGrowableObjectArray()) { | |
| const auto& growable_array = GrowableObjectArray::Cast(array_instance); | |
| array ^= growable_array.data(); | |
| array_length = growable_array.Length(); | |
| } else if (array_instance.IsArray()) { | |
| array ^= Array::Cast(array_instance).ptr(); | |
| array_length = array.Length(); | |
| } else { | |
| Exceptions::ThrowArgumentError(array_instance); | |
| UNREACHABLE(); | |
| } | |
| Instance& instance = Instance::Handle(); | |
| uint64_t total_bytes = 0; | |
| const uint64_t kMaxBytes = TypedData::MaxElements(kTypedDataUint8ArrayCid); | |
| for (intptr_t i = 0; i < array_length; i++) { | |
| instance ^= array.At(i); | |
| total_bytes += static_cast<uintptr_t>(GetTypedDataSizeOrThrow(instance)); | |
| if (total_bytes > kMaxBytes) { | |
| const Array& error_args = Array::Handle(Array::New(3)); | |
| error_args.SetAt(0, array); | |
| error_args.SetAt(1, String::Handle(String::New("data"))); | |
| error_args.SetAt( | |
| 2, String::Handle(String::NewFormatted( | |
| "Aggregated list exceeds max size %" Pu64 "", kMaxBytes))); | |
| Exceptions::ThrowByType(Exceptions::kArgumentValue, error_args); | |
| UNREACHABLE(); | |
| } | |
| } | |
| uint8_t* data = reinterpret_cast<uint8_t*>(::malloc(total_bytes)); | |
| if (data == nullptr) { | |
| const Instance& exception = Instance::Handle( | |
| thread->isolate_group()->object_store()->out_of_memory()); | |
| Exceptions::Throw(thread, exception); | |
| UNREACHABLE(); | |
| } | |
| intptr_t offset = 0; | |
| for (intptr_t i = 0; i < array_length; i++) { | |
| instance ^= array.At(i); | |
| { | |
| NoSafepointScope no_safepoint; | |
| const auto& typed_data = TypedDataBase::Cast(instance); | |
| const intptr_t length_in_bytes = typed_data.LengthInBytes(); | |
| void* source = typed_data.DataAddr(0); | |
| // The memory does not overlap. | |
| memcpy(data + offset, source, length_in_bytes); // NOLINT | |
| offset += length_in_bytes; | |
| } | |
| } | |
| ASSERT(static_cast<uintptr_t>(offset) == total_bytes); | |
| return TransferableTypedData::New(data, total_bytes); | |
| } | |
| DEFINE_NATIVE_ENTRY(TransferableTypedData_materialize, 0, 1) { | |
| GET_NON_NULL_NATIVE_ARGUMENT(TransferableTypedData, t, | |
| arguments->NativeArgAt(0)); | |
| void* peer; | |
| { | |
| NoSafepointScope no_safepoint; | |
| peer = thread->heap()->GetPeer(t.ptr()); | |
| // Assume that object's Peer is only used to track transferability state. | |
| ASSERT(peer != nullptr); | |
| } | |
| TransferableTypedDataPeer* tpeer = | |
| reinterpret_cast<TransferableTypedDataPeer*>(peer); | |
| const intptr_t length = tpeer->length(); | |
| uint8_t* data = tpeer->data(); | |
| if (data == nullptr) { | |
| const auto& error = String::Handle(String::New( | |
| "Attempt to materialize object that was transferred already.")); | |
| Exceptions::ThrowArgumentError(error); | |
| UNREACHABLE(); | |
| } | |
| tpeer->handle()->EnsureFreedExternal(IsolateGroup::Current()); | |
| tpeer->ClearData(); | |
| const ExternalTypedData& typed_data = ExternalTypedData::Handle( | |
| ExternalTypedData::New(kExternalTypedDataUint8ArrayCid, data, length, | |
| thread->heap()->SpaceForExternal(length))); | |
| FinalizablePersistentHandle* finalizable_ref = | |
| FinalizablePersistentHandle::New(thread->isolate_group(), typed_data, | |
| /* peer= */ data, | |
| &ExternalTypedDataFinalizer, length, | |
| /*auto_delete=*/true); | |
| ASSERT(finalizable_ref != nullptr); | |
| return typed_data.ptr(); | |
| } | |
| DEFINE_NATIVE_ENTRY(Timer_postTimerEvent, 0, 1) { | |
| #if !defined(PRODUCT) | |
| GET_NON_NULL_NATIVE_ARGUMENT(Integer, milliseconds_overdue, | |
| arguments->NativeArgAt(0)); | |
| // |milliseconds_overdue| can get truncated on 32-bit platforms, but in | |
| // practice, it should never get close to |INT_MAX|. | |
| Service::SendTimerEvent(isolate, | |
| static_cast<intptr_t>(milliseconds_overdue.Value())); | |
| #endif // !defined(PRODUCT) | |
| return Object::null(); | |
| } | |
| } // namespace dart |