#include "isolate_handle.h" #include "context_handle.h" #include "external_copy_handle.h" #include "isolate/holder.h" #include "script_handle.h" #include "module_handle.h" #include "session_handle.h" #include "external_copy/external_copy.h" #include "lib/lockable.h" #include "isolate/allocator.h" #include "isolate/functor_runners.h" #include "isolate/platform_delegate.h" #include "isolate/remote_handle.h" #include "isolate/three_phase_task.h" #include "isolate/v8_version.h" #include "module/evaluation.h" #include "v8-platform.h" #include "v8-profiler.h" #include #include #include #include using namespace v8; using v8::CpuProfile; using std::shared_ptr; using std::unique_ptr; namespace ivm { /** * IsolateHandle implementation */ IsolateHandle::IsolateHandleTransferable::IsolateHandleTransferable(shared_ptr isolate) : isolate(std::move(isolate)) {} auto IsolateHandle::IsolateHandleTransferable::TransferIn() -> Local { return ClassHandle::NewInstance(isolate); } IsolateHandle::IsolateHandle(shared_ptr isolate) : isolate(std::move(isolate)) {} static std::unique_ptr IsolateHandle_New_Wrapper(v8::MaybeLocal options) { return IsolateHandle::New(options); } auto IsolateHandle::Definition() -> Local { return Inherit(MakeClass( "Isolate", ConstructorFunction{}, "createSnapshot", FreeFunction{}, "compileScript", MemberFunction), &IsolateHandle::CompileScript<1>>{}, "compileScriptSync", MemberFunction), &IsolateHandle::CompileScript<0>>{}, "compileModule", MemberFunction), &IsolateHandle::CompileModule<1>>{}, "compileModuleSync", MemberFunction), &IsolateHandle::CompileModule<0>>{}, "cpuTime", MemberAccessor{}, "createContext", MemberFunction), &IsolateHandle::CreateContext<1>>{}, "createContextSync", MemberFunction), &IsolateHandle::CreateContext<0>>{}, "createInspectorSession", MemberFunction{}, "dispose", MemberFunction{}, "getHeapStatistics", MemberFunction), &IsolateHandle::GetHeapStatistics<1>>{}, "getHeapStatisticsSync", MemberFunction), &IsolateHandle::GetHeapStatistics<0>>{}, "isDisposed", MemberAccessor{}, "referenceCount", MemberAccessor{}, "wallTime", MemberAccessor{}, "startCpuProfiler", MemberFunction{}, "stopCpuProfiler", MemberFunction), &IsolateHandle::StopCpuProfiler<1>>{} )); } /** * Create a new Isolate. It all starts here! */ auto IsolateHandle::New(MaybeLocal maybe_options) -> unique_ptr { shared_ptr snapshot_blob; RemoteHandle error_handler; size_t snapshot_blob_length = 0; size_t memory_limit = 128; bool inspector = false; // Parse options Local options; if (maybe_options.ToLocal(&options)) { // Check memory limits memory_limit = ReadOption(options, "memoryLimit", 128); if (memory_limit < 8) { throw RuntimeGenericError("`memoryLimit` must be at least 8"); } // Set snapshot auto maybe_snapshot = ReadOption>(options, StringTable::Get().snapshot, {}); Local snapshot_handle; if (maybe_snapshot.ToLocal(&snapshot_handle)) { auto* copy_handle = ClassHandle::Unwrap(snapshot_handle.As()); if (copy_handle != nullptr) { ExternalCopyArrayBuffer* copy_ptr = dynamic_cast(copy_handle->GetValue().get()); if (copy_ptr != nullptr) { snapshot_blob = copy_ptr->Acquire(); snapshot_blob_length = snapshot_blob->ByteLength(); } } if (!snapshot_blob) { throw RuntimeTypeError("`snapshot` must be an ExternalCopy to ArrayBuffer"); } } // Check inspector flag inspector = ReadOption(options, StringTable::Get().inspector, false); auto maybe_handler = ReadOption>(options, StringTable::Get().onCatastrophicError, {}); Local error_handler_local; if (maybe_handler.ToLocal(&error_handler_local)) { error_handler = RemoteHandle{error_handler_local}; } } // Return isolate handle auto holder = IsolateEnvironment::New(memory_limit, std::move(snapshot_blob), snapshot_blob_length); auto env = holder->GetIsolate(); env->GetIsolate()->SetHostInitializeImportMetaObjectCallback(ModuleHandle::InitializeImportMeta); env->error_handler = error_handler; if (inspector) { env->EnableInspectorAgent(); } return std::make_unique(holder); } auto IsolateHandle::TransferOut() -> unique_ptr { return std::make_unique(isolate); } /** * Create a new v8::Context in this isolate and returns a ContextHandle */ struct CreateContextRunner : public ThreePhaseTask { bool enable_inspector = false; RemoteHandle context; RemoteHandle global; explicit CreateContextRunner(MaybeLocal& maybe_options) { enable_inspector = ReadOption(maybe_options, StringTable::Get().inspector, false); } void Phase2() final { // Use custom deleter on the shared_ptr which will notify the isolate when we're probably done with this context struct ContextDeleter { void operator() (v8::Persistent& context) const { auto& env = IsolateEnvironment::GetCurrent(); context.Reset(); env.GetIsolate()->ContextDisposedNotification(); } }; auto& env = IsolateEnvironment::GetCurrent(); // Sanity check before we build the context if (enable_inspector && env.GetInspectorAgent() == nullptr) { Context::Scope context_scope{env.DefaultContext()}; // TODO: This is needed to throw, but is stupid and sloppy throw RuntimeGenericError("Inspector is not enabled for this isolate"); } // Make a new context and setup shared pointers IsolateEnvironment::HeapCheck heap_check{env, true}; Local context_handle = env.NewContext(); if (enable_inspector) { env.GetInspectorAgent()->ContextCreated(context_handle, ""); } context = RemoteHandle{context_handle, ContextDeleter{}}; global = RemoteHandle{context_handle->Global()}; heap_check.Epilogue(); } auto Phase3() -> Local final { // Make a new Context{} JS class return ClassHandle::NewInstance(std::move(context), std::move(global)); } }; template auto IsolateHandle::CreateContext(MaybeLocal maybe_options) -> Local { return ThreePhaseTask::Run(*isolate, maybe_options); } /** * Compiles a script in this isolate and returns a ScriptHandle */ struct CompileScriptRunner : public CodeCompilerHolder, public ThreePhaseTask { RemoteHandle script; CompileScriptRunner(const Local& code_handle, const MaybeLocal& maybe_options) : CodeCompilerHolder{code_handle, maybe_options, false} {} void Phase2() final { // Compile in second isolate and return UnboundScript persistent auto& isolate = IsolateEnvironment::GetCurrent(); Context::Scope context_scope(isolate.DefaultContext()); IsolateEnvironment::HeapCheck heap_check{isolate, true}; auto source = GetSource(); ScriptCompiler::CompileOptions compile_options = ScriptCompiler::kNoCompileOptions; if (DidSupplyCachedData()) { compile_options = ScriptCompiler::kConsumeCodeCache; } script = RemoteHandle{RunWithAnnotatedErrors( [&isolate, &source, compile_options]() { return Unmaybe(ScriptCompiler::CompileUnboundScript(isolate, source.get(), compile_options)); } )}; // Check cached data flags if (DidSupplyCachedData()) { SetCachedDataRejected(source->GetCachedData()->rejected); } if (ShouldProduceCachedData()) { ScriptCompiler::CachedData* cached_data = ScriptCompiler::CreateCodeCache(script.Deref()); assert(cached_data != nullptr); SaveCachedData(cached_data); } ResetSource(); heap_check.Epilogue(); } auto Phase3() -> Local final { // Wrap UnboundScript in JS Script{} class Local value = ClassHandle::NewInstance(std::move(script)); WriteCompileResults(value); return value; } }; struct StopCpuProfileRunner: public ThreePhaseTask { const char* title_; std::vector profiles; explicit StopCpuProfileRunner(const char* title): title_(title) {} void Phase2() final { auto& isolate = IsolateEnvironment::GetCurrent(); profiles = isolate.GetCpuProfileManager()->StopProfiling(title_); } auto Phase3() -> Local final { auto* isolate = Isolate::GetCurrent(); auto ctx = isolate->GetCurrentContext(); const int arr_size = static_cast(profiles.size()); auto arr = Array::New(isolate, arr_size); for (int i = 0; i < arr_size; i++) { IVMCpuProfile profile = profiles.at(i); Unmaybe(arr->Set(ctx, i, profile.ToJSObject(isolate))); } return arr; } }; template auto IsolateHandle::CompileScript(Local code_handle, MaybeLocal maybe_options) -> Local { return ThreePhaseTask::Run(*this->isolate, code_handle, maybe_options); } /** * Compiles a module in this isolate and returns a ModuleHandle */ struct CompileModuleRunner : public CodeCompilerHolder, public ThreePhaseTask { shared_ptr module_info; RemoteHandle meta_callback; CompileModuleRunner(const Local& code_handle, const MaybeLocal& maybe_options) : CodeCompilerHolder{code_handle, maybe_options, true} { auto maybe_meta_callback = ReadOption>(maybe_options, StringTable::Get().meta, {}); Local meta_callback; if (maybe_meta_callback.ToLocal(&meta_callback)) { this->meta_callback = RemoteHandle{meta_callback}; } } void Phase2() final { auto& isolate = IsolateEnvironment::GetCurrent(); Context::Scope context_scope(isolate.DefaultContext()); IsolateEnvironment::HeapCheck heap_check{isolate, true}; auto source = GetSource(); auto module_handle = RunWithAnnotatedErrors( [&]() { return Unmaybe(ScriptCompiler::CompileModule(isolate, source.get())); } ); if (DidSupplyCachedData()) { SetCachedDataRejected(source->GetCachedData()->rejected); } if (ShouldProduceCachedData()) { ScriptCompiler::CachedData* cached_data = ScriptCompiler::CreateCodeCache(module_handle->GetUnboundModuleScript()); assert(cached_data != nullptr); SaveCachedData(cached_data); } ResetSource(); module_info = std::make_shared(module_handle); if (meta_callback) { if (meta_callback.GetSharedIsolateHolder() != IsolateEnvironment::GetCurrentHolder()) { throw RuntimeGenericError("`meta` callback must belong to entered isolate"); } module_info->meta_callback = meta_callback; } heap_check.Epilogue(); } auto Phase3() -> Local final { Local value = ClassHandle::NewInstance(std::move(module_info)); WriteCompileResults(value); return value; } }; template auto IsolateHandle::CompileModule(Local code_handle, MaybeLocal maybe_options) -> Local { return ThreePhaseTask::Run(*this->isolate, code_handle, maybe_options); } /** * Create a new channel for debugging on the inspector */ auto IsolateHandle::CreateInspectorSession() -> Local { if (IsolateEnvironment::GetCurrentHolder() == isolate) { throw RuntimeGenericError("An isolate is not debuggable from within itself"); } shared_ptr env = isolate->GetIsolate(); if (!env) { throw RuntimeGenericError("Isolate is diposed"); } if (env->GetInspectorAgent() == nullptr) { throw RuntimeGenericError("Inspector is not enabled for this isolate"); } return ClassHandle::NewInstance(*env); } /** * Dispose an isolate */ auto IsolateHandle::Dispose() -> Local { if (!isolate->Dispose()) { throw RuntimeGenericError("Isolate is already disposed"); } return Undefined(Isolate::GetCurrent()); } /** * Get heap statistics from v8 */ struct HeapStatRunner : public ThreePhaseTask { HeapStatistics heap; size_t externally_allocated_size = 0; size_t adjustment = 0; // Dummy constructor to workaround gcc bug explicit HeapStatRunner(int /*unused*/) {} void Phase2() final { IsolateEnvironment& isolate = IsolateEnvironment::GetCurrent(); isolate->GetHeapStatistics(&heap); adjustment = heap.heap_size_limit() - isolate.GetInitialHeapSizeLimit(); externally_allocated_size = isolate.GetExtraAllocatedMemory(); } auto Phase3() -> Local final { Isolate* isolate = Isolate::GetCurrent(); Local context = isolate->GetCurrentContext(); Local ret = Object::New(isolate); auto& strings = StringTable::Get(); Unmaybe(ret->Set(context, strings.total_heap_size, Number::New(isolate, heap.total_heap_size()))); Unmaybe(ret->Set(context, strings.total_heap_size_executable, Number::New(isolate, heap.total_heap_size_executable()))); Unmaybe(ret->Set(context, strings.total_physical_size, Number::New(isolate, heap.total_physical_size()))); Unmaybe(ret->Set(context, strings.total_available_size, Number::New(isolate, static_cast(heap.total_available_size()) - adjustment))); Unmaybe(ret->Set(context, strings.used_heap_size, Number::New(isolate, heap.used_heap_size()))); Unmaybe(ret->Set(context, strings.heap_size_limit, Number::New(isolate, static_cast(heap.heap_size_limit()) - adjustment))); Unmaybe(ret->Set(context, strings.malloced_memory, Number::New(isolate, heap.malloced_memory()))); Unmaybe(ret->Set(context, strings.peak_malloced_memory, Number::New(isolate, heap.peak_malloced_memory()))); Unmaybe(ret->Set(context, strings.does_zap_garbage, Number::New(isolate, heap.does_zap_garbage()))); Unmaybe(ret->Set(context, strings.externally_allocated_size, Number::New(isolate, externally_allocated_size))); return ret; } }; template auto IsolateHandle::GetHeapStatistics() -> Local { return ThreePhaseTask::Run(*isolate, 0); } /** * Timers */ auto IsolateHandle::GetCpuTime() -> Local { auto env = this->isolate->GetIsolate(); if (!env) { throw RuntimeGenericError("Isolate is disposed"); } uint64_t time = env->GetCpuTime().count(); return HandleCast>(time); } auto IsolateHandle::GetWallTime() -> Local { auto env = this->isolate->GetIsolate(); if (!env) { throw RuntimeGenericError("Isolate is disposed"); } uint64_t time = env->GetWallTime().count(); return HandleCast>(time); } auto IsolateHandle::StartCpuProfiler(v8::Local title) -> Local { auto env = this->isolate->GetIsolate(); if (!env) { throw RuntimeGenericError("Isolate is disposed"); } Isolate* iso = Isolate::GetCurrent(); v8::String::Utf8Value str(iso, title); const char* title_ = *str; env->GetCpuProfileManager()->StartProfiling(title_); return HandleCast>(true); } template auto IsolateHandle::StopCpuProfiler(v8::Local title) -> Local { auto env = this->isolate->GetIsolate(); if (!env) { throw RuntimeGenericError("Isolate is disposed"); } Isolate* iso = Isolate::GetCurrent(); v8::String::Utf8Value str(iso, title); const size_t len = strlen(*str); char* dest = (char*)std::malloc(len + 1); strcpy(dest, *str); return ThreePhaseTask::Run(*isolate, dest); } /** * Reference count */ auto IsolateHandle::GetReferenceCount() -> Local { auto env = this->isolate->GetIsolate(); if (!env) { throw RuntimeGenericError("Isolate is disposed"); } return Number::New(Isolate::GetCurrent(), env->GetRemotesCount()); } /** * Simple disposal checker */ auto IsolateHandle::IsDisposedGetter() -> Local { return Boolean::New(Isolate::GetCurrent(), !isolate->GetIsolate()); } /** * Create a snapshot from some code and return it as an external ArrayBuffer */ static auto SerializeInternalFieldsCallback(Local /*holder*/, int /*index*/, void* /*data*/) -> StartupData { return {nullptr, 0}; } auto IsolateHandle::CreateSnapshot(ArrayRange script_handles, MaybeLocal warmup_handle) -> Local { // Simple platform delegate and task queue using TaskDeque = lockable_t>>; class SnapshotPlatformDelegate : public node::IsolatePlatformDelegate, public TaskRunner, public std::enable_shared_from_this { public: explicit SnapshotPlatformDelegate(TaskDeque& tasks) : tasks{tasks} {} // v8 will continually post delayed tasks so we cut it off when work is done void DoneWithWork() { done = true; } // Methods for IsolatePlatformDelegate auto GetForegroundTaskRunner() -> std::shared_ptr final { return shared_from_this(); } auto IdleTasksEnabled() -> bool final { return false; } // Methods for v8::TaskRunner void PostTaskImpl(std::unique_ptr task, const v8::SourceLocation& /*location*/) final { tasks.write()->push_back(std::move(task)); } void PostDelayedTaskImpl(std::unique_ptr task, double /*delay_in_seconds*/, const v8::SourceLocation& location) final { if (!done) { #if V8_AT_LEAST(13, 3, 241) PostTask(std::move(task), location); #else PostTask(std::move(task)); #endif } } void PostNonNestableTaskImpl(std::unique_ptr task, const v8::SourceLocation& location) final { #if V8_AT_LEAST(13, 3, 241) PostTask(std::move(task), location); #else PostTask(std::move(task)); #endif } private: lockable_t>>& tasks; bool done = false; }; TaskDeque tasks; auto delegate = std::make_shared(tasks); // Copy embed scripts and warmup script from outer isolate std::vector> scripts; Isolate* isolate = Isolate::GetCurrent(); Local context = isolate->GetCurrentContext(); scripts.reserve(std::distance(script_handles.begin(), script_handles.end())); for (auto value : script_handles) { auto script_handle = HandleCast>(value); Local script = Unmaybe(script_handle.As()->Get(context, StringTable::Get().code)); if (!script->IsString()) { throw RuntimeTypeError("`code` property is required"); } scripts.emplace_back(ExternalCopyString{script.As()}, ScriptOriginHolder{script_handle}); } ExternalCopyString warmup_script; if (!warmup_handle.IsEmpty()) { warmup_script = ExternalCopyString{warmup_handle.ToLocalChecked().As()}; } // Create the snapshot StartupData snapshot {}; unique_ptr snapshot_data_ptr; shared_ptr error; { Isolate* isolate; isolate = Isolate::Allocate(); PlatformDelegate::RegisterIsolate(isolate, delegate.get()); SnapshotCreator snapshot_creator{isolate}; { Locker locker(isolate); Isolate::Scope isolate_scope(isolate); HandleScope handle_scope(isolate); Local context = Context::New(isolate); snapshot_creator.SetDefaultContext(context, {&SerializeInternalFieldsCallback, nullptr}); FunctorRunners::RunCatchExternal(context, [&]() { HandleScope handle_scope(isolate); Local context_dirty = Context::New(isolate); for (auto& script : scripts) { Local code = script.first.CopyInto().As(); ScriptOrigin script_origin = ScriptOrigin{script.second}; ScriptCompiler::Source source{code, script_origin}; Local unbound_script; { Context::Scope context_scope{context}; Local