Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(148)

Unified Diff: src/debug/debug.cc

Issue 2537313005: [debug] Revert debug API removal (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: src/debug/debug.cc
diff --git a/src/debug/debug.cc b/src/debug/debug.cc
index 919f968658e3c2bba07de3b49018dead33106e3a..65a708a8f7f818befffd7738096c0a095338442f 100644
--- a/src/debug/debug.cc
+++ b/src/debug/debug.cc
@@ -38,6 +38,9 @@ Debug::Debug(Isolate* isolate)
: debug_context_(Handle<Context>()),
event_listener_(Handle<Object>()),
event_listener_data_(Handle<Object>()),
+ message_handler_(NULL),
+ command_received_(0),
+ command_queue_(isolate->logger(), kQueueInitialSize),
is_active_(false),
is_suppressed_(false),
live_edit_enabled_(true), // TODO(yangguo): set to false by default.
@@ -522,7 +525,7 @@ void Debug::Break(JavaScriptFrame* frame) {
// Clear all current stepping setup.
ClearStepping();
// Notify the debug event listeners.
- OnDebugBreak(break_points_hit);
+ OnDebugBreak(break_points_hit, false);
return;
}
@@ -566,7 +569,7 @@ void Debug::Break(JavaScriptFrame* frame) {
if (step_break) {
// Notify the debug event listeners.
- OnDebugBreak(isolate_->factory()->undefined_value());
+ OnDebugBreak(isolate_->factory()->undefined_value(), false);
} else {
// Re-prepare to continue.
PrepareStep(step_action);
@@ -1744,11 +1747,11 @@ void Debug::OnException(Handle<Object> exception, Handle<Object> promise) {
}
// Process debug event.
- ProcessDebugEvent(v8::Exception, Handle<JSObject>::cast(event_data));
+ ProcessDebugEvent(v8::Exception, Handle<JSObject>::cast(event_data), false);
// Return to continue execution from where the exception was thrown.
}
-void Debug::OnDebugBreak(Handle<Object> break_points_hit) {
+void Debug::OnDebugBreak(Handle<Object> break_points_hit, bool auto_continue) {
// The caller provided for DebugScope.
AssertDebugContext();
// Bail out if there is no listener for this event
@@ -1765,7 +1768,8 @@ void Debug::OnDebugBreak(Handle<Object> break_points_hit) {
if (!MakeBreakEvent(break_points_hit).ToHandle(&event_data)) return;
// Process debug event.
- ProcessDebugEvent(v8::Break, Handle<JSObject>::cast(event_data));
+ ProcessDebugEvent(v8::Break, Handle<JSObject>::cast(event_data),
+ auto_continue);
}
@@ -1773,6 +1777,9 @@ void Debug::OnCompileError(Handle<Script> script) {
ProcessCompileEvent(v8::CompileError, script);
}
+void Debug::OnBeforeCompile(Handle<Script> script) {
+ ProcessCompileEvent(v8::BeforeCompile, script);
+}
// Handle debugger actions when a new script is compiled.
void Debug::OnAfterCompile(Handle<Script> script) {
@@ -1794,11 +1801,12 @@ void Debug::OnAsyncTaskEvent(Handle<String> type, Handle<Object> id,
if (!MakeAsyncTaskEvent(type, id, name).ToHandle(&event_data)) return;
// Process debug event.
- ProcessDebugEvent(v8::AsyncTaskEvent, Handle<JSObject>::cast(event_data));
+ ProcessDebugEvent(v8::AsyncTaskEvent, Handle<JSObject>::cast(event_data),
+ true);
}
-void Debug::ProcessDebugEvent(v8::DebugEvent event,
- Handle<JSObject> event_data) {
+void Debug::ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data,
+ bool auto_continue) {
HandleScope scope(isolate_);
// Create the execution state.
@@ -1806,8 +1814,15 @@ void Debug::ProcessDebugEvent(v8::DebugEvent event,
// Bail out and don't call debugger if exception.
if (!MakeExecutionState().ToHandle(&exec_state)) return;
- // Notify registered debug event listener.
- if (!event_listener_.is_null()) {
+ // First notify the message handler if any.
+ if (message_handler_ != NULL) {
+ NotifyMessageHandler(event, Handle<JSObject>::cast(exec_state), event_data,
+ auto_continue);
+ }
+ // Notify registered debug event listener. This can be either a C or
+ // a JavaScript function. Don't call event listener for v8::Break
+ // here, if it's only a debug command -- they will be processed later.
+ if ((event != v8::Break || !auto_continue) && !event_listener_.is_null()) {
CallEventCallback(event, exec_state, event_data, NULL);
}
}
@@ -1859,6 +1874,7 @@ void Debug::ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script) {
}
SuppressDebug while_processing(this);
+ bool in_nested_debug_scope = in_debug_scope();
HandleScope scope(isolate_);
DebugScope debug_scope(this);
if (debug_scope.failed()) return;
@@ -1878,7 +1894,20 @@ void Debug::ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script) {
// Bail out and don't call debugger if exception.
if (!MakeCompileEvent(script, event).ToHandle(&event_data)) return;
- ProcessDebugEvent(event, Handle<JSObject>::cast(event_data));
+ // Don't call NotifyMessageHandler if already in debug scope to avoid running
+ // nested command loop.
+ if (in_nested_debug_scope) {
+ if (event_listener_.is_null()) return;
+ // Create the execution state.
+ Handle<Object> exec_state;
+ // Bail out and don't call debugger if exception.
+ if (!MakeExecutionState().ToHandle(&exec_state)) return;
+
+ CallEventCallback(event, exec_state, event_data, NULL);
+ } else {
+ // Process debug event.
+ ProcessDebugEvent(event, Handle<JSObject>::cast(event_data), true);
+ }
}
@@ -1890,6 +1919,138 @@ Handle<Context> Debug::GetDebugContext() {
return handle(*debug_context(), isolate_);
}
+void Debug::NotifyMessageHandler(v8::DebugEvent event,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data,
+ bool auto_continue) {
+ // Prevent other interrupts from triggering, for example API callbacks,
+ // while dispatching message handler callbacks.
+ PostponeInterruptsScope no_interrupts(isolate_);
+ DCHECK(is_active_);
+ HandleScope scope(isolate_);
+ // Process the individual events.
+ bool sendEventMessage = false;
+ switch (event) {
+ case v8::Break:
+ sendEventMessage = !auto_continue;
+ break;
+ case v8::NewFunction:
+ case v8::BeforeCompile:
+ case v8::CompileError:
+ case v8::AsyncTaskEvent:
+ break;
+ case v8::Exception:
+ case v8::AfterCompile:
+ sendEventMessage = true;
+ break;
+ }
+
+ // The debug command interrupt flag might have been set when the command was
+ // added. It should be enough to clear the flag only once while we are in the
+ // debugger.
+ DCHECK(in_debug_scope());
+ isolate_->stack_guard()->ClearDebugCommand();
+
+ // Notify the debugger that a debug event has occurred unless auto continue is
+ // active in which case no event is send.
+ if (sendEventMessage) {
+ MessageImpl message = MessageImpl::NewEvent(
+ event, auto_continue, Handle<JSObject>::cast(exec_state),
+ Handle<JSObject>::cast(event_data));
+ InvokeMessageHandler(message);
+ }
+
+ // If auto continue don't make the event cause a break, but process messages
+ // in the queue if any. For script collected events don't even process
+ // messages in the queue as the execution state might not be what is expected
+ // by the client.
+ if (auto_continue && !has_commands()) return;
+
+ // DebugCommandProcessor goes here.
+ bool running = auto_continue;
+
+ Handle<Object> cmd_processor_ctor =
+ JSReceiver::GetProperty(isolate_, exec_state, "debugCommandProcessor")
+ .ToHandleChecked();
+ Handle<Object> ctor_args[] = {isolate_->factory()->ToBoolean(running)};
+ Handle<JSReceiver> cmd_processor = Handle<JSReceiver>::cast(
+ Execution::Call(isolate_, cmd_processor_ctor, exec_state, 1, ctor_args)
+ .ToHandleChecked());
+ Handle<JSFunction> process_debug_request = Handle<JSFunction>::cast(
+ JSReceiver::GetProperty(isolate_, cmd_processor, "processDebugRequest")
+ .ToHandleChecked());
+ Handle<Object> is_running =
+ JSReceiver::GetProperty(isolate_, cmd_processor, "isRunning")
+ .ToHandleChecked();
+
+ // Process requests from the debugger.
+ do {
+ // Wait for new command in the queue.
+ command_received_.Wait();
+
+ // Get the command from the queue.
+ CommandMessage command = command_queue_.Get();
+ isolate_->logger()->DebugTag(
+ "Got request from command queue, in interactive loop.");
+ if (!is_active()) {
+ // Delete command text and user data.
+ command.Dispose();
+ return;
+ }
+
+ Vector<const uc16> command_text(
+ const_cast<const uc16*>(command.text().start()),
+ command.text().length());
+ Handle<String> request_text = isolate_->factory()
+ ->NewStringFromTwoByte(command_text)
+ .ToHandleChecked();
+ Handle<Object> request_args[] = {request_text};
+ Handle<Object> answer_value;
+ Handle<String> answer;
+ MaybeHandle<Object> maybe_exception;
+ MaybeHandle<Object> maybe_result =
+ Execution::TryCall(isolate_, process_debug_request, cmd_processor, 1,
+ request_args, &maybe_exception);
+
+ if (maybe_result.ToHandle(&answer_value)) {
+ if (answer_value->IsUndefined(isolate_)) {
+ answer = isolate_->factory()->empty_string();
+ } else {
+ answer = Handle<String>::cast(answer_value);
+ }
+
+ // Log the JSON request/response.
+ if (FLAG_trace_debug_json) {
+ PrintF("%s\n", request_text->ToCString().get());
+ PrintF("%s\n", answer->ToCString().get());
+ }
+
+ Handle<Object> is_running_args[] = {answer};
+ maybe_result = Execution::Call(isolate_, is_running, cmd_processor, 1,
+ is_running_args);
+ Handle<Object> result;
+ if (!maybe_result.ToHandle(&result)) break;
+ running = result->IsTrue(isolate_);
+ } else {
+ Handle<Object> exception;
+ if (!maybe_exception.ToHandle(&exception)) break;
+ Handle<Object> result;
+ if (!Object::ToString(isolate_, exception).ToHandle(&result)) break;
+ answer = Handle<String>::cast(result);
+ }
+
+ // Return the result.
+ MessageImpl message = MessageImpl::NewResponse(
+ event, running, exec_state, event_data, answer, command.client_data());
+ InvokeMessageHandler(message);
+ command.Dispose();
+
+ // Return from debug event processing if either the VM is put into the
+ // running state (through a continue command) or auto continue is active
+ // and there are no more commands queued.
+ } while (!running || has_commands());
+ command_queue_.Clear();
+}
void Debug::SetEventListener(Handle<Object> callback,
Handle<Object> data) {
@@ -1911,9 +2072,18 @@ void Debug::SetEventListener(Handle<Object> callback,
UpdateState();
}
+void Debug::SetMessageHandler(v8::Debug::MessageHandler handler) {
+ message_handler_ = handler;
+ UpdateState();
+ if (handler == NULL && in_debug_scope()) {
+ // Send an empty command to the debugger if in a break to make JavaScript
+ // run again if the debugger is closed.
+ EnqueueCommandMessage(Vector<const uint16_t>::empty());
+ }
+}
void Debug::UpdateState() {
- bool is_active = !event_listener_.is_null();
+ bool is_active = message_handler_ != NULL || !event_listener_.is_null();
if (is_active || in_debug_scope()) {
// Note that the debug context could have already been loaded to
// bootstrap test cases.
@@ -1926,6 +2096,30 @@ void Debug::UpdateState() {
is_active_ = is_active;
}
+// Calls the registered debug message handler. This callback is part of the
+// public API.
+void Debug::InvokeMessageHandler(MessageImpl message) {
+ if (message_handler_ != NULL) message_handler_(message);
+}
+
+// Puts a command coming from the public API on the queue. Creates
+// a copy of the command string managed by the debugger. Up to this
+// point, the command data was managed by the API client. Called
+// by the API client thread.
+void Debug::EnqueueCommandMessage(Vector<const uint16_t> command,
+ v8::Debug::ClientData* client_data) {
+ // Need to cast away const.
+ CommandMessage message = CommandMessage::New(
+ Vector<uint16_t>(const_cast<uint16_t*>(command.start()),
+ command.length()),
+ client_data);
+ isolate_->logger()->DebugTag("Put command on command_queue.");
+ command_queue_.Put(message);
+ command_received_.Signal();
+
+ // Set the debug command break flag to have the command processed.
+ if (!in_debug_scope()) isolate_->stack_guard()->RequestDebugCommand();
+}
MaybeHandle<Object> Debug::Call(Handle<Object> fun, Handle<Object> data) {
DebugScope debug_scope(this);
@@ -1973,17 +2167,31 @@ void Debug::HandleDebugBreak() {
}
}
+ // Collect the break state before clearing the flags.
+ bool debug_command_only = isolate_->stack_guard()->CheckDebugCommand() &&
+ !isolate_->stack_guard()->CheckDebugBreak();
+
isolate_->stack_guard()->ClearDebugBreak();
// Clear stepping to avoid duplicate breaks.
ClearStepping();
+ ProcessDebugMessages(debug_command_only);
+}
+
+void Debug::ProcessDebugMessages(bool debug_command_only) {
+ isolate_->stack_guard()->ClearDebugCommand();
+
+ StackLimitCheck check(isolate_);
+ if (check.HasOverflowed()) return;
+
HandleScope scope(isolate_);
DebugScope debug_scope(this);
if (debug_scope.failed()) return;
- // Notify the debug event listeners.
- OnDebugBreak(isolate_->factory()->undefined_value());
+ // Notify the debug event listeners. Indicate auto continue if the break was
+ // a debug command break.
+ OnDebugBreak(isolate_->factory()->undefined_value(), debug_command_only);
}
#ifdef DEBUG
@@ -2067,6 +2275,10 @@ DebugScope::~DebugScope() {
// JavaScript. This can happen if the v8::Debug::Call is used in which
// case the exception should end up in the calling code.
if (!isolate()->has_pending_exception()) debug_->ClearMirrorCache();
+
+ // If there are commands in the queue when leaving the debugger request
+ // that these commands are processed.
+ if (debug_->has_commands()) isolate()->stack_guard()->RequestDebugCommand();
}
// Leaving this debugger entry.
@@ -2081,6 +2293,104 @@ DebugScope::~DebugScope() {
debug_->UpdateState();
}
+MessageImpl MessageImpl::NewEvent(DebugEvent event, bool running,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data) {
+ MessageImpl message(true, event, running, exec_state, event_data,
+ Handle<String>(), NULL);
+ return message;
+}
+
+MessageImpl MessageImpl::NewResponse(DebugEvent event, bool running,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data,
+ Handle<String> response_json,
+ v8::Debug::ClientData* client_data) {
+ MessageImpl message(false, event, running, exec_state, event_data,
+ response_json, client_data);
+ return message;
+}
+
+MessageImpl::MessageImpl(bool is_event, DebugEvent event, bool running,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data,
+ Handle<String> response_json,
+ v8::Debug::ClientData* client_data)
+ : is_event_(is_event),
+ event_(event),
+ running_(running),
+ exec_state_(exec_state),
+ event_data_(event_data),
+ response_json_(response_json),
+ client_data_(client_data) {}
+
+bool MessageImpl::IsEvent() const { return is_event_; }
+
+bool MessageImpl::IsResponse() const { return !is_event_; }
+
+DebugEvent MessageImpl::GetEvent() const { return event_; }
+
+bool MessageImpl::WillStartRunning() const { return running_; }
+
+v8::Local<v8::Object> MessageImpl::GetExecutionState() const {
+ return v8::Utils::ToLocal(exec_state_);
+}
+
+v8::Isolate* MessageImpl::GetIsolate() const {
+ return reinterpret_cast<v8::Isolate*>(exec_state_->GetIsolate());
+}
+
+v8::Local<v8::Object> MessageImpl::GetEventData() const {
+ return v8::Utils::ToLocal(event_data_);
+}
+
+v8::Local<v8::String> MessageImpl::GetJSON() const {
+ Isolate* isolate = event_data_->GetIsolate();
+ v8::EscapableHandleScope scope(reinterpret_cast<v8::Isolate*>(isolate));
+
+ if (IsEvent()) {
+ // Call toJSONProtocol on the debug event object.
+ Handle<Object> fun =
+ JSReceiver::GetProperty(isolate, event_data_, "toJSONProtocol")
+ .ToHandleChecked();
+ if (!fun->IsJSFunction()) {
+ return v8::Local<v8::String>();
+ }
+
+ MaybeHandle<Object> maybe_json =
+ Execution::TryCall(isolate, fun, event_data_, 0, NULL);
+ Handle<Object> json;
+ if (!maybe_json.ToHandle(&json) || !json->IsString()) {
+ return v8::Local<v8::String>();
+ }
+ return scope.Escape(v8::Utils::ToLocal(Handle<String>::cast(json)));
+ } else {
+ return v8::Utils::ToLocal(response_json_);
+ }
+}
+
+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);
+ // Isolate::context() may be NULL when "script collected" event occurs.
+ DCHECK(!context.IsEmpty());
+ return context;
+}
+
+v8::Debug::ClientData* MessageImpl::GetClientData() const {
+ return client_data_;
+}
EventDetailsImpl::EventDetailsImpl(DebugEvent event,
Handle<JSObject> exec_state,
@@ -2110,11 +2420,7 @@ v8::Local<v8::Object> EventDetailsImpl::GetEventData() const {
v8::Local<v8::Context> EventDetailsImpl::GetEventContext() const {
- Handle<Context> context =
- exec_state_->GetIsolate()->debug()->debugger_entry()->GetContext();
- if (context.is_null()) return v8::Local<v8::Context>();
- Handle<Context> native_context(context->native_context());
- return v8::Utils::ToLocal(native_context);
+ return GetDebugEventContext(exec_state_->GetIsolate());
}
@@ -2131,5 +2437,87 @@ v8::Isolate* EventDetailsImpl::GetIsolate() const {
return reinterpret_cast<v8::Isolate*>(exec_state_->GetIsolate());
}
+CommandMessage::CommandMessage()
+ : text_(Vector<uint16_t>::empty()), client_data_(NULL) {}
+
+CommandMessage::CommandMessage(const Vector<uint16_t>& text,
+ v8::Debug::ClientData* data)
+ : text_(text), client_data_(data) {}
+
+void CommandMessage::Dispose() {
+ text_.Dispose();
+ delete client_data_;
+ client_data_ = NULL;
+}
+
+CommandMessage CommandMessage::New(const Vector<uint16_t>& command,
+ v8::Debug::ClientData* data) {
+ return CommandMessage(command.Clone(), data);
+}
+
+CommandMessageQueue::CommandMessageQueue(int size)
+ : start_(0), end_(0), size_(size) {
+ messages_ = NewArray<CommandMessage>(size);
+}
+
+CommandMessageQueue::~CommandMessageQueue() {
+ while (!IsEmpty()) Get().Dispose();
+ DeleteArray(messages_);
+}
+
+CommandMessage CommandMessageQueue::Get() {
+ DCHECK(!IsEmpty());
+ int result = start_;
+ start_ = (start_ + 1) % size_;
+ return messages_[result];
+}
+
+void CommandMessageQueue::Put(const CommandMessage& message) {
+ if ((end_ + 1) % size_ == start_) {
+ Expand();
+ }
+ messages_[end_] = message;
+ end_ = (end_ + 1) % size_;
+}
+
+void CommandMessageQueue::Expand() {
+ CommandMessageQueue new_queue(size_ * 2);
+ while (!IsEmpty()) {
+ new_queue.Put(Get());
+ }
+ CommandMessage* array_to_free = messages_;
+ *this = new_queue;
+ new_queue.messages_ = array_to_free;
+ // Make the new_queue empty so that it doesn't call Dispose on any messages.
+ new_queue.start_ = new_queue.end_;
+ // Automatic destructor called on new_queue, freeing array_to_free.
+}
+
+LockingCommandMessageQueue::LockingCommandMessageQueue(Logger* logger, int size)
+ : logger_(logger), queue_(size) {}
+
+bool LockingCommandMessageQueue::IsEmpty() const {
+ base::LockGuard<base::Mutex> lock_guard(&mutex_);
+ return queue_.IsEmpty();
+}
+
+CommandMessage LockingCommandMessageQueue::Get() {
+ base::LockGuard<base::Mutex> lock_guard(&mutex_);
+ CommandMessage result = queue_.Get();
+ logger_->DebugEvent("Get", result.text());
+ return result;
+}
+
+void LockingCommandMessageQueue::Put(const CommandMessage& message) {
+ base::LockGuard<base::Mutex> lock_guard(&mutex_);
+ queue_.Put(message);
+ logger_->DebugEvent("Put", message.text());
+}
+
+void LockingCommandMessageQueue::Clear() {
+ base::LockGuard<base::Mutex> lock_guard(&mutex_);
+ queue_.Clear();
+}
+
} // namespace internal
} // namespace v8

Powered by Google App Engine
This is Rietveld 408576698