// 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 #include #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, 1) { ASSERT( TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0)).IsNull()); // 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(id >> 32); int32_t lo = static_cast(id); int32_t hash = (hi ^ lo) & kSmiMax; return Smi::New(hash); } DEFINE_NATIVE_ENTRY(RawReceivePort_factory, 0, 2) { ASSERT( TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0)).IsNull()); GET_NON_NULL_NATIVE_ARGUMENT(String, debug_name, arguments->NativeArgAt(1)); 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(id >> 32); int32_t lo = static_cast(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(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( 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(object)); // Don't write back: doesn't agree with dart:core's identityHash. break; case kDoubleCid: hash = bit_cast(Double::Value(static_cast(object))); // Don't write back: doesn't agree with dart:core's identityHash. break; case kOneByteStringCid: case kTwoByteStringCid: hash = String::Hash(static_cast(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( 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( reinterpret_cast(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_; }; // 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 = [ // , // , // , // ] 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(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 serialized_args_; std::unique_ptr 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()) { // Check whether main is reexported from the root library. const Object& obj = Object::Handle(zone, lib.LookupReExport(main)); if (obj.IsFunction()) { func ^= obj.ptr(); } } 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 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)); } 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); HandleScope hs(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())); 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(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 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(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 [, ] 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 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(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 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(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()))); 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, 2) { ASSERT( TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0)).IsNull()); GET_NON_NULL_NATIVE_ARGUMENT(Instance, array_instance, arguments->NativeArgAt(1)); 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(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(::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(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(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(milliseconds_overdue.Value())); #endif // !defined(PRODUCT) return Object::null(); } } // namespace dart