// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include "ClearScriptV8Native.h" //----------------------------------------------------------------------------- // V8Platform //----------------------------------------------------------------------------- class V8Platform final: public v8::Platform { public: static V8Platform& GetInstance(); void EnsureInitialized(); V8GlobalFlags GetGlobalFlags() const; virtual int NumberOfWorkerThreads() override; virtual std::shared_ptr<:taskrunner> GetForegroundTaskRunner(v8::Isolate* pIsolate) override; virtual void CallOnWorkerThread(std::unique_ptr<:task> upTask) override; virtual void CallDelayedOnWorkerThread(std::unique_ptr<:task> upTask, double delayInSeconds) override; virtual std::unique_ptr<:jobhandle> PostJob(v8::TaskPriority priority, std::unique_ptr<:jobtask> upJobTask) override; virtual double MonotonicallyIncreasingTime() override; virtual double CurrentClockTimeMillis() override; virtual v8::TracingController* GetTracingController() override; private: V8Platform(); static V8Platform ms_Instance; OnceFlag m_InitializationFlag; V8GlobalFlags m_GlobalFlags; v8::TracingController m_TracingController; }; //----------------------------------------------------------------------------- V8Platform& V8Platform::GetInstance() { return ms_Instance; } //----------------------------------------------------------------------------- V8GlobalFlags V8Platform::GetGlobalFlags() const { return m_GlobalFlags; } //----------------------------------------------------------------------------- void V8Platform::EnsureInitialized() { m_InitializationFlag.CallOnce([this] { v8::V8::InitializePlatform(&ms_Instance); ASSERT_EVAL(v8::V8::Initialize()); m_GlobalFlags = V8_SPLIT_PROXY_MANAGED_INVOKE_NOTHROW(V8GlobalFlags, GetGlobalFlags); std::vector<:string> flagStrings; #ifdef CLEARSCRIPT_TOP_LEVEL_AWAIT_CONTROL if (!HasFlag(globalFlags, V8GlobalFlags::EnableTopLevelAwait)) { flagStrings.push_back("--no_harmony_top_level_await"); } #endif // CLEARSCRIPT_TOP_LEVEL_AWAIT_CONTROL if (HasFlag(m_GlobalFlags, V8GlobalFlags::DisableJITCompilation)) { flagStrings.push_back("--jitless"); } if (HasFlag(m_GlobalFlags, V8GlobalFlags::DisableBackgroundWork)) { flagStrings.push_back("--single_threaded"); } if (!flagStrings.empty()) { std::string flagsString(flagStrings[0]); for (size_t index = 1; index < flagStrings.size(); ++index) { flagsString += " "; flagsString += flagStrings[index]; } v8::V8::SetFlagsFromString(flagsString.c_str(), flagsString.length()); } }); } //----------------------------------------------------------------------------- int V8Platform::NumberOfWorkerThreads() { return static_cast(HighResolutionClock::GetHardwareConcurrency()); } //----------------------------------------------------------------------------- std::shared_ptr<:taskrunner> V8Platform::GetForegroundTaskRunner(v8::Isolate* pIsolate) { return V8IsolateImpl::GetInstanceFromIsolate(pIsolate)->GetForegroundTaskRunner(); } //----------------------------------------------------------------------------- void V8Platform::CallOnWorkerThread(std::unique_ptr<:task> upTask) { auto pIsolate = v8::Isolate::GetCurrent(); if (pIsolate == nullptr) { upTask->Run(); } else { V8IsolateImpl::GetInstanceFromIsolate(pIsolate)->RunTaskAsync(std::move(upTask)); } } //----------------------------------------------------------------------------- void V8Platform::CallDelayedOnWorkerThread(std::unique_ptr<:task> upTask, double delayInSeconds) { auto pIsolate = v8::Isolate::GetCurrent(); if (pIsolate != nullptr) { V8IsolateImpl::GetInstanceFromIsolate(pIsolate)->RunTaskDelayed(std::move(upTask), delayInSeconds); } } //----------------------------------------------------------------------------- std::unique_ptr<:jobhandle> V8Platform::PostJob(v8::TaskPriority priority, std::unique_ptr<:jobtask> upJobTask) { return v8::platform::NewDefaultJobHandle(this, priority, std::move(upJobTask), NumberOfWorkerThreads()); } //----------------------------------------------------------------------------- double V8Platform::MonotonicallyIncreasingTime() { return HighResolutionClock::GetRelativeSeconds(); } //----------------------------------------------------------------------------- double V8Platform::CurrentClockTimeMillis() { return std::chrono::duration_cast<:chrono::duration std::milli>>(std::chrono::system_clock::now().time_since_epoch()).count(); } //----------------------------------------------------------------------------- v8::TracingController* V8Platform::GetTracingController() { return &m_TracingController; } //----------------------------------------------------------------------------- V8Platform::V8Platform(): m_GlobalFlags(V8GlobalFlags::None) { } //----------------------------------------------------------------------------- V8Platform V8Platform::ms_Instance; //----------------------------------------------------------------------------- // V8ForegroundTaskRunner //----------------------------------------------------------------------------- class V8ForegroundTaskRunner final: public v8::TaskRunner { PROHIBIT_COPY(V8ForegroundTaskRunner) public: V8ForegroundTaskRunner(V8IsolateImpl* pIsolateImpl); virtual void PostTask(std::unique_ptr<:task> upTask) override; virtual void PostNonNestableTask(std::unique_ptr<:task> upTask) override; virtual void PostDelayedTask(std::unique_ptr<:task> upTask, double delayInSeconds) override; virtual void PostNonNestableDelayedTask(std::unique_ptr<:task> upTask, double delayInSeconds) override; virtual void PostIdleTask(std::unique_ptr<:idletask> upTask) override; virtual bool IdleTasksEnabled() override; virtual bool NonNestableTasksEnabled() const override; virtual bool NonNestableDelayedTasksEnabled() const override; private: V8IsolateImpl* m_pIsolateImpl; WeakRef m_wrIsolate; }; //----------------------------------------------------------------------------- V8ForegroundTaskRunner::V8ForegroundTaskRunner(V8IsolateImpl* pIsolateImpl): m_pIsolateImpl(pIsolateImpl), m_wrIsolate(pIsolateImpl->CreateWeakRef()) { } //----------------------------------------------------------------------------- void V8ForegroundTaskRunner::PostTask(std::unique_ptr<:task> upTask) { auto spIsolate = m_wrIsolate.GetTarget(); if (spIsolate.IsEmpty()) { upTask->Run(); } else { m_pIsolateImpl->RunTaskWithLockAsync(true /*allowNesting*/, std::move(upTask)); } } //----------------------------------------------------------------------------- void V8ForegroundTaskRunner::PostNonNestableTask(std::unique_ptr<:task> upTask) { auto spIsolate = m_wrIsolate.GetTarget(); if (!spIsolate.IsEmpty()) { m_pIsolateImpl->RunTaskWithLockAsync(false /*allowNesting*/, std::move(upTask)); } } //----------------------------------------------------------------------------- void V8ForegroundTaskRunner::PostDelayedTask(std::unique_ptr<:task> upTask, double delayInSeconds) { auto spIsolate = m_wrIsolate.GetTarget(); if (!spIsolate.IsEmpty()) { m_pIsolateImpl->RunTaskWithLockDelayed(true /*allowNesting*/, std::move(upTask), delayInSeconds); } } //----------------------------------------------------------------------------- void V8ForegroundTaskRunner::PostNonNestableDelayedTask(std::unique_ptr<:task> upTask, double delayInSeconds) { auto spIsolate = m_wrIsolate.GetTarget(); if (!spIsolate.IsEmpty()) { m_pIsolateImpl->RunTaskWithLockDelayed(false /*allowNesting*/, std::move(upTask), delayInSeconds); } } //----------------------------------------------------------------------------- void V8ForegroundTaskRunner::PostIdleTask(std::unique_ptr<:idletask> /*upTask*/) { // unexpected call to unsupported method std::terminate(); } //----------------------------------------------------------------------------- bool V8ForegroundTaskRunner::IdleTasksEnabled() { return false; } //----------------------------------------------------------------------------- bool V8ForegroundTaskRunner::NonNestableTasksEnabled() const { return true; } //----------------------------------------------------------------------------- bool V8ForegroundTaskRunner::NonNestableDelayedTasksEnabled() const { return true; } //----------------------------------------------------------------------------- // V8ArrayBufferAllocator //----------------------------------------------------------------------------- class V8ArrayBufferAllocator final: public v8::ArrayBuffer::Allocator { public: static V8ArrayBufferAllocator& GetInstance(); virtual void* Allocate(size_t size) override; virtual void* AllocateUninitialized(size_t size) override; virtual void Free(void* pvData, size_t size) override; private: V8ArrayBufferAllocator(); static V8ArrayBufferAllocator ms_Instance; }; //----------------------------------------------------------------------------- V8ArrayBufferAllocator& V8ArrayBufferAllocator::GetInstance() { return ms_Instance; } //----------------------------------------------------------------------------- void* V8ArrayBufferAllocator::Allocate(size_t size) { auto pIsolate = v8::Isolate::GetCurrent(); if (pIsolate) { return V8IsolateImpl::GetInstanceFromIsolate(pIsolate)->AllocateArrayBuffer(size); } return nullptr; } //----------------------------------------------------------------------------- void* V8ArrayBufferAllocator::AllocateUninitialized(size_t size) { auto pIsolate = v8::Isolate::GetCurrent(); if (pIsolate) { return V8IsolateImpl::GetInstanceFromIsolate(pIsolate)->AllocateUninitializedArrayBuffer(size); } return nullptr; } //----------------------------------------------------------------------------- void V8ArrayBufferAllocator::Free(void* pvData, size_t size) { auto pIsolate = v8::Isolate::GetCurrent(); if (pIsolate) { V8IsolateImpl::GetInstanceFromIsolate(pIsolate)->FreeArrayBuffer(pvData, size); } } //----------------------------------------------------------------------------- V8ArrayBufferAllocator::V8ArrayBufferAllocator() { } //----------------------------------------------------------------------------- V8ArrayBufferAllocator V8ArrayBufferAllocator::ms_Instance; //----------------------------------------------------------------------------- // V8OutputStream //----------------------------------------------------------------------------- class V8OutputStream final: public v8::OutputStream { PROHIBIT_COPY(V8OutputStream) public: explicit V8OutputStream(void* pvStream): m_pvStream(pvStream) { } virtual int GetChunkSize() override; virtual WriteResult WriteAsciiChunk(char* pData, int size) override; virtual void EndOfStream() override; private: void* m_pvStream; }; //----------------------------------------------------------------------------- int V8OutputStream::GetChunkSize() { return 64 * 1024; } //----------------------------------------------------------------------------- V8OutputStream::WriteResult V8OutputStream::WriteAsciiChunk(char* pData, int size) { try { V8_SPLIT_PROXY_MANAGED_INVOKE_VOID(WriteBytesToStream, m_pvStream, reinterpret_cast(pData), size); return kContinue; } catch (const HostException& exception) { V8_SPLIT_PROXY_MANAGED_INVOKE_VOID(ScheduleForwardingException, exception.GetException()); return kAbort; } } //----------------------------------------------------------------------------- void V8OutputStream::EndOfStream() { } //----------------------------------------------------------------------------- // V8IsolateImpl implementation //----------------------------------------------------------------------------- #define BEGIN_ISOLATE_NATIVE_SCOPE \ { \ DISABLE_WARNING(4456) /* declaration hides previous local declaration */ \ NativeScope t_IsolateNativeScope(*this); \ DEFAULT_WARNING(4456) #define END_ISOLATE_NATIVE_SCOPE \ IGNORE_UNUSED(t_IsolateNativeScope); \ } #define BEGIN_ISOLATE_SCOPE \ { \ DISABLE_WARNING(4456) /* declaration hides previous local declaration */ \ Scope t_IsolateScope(*this); \ DEFAULT_WARNING(4456) #define END_ISOLATE_SCOPE \ IGNORE_UNUSED(t_IsolateScope); \ } #define BEGIN_PROMISE_HOOK_SCOPE \ { \ DISABLE_WARNING(4456) /* declaration hides previous local declaration */ \ PromiseHookScope t_PromiseHookScope(*this); \ DEFAULT_WARNING(4456) #define END_PROMISE_HOOK_SCOPE \ IGNORE_UNUSED(t_PromiseHookScope); \ } //----------------------------------------------------------------------------- static std::atomic s_InstanceCount(0); static const int s_ContextGroupId = 1; static const size_t s_StackBreathingRoom = static_cast(16 * 1024); static size_t* const s_pMinStackLimit = reinterpret_cast(sizeof(size_t)); //----------------------------------------------------------------------------- V8IsolateImpl::V8IsolateImpl(const StdString& name, const v8::ResourceConstraints* pConstraints, const Options& options): m_Name(name), m_CallWithLockLevel(0), m_DebuggingEnabled(false), m_MaxArrayBufferAllocation(options.MaxArrayBufferAllocation), m_ArrayBufferAllocation(0), m_MaxHeapSize(0), m_HeapSizeSampleInterval(0.0), m_HeapWatchLevel(0), m_HeapExpansionMultiplier(options.HeapExpansionMultiplier), m_MaxStackUsage(0), m_EnableInterruptPropagation(false), m_CpuProfileSampleInterval(1000U), m_StackWatchLevel(0), m_pStackLimit(nullptr), m_pExecutionScope(nullptr), m_pDocumentInfo(nullptr), m_IsOutOfMemory(false), m_IsExecutionTerminating(false), m_Released(false) { V8Platform::GetInstance().EnsureInitialized(); v8::Isolate::CreateParams params; params.array_buffer_allocator = &V8ArrayBufferAllocator::GetInstance(); if (pConstraints != nullptr) { params.constraints.set_max_young_generation_size_in_bytes(pConstraints->max_young_generation_size_in_bytes()); params.constraints.set_max_old_generation_size_in_bytes(pConstraints->max_old_generation_size_in_bytes()); } m_upIsolate.reset(v8::Isolate::Allocate()); m_upIsolate->SetData(0, this); BEGIN_ADDREF_SCOPE v8::Isolate::Initialize(m_upIsolate.get(), params); m_upIsolate->AddNearHeapLimitCallback(HeapExpansionCallback, this); m_upIsolate->AddBeforeCallEnteredCallback(OnBeforeCallEntered); BEGIN_ISOLATE_SCOPE m_upIsolate->SetCaptureStackTraceForUncaughtExceptions(true, 64, v8::StackTrace::kDetailed); m_hHostObjectHolderKey = CreatePersistent(CreatePrivate()); if (options.EnableDebugging) { EnableDebugging(options.DebugPort, options.EnableRemoteDebugging); } m_upIsolate->SetHostInitializeImportMetaObjectCallback(ImportMetaInitializeCallback); if (options.EnableDynamicModuleImports) { m_upIsolate->SetHostImportModuleDynamicallyCallback(ModuleImportCallback); } END_ISOLATE_SCOPE END_ADDREF_SCOPE ++s_InstanceCount; } //----------------------------------------------------------------------------- V8IsolateImpl* V8IsolateImpl::GetInstanceFromIsolate(v8::Isolate* pIsolate) { _ASSERTE(pIsolate); return static_cast(pIsolate->GetData(0)); } //----------------------------------------------------------------------------- size_t V8IsolateImpl::GetInstanceCount() { return s_InstanceCount; } //----------------------------------------------------------------------------- void V8IsolateImpl::AddContext(V8ContextImpl* pContextImpl, const V8Context::Options& options) { _ASSERTE(IsCurrent() && IsLocked()); if (!options.EnableDebugging) { m_ContextEntries.emplace_back(pContextImpl); } else { m_ContextEntries.emplace_front(pContextImpl); EnableDebugging(options.DebugPort, options.EnableRemoteDebugging); } if (options.EnableDynamicModuleImports) { m_upIsolate->SetHostImportModuleDynamicallyCallback(ModuleImportCallback); } if (m_upInspector) { m_upInspector->contextCreated(v8_inspector::V8ContextInfo(pContextImpl->GetContext(), s_ContextGroupId, pContextImpl->GetName().GetStringView())); } } //----------------------------------------------------------------------------- void V8IsolateImpl::RemoveContext(V8ContextImpl* pContextImpl) { _ASSERTE(IsCurrent() && IsLocked()); if (m_upInspector) { m_upInspector->contextDestroyed(pContextImpl->GetContext()); } m_ContextEntries.remove_if([pContextImpl] (const ContextEntry& contextEntry) { return contextEntry.pContextImpl == pContextImpl; }); } //----------------------------------------------------------------------------- V8ContextImpl* V8IsolateImpl::FindContext(v8::Local<:context> hContext) { _ASSERTE(IsCurrent() && IsLocked()); for (const auto& entry : m_ContextEntries) { if (entry.pContextImpl->GetContext() == hContext) { return entry.pContextImpl; } } return nullptr; } //----------------------------------------------------------------------------- void V8IsolateImpl::EnableDebugging(int port, bool remote) { _ASSERTE(IsCurrent() && IsLocked()); if (!m_DebuggingEnabled) { const char* pVersion = v8::V8::GetVersion(); StdString version(v8_inspector::StringView(reinterpret_cast(pVersion), strlen(pVersion))); if (port < 1) { port = 9222; } auto wrIsolate = CreateWeakRef(); m_pvDebugAgent = HostObjectUtil::GetInstance().CreateDebugAgent(m_Name, version, port, remote, [this, wrIsolate] (IHostObjectUtil::DebugDirective directive, const StdString* pCommand) { auto spIsolate = wrIsolate.GetTarget(); if (!spIsolate.IsEmpty()) { if (directive == IHostObjectUtil::DebugDirective::ConnectClient) { ConnectDebugClient(); } else if ((directive == IHostObjectUtil::DebugDirective::SendCommand) && pCommand) { SendDebugCommand(*pCommand); } else if (directive == IHostObjectUtil::DebugDirective::DisconnectClient) { DisconnectDebugClient(); } } }); m_upInspector = v8_inspector::V8Inspector::create(m_upIsolate.get(), this); m_DebuggingEnabled = true; m_DebugPort = port; } } //----------------------------------------------------------------------------- void V8IsolateImpl::DisableDebugging() { _ASSERTE(IsCurrent() && IsLocked()); if (m_DebuggingEnabled) { m_upInspectorSession.reset(); m_upInspector.reset(); HostObjectUtil::GetInstance().DestroyDebugAgent(m_pvDebugAgent); m_DebuggingEnabled = false; } } //----------------------------------------------------------------------------- size_t V8IsolateImpl::GetMaxHeapSize() { return m_MaxHeapSize; } //----------------------------------------------------------------------------- void V8IsolateImpl::SetMaxHeapSize(size_t value) { m_MaxHeapSize = value; m_IsOutOfMemory = false; } //----------------------------------------------------------------------------- double V8IsolateImpl::GetHeapSizeSampleInterval() { return m_HeapSizeSampleInterval; } //----------------------------------------------------------------------------- void V8IsolateImpl::SetHeapSizeSampleInterval(double value) { m_HeapSizeSampleInterval = value; } //----------------------------------------------------------------------------- size_t V8IsolateImpl::GetMaxStackUsage() { return m_MaxStackUsage; } //----------------------------------------------------------------------------- void V8IsolateImpl::SetMaxStackUsage(size_t value) { m_MaxStackUsage = value; } //----------------------------------------------------------------------------- void V8IsolateImpl::AwaitDebuggerAndPause() { BEGIN_ISOLATE_SCOPE if (m_DebuggingEnabled) { if (!m_upInspectorSession) { auto exitReason = RunMessageLoop(RunMessageLoopReason::AwaitingDebugger); switch (exitReason) { case ExitMessageLoopReason::TerminatedExecution: throw V8Exception(V8Exception::Type::Interrupt, m_Name, StdString(SL("Script execution interrupted by host while awaiting debugger connection")), false); case ExitMessageLoopReason::CanceledAwaitDebugger: return; default: _ASSERTE(exitReason == ExitMessageLoopReason::ResumedExecution); } } _ASSERTE(m_upInspectorSession); if (m_upInspectorSession) { StdString breakReason(SL("Break on debugger connection")); m_upInspectorSession->schedulePauseOnNextStatement(breakReason.GetStringView(), breakReason.GetStringView()); } } END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- void V8IsolateImpl::CancelAwaitDebugger() { BEGIN_MUTEX_SCOPE(m_DataMutex) if (m_optRunMessageLoopReason == RunMessageLoopReason::AwaitingDebugger) { m_optExitMessageLoopReason = ExitMessageLoopReason::CanceledAwaitDebugger; m_CallWithLockQueueChanged.notify_one(); } END_MUTEX_SCOPE } //----------------------------------------------------------------------------- V8ScriptHolder* V8IsolateImpl::Compile(const V8DocumentInfo& documentInfo, StdString&& code) { BEGIN_ISOLATE_SCOPE SharedPtr spContextImpl((m_ContextEntries.size() > 0) ? m_ContextEntries.front().pContextImpl : new V8ContextImpl(this, m_Name)); return spContextImpl->Compile(documentInfo, std::move(code)); END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- V8ScriptHolder* V8IsolateImpl::Compile(const V8DocumentInfo& documentInfo, StdString&& code, V8CacheType cacheType, std::vector& cacheBytes) { BEGIN_ISOLATE_SCOPE SharedPtr spContextImpl((m_ContextEntries.size() > 0) ? m_ContextEntries.front().pContextImpl : new V8ContextImpl(this, m_Name)); return spContextImpl->Compile(documentInfo, std::move(code), cacheType, cacheBytes); END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- V8ScriptHolder* V8IsolateImpl::Compile(const V8DocumentInfo& documentInfo, StdString&& code, V8CacheType cacheType, const std::vector& cacheBytes, bool& cacheAccepted) { BEGIN_ISOLATE_SCOPE SharedPtr spContextImpl((m_ContextEntries.size() > 0) ? m_ContextEntries.front().pContextImpl : new V8ContextImpl(this, m_Name)); return spContextImpl->Compile(documentInfo, std::move(code), cacheType, cacheBytes, cacheAccepted); END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- bool V8IsolateImpl::GetEnableInterruptPropagation() { return m_EnableInterruptPropagation; } //----------------------------------------------------------------------------- void V8IsolateImpl::SetEnableInterruptPropagation(bool value) { m_EnableInterruptPropagation = value; } //----------------------------------------------------------------------------- void V8IsolateImpl::GetHeapStatistics(v8::HeapStatistics& heapStatistics) { BEGIN_ISOLATE_SCOPE m_upIsolate->GetHeapStatistics(&heapStatistics); END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- V8Isolate::Statistics V8IsolateImpl::GetStatistics() { BEGIN_ISOLATE_SCOPE BEGIN_MUTEX_SCOPE(m_DataMutex) return m_Statistics; END_MUTEX_SCOPE END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- void V8IsolateImpl::CollectGarbage(bool exhaustive) { BEGIN_ISOLATE_SCOPE if (exhaustive) { ClearScriptCache(); LowMemoryNotification(); } else { while (!IdleNotificationDeadline(V8Platform::GetInstance().MonotonicallyIncreasingTime() + 0.1)); } END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- bool V8IsolateImpl::BeginCpuProfile(const StdString& name, v8::CpuProfilingMode mode, bool recordSamples) { BEGIN_ISOLATE_SCOPE if (!m_upCpuProfiler) { m_upCpuProfiler.reset(v8::CpuProfiler::New(m_upIsolate.get())); } v8::Local<:string> hName; if (!CreateString(name).ToLocal(&hName)) { return false; } m_upCpuProfiler->StartProfiling(hName, mode, recordSamples); return true; END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- bool V8IsolateImpl::EndCpuProfile(const StdString& name, CpuProfileCallback* pCallback, void* pvArg) { BEGIN_ISOLATE_SCOPE if (!m_upCpuProfiler) { return false; } v8::Local<:string> hName; if (!CreateString(name).ToLocal(&hName)) { return false; } UniqueDeletePtr<:cpuprofile> upProfile(m_upCpuProfiler->StopProfiling(hName)); if (!upProfile) { return false; } if (pCallback) { pCallback(*upProfile, pvArg); } return true; END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- void V8IsolateImpl::CollectCpuProfileSample() { BEGIN_ISOLATE_SCOPE v8::CpuProfiler::CollectSample(m_upIsolate.get()); END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- uint32_t V8IsolateImpl::GetCpuProfileSampleInterval() { return m_CpuProfileSampleInterval; } //----------------------------------------------------------------------------- void V8IsolateImpl::SetCpuProfileSampleInterval(uint32_t value) { BEGIN_ISOLATE_SCOPE if (value != m_CpuProfileSampleInterval) { m_CpuProfileSampleInterval = std::min(std::max(value, 125U), static_cast(INT_MAX)); if (!m_upCpuProfiler) { m_upCpuProfiler.reset(v8::CpuProfiler::New(m_upIsolate.get())); } m_upCpuProfiler->SetSamplingInterval(static_cast(m_CpuProfileSampleInterval)); } END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- void V8IsolateImpl::WriteHeapSnapshot(void* pvStream) { BEGIN_ISOLATE_SCOPE auto pSnapshot = m_upIsolate->GetHeapProfiler()->TakeHeapSnapshot(); V8OutputStream stream(pvStream); pSnapshot->Serialize(&stream); const_cast<:heapsnapshot>(pSnapshot)->Delete(); END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- void V8IsolateImpl::runMessageLoopOnPause(int /*contextGroupId*/) { RunMessageLoop(RunMessageLoopReason::PausedInDebugger); } //----------------------------------------------------------------------------- void V8IsolateImpl::quitMessageLoopOnPause() { _ASSERTE(IsCurrent() && IsLocked()); BEGIN_MUTEX_SCOPE(m_DataMutex) m_optExitMessageLoopReason = ExitMessageLoopReason::ResumedExecution; END_MUTEX_SCOPE } //----------------------------------------------------------------------------- void V8IsolateImpl::runIfWaitingForDebugger(int /*contextGroupId*/) { quitMessageLoopOnPause(); } //----------------------------------------------------------------------------- v8::Local<:context> V8IsolateImpl::ensureDefaultContextInGroup(int contextGroupId) { _ASSERTE(IsCurrent() && IsLocked()); if (m_ContextEntries.size() > 0) { return m_ContextEntries.front().pContextImpl->GetContext(); } return v8_inspector::V8InspectorClient::ensureDefaultContextInGroup(contextGroupId); } //----------------------------------------------------------------------------- double V8IsolateImpl::currentTimeMS() { return HighResolutionClock::GetRelativeSeconds() * 1000; } //----------------------------------------------------------------------------- void V8IsolateImpl::sendResponse(int /*callId*/, std::unique_ptr<:stringbuffer> upMessage) { _ASSERTE(IsCurrent() && IsLocked()); if (m_pvDebugAgent) { HostObjectUtil::GetInstance().SendDebugMessage(m_pvDebugAgent, StdString(upMessage->string())); } } //----------------------------------------------------------------------------- void V8IsolateImpl::sendNotification(std::unique_ptr<:stringbuffer> upMessage) { sendResponse(0, std::move(upMessage)); } //----------------------------------------------------------------------------- void V8IsolateImpl::flushProtocolNotifications() { } //----------------------------------------------------------------------------- void* V8IsolateImpl::AddRefV8Object(void* pvObject) { BEGIN_ISOLATE_SCOPE return ::PtrFromHandle(CreatePersistent(::HandleFromPtr<:object>(pvObject))); END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- void V8IsolateImpl::ReleaseV8Object(void* pvObject) { CallWithLockNoWait(true /*allowNesting*/, [pvObject] (V8IsolateImpl* pIsolateImpl) { pIsolateImpl->Dispose(::HandleFromPtr<:object>(pvObject)); }); } //----------------------------------------------------------------------------- void* V8IsolateImpl::AddRefV8Script(void* pvScript) { BEGIN_ISOLATE_SCOPE return ::PtrFromHandle(CreatePersistent(::HandleFromPtr<:unboundscript>(pvScript))); END_ISOLATE_SCOPE } //----------------------------------------------------------------------------- void V8IsolateImpl::ReleaseV8Script(void* pvScript) { CallWithLockNoWait(true /*allowNesting*/, [pvScript] (V8IsolateImpl* pIsolateImpl) { pIsolateImpl->Dispose(::HandleFromPtr<:script>(pvScript)); }); } //----------------------------------------------------------------------------- void V8IsolateImpl::RunTaskAsync(std::unique_ptr<:task> upTask) { if (upTask) { if (m_Released) { upTask->Run(); } else { std::shared_ptr<:task> spTask(std::move(upTask)); std::weak_ptr<:task> wpTask(spTask); BEGIN_MUTEX_SCOPE(m_DataMutex) m_AsyncTasks.push_back(std::move(spTask)); m_Statistics.BumpPostedTaskCount(TaskKind::Worker); END_MUTEX_SCOPE auto wrIsolate = CreateWeakRef(); HostObjectUtil::GetInstance().QueueNativeCallback([this, wrIsolate, wpTask] () { auto spIsolate = wrIsolate.GetTarget(); if (!spIsolate.IsEmpty()) { auto spTask = wpTask.lock(); if (spTask) { spTask->Run(); BEGIN_MUTEX_SCOPE(m_DataMutex) auto it = std::remove(m_AsyncTasks.begin(), m_AsyncTasks.end(), spTask); m_AsyncTasks.erase(it, m_AsyncTasks.end()); m_Statistics.BumpInvokedTaskCount(TaskKind::Worker); END_MUTEX_SCOPE } } }); } } } //----------------------------------------------------------------------------- void V8IsolateImpl::RunTaskDelayed(std::unique_ptr<:task> upTask, double delayInSeconds) { if (upTask && !m_Released) { std::shared_ptr<:task> spTask(std::move(upTask)); auto wrIsolate = CreateWeakRef(); SharedPtr spTimer(new Timer(static_cast(delayInSeconds * 1000), -1, [this, wrIsolate, spTask] (Timer* pTimer) mutable { auto spIsolate = wrIsolate.GetTarget(); if (!spIsolate.IsEmpty()) { spTask->Run(); // Release the timer's strong task reference. Doing so avoids a deadlock when // spIsolate's implicit destruction below triggers immediate isolate teardown. spTask.reset(); // the timer has fired; discard it BEGIN_MUTEX_SCOPE(m_DataMutex) auto it = std::remove(m_TaskTimers.begin(), m_TaskTimers.end(), SharedPtr(pTimer)); m_TaskTimers.erase(it, m_TaskTimers.end()); m_Statistics.BumpInvokedTaskCount(TaskKind::DelayedWorker); END_MUTEX_SCOPE } else { // Release the timer's strong task reference. Doing so avoids a deadlock if the // isolate is awaiting task completion on the managed finalization thread. spTask.reset(); } })); // hold on to the timer to ensure callback execution BEGIN_MUTEX_SCOPE(m_DataMutex) m_TaskTimers.push_back(spTimer); m_Statistics.BumpPostedTaskCount(TaskKind::DelayedWorker); END_MUTEX_SCOPE // Release the local task reference explicitly. Doing so avoids a deadlock if the callback is // executed synchronously. That shouldn't happen given the current timer implementation. spTask.reset(); // now it's safe to start the timer spTimer->Start(); } } //----------------------------------------------------------------------------- void V8IsolateImpl::RunTaskWithLockAsync(bool allowNesting, std::unique_ptr<:task> upTask) { if (upTask) { if (m_Released) { if (allowNesting) { upTask->Run(); } } else { std::shared_ptr<:task> spTask(std::move(upTask)); CallWithLockAsync(allowNesting, [allowNesting, spTask] (V8IsolateImpl* pIsolateImpl) { spTask->Run(); BEGIN_MUTEX_SCOPE(pIsolateImpl->m_DataMutex) pIsolateImpl->m_Statistics.BumpInvokedTaskCount(allowNesting ? TaskKind::Foreground : TaskKind::NonNestableForeground); END_MUTEX_SCOPE }); BEGIN_MUTEX_SCOPE(m_DataMutex) m_Statistics.BumpPostedTaskCount(allowNesting ? TaskKind::Foreground : TaskKind::NonNestableForeground); END_MUTEX_SCOPE } } } //----------------------------------------------------------------------------- void V8IsolateImpl::RunTaskWithLockDelayed(bool allowNesting, std::unique_ptr<:task> upTask, double delayInSeconds) { if (upTask && !m_Released) { std::shared_ptr<:task> spTask(std::move(upTask)); auto wrIsolate = CreateWeakRef(); SharedPtr spTimer(new Timer(static_cast(delayInSeconds * 1000), -1, [this, wrIsolate, allowNesting, spTask] (Timer* pTimer) mutable { auto spIsolate = wrIsolate.GetTarget(); if (!spIsolate.IsEmpty()) { CallWithLockNoWait(allowNesting, [allowNesting, spTask] (V8IsolateImpl* pIsolateImpl) { spTask->Run(); BEGIN_MUTEX_SCOPE(pIsolateImpl->m_DataMutex) pIsolateImpl->m_Statistics.BumpInvokedTaskCount(allowNesting ? TaskKind::DelayedForeground : TaskKind::NonNestableDelayedForeground); END_MUTEX_SCOPE }); // Release the timer's strong task reference. Doing so avoids a deadlock when // spIsolate's implicit destruction below triggers immediate isolate teardown. spTask.reset(); // the timer has fired; discard it BEGIN_MUTEX_SCOPE(m_DataMutex) auto it = std::remove(m_TaskTimers.begin(), m_TaskTimers.end(), pTimer); m_TaskTimers.erase(it, m_TaskTimers.end()); END_MUTEX_SCOPE } else { // Release the timer's strong task reference. Doing so avoids a deadlock if the // isolate is awaiting task completion on the managed finalization thread. spTask.reset(); } })); // hold on to the timer to ensure callback execution BEGIN_MUTEX_SCOPE(m_DataMutex) m_TaskTimers.push_back(spTimer); m_Statistics.BumpPostedTaskCount(allowNesting ? TaskKind::DelayedForeground : TaskKind::NonNestableDelayedForeground); END_MUTEX_SCOPE // Release the local task reference explicitly. Doing so avoids a deadlock if the callback is // executed synchronously. That shouldn't happen given the current timer implementation. spTask.reset(); // now it's safe to start the timer spTimer->Start(); } } //----------------------------------------------------------------------------- std::shared_ptr<:taskrunner> V8IsolateImpl::GetForegroundTaskRunner() { BEGIN_MUTEX_SCOPE(m_DataMutex) if (!m_spForegroundTaskRunner) { m_spForegroundTaskRunner = std::make_shared(this); } return m_spForegroundTaskRunner; END_MUTEX_SCOPE } //----------------------------------------------------------------------------- void* V8IsolateImpl::AllocateArrayBuffer(size_t size) { auto newArrayBufferAllocation = m_ArrayBufferAllocation + size; if ((newArrayBufferAllocation >= m_ArrayBufferAllocation) && (newArrayBufferAllocation <= m_MaxArrayBufferAllocation)) { auto pvData = ::calloc(1, size); if (pvData) { m_ArrayBufferAllocation = newArrayBufferAllocation; return pvData; } } return nullptr; } //----------------------------------------------------------------------------- void* V8IsolateImpl::AllocateUninitializedArrayBuffer(size_t size) { auto newArrayBufferAllocation = m_ArrayBufferAllocation + size; if ((newArrayBufferAllocation >= m_ArrayBufferAllocation) && (newArrayBufferAllocation <= m_MaxArrayBufferAllocation)) { auto pvData = ::malloc(size); if (pvData) { m_ArrayBufferAllocation = newArrayBufferAllocation; return pvData; } } return nullptr; } //----------------------------------------------------------------------------- void V8IsolateImpl::FreeArrayBuffer(void* pvData, size_t size) { if (pvData) { ::free(pvData); if (m_ArrayBufferAllocation >= size) { m_ArrayBufferAllocation -= size; } } } //----------------------------------------------------------------------------- void V8IsolateImpl::CallWithLockNoWait(bool allowNesting, CallWithLockCallback&& callback) { if (callback) { if (m_Mutex.TryLock()) { // the callback may release this instance; hold it for destruction outside isolate scope SharedPtr spThis(this); MutexLock lock(m_Mutex, false); if (allowNesting || (m_CallWithLockLevel < 1)) { BEGIN_ISOLATE_NATIVE_SCOPE BEGIN_PULSE_VALUE_SCOPE(&m_CallWithLockLevel, m_CallWithLockLevel + 1) callback(this); return; END_PULSE_VALUE_SCOPE END_ISOLATE_NATIVE_SCOPE } } CallWithLockAsync(allowNesting, std::move(callback)); } } //----------------------------------------------------------------------------- void NORETURN V8IsolateImpl::ThrowOutOfMemoryException() { m_IsOutOfMemory = true; throw V8Exception(V8Exception::Type::Fatal, m_Name, StdString(SL("The V8 runtime has exceeded its memory limit")), (m_pExecutionScope != nullptr) ? m_pExecutionScope->ExecutionStarted() : false); } //----------------------------------------------------------------------------- void V8IsolateImpl::ImportMetaInitializeCallback(v8::Local<:context> hContext, v8::Local<:module> hModule, v8::Local<:object> hMeta) { GetInstanceFromIsolate(hContext->GetIsolate())->InitializeImportMeta(hContext, hModule, hMeta); } //----------------------------------------------------------------------------- v8::MaybeLocal<:promise> V8IsolateImpl::ModuleImportCallback(v8::Local<:context> hContext, v8::Local<:data> hHostDefinedOptions, v8::Local<:value> hResourceName, v8::Local<:string> hSpecifier, v8::Local<:fixedarray> hImportAssertions) { return GetInstanceFromIsolate(hContext->GetIsolate())->ImportModule(hContext, hHostDefinedOptions, hResourceName, hSpecifier, hImportAssertions); } //----------------------------------------------------------------------------- v8::MaybeLocal<:module> V8IsolateImpl::ModuleResolveCallback(v8::Local<:context> hContext, v8::Local<:string> hSpecifier, v8::Local<:fixedarray> /*importAssertions*/, v8::Local<:module> hReferrer) { return GetInstanceFromIsolate(hContext->GetIsolate())->ResolveModule(hContext, hSpecifier, hReferrer); } //----------------------------------------------------------------------------- void V8IsolateImpl::InitializeImportMeta(v8::Local<:context> hContext, v8::Local<:module> hModule, v8::Local<:object> hMeta) { _ASSERTE(IsCurrent() && IsLocked()); auto pContextImpl = FindContext(hContext); if (pContextImpl) { return pContextImpl->InitializeImportMeta(hContext, hModule, hMeta); } } //----------------------------------------------------------------------------- v8::MaybeLocal<:promise> V8IsolateImpl::ImportModule(v8::Local<:context> hContext, v8::Local<:data> hHostDefinedOptions, v8::Local<:value> hResourceName, v8::Local<:string> hSpecifier, v8::Local<:fixedarray> hImportAssertions) { _ASSERTE(IsCurrent() && IsLocked()); auto pContextImpl = FindContext(hContext); if (pContextImpl) { return pContextImpl->ImportModule(hHostDefinedOptions, hResourceName, hSpecifier, hImportAssertions); } return v8::MaybeLocal<:promise>(); } //----------------------------------------------------------------------------- v8::MaybeLocal<:module> V8IsolateImpl::ResolveModule(v8::Local<:context> hContext, v8::Local<:string> hSpecifier, v8::Local<:module> hReferrer) { _ASSERTE(IsCurrent() && IsLocked()); auto pContextImpl = FindContext(hContext); if (pContextImpl) { return pContextImpl->ResolveModule(hSpecifier, hReferrer); } return v8::MaybeLocal<:module>(); } //----------------------------------------------------------------------------- bool V8IsolateImpl::TryGetCachedScriptInfo(uint64_t uniqueId, V8DocumentInfo& documentInfo) { _ASSERTE(IsCurrent() && IsLocked()); for (auto it = m_ScriptCache.begin(); it != m_ScriptCache.end(); it++) { if (it->DocumentInfo.GetUniqueId() == uniqueId) { m_ScriptCache.splice(m_ScriptCache.begin(), m_ScriptCache, it); documentInfo = it->DocumentInfo; return true; } } return false; } //----------------------------------------------------------------------------- v8::Local<:unboundscript> V8IsolateImpl::GetCachedScript(uint64_t uniqueId, size_t codeDigest) { _ASSERTE(IsCurrent() && IsLocked()); for (auto it = m_ScriptCache.begin(); it != m_ScriptCache.end(); it++) { if ((it->DocumentInfo.GetUniqueId() == uniqueId) && (it->CodeDigest == codeDigest)) { m_ScriptCache.splice(m_ScriptCache.begin(), m_ScriptCache, it); return it->hScript; } } return v8::Local<:unboundscript>(); } //----------------------------------------------------------------------------- void V8IsolateImpl::CacheScript(const V8DocumentInfo& documentInfo, size_t codeDigest, v8::Local<:unboundscript> hScript) { _ASSERTE(IsCurrent() && IsLocked()); auto maxScriptCacheSize = HostObjectUtil::GetInstance().GetMaxScriptCacheSize(); while (m_ScriptCache.size() >= maxScriptCacheSize) { Dispose(m_ScriptCache.back().hScript); m_ScriptCache.pop_back(); } _ASSERTE(std::none_of(m_ScriptCache.begin(), m_ScriptCache.end(), [&documentInfo, codeDigest] (const ScriptCacheEntry& entry) { return (entry.DocumentInfo.GetUniqueId() == documentInfo.GetUniqueId()) && (entry.CodeDigest == codeDigest); })); ScriptCacheEntry entry { documentInfo, codeDigest, CreatePersistent(hScript) }; m_ScriptCache.push_front(std::move(entry)); m_Statistics.ScriptCacheSize = m_ScriptCache.size(); } //----------------------------------------------------------------------------- void V8IsolateImpl::ClearScriptCache() { _ASSERTE(IsCurrent() && IsLocked()); while (m_ScriptCache.size() > 0) { Dispose(m_ScriptCache.front().hScript); m_ScriptCache.pop_front(); } m_Statistics.ScriptCacheSize = m_ScriptCache.size(); } //----------------------------------------------------------------------------- V8IsolateImpl::~V8IsolateImpl() { --s_InstanceCount; m_Released = true; // Entering the isolate scope triggers call-with-lock queue processing. It should always be // done here, if for no other reason than that it may prevent deadlocks in V8 isolate disposal. BEGIN_ISOLATE_SCOPE DisableDebugging(); ClearScriptCache(); END_ISOLATE_SCOPE { std::vector<:shared_ptr>> asyncTasks; std::vector> taskTimers; BEGIN_MUTEX_SCOPE(m_DataMutex) std::swap(asyncTasks, m_AsyncTasks); std::swap(taskTimers, m_TaskTimers); END_MUTEX_SCOPE for (const auto& spTask : asyncTasks) { spTask->Run(); } } Dispose(m_hHostObjectHolderKey); m_upIsolate->SetHostImportModuleDynamicallyCallback(static_cast<:hostimportmoduledynamicallycallback>(nullptr)); m_upIsolate->SetHostInitializeImportMetaObjectCallback(nullptr); m_upIsolate->RemoveBeforeCallEnteredCallback(OnBeforeCallEntered); m_upIsolate->RemoveNearHeapLimitCallback(HeapExpansionCallback, 0); } //----------------------------------------------------------------------------- V8IsolateImpl::ExitMessageLoopReason V8IsolateImpl::RunMessageLoop(RunMessageLoopReason reason) { _ASSERTE(IsCurrent() && IsLocked()); std::unique_lock<:mutex> lock(m_DataMutex.GetImpl()); if (!m_optRunMessageLoopReason) { m_optExitMessageLoopReason.reset(); BEGIN_PULSE_VALUE_SCOPE(&m_optRunMessageLoopReason, reason) ProcessCallWithLockQueue(lock); while (true) { m_CallWithLockQueueChanged.wait(lock); ProcessCallWithLockQueue(lock); if (m_optExitMessageLoopReason) { break; } } END_PULSE_VALUE_SCOPE ProcessCallWithLockQueue(lock); return *m_optExitMessageLoopReason; } return ExitMessageLoopReason::NestedInvocation; } //----------------------------------------------------------------------------- void V8IsolateImpl::CallWithLockAsync(bool allowNesting, CallWithLockCallback&& callback) { if (callback) { BEGIN_MUTEX_SCOPE(m_DataMutex) m_CallWithLockQueue.push(std::make_pair(allowNesting, std::move(callback))); if (m_optRunMessageLoopReason) { m_CallWithLockQueueChanged.notify_one(); return; } if (m_CallWithLockQueue.size() > 1) { return; } END_MUTEX_SCOPE // trigger asynchronous queue processing auto wrIsolate = CreateWeakRef(); HostObjectUtil::GetInstance().QueueNativeCallback([this, wrIsolate] () { auto spIsolate = wrIsolate.GetTarget(); if (!spIsolate.IsEmpty()) { if (m_Mutex.TryLock()) { MutexLock lock(m_Mutex, false); BEGIN_ISOLATE_NATIVE_SCOPE // do nothing; scope entry triggers automatic queue processing END_ISOLATE_NATIVE_SCOPE } else { // The isolate is active on another thread, and the queue will be processed automatically // at scope exit, but an interrupt ensures relatively timely processing. RequestInterrupt(ProcessCallWithLockQueue, this); } } }); } } //----------------------------------------------------------------------------- void V8IsolateImpl::ProcessCallWithLockQueue(v8::Isolate* /*pIsolate*/, void* pvIsolateImpl) { static_cast(pvIsolateImpl)->ProcessCallWithLockQueue(); } //----------------------------------------------------------------------------- void V8IsolateImpl::ProcessCallWithLockQueue() { BEGIN_PROMISE_HOOK_SCOPE std::unique_lock<:mutex> lock(m_DataMutex.GetImpl()); ProcessCallWithLockQueue(lock); END_PROMISE_HOOK_SCOPE } //----------------------------------------------------------------------------- void V8IsolateImpl::ProcessCallWithLockQueue(std::unique_lock<:mutex>& lock) { _ASSERTE(lock.mutex() == &m_DataMutex.GetImpl()); _ASSERTE(lock.owns_lock()); CallWithLockQueue callWithLockQueue(PopCallWithLockQueue(lock)); while (callWithLockQueue.size() > 0) { lock.unlock(); ProcessCallWithLockQueue(callWithLockQueue); lock.lock(); callWithLockQueue = PopCallWithLockQueue(lock); } } //----------------------------------------------------------------------------- void V8IsolateImpl::ProcessCallWithLockQueue(CallWithLockQueue& callWithLockQueue) { _ASSERTE(IsCurrent() && IsLocked()); BEGIN_PULSE_VALUE_SCOPE(&m_CallWithLockLevel, m_CallWithLockLevel + 1) while (callWithLockQueue.size() > 0) { try { callWithLockQueue.front().second(this); } catch (...) { } callWithLockQueue.pop(); } END_PULSE_VALUE_SCOPE } //----------------------------------------------------------------------------- V8IsolateImpl::CallWithLockQueue V8IsolateImpl::PopCallWithLockQueue(const std::unique_lock<:mutex>& lock) { _ASSERTE(IsCurrent() && IsLocked()); _ASSERTE(lock.mutex() == &m_DataMutex.GetImpl()); _ASSERTE(lock.owns_lock()); IGNORE_UNUSED(lock); if (m_CallWithLockLevel < 1) { return std::move(m_CallWithLockQueue); } CallWithLockQueue nestableCallWithLockQueue; CallWithLockQueue nonNestableCallWithLockQueue; while (m_CallWithLockQueue.size() > 0) { auto& callWithLockEntry = m_CallWithLockQueue.front(); auto& callWithLockQueue = callWithLockEntry.first ? nestableCallWithLockQueue : nonNestableCallWithLockQueue; callWithLockQueue.push(std::move(callWithLockEntry)); m_CallWithLockQueue.pop(); } m_CallWithLockQueue = std::move(nonNestableCallWithLockQueue); return nestableCallWithLockQueue; } //----------------------------------------------------------------------------- void V8IsolateImpl::ConnectDebugClient() { CallWithLockNoWait(true /*allowNesting*/, [] (V8IsolateImpl* pIsolateImpl) { if (pIsolateImpl->m_upInspector && !pIsolateImpl->m_upInspectorSession) { pIsolateImpl->m_upInspectorSession = pIsolateImpl->m_upInspector->connect(s_ContextGroupId, pIsolateImpl, v8_inspector::StringView()); } }); } //----------------------------------------------------------------------------- void V8IsolateImpl::SendDebugCommand(const StdString& command) { CallWithLockNoWait(true /*allowNesting*/, [command] (V8IsolateImpl* pIsolateImpl) { if (pIsolateImpl->m_upInspectorSession) { pIsolateImpl->m_upInspectorSession->dispatchProtocolMessage(command.GetStringView()); } }); } //----------------------------------------------------------------------------- void V8IsolateImpl::DisconnectDebugClient() { CallWithLockNoWait(true /*allowNesting*/, [] (V8IsolateImpl* pIsolateImpl) { pIsolateImpl->m_upInspectorSession.reset(); }); } //----------------------------------------------------------------------------- V8IsolateImpl::ExecutionScope* V8IsolateImpl::EnterExecutionScope(ExecutionScope* pExecutionScope, size_t* pStackMarker) { _ASSERTE(IsCurrent() && IsLocked()); // is heap size monitoring in progress? if (m_HeapWatchLevel == 0) { // no; there should be no heap watch timer _ASSERTE(m_spHeapWatchTimer.IsEmpty()); // is a heap size limit specified? size_t maxHeapSize = m_MaxHeapSize; if (maxHeapSize > 0) { // yes; perform initial check and set up heap watch timer CheckHeapSize(maxHeapSize); // enter outermost heap size monitoring scope m_HeapWatchLevel = 1; } } else { // heap size monitoring in progress; enter nested scope m_HeapWatchLevel++; } // is stack usage monitoring in progress? if (m_StackWatchLevel == 0) { // no; there should be no stack address limit _ASSERTE(m_pStackLimit == nullptr); // is a stack usage limit specified? size_t maxStackUsage = m_MaxStackUsage; if (maxStackUsage > 0) { // yes; ensure minimum breathing room maxStackUsage = std::max(maxStackUsage, s_StackBreathingRoom); // calculate stack address limit size_t* pStackLimit = pStackMarker - (maxStackUsage / sizeof(size_t)); if ((pStackLimit < s_pMinStackLimit) || (pStackLimit > pStackMarker)) { // underflow; use minimum non-null stack address pStackLimit = s_pMinStackLimit; } else { // check stack address limit sanity _ASSERTE(static_cast(pStackMarker - pStackLimit) >= (s_StackBreathingRoom / sizeof(size_t))); } // set and record stack address limit m_upIsolate->SetStackLimit(reinterpret_cast(pStackLimit)); m_pStackLimit = pStackLimit; // enter outermost stack usage monitoring scope m_StackWatchLevel = 1; } } else { // stack usage monitoring in progress if ((m_pStackLimit != nullptr) && (pStackMarker < m_pStackLimit)) { // stack usage limit exceeded (host-side detection) throw V8Exception(V8Exception::Type::General, m_Name, StdString(SL("The V8 runtime has exceeded its stack usage limit")), false /*executionStarted*/); } // enter nested stack usage monitoring scope m_StackWatchLevel++; } // clear termination flag m_IsExecutionTerminating = false; // mark execution scope auto pPreviousExecutionScope = m_pExecutionScope; m_pExecutionScope = pExecutionScope; return pPreviousExecutionScope; } //----------------------------------------------------------------------------- void V8IsolateImpl::ExitExecutionScope(ExecutionScope* pPreviousExecutionScope) { _ASSERTE(IsCurrent() && IsLocked()); // reset execution scope m_pExecutionScope = pPreviousExecutionScope; // is interrupt propagation enabled? if (!m_EnableInterruptPropagation) { // no; cancel termination to allow remaining script frames to execute CancelTerminateExecution(); } // is stack usage monitoring in progress? if (m_StackWatchLevel > 0) { // yes; exit stack usage monitoring scope if (--m_StackWatchLevel == 0) { // exited outermost scope; remove stack address limit if (m_pStackLimit != nullptr) { // V8 has no API for removing a stack address limit m_upIsolate->SetStackLimit(reinterpret_cast(s_pMinStackLimit)); m_pStackLimit = nullptr; } } } // is heap size monitoring in progress? if (m_HeapWatchLevel > 0) { // yes; exit heap size monitoring scope if (--m_HeapWatchLevel == 0) { // exited outermost scope; destroy heap watch timer m_spHeapWatchTimer.Empty(); } } } //----------------------------------------------------------------------------- void V8IsolateImpl::SetUpHeapWatchTimer(size_t maxHeapSize) { _ASSERTE(IsCurrent() && IsLocked()); // create heap watch timer auto wrIsolate = CreateWeakRef(); m_spHeapWatchTimer = new Timer(static_cast(std::max(GetHeapSizeSampleInterval(), 125.0)), -1, [this, wrIsolate, maxHeapSize] (Timer* pTimer) { // heap watch callback; is the isolate still alive? auto spIsolate = wrIsolate.GetTarget(); if (!spIsolate.IsEmpty()) { // yes; request callback on execution thread auto wrTimer = pTimer->CreateWeakRef(); CallWithLockAsync(true /*allowNesting*/, [wrTimer, maxHeapSize] (V8IsolateImpl* pIsolateImpl) { // execution thread callback; is the timer still alive? auto spTimer = wrTimer.GetTarget(); if (!spTimer.IsEmpty()) { // yes; check heap size pIsolateImpl->CheckHeapSize(maxHeapSize); } }); } }); // start heap watch timer m_spHeapWatchTimer->Start(); } //----------------------------------------------------------------------------- void V8IsolateImpl::CheckHeapSize(size_t maxHeapSize) { _ASSERTE(IsCurrent() && IsLocked()); // is the total heap size over the limit? v8::HeapStatistics heapStatistics; GetHeapStatistics(heapStatistics); if (heapStatistics.total_heap_size() > maxHeapSize) { // yes; collect garbage LowMemoryNotification(); // is the total heap size still over the limit? GetHeapStatistics(heapStatistics); if (heapStatistics.total_heap_size() > maxHeapSize) { // yes; the isolate is out of memory; request script termination m_IsOutOfMemory = true; TerminateExecution(); return; } } // the isolate is not out of memory; restart heap watch timer SetUpHeapWatchTimer(maxHeapSize); } //----------------------------------------------------------------------------- void V8IsolateImpl::OnBeforeCallEntered(v8::Isolate* pIsolate) { GetInstanceFromIsolate(pIsolate)->OnBeforeCallEntered(); } //----------------------------------------------------------------------------- void V8IsolateImpl::OnBeforeCallEntered() { _ASSERTE(IsCurrent() && IsLocked()); if (m_pExecutionScope) { m_pExecutionScope->OnExecutionStarted(); } } //----------------------------------------------------------------------------- void V8IsolateImpl::PromiseHook(v8::PromiseHookType type, v8::Local<:promise> hPromise, v8::Local<:value> /*hParent*/) { if ((type == v8::PromiseHookType::kResolve) && !hPromise.IsEmpty()) { auto hContext = hPromise->GetCreationContext().FromMaybe(v8::Local<:context>()); if (!hContext.IsEmpty()) { GetInstanceFromIsolate(hContext->GetIsolate())->FlushContextAsync(hContext); } } } //----------------------------------------------------------------------------- void V8IsolateImpl::FlushContextAsync(v8::Local<:context> hContext) { _ASSERTE(IsCurrent() && IsLocked()); for (auto& contextEntry : m_ContextEntries) { if (contextEntry.pContextImpl->GetContext() == hContext) { FlushContextAsync(contextEntry); break; } } } //----------------------------------------------------------------------------- void V8IsolateImpl::FlushContextAsync(ContextEntry& contextEntry) { auto expected = false; if (contextEntry.FlushPending.compare_exchange_strong(expected, true)) { auto wrContext = contextEntry.pContextImpl->CreateWeakRef(); CallWithLockAsync(true /*allowNesting*/, [wrContext] (V8IsolateImpl* pIsolateImpl) { auto spContext = wrContext.GetTarget(); if (!spContext.IsEmpty()) { pIsolateImpl->FlushContext(spContext.DerefAs()); } }); } } //----------------------------------------------------------------------------- void V8IsolateImpl::FlushContext(V8ContextImpl& contextImpl) { _ASSERTE(IsCurrent() && IsLocked()); for (auto& contextEntry : m_ContextEntries) { if (contextEntry.pContextImpl == &contextImpl) { contextEntry.FlushPending = false; break; } } contextImpl.Flush(); } //----------------------------------------------------------------------------- size_t V8IsolateImpl::HeapExpansionCallback(void* pvData, size_t currentLimit, size_t /*initialLimit*/) { if (pvData) { auto multiplier = static_cast(pvData)->m_HeapExpansionMultiplier; if (multiplier > 1.0) { return static_cast(multiplier * currentLimit); } } return currentLimit; }