Commit f3dcdf88 authored by kozyatinskiy's avatar kozyatinskiy Committed by Commit bot

[inspector] introduced debug::SetBreakEventListener,SetExceptionEventListener

Inspector is moved to per-event-type callbacks instead of general v8::debug::SetDebugEventListener. It allows to:
- remove any usage of v8::Debug::EventDetails in debug-interface,
- avoid redundant JS call on each event to get properties of event objects,
- introduce better pure C++ API for these events later.

BUG=v8:5510
R=yangguo@chromium.org,jgruber@chromium.org,dgozman@chromium.org

Review-Url: https://codereview.chromium.org/2622253004
Cr-Commit-Position: refs/heads/master@{#42483}
parent ea4f834c
......@@ -8987,19 +8987,6 @@ MaybeLocal<Array> Debug::GetInternalProperties(Isolate* v8_isolate,
return Utils::ToLocal(result);
}
bool debug::SetDebugEventListener(Isolate* isolate, debug::EventCallback that,
Local<Value> data) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
ENTER_V8(i_isolate);
i::HandleScope scope(i_isolate);
i::Handle<i::Object> foreign = i_isolate->factory()->undefined_value();
if (that != NULL) {
foreign = i_isolate->factory()->NewForeign(FUNCTION_ADDR(that));
}
i_isolate->debug()->SetEventListener(foreign, Utils::OpenHandle(*data, true));
return true;
}
Local<Context> debug::GetDebugContext(Isolate* isolate) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
ENTER_V8(i_isolate);
......@@ -9331,20 +9318,11 @@ MaybeLocal<UnboundScript> debug::CompileInspectorScript(Isolate* v8_isolate,
RETURN_ESCAPED(ToApiHandle<UnboundScript>(result));
}
void debug::SetAsyncTaskListener(Isolate* v8_isolate,
debug::AsyncTaskListener listener,
void* data) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
ENTER_V8(isolate);
isolate->debug()->SetAsyncTaskListener(listener, data);
}
void debug::SetCompileEventListener(Isolate* v8_isolate,
debug::CompileEventListener listener,
void* data) {
void debug::SetDebugEventListener(Isolate* v8_isolate,
debug::DebugEventListener* listener) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
ENTER_V8(isolate);
isolate->debug()->SetCompileEventListener(listener, data);
isolate->debug()->SetDebugEventListener(listener);
}
Local<String> CpuProfileNode::GetFunctionName() const {
......
......@@ -16,52 +16,6 @@
namespace v8 {
namespace debug {
/**
* An event details object passed to the debug event listener.
*/
class EventDetails : public v8::Debug::EventDetails {
public:
/**
* Event type.
*/
virtual v8::DebugEvent GetEvent() const = 0;
/**
* Access to execution state and event data of the debug event. Don't store
* these cross callbacks as their content becomes invalid.
*/
virtual Local<Object> GetExecutionState() const = 0;
virtual Local<Object> GetEventData() const = 0;
/**
* Get the context active when the debug event happened. Note this is not
* the current active context as the JavaScript part of the debugger is
* running in its own context which is entered at this point.
*/
virtual Local<Context> GetEventContext() const = 0;
/**
* Client data passed with the corresponding callback when it was
* registered.
*/
virtual Local<Value> GetCallbackData() const = 0;
virtual ~EventDetails() {}
};
/**
* Debug event callback function.
*
* \param event_details object providing information about the debug event
*
* A EventCallback does not take possession of the event data,
* and must not rely on the data persisting after the handler returns.
*/
typedef void (*EventCallback)(const EventDetails& event_details);
bool SetDebugEventListener(Isolate* isolate, EventCallback that,
Local<Value> data = Local<Value>());
/**
* Debugger is running in its own context which is entered while debugger
* messages are being dispatched. This is an explicit getter for this
......@@ -193,17 +147,23 @@ void GetLoadedScripts(Isolate* isolate, PersistentValueVector<Script>& scripts);
MaybeLocal<UnboundScript> CompileInspectorScript(Isolate* isolate,
Local<String> source);
typedef std::function<void(debug::PromiseDebugActionType type, int id,
void* data)>
AsyncTaskListener;
void SetAsyncTaskListener(Isolate* isolate, AsyncTaskListener listener,
void* data);
typedef std::function<void(v8::Local<Script> script, bool has_compile_error,
void* data)>
CompileEventListener;
void SetCompileEventListener(Isolate* isolate, CompileEventListener listener,
void* data);
class DebugEventListener {
public:
virtual ~DebugEventListener() {}
virtual void PromiseEventOccurred(debug::PromiseDebugActionType type,
int id) {}
virtual void ScriptCompiled(v8::Local<Script> script,
bool has_compile_error) {}
virtual void BreakProgramRequested(v8::Local<v8::Context> paused_context,
v8::Local<v8::Object> exec_state,
v8::Local<v8::Value> break_points_hit) {}
virtual void ExceptionThrown(v8::Local<v8::Context> paused_context,
v8::Local<v8::Object> exec_state,
v8::Local<v8::Value> exception,
bool is_promise_rejection, bool is_uncaught) {}
};
void SetDebugEventListener(Isolate* isolate, DebugEventListener* listener);
} // namespace debug
} // namespace v8
......
......@@ -1715,6 +1715,16 @@ void Debug::OnPromiseReject(Handle<Object> promise, Handle<Object> value) {
}
}
namespace {
v8::Local<v8::Context> GetDebugEventContext(Isolate* isolate) {
Handle<Context> context = isolate->debug()->debugger_entry()->GetContext();
// Isolate::context() may have been NULL when "script collected" event
// occured.
if (context.is_null()) return v8::Local<v8::Context>();
Handle<Context> native_context(context->native_context());
return v8::Utils::ToLocal(native_context);
}
} // anonymous namespace
void Debug::OnException(Handle<Object> exception, Handle<Object> promise) {
// We cannot generate debug events when JS execution is disallowed.
......@@ -1754,6 +1764,21 @@ void Debug::OnException(Handle<Object> exception, Handle<Object> promise) {
DebugScope debug_scope(this);
if (debug_scope.failed()) return;
if (debug_event_listener_) {
HandleScope scope(isolate_);
// Create the execution state.
Handle<Object> exec_state;
// Bail out and don't call debugger if exception.
if (!MakeExecutionState().ToHandle(&exec_state)) return;
debug_event_listener_->ExceptionThrown(
GetDebugEventContext(isolate_),
v8::Utils::ToLocal(Handle<JSObject>::cast(exec_state)),
v8::Utils::ToLocal(exception), promise->IsJSObject(), uncaught);
if (!non_inspector_listener_exists()) return;
}
// Create the event data object.
Handle<Object> event_data;
// Bail out and don't call debugger if exception.
......@@ -1777,6 +1802,24 @@ void Debug::OnDebugBreak(Handle<Object> break_points_hit, bool auto_continue) {
PrintBreakLocation();
#endif // DEBUG
if (debug_event_listener_) {
HandleScope scope(isolate_);
// Create the execution state.
Handle<Object> exec_state;
// Bail out and don't call debugger if exception.
if (!MakeExecutionState().ToHandle(&exec_state)) return;
bool previous = in_debug_event_listener_;
in_debug_event_listener_ = true;
debug_event_listener_->BreakProgramRequested(
GetDebugEventContext(isolate_),
v8::Utils::ToLocal(Handle<JSObject>::cast(exec_state)),
v8::Utils::ToLocal(break_points_hit));
in_debug_event_listener_ = previous;
if (!non_inspector_listener_exists()) return;
}
HandleScope scope(isolate_);
// Create the event data object.
Handle<Object> event_data;
......@@ -1854,18 +1897,11 @@ int Debug::NextAsyncTaskId(Handle<JSObject> promise) {
return async_id->value();
}
void Debug::SetAsyncTaskListener(debug::AsyncTaskListener listener,
void* data) {
async_task_listener_ = listener;
async_task_listener_data_ = data;
UpdateState();
}
void Debug::OnAsyncTaskEvent(debug::PromiseDebugActionType type, int id) {
if (in_debug_scope() || ignore_events()) return;
if (async_task_listener_) {
async_task_listener_(type, id, async_task_listener_data_);
if (debug_event_listener_) {
debug_event_listener_->PromiseEventOccurred(type, id);
if (!non_inspector_listener_exists()) return;
}
......@@ -1920,7 +1956,7 @@ void Debug::CallEventCallback(v8::DebugEvent event,
in_debug_event_listener_ = true;
if (event_listener_->IsForeign()) {
// Invoke the C debug event listener.
debug::EventCallback callback = FUNCTION_CAST<debug::EventCallback>(
v8::Debug::EventCallback callback = FUNCTION_CAST<v8::Debug::EventCallback>(
Handle<Foreign>::cast(event_listener_)->foreign_address());
EventDetailsImpl event_details(event,
Handle<JSObject>::cast(exec_state),
......@@ -1945,13 +1981,6 @@ void Debug::CallEventCallback(v8::DebugEvent event,
in_debug_event_listener_ = previous;
}
void Debug::SetCompileEventListener(debug::CompileEventListener listener,
void* data) {
compile_event_listener_ = listener;
compile_event_listener_data_ = data;
UpdateState();
}
void Debug::ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script) {
if (ignore_events()) return;
if (script->type() != i::Script::TYPE_NORMAL &&
......@@ -1963,10 +1992,9 @@ void Debug::ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script) {
DebugScope debug_scope(this);
if (debug_scope.failed()) return;
if (compile_event_listener_) {
compile_event_listener_(ToApiHandle<debug::Script>(script),
event != v8::AfterCompile,
compile_event_listener_data_);
if (debug_event_listener_) {
debug_event_listener_->ScriptCompiled(ToApiHandle<debug::Script>(script),
event != v8::AfterCompile);
if (!non_inspector_listener_exists()) return;
}
......@@ -2162,10 +2190,14 @@ void Debug::SetMessageHandler(v8::Debug::MessageHandler handler) {
}
}
void Debug::SetDebugEventListener(debug::DebugEventListener* listener) {
debug_event_listener_ = listener;
UpdateState();
}
void Debug::UpdateState() {
bool is_active = message_handler_ != nullptr || !event_listener_.is_null() ||
async_task_listener_ != nullptr ||
compile_event_listener_ != nullptr;
debug_event_listener_ != nullptr;
if (is_active || in_debug_scope()) {
// Note that the debug context could have already been loaded to
// bootstrap test cases.
......@@ -2503,17 +2535,6 @@ v8::Local<v8::String> MessageImpl::GetJSON() const {
}
}
namespace {
v8::Local<v8::Context> GetDebugEventContext(Isolate* isolate) {
Handle<Context> context = isolate->debug()->debugger_entry()->GetContext();
// Isolate::context() may have been NULL when "script collected" event
// occured.
if (context.is_null()) return v8::Local<v8::Context>();
Handle<Context> native_context(context->native_context());
return v8::Utils::ToLocal(native_context);
}
} // anonymous namespace
v8::Local<v8::Context> MessageImpl::GetEventContext() const {
Isolate* isolate = event_data_->GetIsolate();
v8::Local<v8::Context> context = GetDebugEventContext(isolate);
......
......@@ -286,7 +286,7 @@ class MessageImpl : public v8::Debug::Message {
};
// Details of the debug event delivered to the debug event listener.
class EventDetailsImpl : public debug::EventDetails {
class EventDetailsImpl : public v8::Debug::EventDetails {
public:
EventDetailsImpl(DebugEvent event,
Handle<JSObject> exec_state,
......@@ -466,9 +466,7 @@ class Debug {
int NextAsyncTaskId(Handle<JSObject> promise);
void SetAsyncTaskListener(debug::AsyncTaskListener listener, void* data);
void SetCompileEventListener(debug::CompileEventListener listener,
void* data);
void SetDebugEventListener(debug::DebugEventListener* listener);
// Returns whether the operation succeeded. Compilation can only be triggered
// if a valid closure is passed as the second argument, otherwise the shared
......@@ -679,10 +677,7 @@ class Debug {
v8::Debug::MessageHandler message_handler_;
debug::AsyncTaskListener async_task_listener_ = nullptr;
void* async_task_listener_data_ = nullptr;
debug::CompileEventListener compile_event_listener_ = nullptr;
void* compile_event_listener_data_ = nullptr;
debug::DebugEventListener* debug_event_listener_ = nullptr;
static const int kQueueInitialSize = 4;
base::Semaphore command_received_; // Signaled for each command received.
......
......@@ -239,11 +239,10 @@ DebuggerScript.setBreakpointsActivated = function(execState, info)
}
/**
* @param {!BreakEvent} eventData
* @param {!Array<!BreakPoint>|undefined} breakpoints
*/
DebuggerScript.getBreakpointNumbers = function(eventData)
DebuggerScript.getBreakpointNumbers = function(breakpoints)
{
var breakpoints = eventData.breakPointsHit();
var numbers = [];
if (!breakpoints)
return numbers;
......
......@@ -159,13 +159,6 @@ BreakPoint.prototype.script_break_point = function() {}
BreakPoint.prototype.number = function() {}
/** @interface */
function BreakEvent() {}
/** @return {!Array<!BreakPoint>|undefined} */
BreakEvent.prototype.breakPointsHit = function() {}
/** @interface */
function ExecutionState() {}
......
......@@ -68,12 +68,7 @@ void V8Debugger::enable() {
if (m_enableCount++) return;
DCHECK(!enabled());
v8::HandleScope scope(m_isolate);
v8::debug::SetDebugEventListener(m_isolate, &V8Debugger::v8DebugEventCallback,
v8::External::New(m_isolate, this));
v8::debug::SetAsyncTaskListener(m_isolate, &V8Debugger::v8AsyncTaskListener,
this);
v8::debug::SetCompileEventListener(m_isolate,
&V8Debugger::v8CompileEventListener, this);
v8::debug::SetDebugEventListener(m_isolate, this);
v8::debug::SetOutOfMemoryCallback(m_isolate, &V8Debugger::v8OOMCallback,
this);
m_debuggerContext.Reset(m_isolate, v8::debug::GetDebugContext(m_isolate));
......@@ -91,8 +86,6 @@ void V8Debugger::disable() {
allAsyncTasksCanceled();
m_wasmTranslation.Clear();
v8::debug::SetDebugEventListener(m_isolate, nullptr);
v8::debug::SetAsyncTaskListener(m_isolate, nullptr, nullptr);
v8::debug::SetCompileEventListener(m_isolate, nullptr, nullptr);
v8::debug::SetOutOfMemoryCallback(m_isolate, nullptr, nullptr);
m_isolate->RestoreOriginalHeapLimit();
}
......@@ -526,119 +519,77 @@ void V8Debugger::v8OOMCallback(void* data) {
thisPtr->setPauseOnNextStatement(true);
}
void V8Debugger::v8DebugEventCallback(
const v8::debug::EventDetails& eventDetails) {
V8Debugger* thisPtr = toV8Debugger(eventDetails.GetCallbackData());
thisPtr->handleV8DebugEvent(eventDetails);
}
v8::Local<v8::Value> V8Debugger::callInternalGetterFunction(
v8::Local<v8::Object> object, const char* functionName) {
v8::MicrotasksScope microtasks(m_isolate,
v8::MicrotasksScope::kDoNotRunMicrotasks);
v8::Local<v8::Value> getterValue =
object
->Get(m_isolate->GetCurrentContext(),
toV8StringInternalized(m_isolate, functionName))
.ToLocalChecked();
DCHECK(!getterValue.IsEmpty() && getterValue->IsFunction());
return v8::Local<v8::Function>::Cast(getterValue)
->Call(m_isolate->GetCurrentContext(), object, 0, nullptr)
.ToLocalChecked();
}
void V8Debugger::handleV8DebugEvent(
const v8::debug::EventDetails& eventDetails) {
if (!enabled()) return;
v8::HandleScope scope(m_isolate);
v8::DebugEvent event = eventDetails.GetEvent();
if (event != v8::Break && event != v8::Exception) return;
v8::Local<v8::Context> eventContext = eventDetails.GetEventContext();
DCHECK(!eventContext.IsEmpty());
V8DebuggerAgentImpl* agent = m_inspector->enabledDebuggerAgentForGroup(
m_inspector->contextGroupId(eventContext));
if (!agent) return;
if (event == v8::Exception) {
v8::Local<v8::Context> context = debuggerContext();
v8::Local<v8::Object> eventData = eventDetails.GetEventData();
v8::Local<v8::Value> exception =
callInternalGetterFunction(eventData, "exception");
v8::Local<v8::Value> promise =
callInternalGetterFunction(eventData, "promise");
bool isPromiseRejection = !promise.IsEmpty() && promise->IsObject();
v8::Local<v8::Value> uncaught =
callInternalGetterFunction(eventData, "uncaught");
bool isUncaught = uncaught->BooleanValue(context).FromJust();
handleProgramBreak(eventContext, eventDetails.GetExecutionState(),
exception, v8::Local<v8::Array>(), isPromiseRejection,
isUncaught);
} else if (event == v8::Break) {
v8::Local<v8::Value> argv[] = {eventDetails.GetEventData()};
v8::Local<v8::Value> hitBreakpoints;
if (!callDebuggerMethod("getBreakpointNumbers", 1, argv)
.ToLocal(&hitBreakpoints))
return;
DCHECK(hitBreakpoints->IsArray());
handleProgramBreak(eventContext, eventDetails.GetExecutionState(),
v8::Local<v8::Value>(), hitBreakpoints.As<v8::Array>());
}
}
void V8Debugger::v8CompileEventListener(v8::Local<v8::debug::Script> script,
bool has_compile_error, void* data) {
V8Debugger* debugger = static_cast<V8Debugger*>(data);
void V8Debugger::ScriptCompiled(v8::Local<v8::debug::Script> script,
bool has_compile_error) {
v8::Local<v8::Value> contextData;
if (!script->ContextData().ToLocal(&contextData) || !contextData->IsInt32()) {
return;
}
int contextId = static_cast<int>(contextData.As<v8::Int32>()->Value());
int contextGroupId = debugger->m_inspector->contextGroupId(contextId);
int contextGroupId = m_inspector->contextGroupId(contextId);
if (!contextGroupId) return;
V8DebuggerAgentImpl* agent =
debugger->m_inspector->enabledDebuggerAgentForGroup(contextGroupId);
m_inspector->enabledDebuggerAgentForGroup(contextGroupId);
if (!agent) return;
if (script->IsWasm()) {
debugger->m_wasmTranslation.AddScript(script.As<v8::debug::WasmScript>(),
agent);
} else if (debugger->m_ignoreScriptParsedEventsCounter == 0) {
m_wasmTranslation.AddScript(script.As<v8::debug::WasmScript>(), agent);
} else if (m_ignoreScriptParsedEventsCounter == 0) {
agent->didParseSource(
V8DebuggerScript::Create(debugger->m_isolate, script, inLiveEditScope),
V8DebuggerScript::Create(m_isolate, script, inLiveEditScope),
!has_compile_error);
}
}
void V8Debugger::v8AsyncTaskListener(v8::debug::PromiseDebugActionType type,
int id, void* data) {
V8Debugger* debugger = static_cast<V8Debugger*>(data);
if (!debugger->m_maxAsyncCallStackDepth) return;
void V8Debugger::BreakProgramRequested(v8::Local<v8::Context> pausedContext,
v8::Local<v8::Object> execState,
v8::Local<v8::Value> breakPointsHit) {
v8::Local<v8::Value> argv[] = {breakPointsHit};
v8::Local<v8::Value> hitBreakpoints;
if (!callDebuggerMethod("getBreakpointNumbers", 1, argv)
.ToLocal(&hitBreakpoints)) {
return;
}
DCHECK(hitBreakpoints->IsArray());
handleProgramBreak(pausedContext, execState, v8::Local<v8::Value>(),
hitBreakpoints.As<v8::Array>());
}
void V8Debugger::ExceptionThrown(v8::Local<v8::Context> pausedContext,
v8::Local<v8::Object> execState,
v8::Local<v8::Value> exception,
bool isPromiseRejection, bool isUncaught) {
handleProgramBreak(pausedContext, execState, exception,
v8::Local<v8::Array>(), isPromiseRejection, isUncaught);
}
void V8Debugger::PromiseEventOccurred(v8::debug::PromiseDebugActionType type,
int id) {
if (!m_maxAsyncCallStackDepth) return;
// 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);
switch (type) {
case v8::debug::kDebugEnqueueAsyncFunction:
debugger->asyncTaskScheduled("async function", ptr, true);
asyncTaskScheduled("async function", ptr, true);
break;
case v8::debug::kDebugEnqueuePromiseResolve:
debugger->asyncTaskScheduled("Promise.resolve", ptr, true);
asyncTaskScheduled("Promise.resolve", ptr, true);
break;
case v8::debug::kDebugEnqueuePromiseReject:
debugger->asyncTaskScheduled("Promise.reject", ptr, true);
asyncTaskScheduled("Promise.reject", ptr, true);
break;
case v8::debug::kDebugEnqueuePromiseResolveThenableJob:
debugger->asyncTaskScheduled("PromiseResolveThenableJob", ptr, true);
asyncTaskScheduled("PromiseResolveThenableJob", ptr, true);
break;
case v8::debug::kDebugPromiseCollected:
debugger->asyncTaskCanceled(ptr);
asyncTaskCanceled(ptr);
break;
case v8::debug::kDebugWillHandle:
debugger->asyncTaskStarted(ptr);
asyncTaskStarted(ptr);
break;
case v8::debug::kDebugDidHandle:
debugger->asyncTaskFinished(ptr);
asyncTaskFinished(ptr);
break;
}
}
......
......@@ -26,7 +26,7 @@ class V8StackTraceImpl;
using protocol::Response;
class V8Debugger {
class V8Debugger : public v8::debug::DebugEventListener {
public:
V8Debugger(v8::Isolate*, V8InspectorImpl*);
~V8Debugger();
......@@ -112,14 +112,6 @@ class V8Debugger {
v8::Local<v8::Array> hitBreakpoints,
bool isPromiseRejection = false,
bool isUncaught = false);
static void v8DebugEventCallback(const v8::debug::EventDetails&);
v8::Local<v8::Value> callInternalGetterFunction(v8::Local<v8::Object>,
const char* functionName);
void handleV8DebugEvent(const v8::debug::EventDetails&);
static void v8AsyncTaskListener(v8::debug::PromiseDebugActionType type,
int id, void* data);
static void v8CompileEventListener(v8::Local<v8::debug::Script> script,
bool has_compile_error, void* data);
v8::Local<v8::Value> collectionEntries(v8::Local<v8::Context>,
v8::Local<v8::Object>);
......@@ -141,6 +133,19 @@ class V8Debugger {
v8::MaybeLocal<v8::Value> generatorScopes(v8::Local<v8::Context>,
v8::Local<v8::Value>);
// v8::debug::DebugEventListener implementation.
void PromiseEventOccurred(v8::debug::PromiseDebugActionType type,
int id) override;
void ScriptCompiled(v8::Local<v8::debug::Script> script,
bool has_compile_error) override;
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
v8::Local<v8::Object> exec_state,
v8::Local<v8::Value> break_points_hit) override;
void ExceptionThrown(v8::Local<v8::Context> paused_context,
v8::Local<v8::Object> exec_state,
v8::Local<v8::Value> exception,
bool is_promise_rejection, bool is_uncaught) override;
v8::Isolate* m_isolate;
V8InspectorImpl* m_inspector;
int m_enableCount;
......
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