// 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(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_ = 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_, this); } } IsolateData* IsolateData::FromContext(v8::Local<v8::Context> context) { return static_cast<IsolateData*>( context->GetAlignedPointerFromEmbedderData(kIsolateDataIndex)); } int IsolateData::CreateContextGroup() { v8::HandleScope handle_scope(isolate_); v8::Local<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(isolate_); for (auto it = setup_global_tasks_.begin(); it != setup_global_tasks_.end(); ++it) { (*it)->Run(isolate_, global_template); } v8::Local<v8::Context> context = v8::Context::New(isolate_, 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_, 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_); } 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_, module); } // static v8::MaybeLocal<v8::Module> IsolateData::ModuleResolveCallback( v8::Local<v8::Context> context, v8::Local<v8::String> specifier, v8::Local<v8::Module> referrer) { std::string str = *v8::String::Utf8Value(specifier); IsolateData* data = IsolateData::FromContext(context); 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); } 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_, 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); } 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); } 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::base::OS::TimeCurrentMillis(); } void IsolateData::SetMemoryInfo(v8::Local<v8::Value> memory_info) { memory_info_.Reset(isolate_, memory_info); } void IsolateData::SetLogConsoleApiMessageCalls(bool log) { log_console_api_message_calls_ = 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_, message); fprintf(stdout, " ("); Print(isolate_, url); fprintf(stdout, ":%d:%d)", lineNumber, columnNumber); Print(isolate_, stack->toString()->string()); fprintf(stdout, "\n"); }