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,
bool breakOnAsyncCall) {
DCHECK(isPaused());
DCHECK(targetContextGroupId);
if (asyncStepOutOfFunction(targetContextGroupId, true)) return;
m_targetContextGroupId = targetContextGroupId;
m_pauseOnAsyncCall = breakOnAsyncCall;
v8::debug::PrepareStep(m_isolate, v8::debug::StepIn);
......@@ -327,6 +328,7 @@ void V8Debugger::stepIntoStatement(int targetContextGroupId,
void V8Debugger::stepOverStatement(int targetContextGroupId) {
DCHECK(isPaused());
DCHECK(targetContextGroupId);
if (asyncStepOutOfFunction(targetContextGroupId, true)) return;
m_targetContextGroupId = targetContextGroupId;
v8::debug::PrepareStep(m_isolate, v8::debug::StepNext);
continueProgram(targetContextGroupId);
......@@ -335,11 +337,44 @@ void V8Debugger::stepOverStatement(int targetContextGroupId) {
void V8Debugger::stepOutOfFunction(int targetContextGroupId) {
DCHECK(isPaused());
DCHECK(targetContextGroupId);
if (asyncStepOutOfFunction(targetContextGroupId, false)) return;
m_targetContextGroupId = targetContextGroupId;
v8::debug::PrepareStep(m_isolate, v8::debug::StepOut);
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(
std::unique_ptr<ScheduleStepIntoAsyncCallback> callback,
int targetContextGroupId) {
......@@ -630,11 +665,17 @@ void V8Debugger::AsyncEventOccurred(v8::debug::DebugAsyncActionType type,
asyncTaskFinishedForStack(task);
asyncTaskFinishedForStepping(task);
break;
case v8::debug::kAsyncFunctionSuspended:
case v8::debug::kAsyncFunctionSuspended: {
if (m_asyncTaskStacks.find(task) == m_asyncTaskStacks.end()) {
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;
}
case v8::debug::kAsyncFunctionFinished:
asyncTaskCanceledForStack(task);
break;
......@@ -946,8 +987,10 @@ void V8Debugger::asyncTaskStartedForStack(void* task) {
// - asyncTaskFinished
m_currentTasks.push_back(task);
AsyncTaskToStackTrace::iterator stackIt = m_asyncTaskStacks.find(task);
if (stackIt != m_asyncTaskStacks.end()) {
m_currentAsyncParent.push_back(stackIt->second.lock());
if (stackIt != m_asyncTaskStacks.end() && !stackIt->second.expired()) {
std::shared_ptr<AsyncStackTrace> stack(stackIt->second);
stack->setSuspendedTaskId(nullptr);
m_currentAsyncParent.push_back(stack);
} else {
m_currentAsyncParent.emplace_back();
}
......
......@@ -185,6 +185,7 @@ class V8Debugger : public v8::debug::DebugDelegate {
const v8::debug::Location& end) override;
int currentContextGroupId();
bool asyncStepOutOfFunction(int targetContextGroupId, bool onlyAtReturn);
v8::Isolate* m_isolate;
V8InspectorImpl* m_inspector;
......
......@@ -369,6 +369,7 @@ AsyncStackTrace::AsyncStackTrace(
const V8StackTraceId& externalParent)
: m_contextGroupId(contextGroupId),
m_id(0),
m_suspendedTaskId(nullptr),
m_description(description),
m_frames(std::move(frames)),
m_asyncParent(asyncParent),
......@@ -386,6 +387,12 @@ AsyncStackTrace::buildInspectorObject(V8Debugger* debugger,
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,
std::shared_ptr<AsyncStackTrace> stack) {
if (stack->m_id) return stack->m_id;
......
......@@ -117,6 +117,16 @@ class AsyncStackTrace {
std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObject(
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;
const String16& description() const;
std::weak_ptr<AsyncStackTrace> parent() const;
......@@ -135,6 +145,7 @@ class AsyncStackTrace {
int m_contextGroupId;
uintptr_t m_id;
void* m_suspendedTaskId;
String16 m_description;
std::vector<std::shared_ptr<StackFrame>> m_frames;
......
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 --
bar (test.js:21:16)
foo (test.js:17:8)
foo (testStepOutPrecision.js:54:22)
test (testStepOutPrecision.js:48:14)
-- async function --
foo (test.js:16:16)
test (test.js:12:8)
test (testStepOutPrecision.js:47:14)
(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 --
test (test.js:11:16)
test (testStepOutPrecision.js:47:14)
(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 --
foo (test.js:16:16)
test (test.js:12:8)
bar (testStepIntoAtReturn.js:92:22)
foo (testStepIntoAtReturn.js:88:14)
-- 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)
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 --
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)
......@@ -6,27 +6,163 @@ let {session, contextGroup, Protocol} =
InspectorTest.start('stepOut async function');
session.setupScriptMap();
contextGroup.addInlineScript(`
async function test() {
await Promise.resolve();
await foo();
}
async function foo() {
await Promise.resolve();
await bar();
}
Protocol.Runtime.enable();
async function bar() {
await Promise.resolve();
debugger;
}
`, 'test.js');
InspectorTest.runAsyncTestSuite([
async function testTrivial() {
InspectorTest.log('Check that we have proper async stack at return');
contextGroup.addInlineScript(`
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() {
Protocol.Runtime.enable();
Protocol.Runtime.onConsoleAPICalled(
msg => InspectorTest.log(msg.params.args[0].value));
async function testStepOverAtReturn() {
InspectorTest.log('Check that stepOver 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('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.setAsyncCallStackDepth({maxDepth: 128});
let finished =
......@@ -35,16 +171,15 @@ async function bar() {
while (true) {
const r = await Promise.race([finished, waitPauseAndDumpStack()]);
if (!r) break;
Protocol.Debugger.stepOut();
Protocol.Debugger[action]();
}
InspectorTest.completeTest();
})()
async function
waitPauseAndDumpStack() {
const {params} = await Protocol.Debugger.oncePaused();
session.logCallFrames(params.callFrames);
session.logAsyncStackTrace(params.asyncStackTrace);
InspectorTest.log('');
return true;
}
await Protocol.Debugger.disable();
}
async function waitPauseAndDumpStack() {
const {params} = await Protocol.Debugger.oncePaused();
session.logCallFrames(params.callFrames);
session.logAsyncStackTrace(params.asyncStackTrace);
InspectorTest.log('');
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