Commit 47b650e2 authored by Alexey Kozyatinskiy's avatar Alexey Kozyatinskiy Committed by Commit Bot

[inspector] nice stepOut from async function

If async function A awaited async function B, stepOut from function B
should go to function A.

Bug: v8:7753
Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng;master.tryserver.blink:linux_trusty_blink_rel
Change-Id: Iedc1d8b85a52aa60519e56b319325436fc2168c9
Reviewed-on: https://chromium-review.googlesource.com/1054618
Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Reviewed-by: 's avatarDmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53451}
parent 85bb3ed2
...@@ -318,6 +318,7 @@ void V8Debugger::stepIntoStatement(int targetContextGroupId, ...@@ -318,6 +318,7 @@ void V8Debugger::stepIntoStatement(int targetContextGroupId,
bool breakOnAsyncCall) { bool breakOnAsyncCall) {
DCHECK(isPaused()); DCHECK(isPaused());
DCHECK(targetContextGroupId); DCHECK(targetContextGroupId);
if (asyncStepOutOfFunction(targetContextGroupId, true)) return;
m_targetContextGroupId = targetContextGroupId; m_targetContextGroupId = targetContextGroupId;
m_pauseOnAsyncCall = breakOnAsyncCall; m_pauseOnAsyncCall = breakOnAsyncCall;
v8::debug::PrepareStep(m_isolate, v8::debug::StepIn); v8::debug::PrepareStep(m_isolate, v8::debug::StepIn);
...@@ -327,6 +328,7 @@ void V8Debugger::stepIntoStatement(int targetContextGroupId, ...@@ -327,6 +328,7 @@ void V8Debugger::stepIntoStatement(int targetContextGroupId,
void V8Debugger::stepOverStatement(int targetContextGroupId) { void V8Debugger::stepOverStatement(int targetContextGroupId) {
DCHECK(isPaused()); DCHECK(isPaused());
DCHECK(targetContextGroupId); DCHECK(targetContextGroupId);
if (asyncStepOutOfFunction(targetContextGroupId, true)) return;
m_targetContextGroupId = targetContextGroupId; m_targetContextGroupId = targetContextGroupId;
v8::debug::PrepareStep(m_isolate, v8::debug::StepNext); v8::debug::PrepareStep(m_isolate, v8::debug::StepNext);
continueProgram(targetContextGroupId); continueProgram(targetContextGroupId);
...@@ -335,11 +337,44 @@ void V8Debugger::stepOverStatement(int targetContextGroupId) { ...@@ -335,11 +337,44 @@ void V8Debugger::stepOverStatement(int targetContextGroupId) {
void V8Debugger::stepOutOfFunction(int targetContextGroupId) { void V8Debugger::stepOutOfFunction(int targetContextGroupId) {
DCHECK(isPaused()); DCHECK(isPaused());
DCHECK(targetContextGroupId); DCHECK(targetContextGroupId);
if (asyncStepOutOfFunction(targetContextGroupId, false)) return;
m_targetContextGroupId = targetContextGroupId; m_targetContextGroupId = targetContextGroupId;
v8::debug::PrepareStep(m_isolate, v8::debug::StepOut); v8::debug::PrepareStep(m_isolate, v8::debug::StepOut);
continueProgram(targetContextGroupId); continueProgram(targetContextGroupId);
} }
bool V8Debugger::asyncStepOutOfFunction(int targetContextGroupId,
bool onlyAtReturn) {
auto iterator = v8::debug::StackTraceIterator::Create(m_isolate);
DCHECK(!iterator->Done());
bool atReturn = !iterator->GetReturnValue().IsEmpty();
iterator->Advance();
// Synchronous stack has more then one frame.
if (!iterator->Done()) return false;
// There is only one synchronous frame but we are not at return position and
// user requests stepOver or stepInto.
if (onlyAtReturn && !atReturn) return false;
// If we are inside async function, current async parent was captured when
// async function was suspended first time and we install that stack as
// current before resume async function. So it represents current async
// function.
auto current = currentAsyncParent();
if (!current) return false;
// Lookup for parent async function.
auto parent = current->parent();
if (parent.expired()) return false;
// Parent async stack will have suspended task id iff callee async function
// is awaiting current async function. We can make stepOut there only in this
// case.
void* parentTask =
std::shared_ptr<AsyncStackTrace>(parent)->suspendedTaskId();
if (!parentTask) return false;
pauseOnAsyncCall(targetContextGroupId,
reinterpret_cast<uintptr_t>(parentTask), String16());
continueProgram(targetContextGroupId);
return true;
}
void V8Debugger::scheduleStepIntoAsync( void V8Debugger::scheduleStepIntoAsync(
std::unique_ptr<ScheduleStepIntoAsyncCallback> callback, std::unique_ptr<ScheduleStepIntoAsyncCallback> callback,
int targetContextGroupId) { int targetContextGroupId) {
...@@ -630,11 +665,17 @@ void V8Debugger::AsyncEventOccurred(v8::debug::DebugAsyncActionType type, ...@@ -630,11 +665,17 @@ void V8Debugger::AsyncEventOccurred(v8::debug::DebugAsyncActionType type,
asyncTaskFinishedForStack(task); asyncTaskFinishedForStack(task);
asyncTaskFinishedForStepping(task); asyncTaskFinishedForStepping(task);
break; break;
case v8::debug::kAsyncFunctionSuspended: case v8::debug::kAsyncFunctionSuspended: {
if (m_asyncTaskStacks.find(task) == m_asyncTaskStacks.end()) { if (m_asyncTaskStacks.find(task) == m_asyncTaskStacks.end()) {
asyncTaskScheduledForStack("async function", task, true); asyncTaskScheduledForStack("async function", task, true);
} }
auto stackIt = m_asyncTaskStacks.find(task);
if (stackIt != m_asyncTaskStacks.end() && !stackIt->second.expired()) {
std::shared_ptr<AsyncStackTrace> stack(stackIt->second);
stack->setSuspendedTaskId(task);
}
break; break;
}
case v8::debug::kAsyncFunctionFinished: case v8::debug::kAsyncFunctionFinished:
asyncTaskCanceledForStack(task); asyncTaskCanceledForStack(task);
break; break;
...@@ -946,8 +987,10 @@ void V8Debugger::asyncTaskStartedForStack(void* task) { ...@@ -946,8 +987,10 @@ void V8Debugger::asyncTaskStartedForStack(void* task) {
// - asyncTaskFinished // - asyncTaskFinished
m_currentTasks.push_back(task); m_currentTasks.push_back(task);
AsyncTaskToStackTrace::iterator stackIt = m_asyncTaskStacks.find(task); AsyncTaskToStackTrace::iterator stackIt = m_asyncTaskStacks.find(task);
if (stackIt != m_asyncTaskStacks.end()) { if (stackIt != m_asyncTaskStacks.end() && !stackIt->second.expired()) {
m_currentAsyncParent.push_back(stackIt->second.lock()); std::shared_ptr<AsyncStackTrace> stack(stackIt->second);
stack->setSuspendedTaskId(nullptr);
m_currentAsyncParent.push_back(stack);
} else { } else {
m_currentAsyncParent.emplace_back(); m_currentAsyncParent.emplace_back();
} }
......
...@@ -185,6 +185,7 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -185,6 +185,7 @@ class V8Debugger : public v8::debug::DebugDelegate {
const v8::debug::Location& end) override; const v8::debug::Location& end) override;
int currentContextGroupId(); int currentContextGroupId();
bool asyncStepOutOfFunction(int targetContextGroupId, bool onlyAtReturn);
v8::Isolate* m_isolate; v8::Isolate* m_isolate;
V8InspectorImpl* m_inspector; V8InspectorImpl* m_inspector;
......
...@@ -369,6 +369,7 @@ AsyncStackTrace::AsyncStackTrace( ...@@ -369,6 +369,7 @@ AsyncStackTrace::AsyncStackTrace(
const V8StackTraceId& externalParent) const V8StackTraceId& externalParent)
: m_contextGroupId(contextGroupId), : m_contextGroupId(contextGroupId),
m_id(0), m_id(0),
m_suspendedTaskId(nullptr),
m_description(description), m_description(description),
m_frames(std::move(frames)), m_frames(std::move(frames)),
m_asyncParent(asyncParent), m_asyncParent(asyncParent),
...@@ -386,6 +387,12 @@ AsyncStackTrace::buildInspectorObject(V8Debugger* debugger, ...@@ -386,6 +387,12 @@ AsyncStackTrace::buildInspectorObject(V8Debugger* debugger,
int AsyncStackTrace::contextGroupId() const { return m_contextGroupId; } int AsyncStackTrace::contextGroupId() const { return m_contextGroupId; }
void AsyncStackTrace::setSuspendedTaskId(void* task) {
m_suspendedTaskId = task;
}
void* AsyncStackTrace::suspendedTaskId() const { return m_suspendedTaskId; }
uintptr_t AsyncStackTrace::store(V8Debugger* debugger, uintptr_t AsyncStackTrace::store(V8Debugger* debugger,
std::shared_ptr<AsyncStackTrace> stack) { std::shared_ptr<AsyncStackTrace> stack) {
if (stack->m_id) return stack->m_id; if (stack->m_id) return stack->m_id;
......
...@@ -117,6 +117,16 @@ class AsyncStackTrace { ...@@ -117,6 +117,16 @@ class AsyncStackTrace {
std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObject( std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObject(
V8Debugger* debugger, int maxAsyncDepth) const; V8Debugger* debugger, int maxAsyncDepth) const;
// If async stack has suspended task id, it means that at moment when we
// capture current stack trace we suspended corresponded asynchronous
// execution flow and it is possible to request pause for a momemnt when
// that flow is resumed.
// E.g. every time when we suspend async function we mark corresponded async
// stack as suspended and every time when this function is resumed we remove
// suspendedTaskId.
void setSuspendedTaskId(void* task);
void* suspendedTaskId() const;
int contextGroupId() const; int contextGroupId() const;
const String16& description() const; const String16& description() const;
std::weak_ptr<AsyncStackTrace> parent() const; std::weak_ptr<AsyncStackTrace> parent() const;
...@@ -135,6 +145,7 @@ class AsyncStackTrace { ...@@ -135,6 +145,7 @@ class AsyncStackTrace {
int m_contextGroupId; int m_contextGroupId;
uintptr_t m_id; uintptr_t m_id;
void* m_suspendedTaskId;
String16 m_description; String16 m_description;
std::vector<std::shared_ptr<StackFrame>> m_frames; std::vector<std::shared_ptr<StackFrame>> m_frames;
......
stepOut async function stepOut async function
bar (test.js:22:2)
Running test: testTrivial
Check that we have proper async stack at return
bar (testTrivial.js:28:8)
-- async function --
bar (testTrivial.js:27:22)
foo (testTrivial.js:23:14)
-- async function --
foo (testTrivial.js:22:22)
test (testTrivial.js:18:14)
-- async function --
test (testTrivial.js:17:22)
(anonymous) (:0:0)
foo (testTrivial.js:24:6)
-- async function --
foo (testTrivial.js:22:22)
test (testTrivial.js:18:14)
-- async function --
test (testTrivial.js:17:22)
(anonymous) (:0:0)
test (testTrivial.js:19:6)
-- async function --
test (testTrivial.js:17:22)
(anonymous) (:0:0)
Running test: testStepOutPrecision
Check that stepOut go to resumed outer generator
bar (testStepOutPrecision.js:61:8)
-- async function --
bar (testStepOutPrecision.js:60:22)
foo (testStepOutPrecision.js:55:14)
-- async function --
foo (testStepOutPrecision.js:54:22)
test (testStepOutPrecision.js:48:14)
-- async function --
test (testStepOutPrecision.js:47:14)
(anonymous) (:0:0)
foo (testStepOutPrecision.js:56:8)
-- async function -- -- async function --
bar (test.js:21:16) foo (testStepOutPrecision.js:54:22)
foo (test.js:17:8) test (testStepOutPrecision.js:48:14)
-- async function -- -- async function --
foo (test.js:16:16) test (testStepOutPrecision.js:47:14)
test (test.js:12:8) (anonymous) (:0:0)
test (testStepOutPrecision.js:49:8)
-- async function --
test (testStepOutPrecision.js:47:14)
(anonymous) (:0:0)
floodWithTimeouts (testStepOutPrecision.js:40:15)
-- setTimeout --
floodWithTimeouts (testStepOutPrecision.js:41:10)
-- setTimeout --
floodWithTimeouts (testStepOutPrecision.js:41:10)
-- setTimeout --
floodWithTimeouts (testStepOutPrecision.js:41:10)
-- setTimeout --
floodWithTimeouts (testStepOutPrecision.js:41:10)
test (testStepOutPrecision.js:46:8)
(anonymous) (:0:0)
test (testStepOutPrecision.js:50:8)
-- async function -- -- async function --
test (test.js:11:16) test (testStepOutPrecision.js:47:14)
(anonymous) (:0:0) (anonymous) (:0:0)
foo (test.js:18:0) floodWithTimeouts (testStepOutPrecision.js:40:15)
-- setTimeout --
floodWithTimeouts (testStepOutPrecision.js:41:10)
-- setTimeout --
floodWithTimeouts (testStepOutPrecision.js:41:10)
-- setTimeout --
floodWithTimeouts (testStepOutPrecision.js:41:10)
-- setTimeout --
floodWithTimeouts (testStepOutPrecision.js:41:10)
-- setTimeout --
floodWithTimeouts (testStepOutPrecision.js:41:10)
test (testStepOutPrecision.js:46:8)
(anonymous) (:0:0)
Running test: testStepIntoAtReturn
Check that stepInto at return go to resumed outer generator
bar (testStepIntoAtReturn.js:93:8)
-- async function -- -- async function --
foo (test.js:16:16) bar (testStepIntoAtReturn.js:92:22)
test (test.js:12:8) foo (testStepIntoAtReturn.js:88:14)
-- async function -- -- async function --
test (test.js:11:16) foo (testStepIntoAtReturn.js:87:22)
test (testStepIntoAtReturn.js:82:14)
-- async function --
test (testStepIntoAtReturn.js:81:14)
(anonymous) (:0:0) (anonymous) (:0:0)
test (test.js:13:0) bar (testStepIntoAtReturn.js:94:6)
-- async function --
bar (testStepIntoAtReturn.js:92:22)
foo (testStepIntoAtReturn.js:88:14)
-- async function --
foo (testStepIntoAtReturn.js:87:22)
test (testStepIntoAtReturn.js:82:14)
-- async function -- -- async function --
test (test.js:11:16) test (testStepIntoAtReturn.js:81:14)
(anonymous) (:0:0)
foo (testStepIntoAtReturn.js:89:6)
-- async function --
foo (testStepIntoAtReturn.js:87:22)
test (testStepIntoAtReturn.js:82:14)
-- async function --
test (testStepIntoAtReturn.js:81:14)
(anonymous) (:0:0)
test (testStepIntoAtReturn.js:83:8)
-- async function --
test (testStepIntoAtReturn.js:81:14)
(anonymous) (:0:0)
test (testStepIntoAtReturn.js:84:6)
-- async function --
test (testStepIntoAtReturn.js:81:14)
(anonymous) (:0:0)
floodWithTimeouts (testStepIntoAtReturn.js:74:15)
-- setTimeout --
floodWithTimeouts (testStepIntoAtReturn.js:75:10)
-- setTimeout --
floodWithTimeouts (testStepIntoAtReturn.js:75:10)
test (testStepIntoAtReturn.js:80:8)
(anonymous) (:0:0)
Running test: testStepOverAtReturn
Check that stepOver at return go to resumed outer generator
bar (testStepIntoAtReturn.js:124:8)
-- async function --
bar (testStepIntoAtReturn.js:123:22)
foo (testStepIntoAtReturn.js:119:14)
-- async function --
foo (testStepIntoAtReturn.js:118:22)
test (testStepIntoAtReturn.js:113:14)
-- async function --
test (testStepIntoAtReturn.js:112:14)
(anonymous) (:0:0)
bar (testStepIntoAtReturn.js:125:6)
-- async function --
bar (testStepIntoAtReturn.js:123:22)
foo (testStepIntoAtReturn.js:119:14)
-- async function --
foo (testStepIntoAtReturn.js:118:22)
test (testStepIntoAtReturn.js:113:14)
-- async function --
test (testStepIntoAtReturn.js:112:14)
(anonymous) (:0:0)
foo (testStepIntoAtReturn.js:120:6)
-- async function --
foo (testStepIntoAtReturn.js:118:22)
test (testStepIntoAtReturn.js:113:14)
-- async function --
test (testStepIntoAtReturn.js:112:14)
(anonymous) (:0:0)
test (testStepIntoAtReturn.js:114:8)
-- async function --
test (testStepIntoAtReturn.js:112:14)
(anonymous) (:0:0)
test (testStepIntoAtReturn.js:115:6)
-- async function --
test (testStepIntoAtReturn.js:112:14)
(anonymous) (:0:0)
floodWithTimeouts (testStepIntoAtReturn.js:105:15)
-- setTimeout --
floodWithTimeouts (testStepIntoAtReturn.js:106:10)
-- setTimeout --
floodWithTimeouts (testStepIntoAtReturn.js:106:10)
test (testStepIntoAtReturn.js:111:8)
(anonymous) (:0:0)
Running test: testStepOutFromNotAwaitedCall
Checks stepOut from not awaited call
bar (testStepIntoAtReturn.js:158:8)
-- async function --
bar (testStepIntoAtReturn.js:157:22)
foo (testStepIntoAtReturn.js:152:8)
-- async function --
foo (testStepIntoAtReturn.js:151:22)
test (testStepIntoAtReturn.js:144:14)
-- async function --
test (testStepIntoAtReturn.js:143:14)
(anonymous) (:0:0)
floodWithTimeouts (testStepIntoAtReturn.js:136:15)
-- setTimeout --
floodWithTimeouts (testStepIntoAtReturn.js:137:10)
-- setTimeout --
floodWithTimeouts (testStepIntoAtReturn.js:137:10)
test (testStepIntoAtReturn.js:142:8)
(anonymous) (:0:0) (anonymous) (:0:0)
...@@ -6,27 +6,163 @@ let {session, contextGroup, Protocol} = ...@@ -6,27 +6,163 @@ let {session, contextGroup, Protocol} =
InspectorTest.start('stepOut async function'); InspectorTest.start('stepOut async function');
session.setupScriptMap(); session.setupScriptMap();
contextGroup.addInlineScript(`
async function test() {
await Promise.resolve();
await foo();
}
async function foo() { Protocol.Runtime.enable();
await Promise.resolve();
await bar();
}
async function bar() { InspectorTest.runAsyncTestSuite([
await Promise.resolve(); async function testTrivial() {
debugger; InspectorTest.log('Check that we have proper async stack at return');
} contextGroup.addInlineScript(`
`, 'test.js'); async function test() {
await Promise.resolve();
await foo();
}
async function foo() {
await Promise.resolve();
await bar();
}
async function bar() {
await Promise.resolve();
debugger;
}`, 'testTrivial.js');
await runTestAndStepAction('stepOut');
},
async function testStepOutPrecision() {
InspectorTest.log('Check that stepOut go to resumed outer generator');
contextGroup.addInlineScript(`
function wait() {
return new Promise(resolve => setTimeout(resolve, 0));
}
function floodWithTimeouts(a) {
if (!a.stop)
setTimeout(floodWithTimeouts.bind(this, a), 0);
}
async function test() {
let a = {};
floodWithTimeouts(a)
await wait();
await foo();
await wait();
a.stop = true;
}
async function foo() {
await Promise.resolve();
await bar();
await wait();
}
async function bar() {
await Promise.resolve();
debugger;
await wait();
}`, 'testStepOutPrecision.js');
await runTestAndStepAction('stepOut');
},
async function testStepIntoAtReturn() {
InspectorTest.log('Check that stepInto at return go to resumed outer generator');
contextGroup.addInlineScript(`
function wait() {
return new Promise(resolve => setTimeout(resolve, 0));
}
function floodWithTimeouts(a) {
if (!a.stop)
setTimeout(floodWithTimeouts.bind(this, a), 0);
}
async function test() {
let a = {};
floodWithTimeouts(a)
await wait();
await foo();
a.stop = true;
}
async function foo() {
await Promise.resolve();
await bar();
}
async function bar() {
await Promise.resolve();
debugger;
}`, 'testStepIntoAtReturn.js');
await runTestAndStepAction('stepInto');
},
(async function test() { async function testStepOverAtReturn() {
Protocol.Runtime.enable(); InspectorTest.log('Check that stepOver at return go to resumed outer generator');
Protocol.Runtime.onConsoleAPICalled( contextGroup.addInlineScript(`
msg => InspectorTest.log(msg.params.args[0].value)); function wait() {
return new Promise(resolve => setTimeout(resolve, 0));
}
function floodWithTimeouts(a) {
if (!a.stop)
setTimeout(floodWithTimeouts.bind(this, a), 0);
}
async function test() {
let a = {};
floodWithTimeouts(a)
await wait();
await foo();
a.stop = true;
}
async function foo() {
await Promise.resolve();
await bar();
}
async function bar() {
await Promise.resolve();
debugger;
}`, 'testStepIntoAtReturn.js');
await runTestAndStepAction('stepOver');
},
async function testStepOutFromNotAwaitedCall() {
InspectorTest.log('Checks stepOut from not awaited call');
contextGroup.addInlineScript(`
function wait() {
return new Promise(resolve => setTimeout(resolve, 0));
}
function floodWithTimeouts(a) {
if (!a.stop)
setTimeout(floodWithTimeouts.bind(this, a), 0);
}
async function test() {
let a = {};
floodWithTimeouts(a)
await wait();
await foo();
a.stop = true;
}
async function foo() {
let a = {};
floodWithTimeouts(a);
await Promise.resolve();
bar();
a.stop = true;
}
async function bar() {
await Promise.resolve();
debugger;
}`, 'testStepIntoAtReturn.js');
await runTestAndStepAction('stepOut');
}
]);
async function runTestAndStepAction(action) {
Protocol.Debugger.enable(); Protocol.Debugger.enable();
Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128}); Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128});
let finished = let finished =
...@@ -35,16 +171,15 @@ async function bar() { ...@@ -35,16 +171,15 @@ async function bar() {
while (true) { while (true) {
const r = await Promise.race([finished, waitPauseAndDumpStack()]); const r = await Promise.race([finished, waitPauseAndDumpStack()]);
if (!r) break; if (!r) break;
Protocol.Debugger.stepOut(); Protocol.Debugger[action]();
} }
InspectorTest.completeTest(); await Protocol.Debugger.disable();
})() }
async function async function waitPauseAndDumpStack() {
waitPauseAndDumpStack() { const {params} = await Protocol.Debugger.oncePaused();
const {params} = await Protocol.Debugger.oncePaused(); session.logCallFrames(params.callFrames);
session.logCallFrames(params.callFrames); session.logAsyncStackTrace(params.asyncStackTrace);
session.logAsyncStackTrace(params.asyncStackTrace); InspectorTest.log('');
InspectorTest.log(''); return true;
return true; }
}
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