From 3a41b697cd9ba043b670a063f595a85c904d3878 Mon Sep 17 00:00:00 2001 From: Alexey Kozyatinskiy Date: Tue, 21 Nov 2017 11:27:58 -0800 Subject: [PATCH] [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/754183 Reviewed-by: Jakob Gruber Reviewed-by: Dmitry Gozman Commit-Queue: Aleksey Kozyatinskiy Cr-Commit-Position: refs/heads/master@{#49582} --- include/v8-inspector.h | 19 ++ src/api.cc | 6 + src/debug/debug-interface.h | 2 + src/inspector/inspector_protocol_config.json | 2 +- src/inspector/js_protocol.json | 38 +++- src/inspector/string-util.cc | 16 ++ src/inspector/string-util.h | 3 + src/inspector/v8-debugger-agent-impl.cc | 44 ++++- src/inspector/v8-debugger-agent-impl.h | 10 +- src/inspector/v8-debugger.cc | 74 ++++++++ src/inspector/v8-debugger.h | 23 +++ src/inspector/v8-inspector-impl.cc | 13 ++ src/inspector/v8-inspector-impl.h | 4 + src/inspector/v8-stack-trace-impl.cc | 67 +++++-- src/inspector/v8-stack-trace-impl.h | 9 +- .../external-stack-trace-expected.txt | 42 +++++ .../debugger/external-stack-trace.js | 170 ++++++++++++++++++ test/inspector/inspector-test.cc | 61 +++++++ test/inspector/isolate-data.cc | 15 ++ test/inspector/isolate-data.h | 6 + test/inspector/protocol-test.js | 7 +- .../runtime/create-context-expected.txt | 1 + 22 files changed, 602 insertions(+), 30 deletions(-) create mode 100644 test/inspector/debugger/external-stack-trace-expected.txt create mode 100644 test/inspector/debugger/external-stack-trace.js diff --git a/include/v8-inspector.h b/include/v8-inspector.h index d0bb9b47fe..5478e127f9 100644 --- a/include/v8-inspector.h +++ b/include/v8-inspector.h @@ -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 debugger_id; + + V8StackTraceId(); + V8StackTraceId(uintptr_t id, const std::pair debugger_id); + ~V8StackTraceId() = default; + + bool IsInvalid() const; +}; + class V8_EXPORT V8Inspector { public: static std::unique_ptr 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, const StringView& message, diff --git a/src/api.cc b/src/api.cc index a767e5741d..79c937e011 100644 --- a/src/api.cc +++ b/src/api.cc @@ -10147,6 +10147,12 @@ int debug::GetNativeAccessorDescriptor(v8::Local context, return result; } +int64_t debug::GetNextRandomInt64(v8::Isolate* v8_isolate) { + return reinterpret_cast(v8_isolate) + ->random_number_generator() + ->NextInt64(); +} + Local CpuProfileNode::GetFunctionName() const { const i::ProfileNode* node = reinterpret_cast(this); i::Isolate* isolate = node->isolate(); diff --git a/src/debug/debug-interface.h b/src/debug/debug-interface.h index 6d36387538..c8c1e76ef2 100644 --- a/src/debug/debug-interface.h +++ b/src/debug/debug-interface.h @@ -498,6 +498,8 @@ int GetNativeAccessorDescriptor(v8::Local context, v8::Local object, v8::Local name); +int64_t GetNextRandomInt64(v8::Isolate* isolate); + } // namespace debug } // namespace v8 diff --git a/src/inspector/inspector_protocol_config.json b/src/inspector/inspector_protocol_config.json index 125a248919..fdb2b64b90 100644 --- a/src/inspector/inspector_protocol_config.json +++ b/src/inspector/inspector_protocol_config.json @@ -12,7 +12,7 @@ { "domain": "Runtime", "async": ["evaluate", "awaitPromise", "callFunctionOn", "runScript"], - "exported": ["StackTrace", "RemoteObject", "ExecutionContextId"] + "exported": ["StackTrace", "StackTraceId", "RemoteObject", "ExecutionContextId"] }, { "domain": "Debugger", diff --git a/src/inspector/js_protocol.json b/src/inspector/js_protocol.json index 2a8d6304b4..20a337f835 100644 --- a/src/inspector/js_protocol.json +++ b/src/inspector/js_protocol.json @@ -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 Runtime.StackTrace and Debugger.paused 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 stackTraceId.", + "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." diff --git a/src/inspector/string-util.cc b/src/inspector/string-util.cc index db6be2fc6d..31a0da0ab3 100644 --- a/src/inspector/string-util.cc +++ b/src/inspector/string-util.cc @@ -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& 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(id)); + return builder.toString(); +} + } // namespace v8_inspector diff --git a/src/inspector/string-util.h b/src/inspector/string-util.h index 80a4a1c24d..8aaf3ce850 100644 --- a/src/inspector/string-util.h +++ b/src/inspector/string-util.h @@ -87,6 +87,9 @@ class StringBufferImpl : public StringBuffer { DISALLOW_COPY_AND_ASSIGN(StringBufferImpl); }; +String16 debuggerIdToString(const std::pair& debuggerId); +String16 stackTraceIdToString(uintptr_t id); + } // namespace v8_inspector #endif // V8_INSPECTOR_STRINGUTIL_H_ diff --git a/src/inspector/v8-debugger-agent-impl.cc b/src/inspector/v8-debugger-agent-impl.cc index 82edf706af..d96c88a493 100644 --- a/src/inspector/v8-debugger-agent-impl.cc +++ b/src/inspector/v8-debugger-agent-impl.cc @@ -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 inStackTraceId, + std::unique_ptr* outStackTrace) { + bool isOk = false; + int64_t id = inStackTraceId->getId().toInteger64(&isOk); + std::pair 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>* newCallFrames, Maybe* stackChanged, Maybe* asyncStackTrace, + Maybe* asyncStackTraceId, Maybe* 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>* newCallFrames, - Maybe* asyncStackTrace) { + Maybe* asyncStackTrace, + Maybe* 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 +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() { diff --git a/src/inspector/v8-debugger-agent-impl.h b/src/inspector/v8-debugger-agent-impl.h index 7d4ed1866a..f64aafc9f9 100644 --- a/src/inspector/v8-debugger-agent-impl.h +++ b/src/inspector/v8-debugger-agent-impl.h @@ -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, Maybe targetCallFrames) override; + Response getStackTrace( + std::unique_ptr inStackTraceId, + std::unique_ptr* outStackTrace) override; Response searchInContent( const String16& scriptId, const String16& query, Maybe optionalCaseSensitive, Maybe optionalIsRegex, @@ -73,12 +76,14 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend { Maybe>* optOutCallFrames, Maybe* optOutStackChanged, Maybe* optOutAsyncStackTrace, + Maybe* optOutAsyncStackTraceId, Maybe* optOutCompileError) override; Response restartFrame( const String16& callFrameId, std::unique_ptr>* newCallFrames, - Maybe* asyncStackTrace) override; + Maybe* asyncStackTrace, + Maybe* 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>*); std::unique_ptr currentAsyncStackTrace(); + std::unique_ptr currentExternalStackTrace(); void setPauseOnExceptionsImpl(int); diff --git a/src/inspector/v8-debugger.cc b/src/inspector/v8-debugger.cc index eb39ecbeba..2444cbe1a4 100644 --- a/src/inspector/v8-debugger.cc +++ b/src/inspector/v8-debugger.cc @@ -562,6 +562,11 @@ std::shared_ptr V8Debugger::currentAsyncParent() { return m_currentAsyncParent.empty() ? nullptr : m_currentAsyncParent.back(); } +V8StackTraceId V8Debugger::currentExternalParent() { + return m_currentExternalParent.empty() ? V8StackTraceId() + : m_currentExternalParent.back(); +} + v8::MaybeLocal V8Debugger::getTargetScopes( v8::Local context, v8::Local value, ScopeTargetKind kind) { @@ -726,6 +731,51 @@ void V8Debugger::setAsyncCallStackDepth(V8DebuggerAgentImpl* agent, int depth) { if (!maxAsyncCallStackDepth) allAsyncTasksCanceled(); } +std::shared_ptr 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 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(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(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 V8Debugger::debuggerIdFor(int contextGroupId) { + auto it = m_contextGroupIdToDebuggerId.find(contextGroupId); + if (it != m_contextGroupIdToDebuggerId.end()) return it->second; + std::pair 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 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()); diff --git a/src/inspector/v8-debugger.h b/src/inspector/v8-debugger.h index 83e1a82cf3..a8a945785b 100644 --- a/src/inspector/v8-debugger.h +++ b/src/inspector/v8-debugger.h @@ -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 currentAsyncParent(); + V8StackTraceId currentExternalParent(); std::shared_ptr symbolize(v8::Local 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 debuggerIdFor(int contextGroupId); + std::pair debuggerIdFor( + const String16& serializedDebuggerId); + std::shared_ptr stackTraceFor(int contextGroupId, + const V8StackTraceId& id); + private: void clearContinueToLocation(); bool shouldContinueToCurrentLocation(); @@ -186,6 +198,7 @@ class V8Debugger : public v8::debug::DebugDelegate { std::vector m_currentTasks; std::vector> m_currentAsyncParent; + std::vector 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>; + StackTraceIdToStackTrace m_storedStackTraces; + uintptr_t m_lastStackTraceId = 0; + + protocol::HashMap> + m_contextGroupIdToDebuggerId; + protocol::HashMap> + m_serializedDebuggerIdToDebuggerId; + WasmTranslation m_wasmTranslation; DISALLOW_COPY_AND_ASSIGN(V8Debugger); diff --git a/src/inspector/v8-inspector-impl.cc b/src/inspector/v8-inspector-impl.cc index 448808cd15..a29f07b0bf 100644 --- a/src/inspector/v8-inspector-impl.cc +++ b/src/inspector/v8-inspector-impl.cc @@ -287,6 +287,19 @@ std::unique_ptr 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; diff --git a/src/inspector/v8-inspector-impl.h b/src/inspector/v8-inspector-impl.h index fa9fdcf1ee..92e7b21960 100644 --- a/src/inspector/v8-inspector-impl.h +++ b/src/inspector/v8-inspector-impl.h @@ -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(); diff --git a/src/inspector/v8-stack-trace-impl.cc b/src/inspector/v8-stack-trace-impl.cc index 21853f05d7..ebb4512a09 100644 --- a/src/inspector/v8-stack-trace-impl.cc +++ b/src/inspector/v8-stack-trace-impl.cc @@ -32,8 +32,10 @@ std::vector> toFramesVector( void calculateAsyncChain(V8Debugger* debugger, int contextGroupId, std::shared_ptr* 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 buildInspectorObjectCommon( const std::vector>& frames, const String16& description, - const std::shared_ptr& asyncParent, int maxAsyncDepth) { + const std::shared_ptr& asyncParent, + const V8StackTraceId& externalParent, int maxAsyncDepth) { if (asyncParent && frames.empty() && description == asyncParent->description()) { return asyncParent->buildInspectorObject(maxAsyncDepth); @@ -75,11 +79,26 @@ std::unique_ptr 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 debugger_id) + : id(id), debugger_id(debugger_id) {} + +bool V8StackTraceId::IsInvalid() const { return !id; } + StackFrame::StackFrame(v8::Local v8Frame) : m_functionName(toProtocolString(v8Frame->GetFunctionName())), m_scriptId(String16::fromInteger(v8Frame->GetScriptId())), @@ -145,10 +164,13 @@ std::unique_ptr V8StackTraceImpl::create( int maxAsyncDepth = 0; std::shared_ptr asyncParent; - calculateAsyncChain(debugger, contextGroupId, &asyncParent, &maxAsyncDepth); - if (frames.empty() && !asyncParent) return nullptr; - return std::unique_ptr( - 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(new V8StackTraceImpl( + std::move(frames), maxAsyncDepth, asyncParent, externalParent)); } // static @@ -168,16 +190,18 @@ std::unique_ptr V8StackTraceImpl::capture( V8StackTraceImpl::V8StackTraceImpl( std::vector> frames, int maxAsyncDepth, - std::shared_ptr asyncParent) + std::shared_ptr 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 V8StackTraceImpl::clone() { - return std::unique_ptr( - new V8StackTraceImpl(m_frames, 0, std::shared_ptr())); + return std::unique_ptr(new V8StackTraceImpl( + m_frames, 0, std::shared_ptr(), V8StackTraceId())); } bool V8StackTraceImpl::isEmpty() const { return m_frames.empty(); } @@ -205,7 +229,7 @@ StringView V8StackTraceImpl::topFunctionName() const { std::unique_ptr V8StackTraceImpl::buildInspectorObjectImpl() const { return buildInspectorObjectCommon(m_frames, String16(), m_asyncParent.lock(), - m_maxAsyncDepth); + m_externalParent, m_maxAsyncDepth); } std::unique_ptr @@ -292,9 +316,12 @@ std::shared_ptr AsyncStackTrace::capture( } std::shared_ptr 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::capture( if (!contextGroupId && asyncParent) { contextGroupId = asyncParent->m_contextGroupId; } - return std::shared_ptr(new AsyncStackTrace( - contextGroupId, description, std::move(frames), asyncParent)); + return std::shared_ptr( + new AsyncStackTrace(contextGroupId, description, std::move(frames), + asyncParent, externalParent)); } AsyncStackTrace::AsyncStackTrace( int contextGroupId, const String16& description, std::vector> frames, - std::shared_ptr asyncParent) + std::shared_ptr 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 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; } diff --git a/src/inspector/v8-stack-trace-impl.h b/src/inspector/v8-stack-trace-impl.h index 0f4e2ebd2c..8c50925989 100644 --- a/src/inspector/v8-stack-trace-impl.h +++ b/src/inspector/v8-stack-trace-impl.h @@ -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> frames, int maxAsyncDepth, - std::shared_ptr asyncParent); + std::shared_ptr asyncParent, + const V8StackTraceId& externalParent); class StackFrameIterator { public: @@ -97,6 +99,7 @@ class V8StackTraceImpl : public V8StackTrace { std::vector> m_frames; int m_maxAsyncDepth; std::weak_ptr 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> frames, - std::shared_ptr asyncParent); + std::shared_ptr asyncParent, + const V8StackTraceId& externalParent); int m_contextGroupId; String16 m_description; std::vector> m_frames; std::weak_ptr m_asyncParent; + V8StackTraceId m_externalParent; DISALLOW_COPY_AND_ASSIGN(AsyncStackTrace); }; diff --git a/test/inspector/debugger/external-stack-trace-expected.txt b/test/inspector/debugger/external-stack-trace-expected.txt new file mode 100644 index 0000000000..fef370e2e8 --- /dev/null +++ b/test/inspector/debugger/external-stack-trace-expected.txt @@ -0,0 +1,42 @@ +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 : + result : { + stackTrace : { + callFrames : [ + [0] : { + columnNumber : 15 + functionName : + lineNumber : 0 + 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) diff --git a/test/inspector/debugger/external-stack-trace.js b/test/inspector/debugger/external-stack-trace.js new file mode 100644 index 0000000000..c8392e28c7 --- /dev/null +++ b/test/inspector/debugger/external-stack-trace.js @@ -0,0 +1,170 @@ +// 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(); + } +]); diff --git a/test/inspector/inspector-test.cc b/test/inspector/inspector-test.cc index 5df79594d8..56c7431af6 100644 --- a/test/inspector/inspector-test.cc +++ b/test/inspector/inspector-test.cc @@ -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& args) { + if (args.Length() != 1 || !args[0]->IsString()) { + fprintf(stderr, + "Internal error: storeCurrentStackTrace('description')\n"); + Exit(); + } + v8::Isolate* isolate = args.GetIsolate(); + v8::Local context = isolate->GetCurrentContext(); + IsolateData* data = IsolateData::FromContext(context); + v8::internal::Vector description = + ToVector(args[0].As()); + v8_inspector::StringView description_view(description.start(), + description.length()); + v8_inspector::V8StackTraceId id = + data->StoreCurrentStackTrace(description_view); + v8::Local buffer = + v8::ArrayBuffer::New(isolate, sizeof(id)); + *static_cast(buffer->GetContents().Data()) = + id; + args.GetReturnValue().Set(buffer); + } + + static void ExternalAsyncTaskStarted( + const v8::FunctionCallbackInfo& args) { + if (args.Length() != 1 || !args[0]->IsArrayBuffer()) { + fprintf(stderr, "Internal error: externalAsyncTaskStarted(id)\n"); + Exit(); + } + v8::Local context = args.GetIsolate()->GetCurrentContext(); + IsolateData* data = IsolateData::FromContext(context); + v8_inspector::V8StackTraceId* id = + static_cast( + args[0].As()->GetContents().Data()); + data->ExternalAsyncTaskStarted(*id); + } + + static void ExternalAsyncTaskFinished( + const v8::FunctionCallbackInfo& args) { + if (args.Length() != 1 || !args[0]->IsArrayBuffer()) { + fprintf(stderr, "Internal error: externalAsyncTaskFinished(id)\n"); + Exit(); + } + v8::Local context = args.GetIsolate()->GetCurrentContext(); + IsolateData* data = IsolateData::FromContext(context); + v8_inspector::V8StackTraceId* id = + static_cast( + args[0].As()->GetContents().Data()); + data->ExternalAsyncTaskFinished(*id); + } }; } // namespace diff --git a/test/inspector/isolate-data.cc b/test/inspector/isolate-data.cc index 5c7845a7af..a18ef90ca0 100644 --- a/test/inspector/isolate-data.cc +++ b/test/inspector/isolate-data.cc @@ -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 object) { auto it = sessions_.find(session_id); diff --git a/test/inspector/isolate-data.h b/test/inspector/isolate-data.h index 6d727e0632..5eb9803a74 100644 --- a/test/inspector/isolate-data.h +++ b/test/inspector/isolate-data.h @@ -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 object); // Test utilities. diff --git a/test/inspector/protocol-test.js b/test/inspector/protocol-test.js index 4ae96614dc..749aa3fecc 100644 --- a/test/inspector/protocol-test.js +++ b/test/inspector/protocol-test.js @@ -37,8 +37,11 @@ InspectorTest.logMessage = function(originalMessage) { if (message.id) message.id = ""; - 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(); diff --git a/test/inspector/runtime/create-context-expected.txt b/test/inspector/runtime/create-context-expected.txt index e64f75bc57..770d2e32d2 100644 --- a/test/inspector/runtime/create-context-expected.txt +++ b/test/inspector/runtime/create-context-expected.txt @@ -22,6 +22,7 @@ Checks createContext(). { id : result : { + debuggerId : } } #debugger;