Commit 3a41b697 authored by Alexey Kozyatinskiy's avatar Alexey Kozyatinskiy Committed by Commit Bot

[inspector] introduced stackTraceId and externalAsyncTask API

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/754183Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarDmitry Gozman <dgozman@chromium.org>
Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#49582}
parent 66287a32
...@@ -215,6 +215,20 @@ class V8_EXPORT V8InspectorClient { ...@@ -215,6 +215,20 @@ class V8_EXPORT V8InspectorClient {
virtual void maxAsyncCallStackDepthChanged(int depth) {} virtual void maxAsyncCallStackDepthChanged(int depth) {}
}; };
// These stack trace ids are intended to be passed between debuggers and be
// resolved later. This allows to track cross-debugger calls and step between
// them if a single client connects to multiple debuggers.
struct V8_EXPORT V8StackTraceId {
uintptr_t id;
std::pair<int64_t, int64_t> debugger_id;
V8StackTraceId();
V8StackTraceId(uintptr_t id, const std::pair<int64_t, int64_t> debugger_id);
~V8StackTraceId() = default;
bool IsInvalid() const;
};
class V8_EXPORT V8Inspector { class V8_EXPORT V8Inspector {
public: public:
static std::unique_ptr<V8Inspector> create(v8::Isolate*, V8InspectorClient*); static std::unique_ptr<V8Inspector> create(v8::Isolate*, V8InspectorClient*);
...@@ -237,6 +251,11 @@ class V8_EXPORT V8Inspector { ...@@ -237,6 +251,11 @@ class V8_EXPORT V8Inspector {
virtual void asyncTaskFinished(void* task) = 0; virtual void asyncTaskFinished(void* task) = 0;
virtual void allAsyncTasksCanceled() = 0; virtual void allAsyncTasksCanceled() = 0;
virtual V8StackTraceId storeCurrentStackTrace(
const StringView& description) = 0;
virtual void externalAsyncTaskStarted(const V8StackTraceId& parent) = 0;
virtual void externalAsyncTaskFinished(const V8StackTraceId& parent) = 0;
// Exceptions instrumentation. // Exceptions instrumentation.
virtual unsigned exceptionThrown( virtual unsigned exceptionThrown(
v8::Local<v8::Context>, const StringView& message, v8::Local<v8::Context>, const StringView& message,
......
...@@ -10147,6 +10147,12 @@ int debug::GetNativeAccessorDescriptor(v8::Local<v8::Context> context, ...@@ -10147,6 +10147,12 @@ int debug::GetNativeAccessorDescriptor(v8::Local<v8::Context> context,
return result; return result;
} }
int64_t debug::GetNextRandomInt64(v8::Isolate* v8_isolate) {
return reinterpret_cast<i::Isolate*>(v8_isolate)
->random_number_generator()
->NextInt64();
}
Local<String> CpuProfileNode::GetFunctionName() const { Local<String> CpuProfileNode::GetFunctionName() const {
const i::ProfileNode* node = reinterpret_cast<const i::ProfileNode*>(this); const i::ProfileNode* node = reinterpret_cast<const i::ProfileNode*>(this);
i::Isolate* isolate = node->isolate(); i::Isolate* isolate = node->isolate();
......
...@@ -498,6 +498,8 @@ int GetNativeAccessorDescriptor(v8::Local<v8::Context> context, ...@@ -498,6 +498,8 @@ int GetNativeAccessorDescriptor(v8::Local<v8::Context> context,
v8::Local<v8::Object> object, v8::Local<v8::Object> object,
v8::Local<v8::Name> name); v8::Local<v8::Name> name);
int64_t GetNextRandomInt64(v8::Isolate* isolate);
} // namespace debug } // namespace debug
} // namespace v8 } // namespace v8
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
{ {
"domain": "Runtime", "domain": "Runtime",
"async": ["evaluate", "awaitPromise", "callFunctionOn", "runScript"], "async": ["evaluate", "awaitPromise", "callFunctionOn", "runScript"],
"exported": ["StackTrace", "RemoteObject", "ExecutionContextId"] "exported": ["StackTrace", "StackTraceId", "RemoteObject", "ExecutionContextId"]
}, },
{ {
"domain": "Debugger", "domain": "Debugger",
......
...@@ -201,13 +201,30 @@ ...@@ -201,13 +201,30 @@
"properties": [ "properties": [
{ "name": "description", "type": "string", "optional": true, "description": "String label of this stack trace. For async traces this may be a name of the function that initiated the async call." }, { "name": "description", "type": "string", "optional": true, "description": "String label of this stack trace. For async traces this may be a name of the function that initiated the async call." },
{ "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame" }, "description": "JavaScript function name." }, { "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame" }, "description": "JavaScript function name." },
{ "name": "parent", "$ref": "StackTrace", "optional": true, "description": "Asynchronous JavaScript stack trace that preceded this stack, if available." } { "name": "parent", "$ref": "StackTrace", "optional": true, "description": "Asynchronous JavaScript stack trace that preceded this stack, if available." },
{ "name": "parentId", "$ref": "StackTraceId", "optional": true, "experimental": true, "description": "Asynchronous JavaScript stack trace that preceded this stack, if available." }
] ]
}, },
{ {
"id": "AsyncTaskId", "id": "AsyncTaskId",
"type": "string", "type": "string",
"experimental": true "experimental": true
},
{
"id": "UniqueDebuggerId",
"type": "string",
"description": "Unique identifier of current debugger.",
"experimental": true
},
{
"id": "StackTraceId",
"type": "object",
"description": "External stack trace comes from another debugger and can be resolved there. This allows to track cross-debugger calls. See <code>Runtime.StackTrace</code> and <code>Debugger.paused</code> for usages.",
"properties": [
{ "name": "id", "type": "string" },
{ "name": "debuggerId", "$ref": "UniqueDebuggerId" }
],
"experimental": true
} }
], ],
"commands": [ "commands": [
...@@ -516,6 +533,9 @@ ...@@ -516,6 +533,9 @@
"commands": [ "commands": [
{ {
"name": "enable", "name": "enable",
"returns": [
{ "name": "debuggerId", "$ref": "Runtime.UniqueDebuggerId", "experimental": true, "description": "Unique identifier of the debugger." }
],
"description": "Enables debugger for the given page. Clients should not assume that the debugging has been enabled until the result for this command is received." "description": "Enables debugger for the given page. Clients should not assume that the debugging has been enabled until the result for this command is received."
}, },
{ {
...@@ -627,6 +647,17 @@ ...@@ -627,6 +647,17 @@
"name": "resume", "name": "resume",
"description": "Resumes JavaScript execution." "description": "Resumes JavaScript execution."
}, },
{
"name": "getStackTrace",
"parameters": [
{ "name": "stackTraceId", "$ref": "Runtime.StackTraceId" }
],
"returns": [
{ "name": "stackTrace", "$ref": "Runtime.StackTrace" }
],
"description": "Returns stack trace with given <code>stackTraceId</code>.",
"experimental": true
},
{ {
"name": "searchInContent", "name": "searchInContent",
"parameters": [ "parameters": [
...@@ -652,6 +683,7 @@ ...@@ -652,6 +683,7 @@
{ "name": "callFrames", "type": "array", "optional": true, "items": { "$ref": "CallFrame" }, "description": "New stack trace in case editing has happened while VM was stopped." }, { "name": "callFrames", "type": "array", "optional": true, "items": { "$ref": "CallFrame" }, "description": "New stack trace in case editing has happened while VM was stopped." },
{ "name": "stackChanged", "type": "boolean", "optional": true, "description": "Whether current call stack was modified after applying the changes." }, { "name": "stackChanged", "type": "boolean", "optional": true, "description": "Whether current call stack was modified after applying the changes." },
{ "name": "asyncStackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "Async stack trace, if any." }, { "name": "asyncStackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "Async stack trace, if any." },
{ "name": "asyncStackTraceId", "$ref": "Runtime.StackTraceId", "optional": true, "experimental": true, "description": "Async stack trace, if any." },
{ "name": "exceptionDetails", "optional": true, "$ref": "Runtime.ExceptionDetails", "description": "Exception details if any." } { "name": "exceptionDetails", "optional": true, "$ref": "Runtime.ExceptionDetails", "description": "Exception details if any." }
], ],
"description": "Edits JavaScript source live." "description": "Edits JavaScript source live."
...@@ -663,7 +695,8 @@ ...@@ -663,7 +695,8 @@
], ],
"returns": [ "returns": [
{ "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame" }, "description": "New stack trace." }, { "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame" }, "description": "New stack trace." },
{ "name": "asyncStackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "Async stack trace, if any." } { "name": "asyncStackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "Async stack trace, if any." },
{ "name": "asyncStackTraceId", "$ref": "Runtime.StackTraceId", "optional": true, "experimental": true, "description": "Async stack trace, if any." }
], ],
"description": "Restarts particular call frame from the beginning." "description": "Restarts particular call frame from the beginning."
}, },
...@@ -803,6 +836,7 @@ ...@@ -803,6 +836,7 @@
{ "name": "data", "type": "object", "optional": true, "description": "Object containing break-specific auxiliary properties." }, { "name": "data", "type": "object", "optional": true, "description": "Object containing break-specific auxiliary properties." },
{ "name": "hitBreakpoints", "type": "array", "optional": true, "items": { "type": "string" }, "description": "Hit breakpoints IDs" }, { "name": "hitBreakpoints", "type": "array", "optional": true, "items": { "type": "string" }, "description": "Hit breakpoints IDs" },
{ "name": "asyncStackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "Async stack trace, if any." }, { "name": "asyncStackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "Async stack trace, if any." },
{ "name": "asyncStackTraceId", "$ref": "Runtime.StackTraceId", "optional": true, "experimental": true, "description": "Async stack trace, if any." },
{ "name": "scheduledAsyncTaskId", "$ref": "Runtime.AsyncTaskId", "optional": true, "experimental": true, "description": "Scheduled async task id." } { "name": "scheduledAsyncTaskId", "$ref": "Runtime.AsyncTaskId", "optional": true, "experimental": true, "description": "Scheduled async task id." }
], ],
"description": "Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria." "description": "Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria."
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "src/inspector/string-util.h" #include "src/inspector/string-util.h"
#include "src/base/platform/platform.h"
#include "src/conversions.h" #include "src/conversions.h"
#include "src/inspector/protocol/Protocol.h" #include "src/inspector/protocol/Protocol.h"
#include "src/unicode-cache.h" #include "src/unicode-cache.h"
...@@ -151,4 +152,19 @@ StringBufferImpl::StringBufferImpl(String16& string) { ...@@ -151,4 +152,19 @@ StringBufferImpl::StringBufferImpl(String16& string) {
m_string = toStringView(m_owner); m_string = toStringView(m_owner);
} }
String16 debuggerIdToString(const std::pair<int64_t, int64_t>& debuggerId) {
const size_t kBufferSize = 35;
char buffer[kBufferSize];
v8::base::OS::SNPrintF(buffer, kBufferSize, "(%08" PRIX64 "%08" PRIX64 ")",
debuggerId.first, debuggerId.second);
return String16(buffer);
}
String16 stackTraceIdToString(uintptr_t id) {
String16Builder builder;
builder.appendNumber(reinterpret_cast<size_t>(id));
return builder.toString();
}
} // namespace v8_inspector } // namespace v8_inspector
...@@ -87,6 +87,9 @@ class StringBufferImpl : public StringBuffer { ...@@ -87,6 +87,9 @@ class StringBufferImpl : public StringBuffer {
DISALLOW_COPY_AND_ASSIGN(StringBufferImpl); DISALLOW_COPY_AND_ASSIGN(StringBufferImpl);
}; };
String16 debuggerIdToString(const std::pair<int64_t, int64_t>& debuggerId);
String16 stackTraceIdToString(uintptr_t id);
} // namespace v8_inspector } // namespace v8_inspector
#endif // V8_INSPECTOR_STRINGUTIL_H_ #endif // V8_INSPECTOR_STRINGUTIL_H_
...@@ -370,7 +370,9 @@ void V8DebuggerAgentImpl::enableImpl() { ...@@ -370,7 +370,9 @@ void V8DebuggerAgentImpl::enableImpl() {
} }
} }
Response V8DebuggerAgentImpl::enable() { Response V8DebuggerAgentImpl::enable(String16* outDebuggerId) {
*outDebuggerId = debuggerIdToString(
m_debugger->debuggerIdFor(m_session->contextGroupId()));
if (enabled()) return Response::OK(); if (enabled()) return Response::OK();
if (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId())) if (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId()))
...@@ -715,6 +717,27 @@ Response V8DebuggerAgentImpl::continueToLocation( ...@@ -715,6 +717,27 @@ Response V8DebuggerAgentImpl::continueToLocation(
protocol::Debugger::ContinueToLocation::TargetCallFramesEnum::Any)); protocol::Debugger::ContinueToLocation::TargetCallFramesEnum::Any));
} }
Response V8DebuggerAgentImpl::getStackTrace(
std::unique_ptr<protocol::Runtime::StackTraceId> inStackTraceId,
std::unique_ptr<protocol::Runtime::StackTrace>* outStackTrace) {
bool isOk = false;
int64_t id = inStackTraceId->getId().toInteger64(&isOk);
std::pair<int64_t, int64_t> debuggerId =
m_debugger->debuggerIdFor(inStackTraceId->getDebuggerId());
V8StackTraceId v8StackTraceId(id, debuggerId);
if (!isOk || v8StackTraceId.IsInvalid()) {
return Response::Error("Invalid stack trace id");
}
auto stack =
m_debugger->stackTraceFor(m_session->contextGroupId(), v8StackTraceId);
if (!stack) {
return Response::Error("Stack trace with given id is not found");
}
*outStackTrace =
stack->buildInspectorObject(m_debugger->maxAsyncCallChainDepth());
return Response::OK();
}
bool V8DebuggerAgentImpl::isFunctionBlackboxed(const String16& scriptId, bool V8DebuggerAgentImpl::isFunctionBlackboxed(const String16& scriptId,
const v8::debug::Location& start, const v8::debug::Location& start,
const v8::debug::Location& end) { const v8::debug::Location& end) {
...@@ -816,6 +839,7 @@ Response V8DebuggerAgentImpl::setScriptSource( ...@@ -816,6 +839,7 @@ Response V8DebuggerAgentImpl::setScriptSource(
Maybe<protocol::Array<protocol::Debugger::CallFrame>>* newCallFrames, Maybe<protocol::Array<protocol::Debugger::CallFrame>>* newCallFrames,
Maybe<bool>* stackChanged, Maybe<bool>* stackChanged,
Maybe<protocol::Runtime::StackTrace>* asyncStackTrace, Maybe<protocol::Runtime::StackTrace>* asyncStackTrace,
Maybe<protocol::Runtime::StackTraceId>* asyncStackTraceId,
Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) { Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) {
if (!enabled()) return Response::Error(kDebuggerNotEnabled); if (!enabled()) return Response::Error(kDebuggerNotEnabled);
...@@ -858,13 +882,15 @@ Response V8DebuggerAgentImpl::setScriptSource( ...@@ -858,13 +882,15 @@ Response V8DebuggerAgentImpl::setScriptSource(
if (!response.isSuccess()) return response; if (!response.isSuccess()) return response;
*newCallFrames = std::move(callFrames); *newCallFrames = std::move(callFrames);
*asyncStackTrace = currentAsyncStackTrace(); *asyncStackTrace = currentAsyncStackTrace();
*asyncStackTraceId = currentExternalStackTrace();
return Response::OK(); return Response::OK();
} }
Response V8DebuggerAgentImpl::restartFrame( Response V8DebuggerAgentImpl::restartFrame(
const String16& callFrameId, const String16& callFrameId,
std::unique_ptr<Array<CallFrame>>* newCallFrames, std::unique_ptr<Array<CallFrame>>* newCallFrames,
Maybe<protocol::Runtime::StackTrace>* asyncStackTrace) { Maybe<protocol::Runtime::StackTrace>* asyncStackTrace,
Maybe<protocol::Runtime::StackTraceId>* asyncStackTraceId) {
if (!isPaused()) return Response::Error(kDebuggerNotPaused); if (!isPaused()) return Response::Error(kDebuggerNotPaused);
InjectedScript::CallFrameScope scope(m_session, callFrameId); InjectedScript::CallFrameScope scope(m_session, callFrameId);
Response response = scope.initialize(); Response response = scope.initialize();
...@@ -880,6 +906,7 @@ Response V8DebuggerAgentImpl::restartFrame( ...@@ -880,6 +906,7 @@ Response V8DebuggerAgentImpl::restartFrame(
response = currentCallFrames(newCallFrames); response = currentCallFrames(newCallFrames);
if (!response.isSuccess()) return response; if (!response.isSuccess()) return response;
*asyncStackTrace = currentAsyncStackTrace(); *asyncStackTrace = currentAsyncStackTrace();
*asyncStackTraceId = currentExternalStackTrace();
return Response::OK(); return Response::OK();
} }
...@@ -1287,6 +1314,16 @@ V8DebuggerAgentImpl::currentAsyncStackTrace() { ...@@ -1287,6 +1314,16 @@ V8DebuggerAgentImpl::currentAsyncStackTrace() {
m_debugger->maxAsyncCallChainDepth() - 1); m_debugger->maxAsyncCallChainDepth() - 1);
} }
std::unique_ptr<protocol::Runtime::StackTraceId>
V8DebuggerAgentImpl::currentExternalStackTrace() {
V8StackTraceId externalParent = m_debugger->currentExternalParent();
if (externalParent.IsInvalid()) return nullptr;
return protocol::Runtime::StackTraceId::create()
.setId(stackTraceIdToString(externalParent.id))
.setDebuggerId(debuggerIdToString(externalParent.debugger_id))
.build();
}
bool V8DebuggerAgentImpl::isPaused() const { bool V8DebuggerAgentImpl::isPaused() const {
return m_debugger->isPausedInContextGroup(m_session->contextGroupId()); return m_debugger->isPausedInContextGroup(m_session->contextGroupId());
} }
...@@ -1498,7 +1535,8 @@ void V8DebuggerAgentImpl::didPause( ...@@ -1498,7 +1535,8 @@ void V8DebuggerAgentImpl::didPause(
m_frontend.paused(std::move(protocolCallFrames), breakReason, m_frontend.paused(std::move(protocolCallFrames), breakReason,
std::move(breakAuxData), std::move(hitBreakpointIds), std::move(breakAuxData), std::move(hitBreakpointIds),
currentAsyncStackTrace(), std::move(scheduledAsyncTaskId)); currentAsyncStackTrace(), currentExternalStackTrace(),
std::move(scheduledAsyncTaskId));
} }
void V8DebuggerAgentImpl::didContinue() { void V8DebuggerAgentImpl::didContinue() {
......
...@@ -39,7 +39,7 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend { ...@@ -39,7 +39,7 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
void restore(); void restore();
// Part of the protocol. // Part of the protocol.
Response enable() override; Response enable(String16* outDebuggerId) override;
Response disable() override; Response disable() override;
Response setBreakpointsActive(bool active) override; Response setBreakpointsActive(bool active) override;
Response setSkipAllPauses(bool skip) override; Response setSkipAllPauses(bool skip) override;
...@@ -57,6 +57,9 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend { ...@@ -57,6 +57,9 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
Response removeBreakpoint(const String16& breakpointId) override; Response removeBreakpoint(const String16& breakpointId) override;
Response continueToLocation(std::unique_ptr<protocol::Debugger::Location>, Response continueToLocation(std::unique_ptr<protocol::Debugger::Location>,
Maybe<String16> targetCallFrames) override; Maybe<String16> targetCallFrames) override;
Response getStackTrace(
std::unique_ptr<protocol::Runtime::StackTraceId> inStackTraceId,
std::unique_ptr<protocol::Runtime::StackTrace>* outStackTrace) override;
Response searchInContent( Response searchInContent(
const String16& scriptId, const String16& query, const String16& scriptId, const String16& query,
Maybe<bool> optionalCaseSensitive, Maybe<bool> optionalIsRegex, Maybe<bool> optionalCaseSensitive, Maybe<bool> optionalIsRegex,
...@@ -73,12 +76,14 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend { ...@@ -73,12 +76,14 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
Maybe<protocol::Array<protocol::Debugger::CallFrame>>* optOutCallFrames, Maybe<protocol::Array<protocol::Debugger::CallFrame>>* optOutCallFrames,
Maybe<bool>* optOutStackChanged, Maybe<bool>* optOutStackChanged,
Maybe<protocol::Runtime::StackTrace>* optOutAsyncStackTrace, Maybe<protocol::Runtime::StackTrace>* optOutAsyncStackTrace,
Maybe<protocol::Runtime::StackTraceId>* optOutAsyncStackTraceId,
Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) override; Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) override;
Response restartFrame( Response restartFrame(
const String16& callFrameId, const String16& callFrameId,
std::unique_ptr<protocol::Array<protocol::Debugger::CallFrame>>* std::unique_ptr<protocol::Array<protocol::Debugger::CallFrame>>*
newCallFrames, newCallFrames,
Maybe<protocol::Runtime::StackTrace>* asyncStackTrace) override; Maybe<protocol::Runtime::StackTrace>* asyncStackTrace,
Maybe<protocol::Runtime::StackTraceId>* asyncStackTraceId) override;
Response getScriptSource(const String16& scriptId, Response getScriptSource(const String16& scriptId,
String16* scriptSource) override; String16* scriptSource) override;
Response pause() override; Response pause() override;
...@@ -149,6 +154,7 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend { ...@@ -149,6 +154,7 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
Response currentCallFrames( Response currentCallFrames(
std::unique_ptr<protocol::Array<protocol::Debugger::CallFrame>>*); std::unique_ptr<protocol::Array<protocol::Debugger::CallFrame>>*);
std::unique_ptr<protocol::Runtime::StackTrace> currentAsyncStackTrace(); std::unique_ptr<protocol::Runtime::StackTrace> currentAsyncStackTrace();
std::unique_ptr<protocol::Runtime::StackTraceId> currentExternalStackTrace();
void setPauseOnExceptionsImpl(int); void setPauseOnExceptionsImpl(int);
......
...@@ -562,6 +562,11 @@ std::shared_ptr<AsyncStackTrace> V8Debugger::currentAsyncParent() { ...@@ -562,6 +562,11 @@ std::shared_ptr<AsyncStackTrace> V8Debugger::currentAsyncParent() {
return m_currentAsyncParent.empty() ? nullptr : m_currentAsyncParent.back(); return m_currentAsyncParent.empty() ? nullptr : m_currentAsyncParent.back();
} }
V8StackTraceId V8Debugger::currentExternalParent() {
return m_currentExternalParent.empty() ? V8StackTraceId()
: m_currentExternalParent.back();
}
v8::MaybeLocal<v8::Value> V8Debugger::getTargetScopes( v8::MaybeLocal<v8::Value> V8Debugger::getTargetScopes(
v8::Local<v8::Context> context, v8::Local<v8::Value> value, v8::Local<v8::Context> context, v8::Local<v8::Value> value,
ScopeTargetKind kind) { ScopeTargetKind kind) {
...@@ -726,6 +731,51 @@ void V8Debugger::setAsyncCallStackDepth(V8DebuggerAgentImpl* agent, int depth) { ...@@ -726,6 +731,51 @@ void V8Debugger::setAsyncCallStackDepth(V8DebuggerAgentImpl* agent, int depth) {
if (!maxAsyncCallStackDepth) allAsyncTasksCanceled(); if (!maxAsyncCallStackDepth) allAsyncTasksCanceled();
} }
std::shared_ptr<AsyncStackTrace> V8Debugger::stackTraceFor(
int contextGroupId, const V8StackTraceId& id) {
if (debuggerIdFor(contextGroupId) != id.debugger_id) return nullptr;
auto it = m_storedStackTraces.find(id.id);
if (it == m_storedStackTraces.end()) return nullptr;
return it->second.lock();
}
V8StackTraceId V8Debugger::storeCurrentStackTrace(
const StringView& description) {
if (!m_maxAsyncCallStackDepth) return V8StackTraceId();
v8::HandleScope scope(m_isolate);
int contextGroupId = currentContextGroupId();
if (!contextGroupId) return V8StackTraceId();
std::shared_ptr<AsyncStackTrace> asyncStack =
AsyncStackTrace::capture(this, contextGroupId, toString16(description),
V8StackTraceImpl::maxCallStackSizeToCapture);
if (!asyncStack) return V8StackTraceId();
uintptr_t id = ++m_lastStackTraceId;
m_storedStackTraces[id] = asyncStack;
m_allAsyncStacks.push_back(std::move(asyncStack));
++m_asyncStacksCount;
collectOldAsyncStacksIfNeeded();
return V8StackTraceId(id, debuggerIdFor(contextGroupId));
}
void V8Debugger::externalAsyncTaskStarted(const V8StackTraceId& parent) {
if (!m_maxAsyncCallStackDepth || parent.IsInvalid()) return;
m_currentExternalParent.push_back(parent);
m_currentAsyncParent.emplace_back();
m_currentTasks.push_back(reinterpret_cast<void*>(parent.id));
}
void V8Debugger::externalAsyncTaskFinished(const V8StackTraceId& parent) {
if (!m_maxAsyncCallStackDepth || m_currentExternalParent.empty()) return;
m_currentExternalParent.pop_back();
m_currentAsyncParent.pop_back();
DCHECK(m_currentTasks.back() == reinterpret_cast<void*>(parent.id));
m_currentTasks.pop_back();
}
void V8Debugger::asyncTaskScheduled(const StringView& taskName, void* task, void V8Debugger::asyncTaskScheduled(const StringView& taskName, void* task,
bool recurring) { bool recurring) {
asyncTaskScheduledForStack(toString16(taskName), task, recurring); asyncTaskScheduledForStack(toString16(taskName), task, recurring);
...@@ -785,6 +835,7 @@ void V8Debugger::asyncTaskStartedForStack(void* task) { ...@@ -785,6 +835,7 @@ void V8Debugger::asyncTaskStartedForStack(void* task) {
} else { } else {
m_currentAsyncParent.emplace_back(); m_currentAsyncParent.emplace_back();
} }
m_currentExternalParent.emplace_back();
} }
void V8Debugger::asyncTaskFinishedForStack(void* task) { void V8Debugger::asyncTaskFinishedForStack(void* task) {
...@@ -795,6 +846,7 @@ void V8Debugger::asyncTaskFinishedForStack(void* task) { ...@@ -795,6 +846,7 @@ void V8Debugger::asyncTaskFinishedForStack(void* task) {
m_currentTasks.pop_back(); m_currentTasks.pop_back();
m_currentAsyncParent.pop_back(); m_currentAsyncParent.pop_back();
m_currentExternalParent.pop_back();
if (m_recurringTasks.find(task) == m_recurringTasks.end()) { if (m_recurringTasks.find(task) == m_recurringTasks.end()) {
asyncTaskCanceledForStack(task); asyncTaskCanceledForStack(task);
...@@ -841,6 +893,7 @@ void V8Debugger::allAsyncTasksCanceled() { ...@@ -841,6 +893,7 @@ void V8Debugger::allAsyncTasksCanceled() {
m_asyncTaskStacks.clear(); m_asyncTaskStacks.clear();
m_recurringTasks.clear(); m_recurringTasks.clear();
m_currentAsyncParent.clear(); m_currentAsyncParent.clear();
m_currentExternalParent.clear();
m_currentTasks.clear(); m_currentTasks.clear();
m_framesCache.clear(); m_framesCache.clear();
...@@ -892,6 +945,7 @@ void V8Debugger::collectOldAsyncStacksIfNeeded() { ...@@ -892,6 +945,7 @@ void V8Debugger::collectOldAsyncStacksIfNeeded() {
--m_asyncStacksCount; --m_asyncStacksCount;
} }
cleanupExpiredWeakPointers(m_asyncTaskStacks); cleanupExpiredWeakPointers(m_asyncTaskStacks);
cleanupExpiredWeakPointers(m_storedStackTraces);
for (auto it = m_recurringTasks.begin(); it != m_recurringTasks.end();) { for (auto it = m_recurringTasks.begin(); it != m_recurringTasks.end();) {
if (m_asyncTaskStacks.find(*it) == m_asyncTaskStacks.end()) { if (m_asyncTaskStacks.find(*it) == m_asyncTaskStacks.end()) {
it = m_recurringTasks.erase(it); it = m_recurringTasks.erase(it);
...@@ -927,6 +981,26 @@ void V8Debugger::setMaxAsyncTaskStacksForTest(int limit) { ...@@ -927,6 +981,26 @@ void V8Debugger::setMaxAsyncTaskStacksForTest(int limit) {
m_maxAsyncCallStacks = limit; m_maxAsyncCallStacks = limit;
} }
std::pair<int64_t, int64_t> V8Debugger::debuggerIdFor(int contextGroupId) {
auto it = m_contextGroupIdToDebuggerId.find(contextGroupId);
if (it != m_contextGroupIdToDebuggerId.end()) return it->second;
std::pair<int64_t, int64_t> debuggerId(
v8::debug::GetNextRandomInt64(m_isolate),
v8::debug::GetNextRandomInt64(m_isolate));
m_contextGroupIdToDebuggerId.insert(
it, std::make_pair(contextGroupId, debuggerId));
m_serializedDebuggerIdToDebuggerId.insert(
std::make_pair(debuggerIdToString(debuggerId), debuggerId));
return debuggerId;
}
std::pair<int64_t, int64_t> V8Debugger::debuggerIdFor(
const String16& serializedDebuggerId) {
auto it = m_serializedDebuggerIdToDebuggerId.find(serializedDebuggerId);
if (it != m_serializedDebuggerIdToDebuggerId.end()) return it->second;
return std::make_pair(0, 0);
}
void V8Debugger::dumpAsyncTaskStacksStateForTest() { void V8Debugger::dumpAsyncTaskStacksStateForTest() {
fprintf(stdout, "Async stacks count: %d\n", m_asyncStacksCount); fprintf(stdout, "Async stacks count: %d\n", m_asyncStacksCount);
fprintf(stdout, "Scheduled async tasks: %zu\n", m_asyncTaskStacks.size()); fprintf(stdout, "Scheduled async tasks: %zu\n", m_asyncTaskStacks.size());
......
...@@ -27,6 +27,7 @@ class V8Debugger; ...@@ -27,6 +27,7 @@ class V8Debugger;
class V8DebuggerAgentImpl; class V8DebuggerAgentImpl;
class V8InspectorImpl; class V8InspectorImpl;
class V8StackTraceImpl; class V8StackTraceImpl;
struct V8StackTraceId;
using protocol::Response; using protocol::Response;
using ScheduleStepIntoAsyncCallback = using ScheduleStepIntoAsyncCallback =
...@@ -79,6 +80,7 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -79,6 +80,7 @@ class V8Debugger : public v8::debug::DebugDelegate {
void setAsyncCallStackDepth(V8DebuggerAgentImpl*, int); void setAsyncCallStackDepth(V8DebuggerAgentImpl*, int);
std::shared_ptr<AsyncStackTrace> currentAsyncParent(); std::shared_ptr<AsyncStackTrace> currentAsyncParent();
V8StackTraceId currentExternalParent();
std::shared_ptr<StackFrame> symbolize(v8::Local<v8::StackFrame> v8Frame); std::shared_ptr<StackFrame> symbolize(v8::Local<v8::StackFrame> v8Frame);
...@@ -98,6 +100,10 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -98,6 +100,10 @@ class V8Debugger : public v8::debug::DebugDelegate {
void asyncTaskFinished(void* task); void asyncTaskFinished(void* task);
void allAsyncTasksCanceled(); void allAsyncTasksCanceled();
V8StackTraceId storeCurrentStackTrace(const StringView& description);
void externalAsyncTaskStarted(const V8StackTraceId& parent);
void externalAsyncTaskFinished(const V8StackTraceId& parent);
void muteScriptParsedEvents(); void muteScriptParsedEvents();
void unmuteScriptParsedEvents(); void unmuteScriptParsedEvents();
...@@ -110,6 +116,12 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -110,6 +116,12 @@ class V8Debugger : public v8::debug::DebugDelegate {
void* scheduledAsyncTask() { return m_scheduledAsyncTask; } void* scheduledAsyncTask() { return m_scheduledAsyncTask; }
std::pair<int64_t, int64_t> debuggerIdFor(int contextGroupId);
std::pair<int64_t, int64_t> debuggerIdFor(
const String16& serializedDebuggerId);
std::shared_ptr<AsyncStackTrace> stackTraceFor(int contextGroupId,
const V8StackTraceId& id);
private: private:
void clearContinueToLocation(); void clearContinueToLocation();
bool shouldContinueToCurrentLocation(); bool shouldContinueToCurrentLocation();
...@@ -186,6 +198,7 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -186,6 +198,7 @@ class V8Debugger : public v8::debug::DebugDelegate {
std::vector<void*> m_currentTasks; std::vector<void*> m_currentTasks;
std::vector<std::shared_ptr<AsyncStackTrace>> m_currentAsyncParent; std::vector<std::shared_ptr<AsyncStackTrace>> m_currentAsyncParent;
std::vector<V8StackTraceId> m_currentExternalParent;
void collectOldAsyncStacksIfNeeded(); void collectOldAsyncStacksIfNeeded();
int m_asyncStacksCount = 0; int m_asyncStacksCount = 0;
...@@ -204,6 +217,16 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -204,6 +217,16 @@ class V8Debugger : public v8::debug::DebugDelegate {
bool m_pauseOnAsyncCall = false; bool m_pauseOnAsyncCall = false;
void* m_scheduledAsyncTask = nullptr; void* m_scheduledAsyncTask = nullptr;
using StackTraceIdToStackTrace =
protocol::HashMap<uintptr_t, std::weak_ptr<AsyncStackTrace>>;
StackTraceIdToStackTrace m_storedStackTraces;
uintptr_t m_lastStackTraceId = 0;
protocol::HashMap<int, std::pair<int64_t, int64_t>>
m_contextGroupIdToDebuggerId;
protocol::HashMap<String16, std::pair<int64_t, int64_t>>
m_serializedDebuggerIdToDebuggerId;
WasmTranslation m_wasmTranslation; WasmTranslation m_wasmTranslation;
DISALLOW_COPY_AND_ASSIGN(V8Debugger); DISALLOW_COPY_AND_ASSIGN(V8Debugger);
......
...@@ -287,6 +287,19 @@ std::unique_ptr<V8StackTrace> V8InspectorImpl::captureStackTrace( ...@@ -287,6 +287,19 @@ std::unique_ptr<V8StackTrace> V8InspectorImpl::captureStackTrace(
return m_debugger->captureStackTrace(fullStack); return m_debugger->captureStackTrace(fullStack);
} }
V8StackTraceId V8InspectorImpl::storeCurrentStackTrace(
const StringView& description) {
return m_debugger->storeCurrentStackTrace(description);
}
void V8InspectorImpl::externalAsyncTaskStarted(const V8StackTraceId& parent) {
m_debugger->externalAsyncTaskStarted(parent);
}
void V8InspectorImpl::externalAsyncTaskFinished(const V8StackTraceId& parent) {
m_debugger->externalAsyncTaskFinished(parent);
}
void V8InspectorImpl::asyncTaskScheduled(const StringView& taskName, void* task, void V8InspectorImpl::asyncTaskScheduled(const StringView& taskName, void* task,
bool recurring) { bool recurring) {
if (!task) return; if (!task) return;
......
...@@ -97,6 +97,10 @@ class V8InspectorImpl : public V8Inspector { ...@@ -97,6 +97,10 @@ class V8InspectorImpl : public V8Inspector {
void asyncTaskFinished(void* task) override; void asyncTaskFinished(void* task) override;
void allAsyncTasksCanceled() override; void allAsyncTasksCanceled() override;
V8StackTraceId storeCurrentStackTrace(const StringView& description) override;
void externalAsyncTaskStarted(const V8StackTraceId& parent) override;
void externalAsyncTaskFinished(const V8StackTraceId& parent) override;
unsigned nextExceptionId() { return ++m_lastExceptionId; } unsigned nextExceptionId() { return ++m_lastExceptionId; }
void enableStackCapturingIfNeeded(); void enableStackCapturingIfNeeded();
void disableStackCapturingIfNeeded(); void disableStackCapturingIfNeeded();
......
...@@ -32,8 +32,10 @@ std::vector<std::shared_ptr<StackFrame>> toFramesVector( ...@@ -32,8 +32,10 @@ std::vector<std::shared_ptr<StackFrame>> toFramesVector(
void calculateAsyncChain(V8Debugger* debugger, int contextGroupId, void calculateAsyncChain(V8Debugger* debugger, int contextGroupId,
std::shared_ptr<AsyncStackTrace>* asyncParent, std::shared_ptr<AsyncStackTrace>* asyncParent,
int* maxAsyncDepth) { V8StackTraceId* externalParent, int* maxAsyncDepth) {
*asyncParent = debugger->currentAsyncParent(); *asyncParent = debugger->currentAsyncParent();
*externalParent = debugger->currentExternalParent();
DCHECK(externalParent->IsInvalid() || !*asyncParent);
if (maxAsyncDepth) *maxAsyncDepth = debugger->maxAsyncCallChainDepth(); if (maxAsyncDepth) *maxAsyncDepth = debugger->maxAsyncCallChainDepth();
// Do not accidentally append async call chain from another group. This should // Do not accidentally append async call chain from another group. This should
...@@ -42,6 +44,7 @@ void calculateAsyncChain(V8Debugger* debugger, int contextGroupId, ...@@ -42,6 +44,7 @@ void calculateAsyncChain(V8Debugger* debugger, int contextGroupId,
if (contextGroupId && *asyncParent && if (contextGroupId && *asyncParent &&
(*asyncParent)->contextGroupId() != contextGroupId) { (*asyncParent)->contextGroupId() != contextGroupId) {
asyncParent->reset(); asyncParent->reset();
*externalParent = V8StackTraceId();
if (maxAsyncDepth) *maxAsyncDepth = 0; if (maxAsyncDepth) *maxAsyncDepth = 0;
return; return;
} }
...@@ -56,7 +59,8 @@ void calculateAsyncChain(V8Debugger* debugger, int contextGroupId, ...@@ -56,7 +59,8 @@ void calculateAsyncChain(V8Debugger* debugger, int contextGroupId,
std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectCommon( std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectCommon(
const std::vector<std::shared_ptr<StackFrame>>& frames, const std::vector<std::shared_ptr<StackFrame>>& frames,
const String16& description, const String16& description,
const std::shared_ptr<AsyncStackTrace>& asyncParent, int maxAsyncDepth) { const std::shared_ptr<AsyncStackTrace>& asyncParent,
const V8StackTraceId& externalParent, int maxAsyncDepth) {
if (asyncParent && frames.empty() && if (asyncParent && frames.empty() &&
description == asyncParent->description()) { description == asyncParent->description()) {
return asyncParent->buildInspectorObject(maxAsyncDepth); return asyncParent->buildInspectorObject(maxAsyncDepth);
...@@ -75,11 +79,26 @@ std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectCommon( ...@@ -75,11 +79,26 @@ std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectCommon(
if (asyncParent && maxAsyncDepth > 0) { if (asyncParent && maxAsyncDepth > 0) {
stackTrace->setParent(asyncParent->buildInspectorObject(maxAsyncDepth - 1)); stackTrace->setParent(asyncParent->buildInspectorObject(maxAsyncDepth - 1));
} }
if (!externalParent.IsInvalid() && maxAsyncDepth > 0) {
stackTrace->setParentId(
protocol::Runtime::StackTraceId::create()
.setId(stackTraceIdToString(externalParent.id))
.setDebuggerId(debuggerIdToString(externalParent.debugger_id))
.build());
}
return stackTrace; return stackTrace;
} }
} // namespace } // namespace
V8StackTraceId::V8StackTraceId() : id(0), debugger_id(std::make_pair(0, 0)) {}
V8StackTraceId::V8StackTraceId(uintptr_t id,
const std::pair<int64_t, int64_t> debugger_id)
: id(id), debugger_id(debugger_id) {}
bool V8StackTraceId::IsInvalid() const { return !id; }
StackFrame::StackFrame(v8::Local<v8::StackFrame> v8Frame) StackFrame::StackFrame(v8::Local<v8::StackFrame> v8Frame)
: m_functionName(toProtocolString(v8Frame->GetFunctionName())), : m_functionName(toProtocolString(v8Frame->GetFunctionName())),
m_scriptId(String16::fromInteger(v8Frame->GetScriptId())), m_scriptId(String16::fromInteger(v8Frame->GetScriptId())),
...@@ -145,10 +164,13 @@ std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::create( ...@@ -145,10 +164,13 @@ std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::create(
int maxAsyncDepth = 0; int maxAsyncDepth = 0;
std::shared_ptr<AsyncStackTrace> asyncParent; std::shared_ptr<AsyncStackTrace> asyncParent;
calculateAsyncChain(debugger, contextGroupId, &asyncParent, &maxAsyncDepth); V8StackTraceId externalParent;
if (frames.empty() && !asyncParent) return nullptr; calculateAsyncChain(debugger, contextGroupId, &asyncParent, &externalParent,
return std::unique_ptr<V8StackTraceImpl>( &maxAsyncDepth);
new V8StackTraceImpl(std::move(frames), maxAsyncDepth, asyncParent)); if (frames.empty() && !asyncParent && externalParent.IsInvalid())
return nullptr;
return std::unique_ptr<V8StackTraceImpl>(new V8StackTraceImpl(
std::move(frames), maxAsyncDepth, asyncParent, externalParent));
} }
// static // static
...@@ -168,16 +190,18 @@ std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::capture( ...@@ -168,16 +190,18 @@ std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::capture(
V8StackTraceImpl::V8StackTraceImpl( V8StackTraceImpl::V8StackTraceImpl(
std::vector<std::shared_ptr<StackFrame>> frames, int maxAsyncDepth, std::vector<std::shared_ptr<StackFrame>> frames, int maxAsyncDepth,
std::shared_ptr<AsyncStackTrace> asyncParent) std::shared_ptr<AsyncStackTrace> asyncParent,
const V8StackTraceId& externalParent)
: m_frames(std::move(frames)), : m_frames(std::move(frames)),
m_maxAsyncDepth(maxAsyncDepth), m_maxAsyncDepth(maxAsyncDepth),
m_asyncParent(asyncParent) {} m_asyncParent(asyncParent),
m_externalParent(externalParent) {}
V8StackTraceImpl::~V8StackTraceImpl() {} V8StackTraceImpl::~V8StackTraceImpl() {}
std::unique_ptr<V8StackTrace> V8StackTraceImpl::clone() { std::unique_ptr<V8StackTrace> V8StackTraceImpl::clone() {
return std::unique_ptr<V8StackTrace>( return std::unique_ptr<V8StackTrace>(new V8StackTraceImpl(
new V8StackTraceImpl(m_frames, 0, std::shared_ptr<AsyncStackTrace>())); m_frames, 0, std::shared_ptr<AsyncStackTrace>(), V8StackTraceId()));
} }
bool V8StackTraceImpl::isEmpty() const { return m_frames.empty(); } bool V8StackTraceImpl::isEmpty() const { return m_frames.empty(); }
...@@ -205,7 +229,7 @@ StringView V8StackTraceImpl::topFunctionName() const { ...@@ -205,7 +229,7 @@ StringView V8StackTraceImpl::topFunctionName() const {
std::unique_ptr<protocol::Runtime::StackTrace> std::unique_ptr<protocol::Runtime::StackTrace>
V8StackTraceImpl::buildInspectorObjectImpl() const { V8StackTraceImpl::buildInspectorObjectImpl() const {
return buildInspectorObjectCommon(m_frames, String16(), m_asyncParent.lock(), return buildInspectorObjectCommon(m_frames, String16(), m_asyncParent.lock(),
m_maxAsyncDepth); m_externalParent, m_maxAsyncDepth);
} }
std::unique_ptr<protocol::Runtime::API::StackTrace> std::unique_ptr<protocol::Runtime::API::StackTrace>
...@@ -292,9 +316,12 @@ std::shared_ptr<AsyncStackTrace> AsyncStackTrace::capture( ...@@ -292,9 +316,12 @@ std::shared_ptr<AsyncStackTrace> AsyncStackTrace::capture(
} }
std::shared_ptr<AsyncStackTrace> asyncParent; std::shared_ptr<AsyncStackTrace> asyncParent;
calculateAsyncChain(debugger, contextGroupId, &asyncParent, nullptr); V8StackTraceId externalParent;
calculateAsyncChain(debugger, contextGroupId, &asyncParent, &externalParent,
nullptr);
if (frames.empty() && !asyncParent) return nullptr; if (frames.empty() && !asyncParent && externalParent.IsInvalid())
return nullptr;
// When async call chain is empty but doesn't contain useful schedule stack // When async call chain is empty but doesn't contain useful schedule stack
// but doesn't synchronous we can merge them together. e.g. Promise // but doesn't synchronous we can merge them together. e.g. Promise
...@@ -308,25 +335,29 @@ std::shared_ptr<AsyncStackTrace> AsyncStackTrace::capture( ...@@ -308,25 +335,29 @@ std::shared_ptr<AsyncStackTrace> AsyncStackTrace::capture(
if (!contextGroupId && asyncParent) { if (!contextGroupId && asyncParent) {
contextGroupId = asyncParent->m_contextGroupId; contextGroupId = asyncParent->m_contextGroupId;
} }
return std::shared_ptr<AsyncStackTrace>(new AsyncStackTrace( return std::shared_ptr<AsyncStackTrace>(
contextGroupId, description, std::move(frames), asyncParent)); new AsyncStackTrace(contextGroupId, description, std::move(frames),
asyncParent, externalParent));
} }
AsyncStackTrace::AsyncStackTrace( AsyncStackTrace::AsyncStackTrace(
int contextGroupId, const String16& description, int contextGroupId, const String16& description,
std::vector<std::shared_ptr<StackFrame>> frames, std::vector<std::shared_ptr<StackFrame>> frames,
std::shared_ptr<AsyncStackTrace> asyncParent) std::shared_ptr<AsyncStackTrace> asyncParent,
const V8StackTraceId& externalParent)
: m_contextGroupId(contextGroupId), : m_contextGroupId(contextGroupId),
m_description(description), m_description(description),
m_frames(std::move(frames)), m_frames(std::move(frames)),
m_asyncParent(asyncParent) { m_asyncParent(asyncParent),
m_externalParent(externalParent) {
DCHECK(m_contextGroupId); DCHECK(m_contextGroupId);
} }
std::unique_ptr<protocol::Runtime::StackTrace> std::unique_ptr<protocol::Runtime::StackTrace>
AsyncStackTrace::buildInspectorObject(int maxAsyncDepth) const { AsyncStackTrace::buildInspectorObject(int maxAsyncDepth) const {
return buildInspectorObjectCommon(m_frames, m_description, return buildInspectorObjectCommon(m_frames, m_description,
m_asyncParent.lock(), maxAsyncDepth); m_asyncParent.lock(), m_externalParent,
maxAsyncDepth);
} }
int AsyncStackTrace::contextGroupId() const { return m_contextGroupId; } int AsyncStackTrace::contextGroupId() const { return m_contextGroupId; }
......
...@@ -19,6 +19,7 @@ namespace v8_inspector { ...@@ -19,6 +19,7 @@ namespace v8_inspector {
class AsyncStackTrace; class AsyncStackTrace;
class V8Debugger; class V8Debugger;
class WasmTranslation; class WasmTranslation;
struct V8StackTraceId;
class StackFrame { class StackFrame {
public: public:
...@@ -78,7 +79,8 @@ class V8StackTraceImpl : public V8StackTrace { ...@@ -78,7 +79,8 @@ class V8StackTraceImpl : public V8StackTrace {
private: private:
V8StackTraceImpl(std::vector<std::shared_ptr<StackFrame>> frames, V8StackTraceImpl(std::vector<std::shared_ptr<StackFrame>> frames,
int maxAsyncDepth, int maxAsyncDepth,
std::shared_ptr<AsyncStackTrace> asyncParent); std::shared_ptr<AsyncStackTrace> asyncParent,
const V8StackTraceId& externalParent);
class StackFrameIterator { class StackFrameIterator {
public: public:
...@@ -97,6 +99,7 @@ class V8StackTraceImpl : public V8StackTrace { ...@@ -97,6 +99,7 @@ class V8StackTraceImpl : public V8StackTrace {
std::vector<std::shared_ptr<StackFrame>> m_frames; std::vector<std::shared_ptr<StackFrame>> m_frames;
int m_maxAsyncDepth; int m_maxAsyncDepth;
std::weak_ptr<AsyncStackTrace> m_asyncParent; std::weak_ptr<AsyncStackTrace> m_asyncParent;
V8StackTraceId m_externalParent;
DISALLOW_COPY_AND_ASSIGN(V8StackTraceImpl); DISALLOW_COPY_AND_ASSIGN(V8StackTraceImpl);
}; };
...@@ -123,13 +126,15 @@ class AsyncStackTrace { ...@@ -123,13 +126,15 @@ class AsyncStackTrace {
private: private:
AsyncStackTrace(int contextGroupId, const String16& description, AsyncStackTrace(int contextGroupId, const String16& description,
std::vector<std::shared_ptr<StackFrame>> frames, std::vector<std::shared_ptr<StackFrame>> frames,
std::shared_ptr<AsyncStackTrace> asyncParent); std::shared_ptr<AsyncStackTrace> asyncParent,
const V8StackTraceId& externalParent);
int m_contextGroupId; int m_contextGroupId;
String16 m_description; String16 m_description;
std::vector<std::shared_ptr<StackFrame>> m_frames; std::vector<std::shared_ptr<StackFrame>> m_frames;
std::weak_ptr<AsyncStackTrace> m_asyncParent; std::weak_ptr<AsyncStackTrace> m_asyncParent;
V8StackTraceId m_externalParent;
DISALLOW_COPY_AND_ASSIGN(AsyncStackTrace); DISALLOW_COPY_AND_ASSIGN(AsyncStackTrace);
}; };
......
Tests external stack traces
Running test: testDebuggerId
Enabling debugger first time..
Enabling debugger again..
> second Debugger.enable returns the same debugger id
Enabling debugger in another context group..
> Debugger.enable in another context group returns own debugger id
Running test: testInstrumentation
{
id : <messageId>
result : {
stackTrace : {
callFrames : [
[0] : {
columnNumber : 15
functionName :
lineNumber : 0
scriptId : <scriptId>
url :
}
]
description : stack
}
}
}
Running test: testDisableStacksAfterStored
> external async stack trace is empty
Running test: testDisableStacksAfterStarted
> external async stack trace is empty
Running test: testExternalStacks
(anonymous) (expr1-2.js:1:6)
-- stack2 --
store (utils.js:2:25)
(anonymous) (expr2.js:1:11)
-- stack --
store (utils.js:2:25)
(anonymous) (expr1-1.js:0:0)
// 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.
InspectorTest.log('Tests external stack traces');
let contextGroup1 = new InspectorTest.ContextGroup();
let session1 = contextGroup1.connect();
let Protocol1 = session1.Protocol;
let contextGroup2 = new InspectorTest.ContextGroup();
let session2 = contextGroup2.connect();
let Protocol2 = session2.Protocol;
let utilsScript = `
function store(description) {
let buffer = inspector.storeCurrentStackTrace(description);
return '[' + new Int32Array(buffer).join(',') + ']';
}
function started(id) {
inspector.externalAsyncTaskStarted(Int32Array.from(JSON.parse(id)).buffer);
}
function finished(id) {
inspector.externalAsyncTaskFinished(Int32Array.from(JSON.parse(id)).buffer);
}
//# sourceURL=utils.js`;
contextGroup1.addScript(utilsScript);
contextGroup2.addScript(utilsScript);
InspectorTest.runAsyncTestSuite([
async function testDebuggerId() {
InspectorTest.log('Enabling debugger first time..');
let {result: {debuggerId}} = await Protocol1.Debugger.enable();
let firstDebuggerId = debuggerId;
InspectorTest.log('Enabling debugger again..');
({result: {debuggerId}} = await Protocol1.Debugger.enable());
if (firstDebuggerId !== debuggerId) {
InspectorTest.log(
'FAIL: second Debugger.enable returns different debugger id');
} else {
InspectorTest.log(
'> second Debugger.enable returns the same debugger id');
}
InspectorTest.log('Enabling debugger in another context group..');
({result: {debuggerId}} = await Protocol2.Debugger.enable());
if (firstDebuggerId === debuggerId) {
InspectorTest.log(
'FAIL: Debugger.enable in another context group returns the same debugger id');
} else {
InspectorTest.log(
'> Debugger.enable in another context group returns own debugger id');
}
},
async function testInstrumentation() {
Protocol1.Debugger.enable();
Protocol1.Debugger.setAsyncCallStackDepth({maxDepth: 32});
let result = await Protocol1.Runtime.evaluate(
{expression: 'id = inspector.storeCurrentStackTrace(\'stack\')'});
let stackTraceId = result.result.result.objectId;
Protocol1.Runtime.evaluate({
expression: `inspector.externalAsyncTaskStarted(id);
debugger;
inspector.externalAsyncTaskFinished(id);`
});
let {params: {callFrames, asyncStackTraceId}} =
await Protocol1.Debugger.oncePaused();
result = await Protocol1.Debugger.getStackTrace(
{stackTraceId: asyncStackTraceId});
InspectorTest.logMessage(result);
await Protocol1.Debugger.disable();
},
async function testDisableStacksAfterStored() {
Protocol1.Debugger.enable();
Protocol1.Debugger.setAsyncCallStackDepth({maxDepth: 32});
let result = await Protocol1.Runtime.evaluate(
{expression: 'id = inspector.storeCurrentStackTrace(\'stack\')'});
let stackTraceId = result.result.result.objectId;
Protocol1.Debugger.setAsyncCallStackDepth({maxDepth: 0});
Protocol1.Runtime.evaluate({
expression: `inspector.externalAsyncTaskStarted(id);
debugger;
inspector.externalAsyncTaskFinished(id);`
});
let {params: {callFrames, asyncStackTraceId}} =
await Protocol1.Debugger.oncePaused();
if (!asyncStackTraceId) {
InspectorTest.log('> external async stack trace is empty');
} else {
InspectorTest.log('FAIL: external async stack trace is reported');
}
await Protocol1.Debugger.disable();
},
async function testDisableStacksAfterStarted() {
Protocol1.Debugger.enable();
Protocol1.Debugger.setAsyncCallStackDepth({maxDepth: 32});
let result = await Protocol1.Runtime.evaluate(
{expression: 'id = inspector.storeCurrentStackTrace(\'stack\')'});
let stackTraceId = result.result.result.objectId;
Protocol1.Runtime.evaluate(
{expression: 'inspector.externalAsyncTaskStarted(id);'});
Protocol1.Debugger.setAsyncCallStackDepth({maxDepth: 0});
Protocol1.Runtime.evaluate({
expression: `debugger;
inspector.externalAsyncTaskFinished(id);`
});
let {params: {callFrames, asyncStackTraceId}} =
await Protocol1.Debugger.oncePaused();
if (!asyncStackTraceId) {
InspectorTest.log('> external async stack trace is empty');
} else {
InspectorTest.log('FAIL: external async stack trace is reported');
}
await Protocol1.Debugger.disable();
},
async function testExternalStacks() {
let debuggerId1 = (await Protocol1.Debugger.enable()).result.debuggerId;
let debuggerId2 = (await Protocol2.Debugger.enable()).result.debuggerId;
Protocol1.Debugger.setAsyncCallStackDepth({maxDepth: 32});
Protocol2.Debugger.setAsyncCallStackDepth({maxDepth: 32});
let stackTraceId1 = (await Protocol1.Runtime.evaluate({
expression: 'store(\'stack\')//# sourceURL=expr1-1.js'
})).result.result.value;
let stackTraceId2 = (await Protocol2.Runtime.evaluate({
expression: `started('${stackTraceId1}');
id = store('stack2');
finished('${stackTraceId1}');
id
//# sourceURL=expr2.js`
})).result.result.value;
Protocol1.Runtime.evaluate({
expression: `started('${stackTraceId2}');
debugger;
finished('${stackTraceId2}');
id
//# sourceURL=expr1-2.js`
});
let {params: {callFrames, asyncStackTraceId}} =
await Protocol1.Debugger.oncePaused();
let debuggers = new Map(
[[debuggerId1, Protocol1.Debugger], [debuggerId2, Protocol2.Debugger]]);
let sessions = new Map([[debuggerId1, session1], [debuggerId2, session2]]);
let currentDebuggerId = debuggerId1;
while (true) {
sessions.get(currentDebuggerId).logCallFrames(callFrames);
if (asyncStackTraceId) {
currentDebuggerId = asyncStackTraceId.debuggerId;
let {result: {stackTrace}} =
await debuggers.get(currentDebuggerId).getStackTrace({
stackTraceId: asyncStackTraceId
});
InspectorTest.log(`-- ${stackTrace.description} --`);
callFrames = stackTrace.callFrames;
asyncStackTraceId = stackTrace.parentId;
} else {
break;
}
}
Protocol1.Debugger.disable();
await Protocol2.Debugger.disable();
}
]);
...@@ -693,6 +693,16 @@ class InspectorExtension : public IsolateData::SetupGlobalTask { ...@@ -693,6 +693,16 @@ class InspectorExtension : public IsolateData::SetupGlobalTask {
inspector->Set(ToV8String(isolate, "createObjectWithAccessor"), inspector->Set(ToV8String(isolate, "createObjectWithAccessor"),
v8::FunctionTemplate::New( v8::FunctionTemplate::New(
isolate, &InspectorExtension::CreateObjectWithAccessor)); isolate, &InspectorExtension::CreateObjectWithAccessor));
inspector->Set(ToV8String(isolate, "storeCurrentStackTrace"),
v8::FunctionTemplate::New(
isolate, &InspectorExtension::StoreCurrentStackTrace));
inspector->Set(ToV8String(isolate, "externalAsyncTaskStarted"),
v8::FunctionTemplate::New(
isolate, &InspectorExtension::ExternalAsyncTaskStarted));
inspector->Set(
ToV8String(isolate, "externalAsyncTaskFinished"),
v8::FunctionTemplate::New(
isolate, &InspectorExtension::ExternalAsyncTaskFinished));
global->Set(ToV8String(isolate, "inspector"), inspector); global->Set(ToV8String(isolate, "inspector"), inspector);
} }
...@@ -865,6 +875,57 @@ class InspectorExtension : public IsolateData::SetupGlobalTask { ...@@ -865,6 +875,57 @@ class InspectorExtension : public IsolateData::SetupGlobalTask {
v8::Isolate* isolate = info.GetIsolate(); v8::Isolate* isolate = info.GetIsolate();
isolate->ThrowException(ToV8String(isolate, "Setter is called")); isolate->ThrowException(ToV8String(isolate, "Setter is called"));
} }
static void StoreCurrentStackTrace(
const v8::FunctionCallbackInfo<v8::Value>& args) {
if (args.Length() != 1 || !args[0]->IsString()) {
fprintf(stderr,
"Internal error: storeCurrentStackTrace('description')\n");
Exit();
}
v8::Isolate* isolate = args.GetIsolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
IsolateData* data = IsolateData::FromContext(context);
v8::internal::Vector<uint16_t> description =
ToVector(args[0].As<v8::String>());
v8_inspector::StringView description_view(description.start(),
description.length());
v8_inspector::V8StackTraceId id =
data->StoreCurrentStackTrace(description_view);
v8::Local<v8::ArrayBuffer> buffer =
v8::ArrayBuffer::New(isolate, sizeof(id));
*static_cast<v8_inspector::V8StackTraceId*>(buffer->GetContents().Data()) =
id;
args.GetReturnValue().Set(buffer);
}
static void ExternalAsyncTaskStarted(
const v8::FunctionCallbackInfo<v8::Value>& args) {
if (args.Length() != 1 || !args[0]->IsArrayBuffer()) {
fprintf(stderr, "Internal error: externalAsyncTaskStarted(id)\n");
Exit();
}
v8::Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
IsolateData* data = IsolateData::FromContext(context);
v8_inspector::V8StackTraceId* id =
static_cast<v8_inspector::V8StackTraceId*>(
args[0].As<v8::ArrayBuffer>()->GetContents().Data());
data->ExternalAsyncTaskStarted(*id);
}
static void ExternalAsyncTaskFinished(
const v8::FunctionCallbackInfo<v8::Value>& args) {
if (args.Length() != 1 || !args[0]->IsArrayBuffer()) {
fprintf(stderr, "Internal error: externalAsyncTaskFinished(id)\n");
Exit();
}
v8::Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
IsolateData* data = IsolateData::FromContext(context);
v8_inspector::V8StackTraceId* id =
static_cast<v8_inspector::V8StackTraceId*>(
args[0].As<v8::ArrayBuffer>()->GetContents().Data());
data->ExternalAsyncTaskFinished(*id);
}
}; };
} // namespace } // namespace
......
...@@ -203,6 +203,21 @@ void IsolateData::AsyncTaskFinished(void* task) { ...@@ -203,6 +203,21 @@ void IsolateData::AsyncTaskFinished(void* task) {
inspector_->asyncTaskFinished(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, void IsolateData::AddInspectedObject(int session_id,
v8::Local<v8::Value> object) { v8::Local<v8::Value> object) {
auto it = sessions_.find(session_id); auto it = sessions_.find(session_id);
......
...@@ -58,6 +58,12 @@ class IsolateData : public v8_inspector::V8InspectorClient { ...@@ -58,6 +58,12 @@ class IsolateData : public v8_inspector::V8InspectorClient {
bool recurring); bool recurring);
void AsyncTaskStarted(void* task); void AsyncTaskStarted(void* task);
void AsyncTaskFinished(void* task); void AsyncTaskFinished(void* task);
v8_inspector::V8StackTraceId StoreCurrentStackTrace(
const v8_inspector::StringView& description);
void ExternalAsyncTaskStarted(const v8_inspector::V8StackTraceId& parent);
void ExternalAsyncTaskFinished(const v8_inspector::V8StackTraceId& parent);
void AddInspectedObject(int session_id, v8::Local<v8::Value> object); void AddInspectedObject(int session_id, v8::Local<v8::Value> object);
// Test utilities. // Test utilities.
......
...@@ -37,8 +37,11 @@ InspectorTest.logMessage = function(originalMessage) { ...@@ -37,8 +37,11 @@ InspectorTest.logMessage = function(originalMessage) {
if (message.id) if (message.id)
message.id = "<messageId>"; message.id = "<messageId>";
const nonStableFields = new Set(["objectId", "scriptId", "exceptionId", "timestamp", const nonStableFields = new Set([
"executionContextId", "callFrameId", "breakpointId", "bindRemoteObjectFunctionId", "formatterObjectId" ]); 'objectId', 'scriptId', 'exceptionId', 'timestamp', 'executionContextId',
'callFrameId', 'breakpointId', 'bindRemoteObjectFunctionId',
'formatterObjectId', 'debuggerId'
]);
var objects = [ message ]; var objects = [ message ];
while (objects.length) { while (objects.length) {
var object = objects.shift(); var object = objects.shift();
......
...@@ -22,6 +22,7 @@ Checks createContext(). ...@@ -22,6 +22,7 @@ Checks createContext().
{ {
id : <messageId> id : <messageId>
result : { result : {
debuggerId : <debuggerId>
} }
} }
#debugger; #debugger;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment