Commit 98dec8f2 authored by Alexey Kozyatinskiy's avatar Alexey Kozyatinskiy Committed by Commit Bot

[inspector] added Runtime.terminateExecution

Runtime.terminateExecution terminates current or next JavaScript
call. Termination flag is automatically reset as soon as v8 call
or microtasks are completed.

R=pfeldman@chromium.org

Bug: chromium:820640
Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel
Change-Id: Ie21c123be3a61fe25cf6e04c38a8b6c664622ed7
Reviewed-on: https://chromium-review.googlesource.com/957386
Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Reviewed-by: 's avatarDmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51912}
parent a16ecd9e
...@@ -618,6 +618,9 @@ Response InjectedScript::wrapEvaluateResult( ...@@ -618,6 +618,9 @@ Response InjectedScript::wrapEvaluateResult(
m_lastEvaluationResult.AnnotateStrongRetainer(kGlobalHandleLabel); m_lastEvaluationResult.AnnotateStrongRetainer(kGlobalHandleLabel);
} }
} else { } else {
if (tryCatch.HasTerminated() || !tryCatch.CanContinue()) {
return Response::Error("Execution was terminated");
}
v8::Local<v8::Value> exception = tryCatch.Exception(); v8::Local<v8::Value> exception = tryCatch.Exception();
Response response = Response response =
wrapObject(exception, objectGroup, false, wrapObject(exception, objectGroup, false,
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
}, },
{ {
"domain": "Runtime", "domain": "Runtime",
"async": ["evaluate", "awaitPromise", "callFunctionOn", "runScript"], "async": ["evaluate", "awaitPromise", "callFunctionOn", "runScript", "terminateExecution"],
"exported": ["StackTrace", "StackTraceId", "RemoteObject", "ExecutionContextId"] "exported": ["StackTrace", "StackTraceId", "RemoteObject", "ExecutionContextId"]
}, },
{ {
......
...@@ -2788,6 +2788,11 @@ ...@@ -2788,6 +2788,11 @@
"type": "boolean" "type": "boolean"
} }
] ]
},
{
"name": "terminateExecution",
"description": "Terminate current or next JavaScript execution.\nWill cancel the termination when the outer-most script execution ends.",
"experimental": true
} }
], ],
"events": [ "events": [
......
...@@ -1280,6 +1280,10 @@ domain Runtime ...@@ -1280,6 +1280,10 @@ domain Runtime
parameters parameters
boolean enabled boolean enabled
# Terminate current or next JavaScript execution.
# Will cancel the termination when the outer-most script execution ends.
experimental command terminateExecution
# Issued when console API was called. # Issued when console API was called.
event consoleAPICalled event consoleAPICalled
parameters parameters
......
...@@ -155,6 +155,12 @@ class MatchPrototypePredicate : public v8::debug::QueryObjectPredicate { ...@@ -155,6 +155,12 @@ class MatchPrototypePredicate : public v8::debug::QueryObjectPredicate {
v8::Local<v8::Value> m_prototype; v8::Local<v8::Value> m_prototype;
}; };
// TODO(kozyatinskiy): add V8Inspector* field on i::Isolate instead.
std::unordered_map<v8::Isolate*, V8Debugger*>& IsolateToDebuggerMap() {
static std::unordered_map<v8::Isolate*, V8Debugger*> map;
return map;
}
} // namespace } // namespace
V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector) V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
...@@ -166,9 +172,19 @@ V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector) ...@@ -166,9 +172,19 @@ V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
m_maxAsyncCallStacks(kMaxAsyncTaskStacks), m_maxAsyncCallStacks(kMaxAsyncTaskStacks),
m_maxAsyncCallStackDepth(0), m_maxAsyncCallStackDepth(0),
m_pauseOnExceptionsState(v8::debug::NoBreakOnException), m_pauseOnExceptionsState(v8::debug::NoBreakOnException),
m_wasmTranslation(isolate) {} m_wasmTranslation(isolate) {
IsolateToDebuggerMap()[isolate] = this;
}
V8Debugger::~V8Debugger() {} V8Debugger::~V8Debugger() {
IsolateToDebuggerMap().erase(m_isolate);
if (m_terminateExecutionCallback) {
m_isolate->RemoveCallCompletedCallback(
&V8Debugger::terminateExecutionCompletedCallback);
m_isolate->RemoveMicrotasksCompletedCallback(
&V8Debugger::terminateExecutionCompletedCallback);
}
}
void V8Debugger::enable() { void V8Debugger::enable() {
if (m_enableCount++) return; if (m_enableCount++) return;
...@@ -334,6 +350,33 @@ void V8Debugger::pauseOnAsyncCall(int targetContextGroupId, uintptr_t task, ...@@ -334,6 +350,33 @@ void V8Debugger::pauseOnAsyncCall(int targetContextGroupId, uintptr_t task,
m_taskWithScheduledBreakDebuggerId = debuggerId; m_taskWithScheduledBreakDebuggerId = debuggerId;
} }
void V8Debugger::terminateExecution(
std::unique_ptr<TerminateExecutionCallback> callback) {
if (m_terminateExecutionCallback) {
callback->sendFailure(
Response::Error("There is current termination request in progress"));
return;
}
m_terminateExecutionCallback = std::move(callback);
m_isolate->AddCallCompletedCallback(
&V8Debugger::terminateExecutionCompletedCallback);
m_isolate->AddMicrotasksCompletedCallback(
&V8Debugger::terminateExecutionCompletedCallback);
m_isolate->TerminateExecution();
}
void V8Debugger::terminateExecutionCompletedCallback(v8::Isolate* isolate) {
isolate->RemoveCallCompletedCallback(
&V8Debugger::terminateExecutionCompletedCallback);
isolate->RemoveMicrotasksCompletedCallback(
&V8Debugger::terminateExecutionCompletedCallback);
V8Debugger* debugger = IsolateToDebuggerMap()[isolate];
DCHECK(debugger);
debugger->m_isolate->CancelTerminateExecution();
debugger->m_terminateExecutionCallback->sendSuccess();
debugger->m_terminateExecutionCallback.reset();
}
Response V8Debugger::continueToLocation( Response V8Debugger::continueToLocation(
int targetContextGroupId, V8DebuggerScript* script, int targetContextGroupId, V8DebuggerScript* script,
std::unique_ptr<protocol::Debugger::Location> location, std::unique_ptr<protocol::Debugger::Location> location,
......
...@@ -32,6 +32,8 @@ struct V8StackTraceId; ...@@ -32,6 +32,8 @@ struct V8StackTraceId;
using protocol::Response; using protocol::Response;
using ScheduleStepIntoAsyncCallback = using ScheduleStepIntoAsyncCallback =
protocol::Debugger::Backend::ScheduleStepIntoAsyncCallback; protocol::Debugger::Backend::ScheduleStepIntoAsyncCallback;
using TerminateExecutionCallback =
protocol::Runtime::Backend::TerminateExecutionCallback;
class V8Debugger : public v8::debug::DebugDelegate { class V8Debugger : public v8::debug::DebugDelegate {
public: public:
...@@ -60,6 +62,8 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -60,6 +62,8 @@ class V8Debugger : public v8::debug::DebugDelegate {
void pauseOnAsyncCall(int targetContextGroupId, uintptr_t task, void pauseOnAsyncCall(int targetContextGroupId, uintptr_t task,
const String16& debuggerId); const String16& debuggerId);
void terminateExecution(std::unique_ptr<TerminateExecutionCallback> callback);
Response continueToLocation(int targetContextGroupId, Response continueToLocation(int targetContextGroupId,
V8DebuggerScript* script, V8DebuggerScript* script,
std::unique_ptr<protocol::Debugger::Location>, std::unique_ptr<protocol::Debugger::Location>,
...@@ -132,6 +136,7 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -132,6 +136,7 @@ class V8Debugger : public v8::debug::DebugDelegate {
bool shouldContinueToCurrentLocation(); bool shouldContinueToCurrentLocation();
static void v8OOMCallback(void* data); static void v8OOMCallback(void* data);
static void terminateExecutionCompletedCallback(v8::Isolate* isolate);
void handleProgramBreak( void handleProgramBreak(
v8::Local<v8::Context> pausedContext, v8::Local<v8::Value> exception, v8::Local<v8::Context> pausedContext, v8::Local<v8::Value> exception,
...@@ -232,6 +237,8 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -232,6 +237,8 @@ class V8Debugger : public v8::debug::DebugDelegate {
protocol::HashMap<String16, std::pair<int64_t, int64_t>> protocol::HashMap<String16, std::pair<int64_t, int64_t>>
m_serializedDebuggerIdToDebuggerId; m_serializedDebuggerIdToDebuggerId;
std::unique_ptr<TerminateExecutionCallback> m_terminateExecutionCallback;
WasmTranslation m_wasmTranslation; WasmTranslation m_wasmTranslation;
DISALLOW_COPY_AND_ASSIGN(V8Debugger); DISALLOW_COPY_AND_ASSIGN(V8Debugger);
......
...@@ -197,8 +197,11 @@ Response V8InspectorSessionImpl::findInjectedScript( ...@@ -197,8 +197,11 @@ Response V8InspectorSessionImpl::findInjectedScript(
if (!context) return Response::Error("Cannot find context with specified id"); if (!context) return Response::Error("Cannot find context with specified id");
injectedScript = context->getInjectedScript(m_sessionId); injectedScript = context->getInjectedScript(m_sessionId);
if (!injectedScript) { if (!injectedScript) {
if (!context->createInjectedScript(m_sessionId)) if (!context->createInjectedScript(m_sessionId)) {
if (m_inspector->isolate()->IsExecutionTerminating())
return Response::Error("Execution was terminated");
return Response::Error("Cannot access specified execution context"); return Response::Error("Cannot access specified execution context");
}
injectedScript = context->getInjectedScript(m_sessionId); injectedScript = context->getInjectedScript(m_sessionId);
if (m_customObjectFormatterEnabled) if (m_customObjectFormatterEnabled)
injectedScript->setCustomObjectFormatterEnabled(true); injectedScript->setCustomObjectFormatterEnabled(true);
......
...@@ -608,6 +608,11 @@ Response V8RuntimeAgentImpl::globalLexicalScopeNames( ...@@ -608,6 +608,11 @@ Response V8RuntimeAgentImpl::globalLexicalScopeNames(
return Response::OK(); return Response::OK();
} }
void V8RuntimeAgentImpl::terminateExecution(
std::unique_ptr<TerminateExecutionCallback> callback) {
m_inspector->debugger()->terminateExecution(std::move(callback));
}
void V8RuntimeAgentImpl::restore() { void V8RuntimeAgentImpl::restore() {
if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false)) if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false))
return; return;
......
...@@ -104,6 +104,8 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend { ...@@ -104,6 +104,8 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend {
Response globalLexicalScopeNames( Response globalLexicalScopeNames(
Maybe<int> executionContextId, Maybe<int> executionContextId,
std::unique_ptr<protocol::Array<String16>>* outNames) override; std::unique_ptr<protocol::Array<String16>>* outNames) override;
void terminateExecution(
std::unique_ptr<TerminateExecutionCallback> callback) override;
void reset(); void reset();
void reportExecutionContextCreated(InspectedContext*); void reportExecutionContextCreated(InspectedContext*);
......
Tests Runtime.terminateExecution
Terminate first evaluation (it forces injected-script-source compilation)
{
id : <messageId>
result : {
}
}
{
error : {
code : -32000
message : Cannot access specified execution context
}
id : <messageId>
}
Checks that we reset termination after evaluation
{
description : 42
type : number
value : 42
}
{
id : <messageId>
result : {
result : {
type : undefined
}
}
}
Terminate evaluation when injected-script-source already compiled
{
id : <messageId>
result : {
}
}
{
error : {
code : -32000
message : Execution was terminated
}
id : <messageId>
}
Terminate script evaluated with v8 API
{
id : <messageId>
result : {
}
}
Terminate chained callback
Pause inside microtask and terminate execution
{
id : <messageId>
result : {
}
}
{
type : string
value : separate eval after while(true)
}
{
id : <messageId>
result : {
result : {
type : undefined
}
}
}
// Copyright 2018 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('Tests Runtime.terminateExecution');
(async function test() {
Protocol.Runtime.enable();
Protocol.Runtime.onConsoleAPICalled(
message => InspectorTest.logMessage(message.params.args[0]));
InspectorTest.log(
'Terminate first evaluation (it forces injected-script-source compilation)');
await Promise.all([
Protocol.Runtime.terminateExecution().then(InspectorTest.logMessage),
Protocol.Runtime.evaluate({expression: 'console.log(42)'})
.then(InspectorTest.logMessage)
]);
InspectorTest.log('Checks that we reset termination after evaluation');
InspectorTest.logMessage(
await Protocol.Runtime.evaluate({expression: 'console.log(42)'}));
InspectorTest.log(
'Terminate evaluation when injected-script-source already compiled');
await Promise.all([
Protocol.Runtime.terminateExecution().then(InspectorTest.logMessage),
Protocol.Runtime.evaluate({expression: 'console.log(42)'})
.then(InspectorTest.logMessage)
]);
InspectorTest.log('Terminate script evaluated with v8 API');
const terminated =
Protocol.Runtime.terminateExecution().then(InspectorTest.logMessage);
contextGroup.addScript('console.log(42)');
await terminated;
InspectorTest.log('Terminate chained callback');
Protocol.Debugger.enable();
const paused = Protocol.Debugger.oncePaused();
await Protocol.Runtime.evaluate({
expression: `let p = new Promise(resolve => setTimeout(resolve, 0));
p.then(() => {
while(true){ debugger; }
}).then(() => console.log('chained after chained callback'));
p.then(() => { console.log('another chained callback'); });
undefined`
});
await paused;
InspectorTest.log('Pause inside microtask and terminate execution');
Protocol.Runtime.terminateExecution().then(InspectorTest.logMessage);
await Protocol.Debugger.resume();
await Protocol.Runtime
.evaluate({expression: `console.log('separate eval after while(true)')`})
.then(InspectorTest.logMessage);
await Protocol.Debugger.disable();
await Protocol.Runtime.disable();
InspectorTest.completeTest();
})();
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