| // Copyright 2017 the V8 project authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "test/inspector/isolate-data.h" | |
| #include "src/inspector/test-interface.h" | |
| #include "test/inspector/task-runner.h" | |
| namespace { | |
| const int kIsolateDataIndex = 2; | |
| const int kContextGroupIdIndex = 3; | |
| v8::internal::Vector<uint16_t> ToVector(v8::Local<v8::String> str) { | |
| v8::internal::Vector<uint16_t> buffer = | |
| v8::internal::Vector<uint16_t>::New(str->Length()); | |
| str->Write(buffer.start(), 0, str->Length()); | |
| return buffer; | |
| } | |
| v8::Local<v8::String> ToString(v8::Isolate* isolate, | |
| const v8_inspector::StringView& string) { | |
| if (string.is8Bit()) | |
| return v8::String::NewFromOneByte(isolate, string.characters8(), | |
| v8::NewStringType::kNormal, | |
| static_cast<int>(string.length())) | |
| .ToLocalChecked(); | |
| else | |
| return v8::String::NewFromTwoByte(isolate, string.characters16(), | |
| v8::NewStringType::kNormal, | |
| static_cast<int>(string.length())) | |
| .ToLocalChecked(); | |
| } | |
| void Print(v8::Isolate* isolate, const v8_inspector::StringView& string) { | |
| v8::Local<v8::String> v8_string = ToString(isolate, string); | |
| v8::String::Utf8Value utf8_string(isolate, v8_string); | |
| fwrite(*utf8_string, sizeof(**utf8_string), utf8_string.length(), stdout); | |
| } | |
| class Inspectable : public v8_inspector::V8InspectorSession::Inspectable { | |
| public: | |
| Inspectable(v8::Isolate* isolate, v8::Local<v8::Value> object) | |
| : object_(isolate, object) {} | |
| ~Inspectable() override {} | |
| v8::Local<v8::Value> get(v8::Local<v8::Context> context) override { | |
| return object_.Get(context->GetIsolate()); | |
| } | |
| private: | |
| v8::Global<v8::Value> object_; | |
| }; | |
| } // namespace | |
| IsolateData::IsolateData(TaskRunner* task_runner, | |
| IsolateData::SetupGlobalTasks setup_global_tasks, | |
| v8::StartupData* startup_data, bool with_inspector) | |
| : task_runner_(task_runner), | |
| setup_global_tasks_(std::move(setup_global_tasks)) { | |
| v8::Isolate::CreateParams params; | |
| params.array_buffer_allocator = | |
| v8::ArrayBuffer::Allocator::NewDefaultAllocator(); | |
| params.snapshot_blob = startup_data; | |
| isolate_.reset(v8::Isolate::New(params)); | |
| isolate_->SetMicrotasksPolicy(v8::MicrotasksPolicy::kScoped); | |
| if (with_inspector) { | |
| isolate_->AddMessageListener(&IsolateData::MessageHandler); | |
| isolate_->SetPromiseRejectCallback(&IsolateData::PromiseRejectHandler); | |
| inspector_ = v8_inspector::V8Inspector::create(isolate_.get(), this); | |
| } | |
| v8::HandleScope handle_scope(isolate_.get()); | |
| not_inspectable_private_.Reset( | |
| isolate_.get(), | |
| v8::Private::ForApi(isolate_.get(), v8::String::NewFromUtf8( | |
| isolate_.get(), "notInspectable", | |
| v8::NewStringType::kNormal) | |
| .ToLocalChecked())); | |
| } | |
| IsolateData* IsolateData::FromContext(v8::Local<v8::Context> context) { | |
| return static_cast<IsolateData*>( | |
| context->GetAlignedPointerFromEmbedderData(kIsolateDataIndex)); | |
| } | |
| int IsolateData::CreateContextGroup() { | |
| v8::HandleScope handle_scope(isolate_.get()); | |
| v8::Local<v8::ObjectTemplate> global_template = | |
| v8::ObjectTemplate::New(isolate_.get()); | |
| for (auto it = setup_global_tasks_.begin(); it != setup_global_tasks_.end(); | |
| ++it) { | |
| (*it)->Run(isolate_.get(), global_template); | |
| } | |
| v8::Local<v8::Context> context = | |
| v8::Context::New(isolate_.get(), nullptr, global_template); | |
| context->SetAlignedPointerInEmbedderData(kIsolateDataIndex, this); | |
| int context_group_id = ++last_context_group_id_; | |
| // Should be 2-byte aligned. | |
| context->SetAlignedPointerInEmbedderData( | |
| kContextGroupIdIndex, reinterpret_cast<void*>(context_group_id * 2)); | |
| contexts_[context_group_id].Reset(isolate_.get(), context); | |
| if (inspector_) FireContextCreated(context, context_group_id); | |
| return context_group_id; | |
| } | |
| v8::Local<v8::Context> IsolateData::GetContext(int context_group_id) { | |
| return contexts_[context_group_id].Get(isolate_.get()); | |
| } | |
| int IsolateData::GetContextGroupId(v8::Local<v8::Context> context) { | |
| return static_cast<int>( | |
| reinterpret_cast<intptr_t>( | |
| context->GetAlignedPointerFromEmbedderData(kContextGroupIdIndex)) / | |
| 2); | |
| } | |
| void IsolateData::RegisterModule(v8::Local<v8::Context> context, | |
| v8::internal::Vector<uint16_t> name, | |
| v8::ScriptCompiler::Source* source) { | |
| v8::Local<v8::Module> module; | |
| if (!v8::ScriptCompiler::CompileModule(isolate(), source).ToLocal(&module)) | |
| return; | |
| if (!module->InstantiateModule(context, &IsolateData::ModuleResolveCallback) | |
| .FromMaybe(false)) { | |
| return; | |
| } | |
| v8::Local<v8::Value> result; | |
| if (!module->Evaluate(context).ToLocal(&result)) return; | |
| modules_[name] = v8::Global<v8::Module>(isolate_.get(), module); | |
| } | |
| // static | |
| v8::MaybeLocal<v8::Module> IsolateData::ModuleResolveCallback( | |
| v8::Local<v8::Context> context, v8::Local<v8::String> specifier, | |
| v8::Local<v8::Module> referrer) { | |
| IsolateData* data = IsolateData::FromContext(context); | |
| std::string str = *v8::String::Utf8Value(data->isolate(), specifier); | |
| return data->modules_[ToVector(specifier)].Get(data->isolate()); | |
| } | |
| int IsolateData::ConnectSession(int context_group_id, | |
| const v8_inspector::StringView& state, | |
| v8_inspector::V8Inspector::Channel* channel) { | |
| int session_id = ++last_session_id_; | |
| sessions_[session_id] = inspector_->connect(context_group_id, channel, state); | |
| context_group_by_session_[sessions_[session_id].get()] = context_group_id; | |
| return session_id; | |
| } | |
| std::unique_ptr<v8_inspector::StringBuffer> IsolateData::DisconnectSession( | |
| int session_id) { | |
| auto it = sessions_.find(session_id); | |
| CHECK(it != sessions_.end()); | |
| context_group_by_session_.erase(it->second.get()); | |
| std::unique_ptr<v8_inspector::StringBuffer> result = it->second->stateJSON(); | |
| sessions_.erase(it); | |
| return result; | |
| } | |
| void IsolateData::SendMessage(int session_id, | |
| const v8_inspector::StringView& message) { | |
| auto it = sessions_.find(session_id); | |
| if (it != sessions_.end()) it->second->dispatchProtocolMessage(message); | |
| } | |
| void IsolateData::BreakProgram(int context_group_id, | |
| const v8_inspector::StringView& reason, | |
| const v8_inspector::StringView& details) { | |
| for (int session_id : GetSessionIds(context_group_id)) { | |
| auto it = sessions_.find(session_id); | |
| if (it != sessions_.end()) it->second->breakProgram(reason, details); | |
| } | |
| } | |
| void IsolateData::SchedulePauseOnNextStatement( | |
| int context_group_id, const v8_inspector::StringView& reason, | |
| const v8_inspector::StringView& details) { | |
| for (int session_id : GetSessionIds(context_group_id)) { | |
| auto it = sessions_.find(session_id); | |
| if (it != sessions_.end()) | |
| it->second->schedulePauseOnNextStatement(reason, details); | |
| } | |
| } | |
| void IsolateData::CancelPauseOnNextStatement(int context_group_id) { | |
| for (int session_id : GetSessionIds(context_group_id)) { | |
| auto it = sessions_.find(session_id); | |
| if (it != sessions_.end()) it->second->cancelPauseOnNextStatement(); | |
| } | |
| } | |
| void IsolateData::AsyncTaskScheduled(const v8_inspector::StringView& name, | |
| void* task, bool recurring) { | |
| inspector_->asyncTaskScheduled(name, task, recurring); | |
| } | |
| void IsolateData::AsyncTaskStarted(void* task) { | |
| inspector_->asyncTaskStarted(task); | |
| } | |
| void IsolateData::AsyncTaskFinished(void* task) { | |
| inspector_->asyncTaskFinished(task); | |
| } | |
| v8_inspector::V8StackTraceId IsolateData::StoreCurrentStackTrace( | |
| const v8_inspector::StringView& description) { | |
| return inspector_->storeCurrentStackTrace(description); | |
| } | |
| void IsolateData::ExternalAsyncTaskStarted( | |
| const v8_inspector::V8StackTraceId& parent) { | |
| inspector_->externalAsyncTaskStarted(parent); | |
| } | |
| void IsolateData::ExternalAsyncTaskFinished( | |
| const v8_inspector::V8StackTraceId& parent) { | |
| inspector_->externalAsyncTaskFinished(parent); | |
| } | |
| void IsolateData::AddInspectedObject(int session_id, | |
| v8::Local<v8::Value> object) { | |
| auto it = sessions_.find(session_id); | |
| if (it == sessions_.end()) return; | |
| std::unique_ptr<Inspectable> inspectable( | |
| new Inspectable(isolate_.get(), object)); | |
| it->second->addInspectedObject(std::move(inspectable)); | |
| } | |
| void IsolateData::SetMaxAsyncTaskStacksForTest(int limit) { | |
| v8_inspector::SetMaxAsyncTaskStacksForTest(inspector_.get(), limit); | |
| } | |
| void IsolateData::DumpAsyncTaskStacksStateForTest() { | |
| v8_inspector::DumpAsyncTaskStacksStateForTest(inspector_.get()); | |
| } | |
| // static | |
| int IsolateData::HandleMessage(v8::Local<v8::Message> message, | |
| v8::Local<v8::Value> exception) { | |
| v8::Isolate* isolate = v8::Isolate::GetCurrent(); | |
| v8::Local<v8::Context> context = isolate->GetEnteredContext(); | |
| if (context.IsEmpty()) return 0; | |
| v8_inspector::V8Inspector* inspector = | |
| IsolateData::FromContext(context)->inspector_.get(); | |
| v8::Local<v8::StackTrace> stack = message->GetStackTrace(); | |
| int script_id = | |
| static_cast<int>(message->GetScriptOrigin().ScriptID()->Value()); | |
| if (!stack.IsEmpty() && stack->GetFrameCount() > 0) { | |
| int top_script_id = stack->GetFrame(0)->GetScriptId(); | |
| if (top_script_id == script_id) script_id = 0; | |
| } | |
| int line_number = message->GetLineNumber(context).FromMaybe(0); | |
| int column_number = 0; | |
| if (message->GetStartColumn(context).IsJust()) | |
| column_number = message->GetStartColumn(context).FromJust() + 1; | |
| v8_inspector::StringView detailed_message; | |
| v8::internal::Vector<uint16_t> message_text_string = ToVector(message->Get()); | |
| v8_inspector::StringView message_text(message_text_string.start(), | |
| message_text_string.length()); | |
| v8::internal::Vector<uint16_t> url_string; | |
| if (message->GetScriptOrigin().ResourceName()->IsString()) { | |
| url_string = | |
| ToVector(message->GetScriptOrigin().ResourceName().As<v8::String>()); | |
| } | |
| v8_inspector::StringView url(url_string.start(), url_string.length()); | |
| return inspector->exceptionThrown( | |
| context, message_text, exception, detailed_message, url, line_number, | |
| column_number, inspector->createStackTrace(stack), script_id); | |
| } | |
| // static | |
| void IsolateData::MessageHandler(v8::Local<v8::Message> message, | |
| v8::Local<v8::Value> exception) { | |
| HandleMessage(message, exception); | |
| } | |
| // static | |
| void IsolateData::PromiseRejectHandler(v8::PromiseRejectMessage data) { | |
| v8::Isolate* isolate = v8::Isolate::GetCurrent(); | |
| v8::Local<v8::Context> context = isolate->GetEnteredContext(); | |
| if (context.IsEmpty()) return; | |
| v8::Local<v8::Promise> promise = data.GetPromise(); | |
| v8::Local<v8::Private> id_private = v8::Private::ForApi( | |
| isolate, | |
| v8::String::NewFromUtf8(isolate, "id", v8::NewStringType::kNormal) | |
| .ToLocalChecked()); | |
| if (data.GetEvent() == v8::kPromiseHandlerAddedAfterReject) { | |
| v8::Local<v8::Value> id; | |
| if (!promise->GetPrivate(context, id_private).ToLocal(&id)) return; | |
| if (!id->IsInt32()) return; | |
| v8_inspector::V8Inspector* inspector = | |
| IsolateData::FromContext(context)->inspector_.get(); | |
| const char* reason_str = "Handler added to rejected promise"; | |
| inspector->exceptionRevoked( | |
| context, id.As<v8::Int32>()->Value(), | |
| v8_inspector::StringView(reinterpret_cast<const uint8_t*>(reason_str), | |
| strlen(reason_str))); | |
| return; | |
| } | |
| v8::Local<v8::Value> exception = data.GetValue(); | |
| int exception_id = HandleMessage( | |
| v8::Exception::CreateMessage(isolate, exception), exception); | |
| if (exception_id) { | |
| promise | |
| ->SetPrivate(isolate->GetCurrentContext(), id_private, | |
| v8::Int32::New(isolate, exception_id)) | |
| .ToChecked(); | |
| } | |
| } | |
| void IsolateData::FireContextCreated(v8::Local<v8::Context> context, | |
| int context_group_id) { | |
| v8_inspector::V8ContextInfo info(context, context_group_id, | |
| v8_inspector::StringView()); | |
| info.hasMemoryOnConsole = true; | |
| inspector_->contextCreated(info); | |
| } | |
| void IsolateData::FireContextDestroyed(v8::Local<v8::Context> context) { | |
| inspector_->contextDestroyed(context); | |
| } | |
| void IsolateData::FreeContext(v8::Local<v8::Context> context) { | |
| int context_group_id = GetContextGroupId(context); | |
| auto it = contexts_.find(context_group_id); | |
| if (it == contexts_.end()) return; | |
| contexts_.erase(it); | |
| } | |
| std::vector<int> IsolateData::GetSessionIds(int context_group_id) { | |
| std::vector<int> result; | |
| for (auto& it : sessions_) { | |
| if (context_group_by_session_[it.second.get()] == context_group_id) | |
| result.push_back(it.first); | |
| } | |
| return result; | |
| } | |
| bool IsolateData::formatAccessorsAsProperties(v8::Local<v8::Value> object) { | |
| v8::Isolate* isolate = v8::Isolate::GetCurrent(); | |
| v8::Local<v8::Context> context = isolate->GetCurrentContext(); | |
| v8::Local<v8::Private> shouldFormatAccessorsPrivate = v8::Private::ForApi( | |
| isolate, v8::String::NewFromUtf8(isolate, "allowAccessorFormatting", | |
| v8::NewStringType::kNormal) | |
| .ToLocalChecked()); | |
| CHECK(object->IsObject()); | |
| return object.As<v8::Object>() | |
| ->HasPrivate(context, shouldFormatAccessorsPrivate) | |
| .FromMaybe(false); | |
| } | |
| bool IsolateData::isInspectableHeapObject(v8::Local<v8::Object> object) { | |
| v8::Isolate* isolate = v8::Isolate::GetCurrent(); | |
| v8::Local<v8::Context> context = isolate->GetCurrentContext(); | |
| v8::MicrotasksScope microtasks_scope( | |
| isolate, v8::MicrotasksScope::kDoNotRunMicrotasks); | |
| return !object->HasPrivate(context, not_inspectable_private_.Get(isolate)) | |
| .FromMaybe(false); | |
| } | |
| v8::Local<v8::Context> IsolateData::ensureDefaultContextInGroup( | |
| int context_group_id) { | |
| return GetContext(context_group_id); | |
| } | |
| void IsolateData::SetCurrentTimeMS(double time) { | |
| current_time_ = time; | |
| current_time_set_ = true; | |
| } | |
| double IsolateData::currentTimeMS() { | |
| if (current_time_set_) return current_time_; | |
| return v8::internal::V8::GetCurrentPlatform()->CurrentClockTimeMillis(); | |
| } | |
| void IsolateData::SetMemoryInfo(v8::Local<v8::Value> memory_info) { | |
| memory_info_.Reset(isolate_.get(), memory_info); | |
| } | |
| void IsolateData::SetLogConsoleApiMessageCalls(bool log) { | |
| log_console_api_message_calls_ = log; | |
| } | |
| void IsolateData::SetLogMaxAsyncCallStackDepthChanged(bool log) { | |
| log_max_async_call_stack_depth_changed_ = log; | |
| } | |
| v8::MaybeLocal<v8::Value> IsolateData::memoryInfo(v8::Isolate* isolate, | |
| v8::Local<v8::Context>) { | |
| if (memory_info_.IsEmpty()) return v8::MaybeLocal<v8::Value>(); | |
| return memory_info_.Get(isolate); | |
| } | |
| void IsolateData::runMessageLoopOnPause(int) { | |
| task_runner_->RunMessageLoop(true); | |
| } | |
| void IsolateData::quitMessageLoopOnPause() { task_runner_->QuitMessageLoop(); } | |
| void IsolateData::consoleAPIMessage(int contextGroupId, | |
| v8::Isolate::MessageErrorLevel level, | |
| const v8_inspector::StringView& message, | |
| const v8_inspector::StringView& url, | |
| unsigned lineNumber, unsigned columnNumber, | |
| v8_inspector::V8StackTrace* stack) { | |
| if (!log_console_api_message_calls_) return; | |
| Print(isolate_.get(), message); | |
| fprintf(stdout, " ("); | |
| Print(isolate_.get(), url); | |
| fprintf(stdout, ":%d:%d)", lineNumber, columnNumber); | |
| Print(isolate_.get(), stack->toString()->string()); | |
| fprintf(stdout, "\n"); | |
| } | |
| void IsolateData::maxAsyncCallStackDepthChanged(int depth) { | |
| if (!log_max_async_call_stack_depth_changed_) return; | |
| fprintf(stdout, "maxAsyncCallStackDepthChanged: %d\n", depth); | |
| } |