From c30472b83e0c8ce92fa12810b173bf0079fb6da5 Mon Sep 17 00:00:00 2001 From: Alexey Kozyatinskiy Date: Wed, 29 Nov 2017 14:28:31 -0800 Subject: [PATCH] [inspector] external stack intrumentation can be called on one debugger Some embedders primitive can trigger execution in current JavaScript instance or in another (e.g. MessageChannel). With this CL external async task can be local as well. R=dgozman@chromium.org Bug: chromium:661705 Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel Change-Id: I82c68a021c2c25bc67a706c4bfed8c1a2b2388c5 Reviewed-on: https://chromium-review.googlesource.com/792015 Commit-Queue: Aleksey Kozyatinskiy Reviewed-by: Dmitry Gozman Cr-Commit-Position: refs/heads/master@{#49728} --- src/inspector/v8-debugger-agent-impl.cc | 32 +++++--- src/inspector/v8-debugger-agent-impl.h | 1 + src/inspector/v8-debugger.cc | 36 +++++---- src/inspector/v8-debugger.h | 8 +- .../debugger/external-stack-trace.js | 1 - ...ernal-async-task-same-context-expected.txt | 14 ++++ ...p-into-external-async-task-same-context.js | 81 +++++++++++++++++++ 7 files changed, 140 insertions(+), 33 deletions(-) create mode 100644 test/inspector/debugger/step-into-external-async-task-same-context-expected.txt create mode 100644 test/inspector/debugger/step-into-external-async-task-same-context.js diff --git a/src/inspector/v8-debugger-agent-impl.cc b/src/inspector/v8-debugger-agent-impl.cc index 8e5142d36e..f7462b452d 100644 --- a/src/inspector/v8-debugger-agent-impl.cc +++ b/src/inspector/v8-debugger-agent-impl.cc @@ -1330,6 +1330,24 @@ V8DebuggerAgentImpl::currentExternalStackTrace() { .build(); } +std::unique_ptr +V8DebuggerAgentImpl::currentScheduledAsyncCall() { + v8_inspector::V8StackTraceId scheduledAsyncCall = + m_debugger->scheduledAsyncCall(); + if (scheduledAsyncCall.IsInvalid()) return nullptr; + std::unique_ptr asyncCallStackTrace = + protocol::Runtime::StackTraceId::create() + .setId(stackTraceIdToString(scheduledAsyncCall.id)) + .build(); + // TODO(kozyatinskiy): extract this check to IsLocal function. + if (scheduledAsyncCall.debugger_id.first || + scheduledAsyncCall.debugger_id.second) { + asyncCallStackTrace->setDebuggerId( + debuggerIdToString(scheduledAsyncCall.debugger_id)); + } + return asyncCallStackTrace; +} + bool V8DebuggerAgentImpl::isPaused() const { return m_debugger->isPausedInContextGroup(m_session->contextGroupId()); } @@ -1532,22 +1550,10 @@ void V8DebuggerAgentImpl::didPause( Response response = currentCallFrames(&protocolCallFrames); if (!response.isSuccess()) protocolCallFrames = Array::create(); - Maybe asyncCallStackTrace; - void* rawScheduledAsyncTask = m_debugger->scheduledAsyncTask(); - if (rawScheduledAsyncTask) { - asyncCallStackTrace = - protocol::Runtime::StackTraceId::create() - .setId(stackTraceIdToString( - reinterpret_cast(rawScheduledAsyncTask))) - .setDebuggerId(debuggerIdToString( - m_debugger->debuggerIdFor(m_session->contextGroupId()))) - .build(); - } - m_frontend.paused(std::move(protocolCallFrames), breakReason, std::move(breakAuxData), std::move(hitBreakpointIds), currentAsyncStackTrace(), currentExternalStackTrace(), - std::move(asyncCallStackTrace)); + currentScheduledAsyncCall()); } void V8DebuggerAgentImpl::didContinue() { diff --git a/src/inspector/v8-debugger-agent-impl.h b/src/inspector/v8-debugger-agent-impl.h index e697b700e9..168c5a7724 100644 --- a/src/inspector/v8-debugger-agent-impl.h +++ b/src/inspector/v8-debugger-agent-impl.h @@ -156,6 +156,7 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend { std::unique_ptr>*); std::unique_ptr currentAsyncStackTrace(); std::unique_ptr currentExternalStackTrace(); + std::unique_ptr currentScheduledAsyncCall(); void setPauseOnExceptionsImpl(int); diff --git a/src/inspector/v8-debugger.cc b/src/inspector/v8-debugger.cc index 8f843b54b2..2a54a136fa 100644 --- a/src/inspector/v8-debugger.cc +++ b/src/inspector/v8-debugger.cc @@ -331,11 +331,7 @@ void V8Debugger::pauseOnAsyncCall(int targetContextGroupId, uintptr_t task, m_targetContextGroupId = targetContextGroupId; m_taskWithScheduledBreak = reinterpret_cast(task); - String16 currentDebuggerId = - debuggerIdToString(debuggerIdFor(targetContextGroupId)); - if (currentDebuggerId != debuggerId) { - m_taskWithScheduledBreakDebuggerId = debuggerId; - } + m_taskWithScheduledBreakDebuggerId = debuggerId; } Response V8Debugger::continueToLocation( @@ -542,19 +538,19 @@ void V8Debugger::PromiseEventOccurred(v8::debug::PromiseDebugActionType type, switch (type) { case v8::debug::kDebugAsyncFunctionPromiseCreated: asyncTaskScheduledForStack("async function", task, true); - if (!isBlackboxed) asyncTaskCandidateForStepping(task); + if (!isBlackboxed) asyncTaskCandidateForStepping(task, true); break; case v8::debug::kDebugPromiseThen: asyncTaskScheduledForStack("Promise.then", task, false); - if (!isBlackboxed) asyncTaskCandidateForStepping(task); + if (!isBlackboxed) asyncTaskCandidateForStepping(task, true); break; case v8::debug::kDebugPromiseCatch: asyncTaskScheduledForStack("Promise.catch", task, false); - if (!isBlackboxed) asyncTaskCandidateForStepping(task); + if (!isBlackboxed) asyncTaskCandidateForStepping(task, true); break; case v8::debug::kDebugPromiseFinally: asyncTaskScheduledForStack("Promise.finally", task, false); - if (!isBlackboxed) asyncTaskCandidateForStepping(task); + if (!isBlackboxed) asyncTaskCandidateForStepping(task, true); break; case v8::debug::kDebugWillHandle: asyncTaskStartedForStack(task); @@ -767,7 +763,7 @@ V8StackTraceId V8Debugger::storeCurrentStackTrace( ++m_asyncStacksCount; collectOldAsyncStacksIfNeeded(); - asyncTaskCandidateForStepping(reinterpret_cast(id)); + asyncTaskCandidateForStepping(reinterpret_cast(id), false); return V8StackTraceId(id, debuggerIdFor(contextGroupId)); } @@ -816,7 +812,7 @@ void V8Debugger::externalAsyncTaskFinished(const V8StackTraceId& parent) { void V8Debugger::asyncTaskScheduled(const StringView& taskName, void* task, bool recurring) { asyncTaskScheduledForStack(toString16(taskName), task, recurring); - asyncTaskCandidateForStepping(task); + asyncTaskCandidateForStepping(task, true); } void V8Debugger::asyncTaskCanceled(void* task) { @@ -890,16 +886,23 @@ void V8Debugger::asyncTaskFinishedForStack(void* task) { } } -void V8Debugger::asyncTaskCandidateForStepping(void* task) { - if (m_pauseOnAsyncCall) { - m_scheduledAsyncTask = task; +void V8Debugger::asyncTaskCandidateForStepping(void* task, bool isLocal) { + int contextGroupId = currentContextGroupId(); + if (m_pauseOnAsyncCall && contextGroupId) { + if (isLocal) { + m_scheduledAsyncCall = v8_inspector::V8StackTraceId( + reinterpret_cast(task), std::make_pair(0, 0)); + } else { + m_scheduledAsyncCall = v8_inspector::V8StackTraceId( + reinterpret_cast(task), debuggerIdFor(contextGroupId)); + } breakProgram(m_targetContextGroupId); - m_scheduledAsyncTask = nullptr; + m_scheduledAsyncCall = v8_inspector::V8StackTraceId(); return; } if (!m_stepIntoAsyncCallback) return; DCHECK(m_targetContextGroupId); - if (currentContextGroupId() != m_targetContextGroupId) return; + if (contextGroupId != m_targetContextGroupId) return; m_taskWithScheduledBreak = task; v8::debug::ClearStepping(m_isolate); m_stepIntoAsyncCallback->sendSuccess(); @@ -1031,6 +1034,7 @@ std::pair V8Debugger::debuggerIdFor(int contextGroupId) { std::pair debuggerId( v8::debug::GetNextRandomInt64(m_isolate), v8::debug::GetNextRandomInt64(m_isolate)); + if (!debuggerId.first && !debuggerId.second) ++debuggerId.first; m_contextGroupIdToDebuggerId.insert( it, std::make_pair(contextGroupId, debuggerId)); m_serializedDebuggerIdToDebuggerId.insert( diff --git a/src/inspector/v8-debugger.h b/src/inspector/v8-debugger.h index 455bb5952d..4828fcad52 100644 --- a/src/inspector/v8-debugger.h +++ b/src/inspector/v8-debugger.h @@ -117,7 +117,9 @@ class V8Debugger : public v8::debug::DebugDelegate { void setMaxAsyncTaskStacksForTest(int limit); void dumpAsyncTaskStacksStateForTest(); - void* scheduledAsyncTask() { return m_scheduledAsyncTask; } + v8_inspector::V8StackTraceId scheduledAsyncCall() { + return m_scheduledAsyncCall; + } std::pair debuggerIdFor(int contextGroupId); std::pair debuggerIdFor( @@ -155,7 +157,7 @@ class V8Debugger : public v8::debug::DebugDelegate { void asyncTaskStartedForStack(void* task); void asyncTaskFinishedForStack(void* task); - void asyncTaskCandidateForStepping(void* task); + void asyncTaskCandidateForStepping(void* task, bool isLocal); void asyncTaskStartedForStepping(void* task); void asyncTaskFinishedForStepping(void* task); void asyncTaskCanceledForStepping(void* task); @@ -219,7 +221,7 @@ class V8Debugger : public v8::debug::DebugDelegate { v8::debug::ExceptionBreakState m_pauseOnExceptionsState; bool m_pauseOnAsyncCall = false; - void* m_scheduledAsyncTask = nullptr; + v8_inspector::V8StackTraceId m_scheduledAsyncCall; using StackTraceIdToStackTrace = protocol::HashMap>; diff --git a/test/inspector/debugger/external-stack-trace.js b/test/inspector/debugger/external-stack-trace.js index c8392e28c7..0b5c084e02 100644 --- a/test/inspector/debugger/external-stack-trace.js +++ b/test/inspector/debugger/external-stack-trace.js @@ -119,7 +119,6 @@ InspectorTest.runAsyncTestSuite([ }, async function testExternalStacks() { - let debuggerId1 = (await Protocol1.Debugger.enable()).result.debuggerId; let debuggerId2 = (await Protocol2.Debugger.enable()).result.debuggerId; Protocol1.Debugger.setAsyncCallStackDepth({maxDepth: 32}); diff --git a/test/inspector/debugger/step-into-external-async-task-same-context-expected.txt b/test/inspector/debugger/step-into-external-async-task-same-context-expected.txt new file mode 100644 index 0000000000..e6ab816810 --- /dev/null +++ b/test/inspector/debugger/step-into-external-async-task-same-context-expected.txt @@ -0,0 +1,14 @@ +Test for step-into remote async task. +Setup debugger agents.. +Pause before stack trace is captured.. +Run stepInto with breakOnAsyncCall flag +Call pauseOnAsyncCall +Trigger external async task on another context group +Dump stack trace +boo (target.js:1:18) +call (framework.js:3:2) +(anonymous) (target.js:0:0) +-- remote-task -- +store (utils.js:2:25) +foo (source.js:1:13) +(anonymous) (source.js:2:6) diff --git a/test/inspector/debugger/step-into-external-async-task-same-context.js b/test/inspector/debugger/step-into-external-async-task-same-context.js new file mode 100644 index 0000000000..fec786422e --- /dev/null +++ b/test/inspector/debugger/step-into-external-async-task-same-context.js @@ -0,0 +1,81 @@ +// 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. + +let {session, contextGroup, Protocol} = + InspectorTest.start('Test for step-into remote async task.'); + +contextGroup.addScript(` +function store(description) { + let buffer = inspector.storeCurrentStackTrace(description); + return '[' + new Int32Array(buffer).join(',') + ']'; +} +//# sourceURL=utils.js`); + +contextGroup.addScript(` +function call(id, f) { + inspector.externalAsyncTaskStarted(Int32Array.from(JSON.parse(id)).buffer); + f(); + inspector.externalAsyncTaskFinished(Int32Array.from(JSON.parse(id)).buffer); +} +//# sourceURL=framework.js`); + +session.setupScriptMap(); + +(async function test() { + InspectorTest.log('Setup debugger agents..'); + let debuggerId = (await Protocol.Debugger.enable()).result.debuggerId; + + Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128}); + Protocol.Debugger.setBlackboxPatterns({patterns: ['framework\.js']}); + + InspectorTest.log('Pause before stack trace is captured..'); + Protocol.Debugger.setBreakpointByUrl( + {lineNumber: 2, columnNumber: 25, url: 'utils.js'}); + let evaluatePromise = Protocol.Runtime.evaluate({ + expression: `(function foo() { + return store('remote-task'); + })() + //# sourceURL=source.js` + }); + await Protocol.Debugger.oncePaused(); + + InspectorTest.log('Run stepInto with breakOnAsyncCall flag'); + Protocol.Debugger.stepInto({breakOnAsyncCall: true}); + let {params: {asyncCallStackTraceId}} = await Protocol.Debugger.oncePaused(); + + InspectorTest.log('Call pauseOnAsyncCall'); + Protocol.Debugger.pauseOnAsyncCall({ + parentStackTraceId: asyncCallStackTraceId, + }); + Protocol.Debugger.resume(); + + InspectorTest.log('Trigger external async task on another context group'); + let stackTraceId = (await evaluatePromise).result.result.value; + Protocol.Runtime.evaluate({ + expression: `call('${stackTraceId}', + function boo() {}) + //# sourceURL=target.js` + }); + + InspectorTest.log('Dump stack trace'); + let {params: {callFrames, asyncStackTraceId}} = + await Protocol.Debugger.oncePaused(); + while (true) { + session.logCallFrames(callFrames); + if (asyncStackTraceId) { + let {result: {stackTrace}} = await Protocol.Debugger.getStackTrace( + {stackTraceId: asyncStackTraceId}); + InspectorTest.log(`-- ${stackTrace.description} --`); + callFrames = stackTrace.callFrames; + asyncStackTraceId = stackTrace.parentId; + } else { + break; + } + } + + Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 0}); + await Protocol.Debugger.disable(); + + InspectorTest.completeTest(); +})()