Commit 97fc20f3 authored by Alexey Kozyatinskiy's avatar Alexey Kozyatinskiy Committed by Commit Bot

Reland "[inspector] added Runtime.terminateExecution"

This is a reland of 14824520

Original change's description:
> [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: Dmitry Gozman <dgozman@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#51912}

Bug: chromium:820640
Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng;master.tryserver.blink:linux_trusty_blink_rel
Change-Id: I6dd30f65c06c2b7eefd1e7beb9a3cf50ea5bf8cd
Reviewed-on: https://chromium-review.googlesource.com/967323
Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Reviewed-by: 's avatarAleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#52004}
parent 7652bd27
...@@ -9110,6 +9110,16 @@ int debug::GetContextId(Local<Context> context) { ...@@ -9110,6 +9110,16 @@ int debug::GetContextId(Local<Context> context) {
return (value->IsSmi()) ? i::Smi::ToInt(value) : 0; return (value->IsSmi()) ? i::Smi::ToInt(value) : 0;
} }
void debug::SetInspector(Isolate* isolate,
v8_inspector::V8Inspector* inspector) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
i_isolate->set_inspector(inspector);
}
v8_inspector::V8Inspector* debug::GetInspector(Isolate* isolate) {
return reinterpret_cast<i::Isolate*>(isolate)->inspector();
}
Local<Context> debug::GetDebugContext(Isolate* isolate) { Local<Context> debug::GetDebugContext(Isolate* isolate) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
ENTER_V8_NO_SCRIPT_NO_EXCEPTION(i_isolate); ENTER_V8_NO_SCRIPT_NO_EXCEPTION(i_isolate);
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef V8_DEBUG_DEBUG_INTERFACE_H_ #ifndef V8_DEBUG_DEBUG_INTERFACE_H_
#define V8_DEBUG_DEBUG_INTERFACE_H_ #define V8_DEBUG_DEBUG_INTERFACE_H_
#include "include/v8-inspector.h"
#include "include/v8-util.h" #include "include/v8-util.h"
#include "include/v8.h" #include "include/v8.h"
...@@ -29,6 +30,9 @@ namespace debug { ...@@ -29,6 +30,9 @@ namespace debug {
void SetContextId(Local<Context> context, int id); void SetContextId(Local<Context> context, int id);
int GetContextId(Local<Context> context); int GetContextId(Local<Context> context);
void SetInspector(Isolate* isolate, v8_inspector::V8Inspector*);
v8_inspector::V8Inspector* GetInspector(Isolate* isolate);
/** /**
* Debugger is running in its own context which is entered while debugger * Debugger is running in its own context which is entered while debugger
* messages are being dispatched. This is an explicit getter for this * messages are being dispatched. This is an explicit getter for this
......
...@@ -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"]
}, },
{ {
......
...@@ -2794,6 +2794,11 @@ ...@@ -2794,6 +2794,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": [
......
...@@ -1282,6 +1282,10 @@ domain Runtime ...@@ -1282,6 +1282,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
......
...@@ -154,7 +154,6 @@ class MatchPrototypePredicate : public v8::debug::QueryObjectPredicate { ...@@ -154,7 +154,6 @@ class MatchPrototypePredicate : public v8::debug::QueryObjectPredicate {
v8::Local<v8::Context> m_context; v8::Local<v8::Context> m_context;
v8::Local<v8::Value> m_prototype; v8::Local<v8::Value> m_prototype;
}; };
} // namespace } // namespace
V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector) V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
...@@ -168,7 +167,14 @@ V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector) ...@@ -168,7 +167,14 @@ V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
m_pauseOnExceptionsState(v8::debug::NoBreakOnException), m_pauseOnExceptionsState(v8::debug::NoBreakOnException),
m_wasmTranslation(isolate) {} m_wasmTranslation(isolate) {}
V8Debugger::~V8Debugger() {} V8Debugger::~V8Debugger() {
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 +340,34 @@ void V8Debugger::pauseOnAsyncCall(int targetContextGroupId, uintptr_t task, ...@@ -334,6 +340,34 @@ 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);
V8InspectorImpl* inspector =
static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate));
V8Debugger* debugger = inspector->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);
......
...@@ -59,10 +59,12 @@ V8InspectorImpl::V8InspectorImpl(v8::Isolate* isolate, ...@@ -59,10 +59,12 @@ V8InspectorImpl::V8InspectorImpl(v8::Isolate* isolate,
m_capturingStackTracesCount(0), m_capturingStackTracesCount(0),
m_lastExceptionId(0), m_lastExceptionId(0),
m_lastContextId(0) { m_lastContextId(0) {
v8::debug::SetInspector(m_isolate, this);
v8::debug::SetConsoleDelegate(m_isolate, console()); v8::debug::SetConsoleDelegate(m_isolate, console());
} }
V8InspectorImpl::~V8InspectorImpl() { V8InspectorImpl::~V8InspectorImpl() {
v8::debug::SetInspector(m_isolate, nullptr);
v8::debug::SetConsoleDelegate(m_isolate, nullptr); v8::debug::SetConsoleDelegate(m_isolate, nullptr);
} }
......
...@@ -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);
......
...@@ -609,6 +609,11 @@ Response V8RuntimeAgentImpl::globalLexicalScopeNames( ...@@ -609,6 +609,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*);
......
...@@ -93,7 +93,9 @@ void Isolate::FireBeforeCallEnteredCallback() { ...@@ -93,7 +93,9 @@ void Isolate::FireBeforeCallEnteredCallback() {
} }
void Isolate::FireMicrotasksCompletedCallback() { void Isolate::FireMicrotasksCompletedCallback() {
for (auto& callback : microtasks_completed_callbacks_) { std::vector<MicrotasksCompletedCallback> callbacks(
microtasks_completed_callbacks_);
for (auto& callback : callbacks) {
callback(reinterpret_cast<v8::Isolate*>(this)); callback(reinterpret_cast<v8::Isolate*>(this));
} }
} }
......
...@@ -3727,7 +3727,8 @@ void Isolate::FireCallCompletedCallback() { ...@@ -3727,7 +3727,8 @@ void Isolate::FireCallCompletedCallback() {
// Fire callbacks. Increase call depth to prevent recursive callbacks. // Fire callbacks. Increase call depth to prevent recursive callbacks.
v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(this); v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(this);
v8::Isolate::SuppressMicrotaskExecutionScope suppress(isolate); v8::Isolate::SuppressMicrotaskExecutionScope suppress(isolate);
for (auto& callback : call_completed_callbacks_) { std::vector<CallCompletedCallback> callbacks(call_completed_callbacks_);
for (auto& callback : callbacks) {
callback(reinterpret_cast<v8::Isolate*>(this)); callback(reinterpret_cast<v8::Isolate*>(this));
} }
} }
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <queue> #include <queue>
#include <vector> #include <vector>
#include "include/v8-inspector.h"
#include "include/v8.h" #include "include/v8.h"
#include "src/allocation.h" #include "src/allocation.h"
#include "src/base/atomicops.h" #include "src/base/atomicops.h"
...@@ -443,7 +444,8 @@ typedef std::vector<HeapObject*> DebugObjectCache; ...@@ -443,7 +444,8 @@ typedef std::vector<HeapObject*> DebugObjectCache;
V(debug::Coverage::Mode, code_coverage_mode, debug::Coverage::kBestEffort) \ V(debug::Coverage::Mode, code_coverage_mode, debug::Coverage::kBestEffort) \
V(debug::TypeProfile::Mode, type_profile_mode, debug::TypeProfile::kNone) \ V(debug::TypeProfile::Mode, type_profile_mode, debug::TypeProfile::kNone) \
V(int, last_stack_frame_info_id, 0) \ V(int, last_stack_frame_info_id, 0) \
V(int, last_console_context_id, 0) V(int, last_console_context_id, 0) \
V(v8_inspector::V8Inspector*, inspector, nullptr)
#define THREAD_LOCAL_TOP_ACCESSOR(type, name) \ #define THREAD_LOCAL_TOP_ACCESSOR(type, name) \
inline void set_##name(type v) { thread_local_top_.name##_ = v; } \ inline void set_##name(type v) { thread_local_top_.name##_ = v; } \
......
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