Commit d0d4189d authored by yangguo's avatar yangguo Committed by Commit bot

[debugger] implement legacy debug event listeners via debug delegate.

R=jgruber@chromium.org
BUG=v8:5530

Review-Url: https://codereview.chromium.org/2682593003
Cr-Commit-Position: refs/heads/master@{#43059}
parent 1d3317ff
......@@ -8912,11 +8912,15 @@ bool Debug::SetDebugEventListener(Isolate* isolate, EventCallback that,
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));
if (that == nullptr) {
i_isolate->debug()->SetDebugDelegate(nullptr, false);
} else {
i::Handle<i::Object> i_data = i_isolate->factory()->undefined_value();
if (!data.IsEmpty()) i_data = Utils::OpenHandle(*data);
i::NativeDebugDelegate* delegate =
new i::NativeDebugDelegate(i_isolate, that, i_data);
i_isolate->debug()->SetDebugDelegate(delegate, true);
}
i_isolate->debug()->SetEventListener(foreign, Utils::OpenHandle(*data, true));
return true;
}
......@@ -9369,7 +9373,7 @@ void debug::SetDebugDelegate(Isolate* v8_isolate,
debug::DebugDelegate* delegate) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
ENTER_V8(isolate);
isolate->debug()->SetDebugDelegate(delegate);
isolate->debug()->SetDebugDelegate(delegate, false);
}
void debug::ResetBlackboxedStateCache(Isolate* v8_isolate,
......
......@@ -163,7 +163,8 @@ class DebugDelegate {
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) {}
v8::Local<v8::Value> promise, bool is_uncaught) {
}
virtual bool IsFunctionBlackboxed(v8::Local<debug::Script> script,
const debug::Location& start,
const debug::Location& end) {
......
This diff is collapsed.
......@@ -230,28 +230,6 @@ class DebugInfoListNode {
DebugInfoListNode* next_;
};
// Details of the debug event delivered to the debug event listener.
class EventDetailsImpl : public v8::Debug::EventDetails {
public:
EventDetailsImpl(DebugEvent event, Handle<JSObject> exec_state,
Handle<JSObject> event_data, Handle<Object> callback_data);
virtual DebugEvent GetEvent() const;
virtual v8::Local<v8::Object> GetExecutionState() const;
virtual v8::Local<v8::Object> GetEventData() const;
virtual v8::Local<v8::Context> GetEventContext() const;
virtual v8::Local<v8::Value> GetCallbackData() const;
virtual v8::Debug::ClientData* GetClientData() const { return nullptr; }
virtual v8::Isolate* GetIsolate() const;
private:
DebugEvent event_; // Debug event causing the break.
Handle<JSObject> exec_state_; // Current execution state.
Handle<JSObject> event_data_; // Data associated with the event.
Handle<Object> callback_data_; // User data passed with the callback
// when it was registered.
};
class DebugFeatureTracker {
public:
enum Feature {
......@@ -290,10 +268,9 @@ class Debug {
void OnPromiseReject(Handle<Object> promise, Handle<Object> value);
void OnCompileError(Handle<Script> script);
void OnAfterCompile(Handle<Script> script);
void OnAsyncTaskEvent(debug::PromiseDebugActionType type, int id);
void OnAsyncTaskEvent(debug::PromiseDebugActionType type, int id,
int parent_id);
// API facing.
void SetEventListener(Handle<Object> callback, Handle<Object> data);
MUST_USE_RESULT MaybeHandle<Object> Call(Handle<Object> fun,
Handle<Object> data);
Handle<Context> GetDebugContext();
......@@ -346,7 +323,7 @@ class Debug {
bool IsBlackboxed(Handle<SharedFunctionInfo> shared);
void SetDebugDelegate(debug::DebugDelegate* delegate);
void SetDebugDelegate(debug::DebugDelegate* delegate, bool pass_ownership);
// Returns whether the operation succeeded.
bool EnsureDebugInfo(Handle<SharedFunctionInfo> shared);
......@@ -446,9 +423,11 @@ class Debug {
private:
explicit Debug(Isolate* isolate);
~Debug() { DCHECK_NULL(debug_delegate_); }
void UpdateState();
void UpdateHookOnFunctionCall();
void RemoveDebugDelegate();
void Unload();
void SetNextBreakId() {
thread_local_.break_id_ = ++thread_local_.break_count_;
......@@ -460,9 +439,7 @@ class Debug {
inline bool ignore_events() const {
return is_suppressed_ || !is_active_ || isolate_->needs_side_effect_check();
}
inline bool break_disabled() const {
return break_disabled_ || in_debug_event_listener_;
}
inline bool break_disabled() const { return break_disabled_; }
void clear_suspended_generator() {
thread_local_.suspended_generator_ = Smi::kZero;
......@@ -472,17 +449,6 @@ class Debug {
return thread_local_.suspended_generator_ != Smi::kZero;
}
// There are three types of event listeners: C++ message_handler,
// JavaScript event listener and C++ event listener.
// Currently inspector still uses C++ event listener and installs
// more specific event listeners for part of events. Calling of
// C++ event listener is redundant when more specific event listener
// is presented. Other clients can install JavaScript event listener
// (e.g. some of NodeJS module).
bool non_inspector_listener_exists() const {
return !event_listener_.is_null() && !event_listener_->IsForeign();
}
bool IsExceptionBlackboxed(bool uncaught);
void OnException(Handle<Object> exception, Handle<Object> promise);
......@@ -497,8 +463,8 @@ class Debug {
Handle<Object> promise);
MUST_USE_RESULT MaybeHandle<Object> MakeCompileEvent(
Handle<Script> script, v8::DebugEvent type);
MUST_USE_RESULT MaybeHandle<Object> MakeAsyncTaskEvent(Handle<Smi> type,
Handle<Smi> id);
MUST_USE_RESULT MaybeHandle<Object> MakeAsyncTaskEvent(
v8::debug::PromiseDebugActionType type, int id);
void ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script);
void ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data);
......@@ -538,10 +504,9 @@ class Debug {
// Global handles.
Handle<Context> debug_context_;
Handle<Object> event_listener_;
Handle<Object> event_listener_data_;
debug::DebugDelegate* debug_delegate_ = nullptr;
bool owns_debug_delegate_ = false;
// Debugger is active, i.e. there is a debug event listener attached.
bool is_active_;
......@@ -556,8 +521,6 @@ class Debug {
bool break_disabled_;
// Do not break on break points.
bool break_points_active_;
// Nested inside a debug event listener.
bool in_debug_event_listener_;
// Trigger debug break events for all exceptions.
bool break_on_exception_;
// Trigger debug break events for uncaught exceptions.
......@@ -621,6 +584,7 @@ class Debug {
friend class LiveEdit;
friend class SuppressDebug;
friend class NoSideEffectScope;
friend class LegacyDebugDelegate;
friend Handle<FixedArray> GetDebuggedFunctions(); // In test-debug.cc
friend void CheckDebuggerUnloaded(bool check_functions); // In test-debug.cc
......@@ -628,6 +592,84 @@ class Debug {
DISALLOW_COPY_AND_ASSIGN(Debug);
};
class LegacyDebugDelegate : public v8::debug::DebugDelegate {
public:
explicit LegacyDebugDelegate(Isolate* isolate) : isolate_(isolate) {}
void PromiseEventOccurred(v8::debug::PromiseDebugActionType type, int id,
int parent_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,
v8::Local<v8::Value> promise, bool is_uncaught) override;
bool IsFunctionBlackboxed(v8::Local<v8::debug::Script> script,
const v8::debug::Location& start,
const v8::debug::Location& end) override {
return false;
}
protected:
Isolate* isolate_;
private:
void ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data);
virtual void ProcessDebugEvent(v8::DebugEvent event,
Handle<JSObject> event_data,
Handle<JSObject> exec_state) = 0;
};
class JavaScriptDebugDelegate : public LegacyDebugDelegate {
public:
JavaScriptDebugDelegate(Isolate* isolate, Handle<JSFunction> listener,
Handle<Object> data);
virtual ~JavaScriptDebugDelegate();
private:
void ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data,
Handle<JSObject> exec_state) override;
Handle<JSFunction> listener_;
Handle<Object> data_;
};
class NativeDebugDelegate : public LegacyDebugDelegate {
public:
NativeDebugDelegate(Isolate* isolate, v8::Debug::EventCallback callback,
Handle<Object> data);
virtual ~NativeDebugDelegate();
private:
// Details of the debug event delivered to the debug event listener.
class EventDetails : public v8::Debug::EventDetails {
public:
EventDetails(DebugEvent event, Handle<JSObject> exec_state,
Handle<JSObject> event_data, Handle<Object> callback_data);
virtual DebugEvent GetEvent() const;
virtual v8::Local<v8::Object> GetExecutionState() const;
virtual v8::Local<v8::Object> GetEventData() const;
virtual v8::Local<v8::Context> GetEventContext() const;
virtual v8::Local<v8::Value> GetCallbackData() const;
virtual v8::Debug::ClientData* GetClientData() const { return nullptr; }
virtual v8::Isolate* GetIsolate() const;
private:
DebugEvent event_; // Debug event causing the break.
Handle<JSObject> exec_state_; // Current execution state.
Handle<JSObject> event_data_; // Data associated with the event.
Handle<Object> callback_data_; // User data passed with the callback
// when it was registered.
};
void ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data,
Handle<JSObject> exec_state) override;
v8::Debug::EventCallback callback_;
Handle<Object> data_;
};
// This scope is used to load and enter the debug context and create a new
// break state. Leaving the scope will restore the previous state.
......@@ -661,21 +703,16 @@ class DebugScope BASE_EMBEDDED {
class DisableBreak BASE_EMBEDDED {
public:
explicit DisableBreak(Debug* debug)
: debug_(debug),
previous_break_disabled_(debug->break_disabled_),
previous_in_debug_event_listener_(debug->in_debug_event_listener_) {
: debug_(debug), previous_break_disabled_(debug->break_disabled_) {
debug_->break_disabled_ = true;
debug_->in_debug_event_listener_ = true;
}
~DisableBreak() {
debug_->break_disabled_ = previous_break_disabled_;
debug_->in_debug_event_listener_ = previous_in_debug_event_listener_;
}
private:
Debug* debug_;
bool previous_break_disabled_;
bool previous_in_debug_event_listener_;
DISALLOW_COPY_AND_ASSIGN(DisableBreak);
};
......
......@@ -628,7 +628,9 @@ void V8Debugger::BreakProgramRequested(v8::Local<v8::Context> pausedContext,
void V8Debugger::ExceptionThrown(v8::Local<v8::Context> pausedContext,
v8::Local<v8::Object> execState,
v8::Local<v8::Value> exception,
bool isPromiseRejection, bool isUncaught) {
v8::Local<v8::Value> promise,
bool isUncaught) {
bool isPromiseRejection = promise->IsPromise();
handleProgramBreak(pausedContext, execState, exception,
v8::Local<v8::Array>(), isPromiseRejection, isUncaught);
}
......
......@@ -140,7 +140,7 @@ class V8Debugger : public v8::debug::DebugDelegate {
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::Local<v8::Value> promise, bool is_uncaught) override;
bool IsFunctionBlackboxed(v8::Local<v8::debug::Script> script,
const v8::debug::Location& start,
const v8::debug::Location& end) override;
......
......@@ -79,8 +79,13 @@ RUNTIME_FUNCTION(Runtime_SetDebugEventListener) {
CHECK(args[0]->IsJSFunction() || args[0]->IsNullOrUndefined(isolate));
CONVERT_ARG_HANDLE_CHECKED(Object, callback, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, data, 1);
isolate->debug()->SetEventListener(callback, data);
if (callback->IsJSFunction()) {
JavaScriptDebugDelegate* delegate = new JavaScriptDebugDelegate(
isolate, Handle<JSFunction>::cast(callback), data);
isolate->debug()->SetDebugDelegate(delegate, true);
} else {
isolate->debug()->SetDebugDelegate(nullptr, false);
}
return isolate->heap()->undefined_value();
}
......@@ -1853,7 +1858,7 @@ RUNTIME_FUNCTION(Runtime_DebugAsyncFunctionPromiseCreated) {
JSObject::SetProperty(promise, async_stack_id_symbol,
handle(Smi::FromInt(id), isolate), STRICT)
.Assert();
isolate->debug()->OnAsyncTaskEvent(debug::kDebugEnqueueAsyncFunction, id);
isolate->debug()->OnAsyncTaskEvent(debug::kDebugEnqueueAsyncFunction, id, 0);
return isolate->heap()->undefined_value();
}
......@@ -1876,7 +1881,7 @@ RUNTIME_FUNCTION(Runtime_DebugAsyncEventEnqueueRecurring) {
isolate->debug()->OnAsyncTaskEvent(
status == v8::Promise::kFulfilled ? debug::kDebugEnqueuePromiseResolve
: debug::kDebugEnqueuePromiseReject,
isolate->debug()->NextAsyncTaskId(promise));
isolate->debug()->NextAsyncTaskId(promise), 0);
}
return isolate->heap()->undefined_value();
}
......
......@@ -45,7 +45,7 @@ RUNTIME_FUNCTION(Runtime_PromiseRejectEventFromStack) {
rejected_promise = isolate->GetPromiseOnStackOnThrow();
isolate->debug()->OnAsyncTaskEvent(
debug::kDebugEnqueuePromiseReject,
isolate->debug()->NextAsyncTaskId(promise));
isolate->debug()->NextAsyncTaskId(promise), 0);
}
PromiseRejectEvent(isolate, promise, rejected_promise, value, true);
return isolate->heap()->undefined_value();
......
......@@ -3756,43 +3756,6 @@ TEST(TryFinallyOriginalMessage) {
}
TEST(EvalJSInDebugEventListenerOnNativeReThrownException) {
DebugLocalContext env;
v8::HandleScope scope(env->GetIsolate());
env.ExposeDebug();
// Create functions for testing break on exception.
v8::Local<v8::Function> noThrowJS = CompileFunction(
&env, "function noThrowJS(){var a=[1]; a.push(2); return a.length;}",
"noThrowJS");
debug_event_listener_callback = noThrowJS;
debug_event_listener_callback_result = 2;
env->GetIsolate()->AddMessageListener(MessageCallbackCount);
v8::Debug::SetDebugEventListener(env->GetIsolate(), DebugEventCounter);
// Break on uncaught exception
ChangeBreakOnException(false, true);
DebugEventCounterClear();
MessageCallbackCountClear();
// ReThrow native error
{
v8::TryCatch tryCatch(env->GetIsolate());
env->GetIsolate()->ThrowException(
v8::Exception::TypeError(v8_str(env->GetIsolate(), "Type error")));
CHECK(tryCatch.HasCaught());
tryCatch.ReThrow();
}
CHECK_EQ(1, exception_hit_count);
CHECK_EQ(1, uncaught_exception_hit_count);
CHECK_EQ(0, message_callback_count); // FIXME: Should it be 1 ?
CHECK(!debug_event_listener_callback.IsEmpty());
debug_event_listener_callback.Clear();
}
// Test break on exception from compiler errors. When compiling using
// v8::Script::Compile there is no JavaScript stack whereas when compiling using
// eval there are JavaScript frames.
......@@ -3822,16 +3785,18 @@ TEST(BreakOnCompileException) {
// Throws SyntaxError: Unexpected end of input
CHECK(
v8::Script::Compile(context, v8_str(env->GetIsolate(), "+++")).IsEmpty());
CHECK_EQ(1, exception_hit_count);
CHECK_EQ(1, uncaught_exception_hit_count);
// Exceptions with no stack are skipped.
CHECK_EQ(0, exception_hit_count);
CHECK_EQ(0, uncaught_exception_hit_count);
CHECK_EQ(1, message_callback_count);
CHECK_EQ(0, last_js_stack_height); // No JavaScript stack.
// Throws SyntaxError: Unexpected identifier
CHECK(
v8::Script::Compile(context, v8_str(env->GetIsolate(), "x x")).IsEmpty());
CHECK_EQ(2, exception_hit_count);
CHECK_EQ(2, uncaught_exception_hit_count);
// Exceptions with no stack are skipped.
CHECK_EQ(0, exception_hit_count);
CHECK_EQ(0, uncaught_exception_hit_count);
CHECK_EQ(2, message_callback_count);
CHECK_EQ(0, last_js_stack_height); // No JavaScript stack.
......@@ -3840,8 +3805,8 @@ TEST(BreakOnCompileException) {
.ToLocalChecked()
->Run(context)
.IsEmpty());
CHECK_EQ(3, exception_hit_count);
CHECK_EQ(3, uncaught_exception_hit_count);
CHECK_EQ(1, exception_hit_count);
CHECK_EQ(1, uncaught_exception_hit_count);
CHECK_EQ(3, message_callback_count);
CHECK_EQ(1, last_js_stack_height);
......@@ -3850,8 +3815,8 @@ TEST(BreakOnCompileException) {
.ToLocalChecked()
->Run(context)
.IsEmpty());
CHECK_EQ(4, exception_hit_count);
CHECK_EQ(4, uncaught_exception_hit_count);
CHECK_EQ(2, exception_hit_count);
CHECK_EQ(2, uncaught_exception_hit_count);
CHECK_EQ(4, message_callback_count);
CHECK_EQ(1, last_js_stack_height);
}
......
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