3a41b697cd
Sometimes we need to capture stack trace on one debugger and use it later as a parent stack on another debugger (e.g. worker.postMessage). This CL includes following addition to our protocol and v8-inspector.h: - added Runtime.StackTraceId, this id represents stack trace captured on debugger with given id, - protocol client can fetch Runtime.StackTrace by Runtime.StacKTraceId using Debugger.getStackTrace method, - externalParent field is added to Debugger.paused event, it may contain external parent stack trace, - V8Inspector::storeCurrentStackTrace captures current stack trace and returns V8StackTraceId for embedder this id can be used as argument for V8Inspector::externalAsyncTaskStarted and V8Inspector::externalAsyncTaskFinished method. Any async stack trace captured between these calls will get passed external stack trace as external parent. These methods are designed to be called on different debuggers. If async task is scheduled and started on one debugger user should continue to use asyncTask* API, - Debugger.enable methods returns unique debuggerId. Bug: chromium:778796 Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.linux:linux_chromium_rel_ng Change-Id: I16aba0d04bfcea90f3e187e635a0588c92354539 Reviewed-on: https://chromium-review.googlesource.com/754183 Reviewed-by: Jakob Gruber <jgruber@chromium.org> Reviewed-by: Dmitry Gozman <dgozman@chromium.org> Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> Cr-Commit-Position: refs/heads/master@{#49582}
425 lines
16 KiB
C++
425 lines
16 KiB
C++
// 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);
|
|
}
|