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 {
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 {
public:
static std::unique_ptr<V8Inspector> create(v8::Isolate*, V8InspectorClient*);
......@@ -237,6 +251,11 @@ class V8_EXPORT V8Inspector {
virtual void asyncTaskFinished(void* task) = 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.
virtual unsigned exceptionThrown(
v8::Local<v8::Context>, const StringView& message,
......
......@@ -10147,6 +10147,12 @@ int debug::GetNativeAccessorDescriptor(v8::Local<v8::Context> context,
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 {
const i::ProfileNode* node = reinterpret_cast<const i::ProfileNode*>(this);
i::Isolate* isolate = node->isolate();
......
......@@ -498,6 +498,8 @@ int GetNativeAccessorDescriptor(v8::Local<v8::Context> context,
v8::Local<v8::Object> object,
v8::Local<v8::Name> name);
int64_t GetNextRandomInt64(v8::Isolate* isolate);
} // namespace debug
} // namespace v8
......
......@@ -12,7 +12,7 @@
{
"domain": "Runtime",
"async": ["evaluate", "awaitPromise", "callFunctionOn", "runScript"],
"exported": ["StackTrace", "RemoteObject", "ExecutionContextId"]
"exported": ["StackTrace", "StackTraceId", "RemoteObject", "ExecutionContextId"]
},
{
"domain": "Debugger",
......
......@@ -201,13 +201,30 @@
"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": "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",
"type": "string",
"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": [
......@@ -516,6 +533,9 @@
"commands": [
{
"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."
},
{
......@@ -627,6 +647,17 @@
"name": "resume",
"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",
"parameters": [
......@@ -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": "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": "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." }
],
"description": "Edits JavaScript source live."
......@@ -663,7 +695,8 @@
],
"returns": [
{ "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."
},
......@@ -803,6 +836,7 @@
{ "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": "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." }
],
"description": "Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria."
......
......@@ -4,6 +4,7 @@
#include "src/inspector/string-util.h"
#include "src/base/platform/platform.h"
#include "src/conversions.h"
#include "src/inspector/protocol/Protocol.h"
#include "src/unicode-cache.h"
......@@ -151,4 +152,19 @@ StringBufferImpl::StringBufferImpl(String16& string) {
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
......@@ -87,6 +87,9 @@ class StringBufferImpl : public StringBuffer {
DISALLOW_COPY_AND_ASSIGN(StringBufferImpl);
};
String16 debuggerIdToString(const std::pair<int64_t, int64_t>& debuggerId);
String16 stackTraceIdToString(uintptr_t id);
} // namespace v8_inspector
#endif // V8_INSPECTOR_STRINGUTIL_H_
......@@ -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 (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId()))
......@@ -715,6 +717,27 @@ Response V8DebuggerAgentImpl::continueToLocation(
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,
const v8::debug::Location& start,
const v8::debug::Location& end) {
......@@ -816,6 +839,7 @@ Response V8DebuggerAgentImpl::setScriptSource(
Maybe<protocol::Array<protocol::Debugger::CallFrame>>* newCallFrames,
Maybe<bool>* stackChanged,
Maybe<protocol::Runtime::StackTrace>* asyncStackTrace,
Maybe<protocol::Runtime::StackTraceId>* asyncStackTraceId,
Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) {
if (!enabled()) return Response::Error(kDebuggerNotEnabled);
......@@ -858,13 +882,15 @@ Response V8DebuggerAgentImpl::setScriptSource(
if (!response.isSuccess()) return response;
*newCallFrames = std::move(callFrames);
*asyncStackTrace = currentAsyncStackTrace();
*asyncStackTraceId = currentExternalStackTrace();
return Response::OK();
}
Response V8DebuggerAgentImpl::restartFrame(
const String16& callFrameId,
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);
InjectedScript::CallFrameScope scope(m_session, callFrameId);
Response response = scope.initialize();
......@@ -880,6 +906,7 @@ Response V8DebuggerAgentImpl::restartFrame(
response = currentCallFrames(newCallFrames);
if (!response.isSuccess()) return response;
*asyncStackTrace = currentAsyncStackTrace();
*asyncStackTraceId = currentExternalStackTrace();
return Response::OK();
}
......@@ -1287,6 +1314,16 @@ V8DebuggerAgentImpl::currentAsyncStackTrace() {
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 {
return m_debugger->isPausedInContextGroup(m_session->contextGroupId());
}
......@@ -1498,7 +1535,8 @@ void V8DebuggerAgentImpl::didPause(
m_frontend.paused(std::move(protocolCallFrames), breakReason,
std::move(breakAuxData), std::move(hitBreakpointIds),
currentAsyncStackTrace(), std::move(scheduledAsyncTaskId));
currentAsyncStackTrace(), currentExternalStackTrace(),
std::move(scheduledAsyncTaskId));
}
void V8DebuggerAgentImpl::didContinue() {
......
......@@ -39,7 +39,7 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
void restore();
// Part of the protocol.
Response enable() override;
Response enable(String16* outDebuggerId) override;
Response disable() override;
Response setBreakpointsActive(bool active) override;
Response setSkipAllPauses(bool skip) override;
......@@ -57,6 +57,9 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
Response removeBreakpoint(const String16& breakpointId) override;
Response continueToLocation(std::unique_ptr<protocol::Debugger::Location>,
Maybe<String16> targetCallFrames) override;
Response getStackTrace(
std::unique_ptr<protocol::Runtime::StackTraceId> inStackTraceId,
std::unique_ptr<protocol::Runtime::StackTrace>* outStackTrace) override;
Response searchInContent(
const String16& scriptId, const String16& query,
Maybe<bool> optionalCaseSensitive, Maybe<bool> optionalIsRegex,
......@@ -73,12 +76,14 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
Maybe<protocol::Array<protocol::Debugger::CallFrame>>* optOutCallFrames,
Maybe<bool>* optOutStackChanged,
Maybe<protocol::Runtime::StackTrace>* optOutAsyncStackTrace,
Maybe<protocol::Runtime::StackTraceId>* optOutAsyncStackTraceId,
Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) override;
Response restartFrame(
const String16& callFrameId,
std::unique_ptr<protocol::Array<protocol::Debugger::CallFrame>>*
newCallFrames,
Maybe<protocol::Runtime::StackTrace>* asyncStackTrace) override;
Maybe<protocol::Runtime::StackTrace>* asyncStackTrace,
Maybe<protocol::Runtime::StackTraceId>* asyncStackTraceId) override;
Response getScriptSource(const String16& scriptId,
String16* scriptSource) override;
Response pause() override;
......@@ -149,6 +154,7 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
Response currentCallFrames(
std::unique_ptr<protocol::Array<protocol::Debugger::CallFrame>>*);
std::unique_ptr<protocol::Runtime::StackTrace> currentAsyncStackTrace();
std::unique_ptr<protocol::Runtime::StackTraceId> currentExternalStackTrace();
void setPauseOnExceptionsImpl(int);
......
......@@ -562,6 +562,11 @@ std::shared_ptr<AsyncStackTrace> V8Debugger::currentAsyncParent() {
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::Local<v8::Context> context, v8::Local<v8::Value> value,
ScopeTargetKind kind) {
......@@ -726,6 +731,51 @@ void V8Debugger::setAsyncCallStackDepth(V8DebuggerAgentImpl* agent, int depth) {
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,
bool recurring) {
asyncTaskScheduledForStack(toString16(taskName), task, recurring);
......@@ -785,6 +835,7 @@ void V8Debugger::asyncTaskStartedForStack(void* task) {
} else {
m_currentAsyncParent.emplace_back();
}
m_currentExternalParent.emplace_back();
}
void V8Debugger::asyncTaskFinishedForStack(void* task) {
......@@ -795,6 +846,7 @@ void V8Debugger::asyncTaskFinishedForStack(void* task) {
m_currentTasks.pop_back();
m_currentAsyncParent.pop_back();
m_currentExternalParent.pop_back();
if (m_recurringTasks.find(task) == m_recurringTasks.end()) {
asyncTaskCanceledForStack(task);
......@@ -841,6 +893,7 @@ void V8Debugger::allAsyncTasksCanceled() {
m_asyncTaskStacks.clear();
m_recurringTasks.clear();
m_currentAsyncParent.clear();
m_currentExternalParent.clear();
m_currentTasks.clear();
m_framesCache.clear();
......@@ -892,6 +945,7 @@ void V8Debugger::collectOldAsyncStacksIfNeeded() {
--m_asyncStacksCount;
}
cleanupExpiredWeakPointers(m_asyncTaskStacks);
cleanupExpiredWeakPointers(m_storedStackTraces);
for (auto it = m_recurringTasks.begin(); it != m_recurringTasks.end();) {
if (m_asyncTaskStacks.find(*it) == m_asyncTaskStacks.end()) {
it = m_recurringTasks.erase(it);
......@@ -927,6 +981,26 @@ void V8Debugger::setMaxAsyncTaskStacksForTest(int 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() {
fprintf(stdout, "Async stacks count: %d\n", m_asyncStacksCount);
fprintf(stdout, "Scheduled async tasks: %zu\n", m_asyncTaskStacks.size());
......
......@@ -27,6 +27,7 @@ class V8Debugger;
class V8DebuggerAgentImpl;
class V8InspectorImpl;
class V8StackTraceImpl;
struct V8StackTraceId;
using protocol::Response;
using ScheduleStepIntoAsyncCallback =
......@@ -79,6 +80,7 @@ class V8Debugger : public v8::debug::DebugDelegate {
void setAsyncCallStackDepth(V8DebuggerAgentImpl*, int);
std::shared_ptr<AsyncStackTrace> currentAsyncParent();
V8StackTraceId currentExternalParent();
std::shared_ptr<StackFrame> symbolize(v8::Local<v8::StackFrame> v8Frame);
......@@ -98,6 +100,10 @@ class V8Debugger : public v8::debug::DebugDelegate {
void asyncTaskFinished(void* task);
void allAsyncTasksCanceled();
V8StackTraceId storeCurrentStackTrace(const StringView& description);
void externalAsyncTaskStarted(const V8StackTraceId& parent);
void externalAsyncTaskFinished(const V8StackTraceId& parent);
void muteScriptParsedEvents();
void unmuteScriptParsedEvents();
......@@ -110,6 +116,12 @@ class V8Debugger : public v8::debug::DebugDelegate {
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:
void clearContinueToLocation();
bool shouldContinueToCurrentLocation();
......@@ -186,6 +198,7 @@ class V8Debugger : public v8::debug::DebugDelegate {
std::vector<void*> m_currentTasks;
std::vector<std::shared_ptr<AsyncStackTrace>> m_currentAsyncParent;
std::vector<V8StackTraceId> m_currentExternalParent;
void collectOldAsyncStacksIfNeeded();
int m_asyncStacksCount = 0;
......@@ -204,6 +217,16 @@ class V8Debugger : public v8::debug::DebugDelegate {
bool m_pauseOnAsyncCall = false;
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;
DISALLOW_COPY_AND_ASSIGN(V8Debugger);
......
......@@ -287,6 +287,19 @@ std::unique_ptr<V8StackTrace> V8InspectorImpl::captureStackTrace(
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,
bool recurring) {
if (!task) return;
......
......@@ -97,6 +97,10 @@ class V8InspectorImpl : public V8Inspector {
void asyncTaskFinished(void* task) 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; }
void enableStackCapturingIfNeeded();
void disableStackCapturingIfNeeded();
......
......@@ -32,8 +32,10 @@ std::vector<std::shared_ptr<StackFrame>> toFramesVector(
void calculateAsyncChain(V8Debugger* debugger, int contextGroupId,
std::shared_ptr<AsyncStackTrace>* asyncParent,
int* maxAsyncDepth) {
V8StackTraceId* externalParent, int* maxAsyncDepth) {
*asyncParent = debugger->currentAsyncParent();
*externalParent = debugger->currentExternalParent();
DCHECK(externalParent->IsInvalid() || !*asyncParent);
if (maxAsyncDepth) *maxAsyncDepth = debugger->maxAsyncCallChainDepth();
// Do not accidentally append async call chain from another group. This should
......@@ -42,6 +44,7 @@ void calculateAsyncChain(V8Debugger* debugger, int contextGroupId,
if (contextGroupId && *asyncParent &&
(*asyncParent)->contextGroupId() != contextGroupId) {
asyncParent->reset();
*externalParent = V8StackTraceId();
if (maxAsyncDepth) *maxAsyncDepth = 0;
return;
}
......@@ -56,7 +59,8 @@ void calculateAsyncChain(V8Debugger* debugger, int contextGroupId,
std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectCommon(
const std::vector<std::shared_ptr<StackFrame>>& frames,
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() &&
description == asyncParent->description()) {
return asyncParent->buildInspectorObject(maxAsyncDepth);
......@@ -75,11 +79,26 @@ std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectCommon(
if (asyncParent && maxAsyncDepth > 0) {
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;
}
} // 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)
: m_functionName(toProtocolString(v8Frame->GetFunctionName())),
m_scriptId(String16::fromInteger(v8Frame->GetScriptId())),
......@@ -145,10 +164,13 @@ std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::create(
int maxAsyncDepth = 0;
std::shared_ptr<AsyncStackTrace> asyncParent;
calculateAsyncChain(debugger, contextGroupId, &asyncParent, &maxAsyncDepth);
if (frames.empty() && !asyncParent) return nullptr;
return std::unique_ptr<V8StackTraceImpl>(
new V8StackTraceImpl(std::move(frames), maxAsyncDepth, asyncParent));
V8StackTraceId externalParent;
calculateAsyncChain(debugger, contextGroupId, &asyncParent, &externalParent,
&maxAsyncDepth);
if (frames.empty() && !asyncParent && externalParent.IsInvalid())
return nullptr;
return std::unique_ptr<V8StackTraceImpl>(new V8StackTraceImpl(
std::move(frames), maxAsyncDepth, asyncParent, externalParent));
}
// static
......@@ -168,16 +190,18 @@ std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::capture(
V8StackTraceImpl::V8StackTraceImpl(
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_maxAsyncDepth(maxAsyncDepth),
m_asyncParent(asyncParent) {}
m_asyncParent(asyncParent),
m_externalParent(externalParent) {}
V8StackTraceImpl::~V8StackTraceImpl() {}
std::unique_ptr<V8StackTrace> V8StackTraceImpl::clone() {
return std::unique_ptr<V8StackTrace>(
new V8StackTraceImpl(m_frames, 0, std::shared_ptr<AsyncStackTrace>()));
return std::unique_ptr<V8StackTrace>(new V8StackTraceImpl(
m_frames, 0, std::shared_ptr<AsyncStackTrace>(), V8StackTraceId()));
}
bool V8StackTraceImpl::isEmpty() const { return m_frames.empty(); }
......@@ -205,7 +229,7 @@ StringView V8StackTraceImpl::topFunctionName() const {
std::unique_ptr<protocol::Runtime::StackTrace>
V8StackTraceImpl::buildInspectorObjectImpl() const {
return buildInspectorObjectCommon(m_frames, String16(), m_asyncParent.lock(),
m_maxAsyncDepth);
m_externalParent, m_maxAsyncDepth);
}
std::unique_ptr<protocol::Runtime::API::StackTrace>
......@@ -292,9 +316,12 @@ std::shared_ptr<AsyncStackTrace> AsyncStackTrace::capture(
}
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
// but doesn't synchronous we can merge them together. e.g. Promise
......@@ -308,25 +335,29 @@ std::shared_ptr<AsyncStackTrace> AsyncStackTrace::capture(
if (!contextGroupId && asyncParent) {
contextGroupId = asyncParent->m_contextGroupId;
}
return std::shared_ptr<AsyncStackTrace>(new AsyncStackTrace(
contextGroupId, description, std::move(frames), asyncParent));
return std::shared_ptr<AsyncStackTrace>(
new AsyncStackTrace(contextGroupId, description, std::move(frames),
asyncParent, externalParent));
}
AsyncStackTrace::AsyncStackTrace(
int contextGroupId, const String16& description,
std::vector<std::shared_ptr<StackFrame>> frames,
std::shared_ptr<AsyncStackTrace> asyncParent)
std::shared_ptr<AsyncStackTrace> asyncParent,
const V8StackTraceId& externalParent)
: m_contextGroupId(contextGroupId),
m_description(description),
m_frames(std::move(frames)),
m_asyncParent(asyncParent) {
m_asyncParent(asyncParent),
m_externalParent(externalParent) {
DCHECK(m_contextGroupId);
}
std::unique_ptr<protocol::Runtime::StackTrace>
AsyncStackTrace::buildInspectorObject(int maxAsyncDepth) const {
return buildInspectorObjectCommon(m_frames, m_description,
m_asyncParent.lock(), maxAsyncDepth);
m_asyncParent.lock(), m_externalParent,
maxAsyncDepth);
}
int AsyncStackTrace::contextGroupId() const { return m_contextGroupId; }
......
......@@ -19,6 +19,7 @@ namespace v8_inspector {
class AsyncStackTrace;
class V8Debugger;
class WasmTranslation;
struct V8StackTraceId;
class StackFrame {
public:
......@@ -78,7 +79,8 @@ class V8StackTraceImpl : public V8StackTrace {
private:
V8StackTraceImpl(std::vector<std::shared_ptr<StackFrame>> frames,
int maxAsyncDepth,
std::shared_ptr<AsyncStackTrace> asyncParent);
std::shared_ptr<AsyncStackTrace> asyncParent,
const V8StackTraceId& externalParent);
class StackFrameIterator {
public:
......@@ -97,6 +99,7 @@ class V8StackTraceImpl : public V8StackTrace {
std::vector<std::shared_ptr<StackFrame>> m_frames;
int m_maxAsyncDepth;
std::weak_ptr<AsyncStackTrace> m_asyncParent;
V8StackTraceId m_externalParent;
DISALLOW_COPY_AND_ASSIGN(V8StackTraceImpl);
};
......@@ -123,13 +126,15 @@ class AsyncStackTrace {
private:
AsyncStackTrace(int contextGroupId, const String16& description,
std::vector<std::shared_ptr<StackFrame>> frames,
std::shared_ptr<AsyncStackTrace> asyncParent);
std::shared_ptr<AsyncStackTrace> asyncParent,
const V8StackTraceId& externalParent);
int m_contextGroupId;
String16 m_description;
std::vector<std::shared_ptr<StackFrame>> m_frames;
std::weak_ptr<AsyncStackTrace> m_asyncParent;
V8StackTraceId m_externalParent;
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 {
inspector->Set(ToV8String(isolate, "createObjectWithAccessor"),
v8::FunctionTemplate::New(
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);
}
......@@ -865,6 +875,57 @@ class InspectorExtension : public IsolateData::SetupGlobalTask {
v8::Isolate* isolate = info.GetIsolate();
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
......
......@@ -203,6 +203,21 @@ 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);
......
......@@ -58,6 +58,12 @@ class IsolateData : public v8_inspector::V8InspectorClient {
bool recurring);
void AsyncTaskStarted(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);
// Test utilities.
......
......@@ -37,8 +37,11 @@ InspectorTest.logMessage = function(originalMessage) {
if (message.id)
message.id = "<messageId>";
const nonStableFields = new Set(["objectId", "scriptId", "exceptionId", "timestamp",
"executionContextId", "callFrameId", "breakpointId", "bindRemoteObjectFunctionId", "formatterObjectId" ]);
const nonStableFields = new Set([
'objectId', 'scriptId', 'exceptionId', 'timestamp', 'executionContextId',
'callFrameId', 'breakpointId', 'bindRemoteObjectFunctionId',
'formatterObjectId', 'debuggerId'
]);
var objects = [ message ];
while (objects.length) {
var object = objects.shift();
......
......@@ -22,6 +22,7 @@ Checks createContext().
{
id : <messageId>
result : {
debuggerId : <debuggerId>
}
}
#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