Commit 95120a7e authored by kozyatinskiy's avatar kozyatinskiy Committed by Commit bot

[inspector] support setTimeout in Debugger.scheduleStepIntoAsync method

BUG=chromium:432469
R=dgozman@chromium.org

Review-Url: https://codereview.chromium.org/2746743002
Cr-Commit-Position: refs/heads/master@{#44270}
parent 5ca9632e
......@@ -9151,7 +9151,6 @@ void debug::PrepareStep(Isolate* v8_isolate, StepAction action) {
void debug::ClearStepping(Isolate* v8_isolate) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
ENTER_V8(isolate);
CHECK(isolate->debug()->CheckExecutionState());
// Clear all current stepping setup.
isolate->debug()->ClearStepping();
}
......
......@@ -746,18 +746,8 @@ void V8DebuggerAgentImpl::scheduleStepIntoAsync(
callback->sendFailure(Response::Error(kDebuggerNotPaused));
return;
}
if (m_stepIntoAsyncCallback) {
m_stepIntoAsyncCallback->sendFailure(Response::Error(
"Current scheduled step into async was overriden with new one."));
}
m_stepIntoAsyncCallback = std::move(callback);
}
bool V8DebuggerAgentImpl::shouldBreakInScheduledAsyncTask() {
if (!m_stepIntoAsyncCallback) return false;
m_stepIntoAsyncCallback->sendSuccess();
m_stepIntoAsyncCallback.reset();
return true;
m_debugger->scheduleStepIntoAsync(std::move(callback),
m_session->contextGroupId());
}
Response V8DebuggerAgentImpl::setPauseOnExceptions(
......@@ -1230,12 +1220,6 @@ void V8DebuggerAgentImpl::didPause(int contextId,
breakAuxData->setArray("reasons", std::move(reasons));
}
if (m_stepIntoAsyncCallback) {
m_stepIntoAsyncCallback->sendFailure(
Response::Error("No async tasks were scheduled before pause."));
m_stepIntoAsyncCallback.reset();
}
std::unique_ptr<Array<CallFrame>> protocolCallFrames;
Response response = currentCallFrames(&protocolCallFrames);
if (!response.isSuccess()) protocolCallFrames = Array<CallFrame>::create();
......
......@@ -143,8 +143,6 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
v8::Isolate* isolate() { return m_isolate; }
bool shouldBreakInScheduledAsyncTask();
private:
void enableImpl();
......@@ -204,8 +202,6 @@ class V8DebuggerAgentImpl : public protocol::Debugger::Backend {
protocol::HashMap<String16, std::vector<std::pair<int, int>>>
m_blackboxedPositions;
std::unique_ptr<ScheduleStepIntoAsyncCallback> m_stepIntoAsyncCallback;
DISALLOW_COPY_AND_ASSIGN(V8DebuggerAgentImpl);
};
......
......@@ -336,6 +336,7 @@ void V8Debugger::setPauseOnNextStatement(bool pause, int targetContextGroupId) {
return;
}
m_targetContextGroupId = targetContextGroupId;
m_breakRequested = pause;
if (pause)
v8::debug::DebugBreak(m_isolate);
else
......@@ -387,6 +388,20 @@ void V8Debugger::stepOutOfFunction(int targetContextGroupId) {
continueProgram();
}
void V8Debugger::scheduleStepIntoAsync(
std::unique_ptr<ScheduleStepIntoAsyncCallback> callback,
int targetContextGroupId) {
DCHECK(isPaused());
DCHECK(!m_executionState.IsEmpty());
DCHECK(targetContextGroupId);
if (m_stepIntoAsyncCallback) {
m_stepIntoAsyncCallback->sendFailure(Response::Error(
"Current scheduled step into async was overriden with new one."));
}
m_targetContextGroupId = targetContextGroupId;
m_stepIntoAsyncCallback = std::move(callback);
}
Response V8Debugger::setScriptSource(
const String16& sourceID, v8::Local<v8::String> newSource, bool dryRun,
Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails,
......@@ -522,6 +537,12 @@ void V8Debugger::handleProgramBreak(v8::Local<v8::Context> pausedContext,
return;
}
m_targetContextGroupId = 0;
if (m_stepIntoAsyncCallback) {
m_stepIntoAsyncCallback->sendFailure(
Response::Error("No async tasks were scheduled before pause."));
m_stepIntoAsyncCallback.reset();
}
m_breakRequested = false;
V8DebuggerAgentImpl* agent = m_inspector->enabledDebuggerAgentForGroup(
m_inspector->contextGroupId(pausedContext));
if (!agent || (agent->skipAllPauses() && !m_scheduledOOMBreak)) return;
......@@ -624,78 +645,42 @@ void V8Debugger::PromiseEventOccurred(v8::Local<v8::Context> context,
int id, int parentId,
bool createdByUser) {
// Async task events from Promises are given misaligned pointers to prevent
// from overlapping with other Blink task identifiers. There is a single
// namespace of such ids, managed by src/js/promise.js.
void* ptr = reinterpret_cast<void*>(id * 2 + 1);
void* parentPtr =
// from overlapping with other Blink task identifiers.
void* task = reinterpret_cast<void*>(id * 2 + 1);
void* parentTask =
parentId ? reinterpret_cast<void*>(parentId * 2 + 1) : nullptr;
handleAsyncTaskStepping(context, type, ptr, parentPtr, createdByUser);
if (!m_maxAsyncCallStackDepth) return;
switch (type) {
case v8::debug::kDebugPromiseCreated:
asyncTaskCreated(ptr, parentPtr);
asyncTaskCreatedForStack(task, parentTask);
if (createdByUser && parentTask) {
v8::Context::Scope contextScope(context);
asyncTaskCandidateForStepping(task);
}
break;
case v8::debug::kDebugEnqueueAsyncFunction:
asyncTaskScheduled("async function", ptr, true);
asyncTaskScheduledForStack("async function", task, true);
break;
case v8::debug::kDebugEnqueuePromiseResolve:
asyncTaskScheduled("Promise.resolve", ptr, true);
asyncTaskScheduledForStack("Promise.resolve", task, true);
break;
case v8::debug::kDebugEnqueuePromiseReject:
asyncTaskScheduled("Promise.reject", ptr, true);
asyncTaskScheduledForStack("Promise.reject", task, true);
break;
case v8::debug::kDebugPromiseCollected:
asyncTaskCanceled(ptr);
asyncTaskCanceledForStack(task);
asyncTaskCanceledForStepping(task);
break;
case v8::debug::kDebugWillHandle:
asyncTaskStarted(ptr);
asyncTaskStartedForStack(task);
asyncTaskStartedForStepping(task);
break;
case v8::debug::kDebugDidHandle:
asyncTaskFinished(ptr);
asyncTaskFinishedForStack(task);
asyncTaskFinishedForStepping(task);
break;
}
}
void V8Debugger::handleAsyncTaskStepping(v8::Local<v8::Context> context,
v8::debug::PromiseDebugActionType type,
void* task, void* parentTask,
bool createdByUser) {
if (type == v8::debug::kDebugEnqueueAsyncFunction ||
type == v8::debug::kDebugEnqueuePromiseResolve ||
type == v8::debug::kDebugEnqueuePromiseReject) {
return;
}
bool isScheduledTask = task == m_taskWithScheduledBreak;
if (type == v8::debug::kDebugPromiseCollected) {
if (isScheduledTask) m_taskWithScheduledBreak = nullptr;
return;
}
if (type == v8::debug::kDebugPromiseCreated && !parentTask) return;
DCHECK(!context.IsEmpty());
int contextGroupId = m_inspector->contextGroupId(context);
V8DebuggerAgentImpl* agent =
m_inspector->enabledDebuggerAgentForGroup(contextGroupId);
if (!agent) return;
if (createdByUser && type == v8::debug::kDebugPromiseCreated) {
if (agent->shouldBreakInScheduledAsyncTask()) {
m_taskWithScheduledBreak = task;
v8::debug::ClearStepping(m_isolate);
}
return;
}
if (!isScheduledTask) return;
if (type == v8::debug::kDebugWillHandle) {
agent->schedulePauseOnNextStatement(
protocol::Debugger::Paused::ReasonEnum::Other, nullptr);
return;
}
DCHECK(type == v8::debug::kDebugDidHandle);
agent->cancelPauseOnNextStatement();
m_taskWithScheduledBreak = nullptr;
}
V8StackTraceImpl* V8Debugger::currentAsyncCallChain() {
if (!m_currentStacks.size()) return nullptr;
return m_currentStacks.back().get();
......@@ -869,11 +854,11 @@ void V8Debugger::registerAsyncTaskIfNeeded(void* task) {
m_idToTask[id] = task;
if (static_cast<int>(m_idToTask.size()) > m_maxAsyncCallStacks) {
void* taskToRemove = m_idToTask.begin()->second;
asyncTaskCanceled(taskToRemove);
asyncTaskCanceledForStack(taskToRemove);
}
}
void V8Debugger::asyncTaskCreated(void* task, void* parentTask) {
void V8Debugger::asyncTaskCreatedForStack(void* task, void* parentTask) {
if (!m_maxAsyncCallStackDepth) return;
if (parentTask) m_parentTask[task] = parentTask;
v8::HandleScope scope(m_isolate);
......@@ -891,12 +876,27 @@ void V8Debugger::asyncTaskCreated(void* task, void* parentTask) {
void V8Debugger::asyncTaskScheduled(const StringView& taskName, void* task,
bool recurring) {
if (!m_maxAsyncCallStackDepth) return;
asyncTaskScheduled(toString16(taskName), task, recurring);
asyncTaskScheduledForStack(toString16(taskName), task, recurring);
asyncTaskCandidateForStepping(task);
}
void V8Debugger::asyncTaskScheduled(const String16& taskName, void* task,
bool recurring) {
void V8Debugger::asyncTaskCanceled(void* task) {
asyncTaskCanceledForStack(task);
asyncTaskCanceledForStepping(task);
}
void V8Debugger::asyncTaskStarted(void* task) {
asyncTaskStartedForStack(task);
asyncTaskStartedForStepping(task);
}
void V8Debugger::asyncTaskFinished(void* task) {
asyncTaskFinishedForStack(task);
asyncTaskFinishedForStepping(task);
}
void V8Debugger::asyncTaskScheduledForStack(const String16& taskName,
void* task, bool recurring) {
if (!m_maxAsyncCallStackDepth) return;
v8::HandleScope scope(m_isolate);
std::unique_ptr<V8StackTraceImpl> chain = V8StackTraceImpl::capture(
......@@ -909,7 +909,7 @@ void V8Debugger::asyncTaskScheduled(const String16& taskName, void* task,
}
}
void V8Debugger::asyncTaskCanceled(void* task) {
void V8Debugger::asyncTaskCanceledForStack(void* task) {
if (!m_maxAsyncCallStackDepth) return;
m_asyncTaskStacks.erase(task);
m_recurringTasks.erase(task);
......@@ -921,7 +921,7 @@ void V8Debugger::asyncTaskCanceled(void* task) {
m_taskToId.erase(it);
}
void V8Debugger::asyncTaskStarted(void* task) {
void V8Debugger::asyncTaskStartedForStack(void* task) {
if (!m_maxAsyncCallStackDepth) return;
m_currentTasks.push_back(task);
auto parentIt = m_parentTask.find(task);
......@@ -944,7 +944,7 @@ void V8Debugger::asyncTaskStarted(void* task) {
m_currentStacks.push_back(std::move(stack));
}
void V8Debugger::asyncTaskFinished(void* task) {
void V8Debugger::asyncTaskFinishedForStack(void* task) {
if (!m_maxAsyncCallStackDepth) return;
// We could start instrumenting half way and the stack is empty.
if (!m_currentStacks.size()) return;
......@@ -954,10 +954,38 @@ void V8Debugger::asyncTaskFinished(void* task) {
m_currentStacks.pop_back();
if (m_recurringTasks.find(task) == m_recurringTasks.end()) {
asyncTaskCanceled(task);
asyncTaskCanceledForStack(task);
}
}
void V8Debugger::asyncTaskCandidateForStepping(void* task) {
if (!m_stepIntoAsyncCallback) return;
DCHECK(m_targetContextGroupId);
if (currentContextGroupId() != m_targetContextGroupId) return;
m_taskWithScheduledBreak = task;
v8::debug::ClearStepping(m_isolate);
m_stepIntoAsyncCallback->sendSuccess();
m_stepIntoAsyncCallback.reset();
}
void V8Debugger::asyncTaskStartedForStepping(void* task) {
if (m_breakRequested) return;
if (task != m_taskWithScheduledBreak) return;
v8::debug::DebugBreak(m_isolate);
}
void V8Debugger::asyncTaskFinishedForStepping(void* task) {
if (task != m_taskWithScheduledBreak) return;
m_taskWithScheduledBreak = nullptr;
if (m_breakRequested) return;
v8::debug::CancelDebugBreak(m_isolate);
}
void V8Debugger::asyncTaskCanceledForStepping(void* task) {
if (task != m_taskWithScheduledBreak) return;
m_taskWithScheduledBreak = nullptr;
}
void V8Debugger::allAsyncTasksCanceled() {
m_asyncTaskStacks.clear();
m_recurringTasks.clear();
......
......@@ -10,6 +10,7 @@
#include "src/base/macros.h"
#include "src/debug/debug-interface.h"
#include "src/inspector/java-script-call-frame.h"
#include "src/inspector/protocol/Debugger.h"
#include "src/inspector/protocol/Forward.h"
#include "src/inspector/protocol/Runtime.h"
#include "src/inspector/v8-debugger-script.h"
......@@ -25,6 +26,8 @@ class V8InspectorImpl;
class V8StackTraceImpl;
using protocol::Response;
using ScheduleStepIntoAsyncCallback =
protocol::Debugger::Backend::ScheduleStepIntoAsyncCallback;
class V8Debugger : public v8::debug::DebugDelegate {
public:
......@@ -49,6 +52,9 @@ class V8Debugger : public v8::debug::DebugDelegate {
void stepIntoStatement(int targetContextGroupId);
void stepOverStatement(int targetContextGroupId);
void stepOutOfFunction(int targetContextGroupId);
void scheduleStepIntoAsync(
std::unique_ptr<ScheduleStepIntoAsyncCallback> callback,
int targetContextGroupId);
Response setScriptSource(
const String16& sourceID, v8::Local<v8::String> newSource, bool dryRun,
......@@ -80,7 +86,6 @@ class V8Debugger : public v8::debug::DebugDelegate {
void asyncTaskScheduled(const StringView& taskName, void* task,
bool recurring);
void asyncTaskScheduled(const String16& taskName, void* task, bool recurring);
void asyncTaskCanceled(void* task);
void asyncTaskStarted(void* task);
void asyncTaskFinished(void* task);
......@@ -126,7 +131,18 @@ class V8Debugger : public v8::debug::DebugDelegate {
v8::MaybeLocal<v8::Value> generatorScopes(v8::Local<v8::Context>,
v8::Local<v8::Value>);
void asyncTaskCreated(void* task, void* parentTask);
void asyncTaskCreatedForStack(void* task, void* parentTask);
void asyncTaskScheduledForStack(const String16& taskName, void* task,
bool recurring);
void asyncTaskCanceledForStack(void* task);
void asyncTaskStartedForStack(void* task);
void asyncTaskFinishedForStack(void* task);
void asyncTaskCandidateForStepping(void* task);
void asyncTaskStartedForStepping(void* task);
void asyncTaskFinishedForStepping(void* task);
void asyncTaskCanceledForStepping(void* task);
void registerAsyncTaskIfNeeded(void* task);
// v8::debug::DebugEventListener implementation.
......@@ -147,10 +163,6 @@ class V8Debugger : public v8::debug::DebugDelegate {
const v8::debug::Location& end) override;
int currentContextGroupId();
void handleAsyncTaskStepping(v8::Local<v8::Context> context,
v8::debug::PromiseDebugActionType type,
void* task, void* parentTask,
bool createdByUser);
v8::Isolate* m_isolate;
V8InspectorImpl* m_inspector;
......@@ -182,6 +194,9 @@ class V8Debugger : public v8::debug::DebugDelegate {
protocol::HashMap<void*, void*> m_firstNextTask;
void* m_taskWithScheduledBreak = nullptr;
std::unique_ptr<ScheduleStepIntoAsyncCallback> m_stepIntoAsyncCallback;
bool m_breakRequested = false;
v8::debug::ExceptionBreakState m_pauseOnExceptionsState;
WasmTranslation m_wasmTranslation;
......
Checks Debugger.scheduleStepIntoAsync with setTimeout.
Running test: testSetTimeout
paused at:
debugger; #setTimeout(() => 1, 0);
paused at:
debugger; setTimeout(#() => 1, 0);
Running test: testDebuggerStmtBeforeCallback1
paused at:
debugger; #setTimeout(() => 1, 0);debugger;
paused at:
debugger; setTimeout(() => 1, 0);#debugger;
paused at:
#debugger//should-break-here
Running test: testDebuggerStmtBeforeCallback2
paused at:
debugger; setTimeout(() => 1#, 0);debugger;
paused at:
#debugger//should-break-here
Running test: testSetTimeoutWithoutJS
paused at:
debugger; #setTimeout('}', 0);
setTimeout('var a = 239;', 0);
paused at:
#debugger//should-break-here
Running test: testResume
paused at:
#setTimeout(() => 42, 0)
paused at:
setTimeout(#() => 42, 0)
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
InspectorTest.log('Checks Debugger.scheduleStepIntoAsync with setTimeout.');
InspectorTest.setupScriptMap();
Protocol.Debugger.enable();
InspectorTest.runAsyncTestSuite([
async function testSetTimeout() {
Protocol.Runtime.evaluate({expression: 'debugger; setTimeout(() => 1, 0);'});
await Protocol.Debugger.oncePaused();
Protocol.Debugger.stepOver();
await waitPauseAndDumpLocation();
Protocol.Debugger.scheduleStepIntoAsync();
Protocol.Debugger.stepOver();
await waitPauseAndDumpLocation();
await Protocol.Debugger.resume();
},
async function testDebuggerStmtBeforeCallback1() {
Protocol.Runtime.evaluate({expression: 'debugger; setTimeout(() => 1, 0);debugger;'});
Protocol.Runtime.evaluate({expression: 'setTimeout(\'debugger//should-break-here\', 0)'});
await Protocol.Debugger.oncePaused();
Protocol.Debugger.stepOver();
await waitPauseAndDumpLocation();
Protocol.Debugger.scheduleStepIntoAsync();
Protocol.Debugger.stepOver();
await waitPauseAndDumpLocation();
await Protocol.Debugger.resume();
await waitPauseAndDumpLocation();
await Protocol.Debugger.resume();
},
async function testDebuggerStmtBeforeCallback2() {
Protocol.Runtime.evaluate({expression: 'debugger;\nsetTimeout(\'debugger//should-break-here\', 0);\nsetTimeout(() => 1, 0);'});
await Protocol.Debugger.oncePaused();
Protocol.Debugger.stepOver();
await Protocol.Debugger.oncePaused();
Protocol.Debugger.stepOver();
await waitPauseAndDumpLocation();
Protocol.Debugger.scheduleStepIntoAsync();
Protocol.Debugger.stepOver();
await waitPauseAndDumpLocation();
await Protocol.Debugger.resume();
await InspectorTest.waitPendingTasks();
},
async function testSetTimeoutWithoutJS() {
Protocol.Runtime.evaluate({expression: 'debugger; setTimeout(\'}\', 0);\nsetTimeout(\'var a = 239;\', 0);\nsetTimeout(\'debugger//should-break-here\', 0);'});
await Protocol.Debugger.oncePaused();
Protocol.Debugger.stepOver();
await waitPauseAndDumpLocation();
Protocol.Debugger.scheduleStepIntoAsync();
Protocol.Debugger.stepOver();
await waitPauseAndDumpLocation();
await Protocol.Debugger.resume();
},
async function testResume() {
Protocol.Debugger.pause();
Protocol.Runtime.evaluate({expression: 'setTimeout(() => 42, 0)'});
await waitPauseAndDumpLocation();
Protocol.Debugger.scheduleStepIntoAsync();
Protocol.Debugger.resume();
await waitPauseAndDumpLocation();
await Protocol.Debugger.resume();
}
]);
async function waitPauseAndDumpLocation() {
var message = await Protocol.Debugger.oncePaused();
InspectorTest.log('paused at:');
await InspectorTest.logSourceLocation(message.params.callFrames[0].location);
return message;
}
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