Commit 7e420cf1 authored by yangguo's avatar yangguo Committed by Commit bot

Revert of [debugger api] remove legacy JSON debug protocol. (patchset #2...

Revert of [debugger api] remove legacy JSON debug protocol. (patchset #2 id:20001 of https://codereview.chromium.org/2642253005/ )

Reason for revert:
Node.js relies on this

Original issue's description:
> [debugger api] remove legacy JSON debug protocol.
>
> R=jgruber@chromium.org
> BUG=v8:5530
>
> Review-Url: https://codereview.chromium.org/2642253005
> Cr-Commit-Position: refs/heads/master@{#42543}
> Committed: https://chromium.googlesource.com/v8/v8/+/e26a58e43c51a680a0a6363e0066886f4971a41f

TBR=jgruber@chromium.org
# Skipping CQ checks because original CL landed less than 1 days ago.
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG=v8:5530

Review-Url: https://codereview.chromium.org/2644233003
Cr-Commit-Position: refs/heads/master@{#42549}
parent dd8881a5
...@@ -23,6 +23,68 @@ enum DebugEvent { ...@@ -23,6 +23,68 @@ enum DebugEvent {
class V8_EXPORT Debug { class V8_EXPORT Debug {
public: public:
/**
* A client object passed to the v8 debugger whose ownership will be taken by
* it. v8 is always responsible for deleting the object.
*/
class ClientData {
public:
virtual ~ClientData() {}
};
/**
* A message object passed to the debug message handler.
*/
class Message {
public:
/**
* Check type of message.
*/
virtual bool IsEvent() const = 0;
virtual bool IsResponse() const = 0;
virtual DebugEvent GetEvent() const = 0;
/**
* Indicate whether this is a response to a continue command which will
* start the VM running after this is processed.
*/
virtual bool WillStartRunning() const = 0;
/**
* Access to execution state and event data. Don't store these cross
* callbacks as their content becomes invalid. These objects are from the
* debugger event that started the debug message loop.
*/
virtual Local<Object> GetExecutionState() const = 0;
virtual Local<Object> GetEventData() const = 0;
/**
* Get the debugger protocol JSON.
*/
virtual Local<String> GetJSON() 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 request if any. This is the
* client_data data value passed into Debug::SendCommand along with the
* request that led to the message or NULL if the message is an event. The
* debugger takes ownership of the data and will delete it even if there is
* no message handler.
*/
virtual ClientData* GetClientData() const = 0;
virtual Isolate* GetIsolate() const = 0;
virtual ~Message() {}
};
/** /**
* An event details object passed to the debug event listener. * An event details object passed to the debug event listener.
*/ */
...@@ -53,6 +115,13 @@ class V8_EXPORT Debug { ...@@ -53,6 +115,13 @@ class V8_EXPORT Debug {
*/ */
virtual Local<Value> GetCallbackData() const = 0; virtual Local<Value> GetCallbackData() const = 0;
/**
* Client data passed to DebugBreakForCommand function. The
* debugger takes ownership of the data and will delete it even if
* there is no message handler.
*/
virtual ClientData* GetClientData() const = 0;
virtual Isolate* GetIsolate() const = 0; virtual Isolate* GetIsolate() const = 0;
virtual ~EventDetails() {} virtual ~EventDetails() {}
...@@ -63,11 +132,26 @@ class V8_EXPORT Debug { ...@@ -63,11 +132,26 @@ class V8_EXPORT Debug {
* *
* \param event_details object providing information about the debug event * \param event_details object providing information about the debug event
* *
* A EventCallback does not take possession of the event data, * A EventCallback2 does not take possession of the event data,
* and must not rely on the data persisting after the handler returns. * and must not rely on the data persisting after the handler returns.
*/ */
typedef void (*EventCallback)(const EventDetails& event_details); typedef void (*EventCallback)(const EventDetails& event_details);
/**
* Debug message callback function.
*
* \param message the debug message handler message object
*
* A MessageHandler does not take possession of the message data,
* and must not rely on the data persisting after the handler returns.
*/
typedef void (*MessageHandler)(const Message& message);
/**
* Callback function for the host to ensure debug messages are processed.
*/
typedef void (*DebugMessageDispatchHandler)();
static bool SetDebugEventListener(Isolate* isolate, EventCallback that, static bool SetDebugEventListener(Isolate* isolate, EventCallback that,
Local<Value> data = Local<Value>()); Local<Value> data = Local<Value>());
...@@ -83,6 +167,16 @@ class V8_EXPORT Debug { ...@@ -83,6 +167,16 @@ class V8_EXPORT Debug {
V8_DEPRECATED("No longer supported", V8_DEPRECATED("No longer supported",
static bool CheckDebugBreak(Isolate* isolate)); static bool CheckDebugBreak(Isolate* isolate));
// Message based interface. The message protocol is JSON.
V8_DEPRECATED("No longer supported",
static void SetMessageHandler(Isolate* isolate,
MessageHandler handler));
V8_DEPRECATED("No longer supported",
static void SendCommand(Isolate* isolate,
const uint16_t* command, int length,
ClientData* client_data = NULL));
/** /**
* Run a JavaScript function in the debugger. * Run a JavaScript function in the debugger.
* \param fun the function to call * \param fun the function to call
...@@ -113,6 +207,40 @@ class V8_EXPORT Debug { ...@@ -113,6 +207,40 @@ class V8_EXPORT Debug {
static MaybeLocal<Value> GetMirror(Local<Context> context, static MaybeLocal<Value> GetMirror(Local<Context> context,
v8::Local<v8::Value> obj)); v8::Local<v8::Value> obj));
/**
* Makes V8 process all pending debug messages.
*
* From V8 point of view all debug messages come asynchronously (e.g. from
* remote debugger) but they all must be handled synchronously: V8 cannot
* do 2 things at one time so normal script execution must be interrupted
* for a while.
*
* Generally when message arrives V8 may be in one of 3 states:
* 1. V8 is running script; V8 will automatically interrupt and process all
* pending messages;
* 2. V8 is suspended on debug breakpoint; in this state V8 is dedicated
* to reading and processing debug messages;
* 3. V8 is not running at all or has called some long-working C++ function;
* by default it means that processing of all debug messages will be deferred
* until V8 gets control again; however, embedding application may improve
* this by manually calling this method.
*
* Technically this method in many senses is equivalent to executing empty
* script:
* 1. It does nothing except for processing all pending debug messages.
* 2. It should be invoked with the same precautions and from the same context
* as V8 script would be invoked from, because:
* a. with "evaluate" command it can do whatever normal script can do,
* including all native calls;
* b. no other thread should call V8 while this method is running
* (v8::Locker may be used here).
*
* "Evaluate" debug command behavior currently is not specified in scope
* of this method.
*/
V8_DEPRECATED("No longer supported",
static void ProcessDebugMessages(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
......
...@@ -8896,6 +8896,24 @@ bool Debug::CheckDebugBreak(Isolate* isolate) { ...@@ -8896,6 +8896,24 @@ bool Debug::CheckDebugBreak(Isolate* isolate) {
} }
void Debug::SetMessageHandler(Isolate* isolate,
v8::Debug::MessageHandler handler) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
ENTER_V8(i_isolate);
i_isolate->debug()->SetMessageHandler(handler);
}
void Debug::SendCommand(Isolate* isolate,
const uint16_t* command,
int length,
ClientData* client_data) {
i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate);
internal_isolate->debug()->EnqueueCommandMessage(
i::Vector<const uint16_t>(command, length), client_data);
}
MaybeLocal<Value> Debug::Call(Local<Context> context, MaybeLocal<Value> Debug::Call(Local<Context> context,
v8::Local<v8::Function> fun, v8::Local<v8::Function> fun,
v8::Local<v8::Value> data) { v8::Local<v8::Value> data) {
...@@ -8935,6 +8953,10 @@ MaybeLocal<Value> Debug::GetMirror(Local<Context> context, ...@@ -8935,6 +8953,10 @@ MaybeLocal<Value> Debug::GetMirror(Local<Context> context,
RETURN_ESCAPED(result); RETURN_ESCAPED(result);
} }
void Debug::ProcessDebugMessages(Isolate* isolate) {
reinterpret_cast<i::Isolate*>(isolate)->debug()->ProcessDebugMessages(true);
}
Local<Context> Debug::GetDebugContext(Isolate* isolate) { Local<Context> Debug::GetDebugContext(Isolate* isolate) {
return debug::GetDebugContext(isolate); return debug::GetDebugContext(isolate);
......
...@@ -40,6 +40,9 @@ Debug::Debug(Isolate* isolate) ...@@ -40,6 +40,9 @@ Debug::Debug(Isolate* isolate)
: debug_context_(Handle<Context>()), : debug_context_(Handle<Context>()),
event_listener_(Handle<Object>()), event_listener_(Handle<Object>()),
event_listener_data_(Handle<Object>()), event_listener_data_(Handle<Object>()),
message_handler_(NULL),
command_received_(0),
command_queue_(isolate->logger(), kQueueInitialSize),
is_active_(false), is_active_(false),
hook_on_function_call_(false), hook_on_function_call_(false),
is_suppressed_(false), is_suppressed_(false),
...@@ -529,7 +532,7 @@ void Debug::Break(JavaScriptFrame* frame) { ...@@ -529,7 +532,7 @@ void Debug::Break(JavaScriptFrame* frame) {
// Notify the debug event listeners. // Notify the debug event listeners.
Handle<JSArray> jsarr = isolate_->factory()->NewJSArrayWithElements( Handle<JSArray> jsarr = isolate_->factory()->NewJSArrayWithElements(
break_points_hit.ToHandleChecked()); break_points_hit.ToHandleChecked());
OnDebugBreak(jsarr); OnDebugBreak(jsarr, false);
return; return;
} }
...@@ -571,7 +574,7 @@ void Debug::Break(JavaScriptFrame* frame) { ...@@ -571,7 +574,7 @@ void Debug::Break(JavaScriptFrame* frame) {
if (step_break) { if (step_break) {
// Notify the debug event listeners. // Notify the debug event listeners.
OnDebugBreak(isolate_->factory()->undefined_value()); OnDebugBreak(isolate_->factory()->undefined_value(), false);
} else { } else {
// Re-prepare to continue. // Re-prepare to continue.
PrepareStep(step_action); PrepareStep(step_action);
...@@ -1783,11 +1786,11 @@ void Debug::OnException(Handle<Object> exception, Handle<Object> promise) { ...@@ -1783,11 +1786,11 @@ void Debug::OnException(Handle<Object> exception, Handle<Object> promise) {
} }
// Process debug event. // 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. // 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. // The caller provided for DebugScope.
AssertDebugContext(); AssertDebugContext();
// Bail out if there is no listener for this event // Bail out if there is no listener for this event
...@@ -1822,7 +1825,8 @@ void Debug::OnDebugBreak(Handle<Object> break_points_hit) { ...@@ -1822,7 +1825,8 @@ void Debug::OnDebugBreak(Handle<Object> break_points_hit) {
if (!MakeBreakEvent(break_points_hit).ToHandle(&event_data)) return; if (!MakeBreakEvent(break_points_hit).ToHandle(&event_data)) return;
// Process debug event. // Process debug event.
ProcessDebugEvent(v8::Break, Handle<JSObject>::cast(event_data)); ProcessDebugEvent(v8::Break, Handle<JSObject>::cast(event_data),
auto_continue);
} }
...@@ -1912,14 +1916,12 @@ void Debug::OnAsyncTaskEvent(debug::PromiseDebugActionType type, int id) { ...@@ -1912,14 +1916,12 @@ void Debug::OnAsyncTaskEvent(debug::PromiseDebugActionType type, int id) {
return; return;
// Process debug event. // 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, void Debug::ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data,
Handle<JSObject> event_data) { bool auto_continue) {
// Notify registered debug event listener. This can be either a C or
// a JavaScript function.
if (event_listener_.is_null()) return;
HandleScope scope(isolate_); HandleScope scope(isolate_);
// Create the execution state. // Create the execution state.
...@@ -1927,6 +1929,24 @@ void Debug::ProcessDebugEvent(v8::DebugEvent event, ...@@ -1927,6 +1929,24 @@ void Debug::ProcessDebugEvent(v8::DebugEvent event,
// Bail out and don't call debugger if exception. // Bail out and don't call debugger if exception.
if (!MakeExecutionState().ToHandle(&exec_state)) return; if (!MakeExecutionState().ToHandle(&exec_state)) return;
// 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);
}
}
void Debug::CallEventCallback(v8::DebugEvent event,
Handle<Object> exec_state,
Handle<Object> event_data,
v8::Debug::ClientData* client_data) {
// Prevent other interrupts from triggering, for example API callbacks, // Prevent other interrupts from triggering, for example API callbacks,
// while dispatching event listners. // while dispatching event listners.
PostponeInterruptsScope postpone(isolate_); PostponeInterruptsScope postpone(isolate_);
...@@ -1936,9 +1956,11 @@ void Debug::ProcessDebugEvent(v8::DebugEvent event, ...@@ -1936,9 +1956,11 @@ void Debug::ProcessDebugEvent(v8::DebugEvent event,
// Invoke the C debug event listener. // Invoke the C debug event listener.
v8::Debug::EventCallback callback = FUNCTION_CAST<v8::Debug::EventCallback>( v8::Debug::EventCallback callback = FUNCTION_CAST<v8::Debug::EventCallback>(
Handle<Foreign>::cast(event_listener_)->foreign_address()); Handle<Foreign>::cast(event_listener_)->foreign_address());
EventDetailsImpl event_details(event, Handle<JSObject>::cast(exec_state), EventDetailsImpl event_details(event,
Handle<JSObject>::cast(exec_state),
Handle<JSObject>::cast(event_data), Handle<JSObject>::cast(event_data),
event_listener_data_); event_listener_data_,
client_data);
callback(event_details); callback(event_details);
CHECK(!isolate_->has_scheduled_exception()); CHECK(!isolate_->has_scheduled_exception());
} else { } else {
...@@ -1964,6 +1986,7 @@ void Debug::ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script) { ...@@ -1964,6 +1986,7 @@ void Debug::ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script) {
return; return;
} }
SuppressDebug while_processing(this); SuppressDebug while_processing(this);
bool in_nested_debug_scope = in_debug_scope();
DebugScope debug_scope(this); DebugScope debug_scope(this);
if (debug_scope.failed()) return; if (debug_scope.failed()) return;
...@@ -1979,8 +2002,20 @@ void Debug::ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script) { ...@@ -1979,8 +2002,20 @@ void Debug::ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script) {
// Bail out and don't call debugger if exception. // Bail out and don't call debugger if exception.
if (!MakeCompileEvent(script, event).ToHandle(&event_data)) return; if (!MakeCompileEvent(script, event).ToHandle(&event_data)) return;
// Process debug event. // Don't call NotifyMessageHandler if already in debug scope to avoid running
ProcessDebugEvent(event, Handle<JSObject>::cast(event_data)); // 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);
}
} }
...@@ -1992,6 +2027,136 @@ Handle<Context> Debug::GetDebugContext() { ...@@ -1992,6 +2027,136 @@ Handle<Context> Debug::GetDebugContext() {
return handle(*debug_context(), isolate_); 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::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,
Execution::MessageHandling::kReport, &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, void Debug::SetEventListener(Handle<Object> callback,
Handle<Object> data) { Handle<Object> data) {
...@@ -2013,6 +2178,15 @@ void Debug::SetEventListener(Handle<Object> callback, ...@@ -2013,6 +2178,15 @@ void Debug::SetEventListener(Handle<Object> callback,
UpdateState(); 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::SetDebugEventListener(debug::DebugEventListener* listener) { void Debug::SetDebugEventListener(debug::DebugEventListener* listener) {
debug_event_listener_ = listener; debug_event_listener_ = listener;
...@@ -2020,8 +2194,8 @@ void Debug::SetDebugEventListener(debug::DebugEventListener* listener) { ...@@ -2020,8 +2194,8 @@ void Debug::SetDebugEventListener(debug::DebugEventListener* listener) {
} }
void Debug::UpdateState() { void Debug::UpdateState() {
bool is_active = bool is_active = message_handler_ != nullptr || !event_listener_.is_null() ||
!event_listener_.is_null() || debug_event_listener_ != nullptr; debug_event_listener_ != nullptr;
if (is_active || in_debug_scope()) { if (is_active || in_debug_scope()) {
// Note that the debug context could have already been loaded to // Note that the debug context could have already been loaded to
// bootstrap test cases. // bootstrap test cases.
...@@ -2041,6 +2215,31 @@ void Debug::UpdateHookOnFunctionCall() { ...@@ -2041,6 +2215,31 @@ void Debug::UpdateHookOnFunctionCall() {
isolate_->needs_side_effect_check(); isolate_->needs_side_effect_check();
} }
// 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) { MaybeHandle<Object> Debug::Call(Handle<Object> fun, Handle<Object> data) {
DebugScope debug_scope(this); DebugScope debug_scope(this);
if (debug_scope.failed()) return isolate_->factory()->undefined_value(); if (debug_scope.failed()) return isolate_->factory()->undefined_value();
...@@ -2087,16 +2286,31 @@ void Debug::HandleDebugBreak() { ...@@ -2087,16 +2286,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(); isolate_->stack_guard()->ClearDebugBreak();
// Clear stepping to avoid duplicate breaks. // Clear stepping to avoid duplicate breaks.
ClearStepping(); 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_); HandleScope scope(isolate_);
DebugScope debug_scope(this); DebugScope debug_scope(this);
if (debug_scope.failed()) return; if (debug_scope.failed()) return;
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 #ifdef DEBUG
...@@ -2179,6 +2393,10 @@ DebugScope::~DebugScope() { ...@@ -2179,6 +2393,10 @@ DebugScope::~DebugScope() {
// JavaScript. This can happen if the v8::Debug::Call is used in which // JavaScript. This can happen if the v8::Debug::Call is used in which
// case the exception should end up in the calling code. // case the exception should end up in the calling code.
if (!isolate()->has_pending_exception()) debug_->ClearMirrorCache(); 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. // Leaving this debugger entry.
...@@ -2237,14 +2455,107 @@ NoSideEffectScope::~NoSideEffectScope() { ...@@ -2237,14 +2455,107 @@ NoSideEffectScope::~NoSideEffectScope() {
isolate_->debug()->side_effect_check_failed_ = false; isolate_->debug()->side_effect_check_failed_ = false;
} }
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_exception;
MaybeHandle<Object> maybe_json = Execution::TryCall(
isolate, fun, event_data_, 0, nullptr,
Execution::MessageHandling::kReport, &maybe_exception);
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_);
}
}
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, EventDetailsImpl::EventDetailsImpl(DebugEvent event,
Handle<JSObject> exec_state, Handle<JSObject> exec_state,
Handle<JSObject> event_data, Handle<JSObject> event_data,
Handle<Object> callback_data) Handle<Object> callback_data,
v8::Debug::ClientData* client_data)
: event_(event), : event_(event),
exec_state_(exec_state), exec_state_(exec_state),
event_data_(event_data), event_data_(event_data),
callback_data_(callback_data) {} callback_data_(callback_data),
client_data_(client_data) {}
DebugEvent EventDetailsImpl::GetEvent() const { DebugEvent EventDetailsImpl::GetEvent() const {
return event_; return event_;
...@@ -2271,9 +2582,95 @@ v8::Local<v8::Value> EventDetailsImpl::GetCallbackData() const { ...@@ -2271,9 +2582,95 @@ v8::Local<v8::Value> EventDetailsImpl::GetCallbackData() const {
} }
v8::Debug::ClientData* EventDetailsImpl::GetClientData() const {
return client_data_;
}
v8::Isolate* EventDetailsImpl::GetIsolate() const { v8::Isolate* EventDetailsImpl::GetIsolate() const {
return reinterpret_cast<v8::Isolate*>(exec_state_->GetIsolate()); 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 internal
} // namespace v8 } // namespace v8
...@@ -244,16 +244,62 @@ class DebugInfoListNode { ...@@ -244,16 +244,62 @@ class DebugInfoListNode {
DebugInfoListNode* next_; DebugInfoListNode* next_;
}; };
// Message delivered to the message handler callback. This is either a debugger
// event or the response to a command.
class MessageImpl : public v8::Debug::Message {
public:
// Create a message object for a debug event.
static MessageImpl NewEvent(DebugEvent event, bool running,
Handle<JSObject> exec_state,
Handle<JSObject> event_data);
// Create a message object for the response to a debug command.
static MessageImpl NewResponse(DebugEvent event, bool running,
Handle<JSObject> exec_state,
Handle<JSObject> event_data,
Handle<String> response_json,
v8::Debug::ClientData* client_data);
// Implementation of interface v8::Debug::Message.
virtual bool IsEvent() const;
virtual bool IsResponse() const;
virtual DebugEvent GetEvent() const;
virtual bool WillStartRunning() const;
virtual v8::Local<v8::Object> GetExecutionState() const;
virtual v8::Local<v8::Object> GetEventData() const;
virtual v8::Local<v8::String> GetJSON() const;
virtual v8::Local<v8::Context> GetEventContext() const;
virtual v8::Debug::ClientData* GetClientData() const;
virtual v8::Isolate* GetIsolate() const;
private:
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);
bool is_event_; // Does this message represent a debug event?
DebugEvent event_; // Debug event causing the break.
bool running_; // Will the VM start running after this event?
Handle<JSObject> exec_state_; // Current execution state.
Handle<JSObject> event_data_; // Data associated with the event.
Handle<String> response_json_; // Response JSON if message holds a response.
v8::Debug::ClientData* client_data_; // Client data passed with the request.
};
// Details of the debug event delivered to the debug event listener. // Details of the debug event delivered to the debug event listener.
class EventDetailsImpl : public v8::Debug::EventDetails { class EventDetailsImpl : public v8::Debug::EventDetails {
public: public:
EventDetailsImpl(DebugEvent event, Handle<JSObject> exec_state, EventDetailsImpl(DebugEvent event,
Handle<JSObject> event_data, Handle<Object> callback_data); Handle<JSObject> exec_state,
Handle<JSObject> event_data,
Handle<Object> callback_data,
v8::Debug::ClientData* client_data);
virtual DebugEvent GetEvent() const; virtual DebugEvent GetEvent() const;
virtual v8::Local<v8::Object> GetExecutionState() const; virtual v8::Local<v8::Object> GetExecutionState() const;
virtual v8::Local<v8::Object> GetEventData() const; virtual v8::Local<v8::Object> GetEventData() const;
virtual v8::Local<v8::Context> GetEventContext() const; virtual v8::Local<v8::Context> GetEventContext() const;
virtual v8::Local<v8::Value> GetCallbackData() const; virtual v8::Local<v8::Value> GetCallbackData() const;
virtual v8::Debug::ClientData* GetClientData() const;
virtual v8::Isolate* GetIsolate() const; virtual v8::Isolate* GetIsolate() const;
private: private:
...@@ -262,8 +308,70 @@ class EventDetailsImpl : public v8::Debug::EventDetails { ...@@ -262,8 +308,70 @@ class EventDetailsImpl : public v8::Debug::EventDetails {
Handle<JSObject> event_data_; // Data associated with the event. Handle<JSObject> event_data_; // Data associated with the event.
Handle<Object> callback_data_; // User data passed with the callback Handle<Object> callback_data_; // User data passed with the callback
// when it was registered. // when it was registered.
v8::Debug::ClientData* client_data_; // Data passed to DebugBreakForCommand.
}; };
// Message send by user to v8 debugger or debugger output message.
// In addition to command text it may contain a pointer to some user data
// which are expected to be passed along with the command reponse to message
// handler.
class CommandMessage {
public:
static CommandMessage New(const Vector<uint16_t>& command,
v8::Debug::ClientData* data);
CommandMessage();
// Deletes user data and disposes of the text.
void Dispose();
Vector<uint16_t> text() const { return text_; }
v8::Debug::ClientData* client_data() const { return client_data_; }
private:
CommandMessage(const Vector<uint16_t>& text, v8::Debug::ClientData* data);
Vector<uint16_t> text_;
v8::Debug::ClientData* client_data_;
};
// A Queue of CommandMessage objects. A thread-safe version is
// LockingCommandMessageQueue, based on this class.
class CommandMessageQueue BASE_EMBEDDED {
public:
explicit CommandMessageQueue(int size);
~CommandMessageQueue();
bool IsEmpty() const { return start_ == end_; }
CommandMessage Get();
void Put(const CommandMessage& message);
void Clear() { start_ = end_ = 0; } // Queue is empty after Clear().
private:
// Doubles the size of the message queue, and copies the messages.
void Expand();
CommandMessage* messages_;
int start_;
int end_;
int size_; // The size of the queue buffer. Queue can hold size-1 messages.
};
// LockingCommandMessageQueue is a thread-safe circular buffer of CommandMessage
// messages. The message data is not managed by LockingCommandMessageQueue.
// Pointers to the data are passed in and out. Implemented by adding a
// Mutex to CommandMessageQueue. Includes logging of all puts and gets.
class LockingCommandMessageQueue BASE_EMBEDDED {
public:
LockingCommandMessageQueue(Logger* logger, int size);
bool IsEmpty() const;
CommandMessage Get();
void Put(const CommandMessage& message);
void Clear();
private:
Logger* logger_;
CommandMessageQueue queue_;
mutable base::Mutex mutex_;
DISALLOW_COPY_AND_ASSIGN(LockingCommandMessageQueue);
};
class DebugFeatureTracker { class DebugFeatureTracker {
public: public:
...@@ -297,7 +405,7 @@ class DebugFeatureTracker { ...@@ -297,7 +405,7 @@ class DebugFeatureTracker {
class Debug { class Debug {
public: public:
// Debug event triggers. // Debug event triggers.
void OnDebugBreak(Handle<Object> break_points_hit); void OnDebugBreak(Handle<Object> break_points_hit, bool auto_continue);
void OnThrow(Handle<Object> exception); void OnThrow(Handle<Object> exception);
void OnPromiseReject(Handle<Object> promise, Handle<Object> value); void OnPromiseReject(Handle<Object> promise, Handle<Object> value);
...@@ -307,10 +415,14 @@ class Debug { ...@@ -307,10 +415,14 @@ class Debug {
// API facing. // API facing.
void SetEventListener(Handle<Object> callback, Handle<Object> data); void SetEventListener(Handle<Object> callback, Handle<Object> data);
void SetMessageHandler(v8::Debug::MessageHandler handler);
void EnqueueCommandMessage(Vector<const uint16_t> command,
v8::Debug::ClientData* client_data = NULL);
MUST_USE_RESULT MaybeHandle<Object> Call(Handle<Object> fun, MUST_USE_RESULT MaybeHandle<Object> Call(Handle<Object> fun,
Handle<Object> data); Handle<Object> data);
Handle<Context> GetDebugContext(); Handle<Context> GetDebugContext();
void HandleDebugBreak(); void HandleDebugBreak();
void ProcessDebugMessages(bool debug_command_only);
// Internal logic // Internal logic
bool Load(); bool Load();
...@@ -467,6 +579,8 @@ class Debug { ...@@ -467,6 +579,8 @@ class Debug {
thread_local_.break_id_ = ++thread_local_.break_count_; thread_local_.break_id_ = ++thread_local_.break_count_;
} }
// Check whether there are commands in the command queue.
inline bool has_commands() const { return !command_queue_.IsEmpty(); }
inline bool ignore_events() const { inline bool ignore_events() const {
return is_suppressed_ || !is_active_ || isolate_->needs_side_effect_check(); return is_suppressed_ || !is_active_ || isolate_->needs_side_effect_check();
} }
...@@ -490,7 +604,8 @@ class Debug { ...@@ -490,7 +604,8 @@ class Debug {
// is presented. Other clients can install JavaScript event listener // is presented. Other clients can install JavaScript event listener
// (e.g. some of NodeJS module). // (e.g. some of NodeJS module).
bool non_inspector_listener_exists() const { bool non_inspector_listener_exists() const {
return !event_listener_.is_null() && !event_listener_->IsForeign(); return message_handler_ != nullptr ||
(!event_listener_.is_null() && !event_listener_->IsForeign());
} }
void OnException(Handle<Object> exception, Handle<Object> promise); void OnException(Handle<Object> exception, Handle<Object> promise);
...@@ -511,8 +626,16 @@ class Debug { ...@@ -511,8 +626,16 @@ class Debug {
// Mirror cache handling. // Mirror cache handling.
void ClearMirrorCache(); void ClearMirrorCache();
void CallEventCallback(v8::DebugEvent event,
Handle<Object> exec_state,
Handle<Object> event_data,
v8::Debug::ClientData* client_data);
void ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script); void ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script);
void ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data); void ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data,
bool auto_continue);
void NotifyMessageHandler(v8::DebugEvent event, Handle<JSObject> exec_state,
Handle<JSObject> event_data, bool auto_continue);
void InvokeMessageHandler(MessageImpl message);
// Find the closest source position for a break point for a given position. // Find the closest source position for a break point for a given position.
int FindBreakablePosition(Handle<DebugInfo> debug_info, int source_position, int FindBreakablePosition(Handle<DebugInfo> debug_info, int source_position,
...@@ -553,8 +676,14 @@ class Debug { ...@@ -553,8 +676,14 @@ class Debug {
Handle<Object> event_listener_; Handle<Object> event_listener_;
Handle<Object> event_listener_data_; Handle<Object> event_listener_data_;
v8::Debug::MessageHandler message_handler_;
debug::DebugEventListener* debug_event_listener_ = nullptr; debug::DebugEventListener* debug_event_listener_ = nullptr;
static const int kQueueInitialSize = 4;
base::Semaphore command_received_; // Signaled for each command received.
LockingCommandMessageQueue command_queue_;
// Debugger is active, i.e. there is a debug event listener attached. // Debugger is active, i.e. there is a debug event listener attached.
bool is_active_; bool is_active_;
// Debugger needs to be notified on every new function call. // Debugger needs to be notified on every new function call.
......
...@@ -12,8 +12,11 @@ var FrameMirror = global.FrameMirror; ...@@ -12,8 +12,11 @@ var FrameMirror = global.FrameMirror;
var GlobalArray = global.Array; var GlobalArray = global.Array;
var GlobalRegExp = global.RegExp; var GlobalRegExp = global.RegExp;
var IsNaN = global.isNaN; var IsNaN = global.isNaN;
var JSONParse = global.JSON.parse;
var JSONStringify = global.JSON.stringify;
var LookupMirror = global.LookupMirror; var LookupMirror = global.LookupMirror;
var MakeMirror = global.MakeMirror; var MakeMirror = global.MakeMirror;
var MakeMirrorSerializer = global.MakeMirrorSerializer;
var MathMin = global.Math.min; var MathMin = global.Math.min;
var Mirror = global.Mirror; var Mirror = global.Mirror;
var MirrorType; var MirrorType;
...@@ -864,6 +867,11 @@ ExecutionState.prototype.selectedFrame = function() { ...@@ -864,6 +867,11 @@ ExecutionState.prototype.selectedFrame = function() {
return this.selected_frame; return this.selected_frame;
}; };
ExecutionState.prototype.debugCommandProcessor = function(opt_is_running) {
return new DebugCommandProcessor(this, opt_is_running);
};
function MakeBreakEvent(break_id, break_points_hit) { function MakeBreakEvent(break_id, break_points_hit) {
return new BreakEvent(break_id, break_points_hit); return new BreakEvent(break_id, break_points_hit);
} }
...@@ -905,6 +913,43 @@ BreakEvent.prototype.breakPointsHit = function() { ...@@ -905,6 +913,43 @@ BreakEvent.prototype.breakPointsHit = function() {
}; };
BreakEvent.prototype.toJSONProtocol = function() {
var o = { seq: next_response_seq++,
type: "event",
event: "break",
body: { invocationText: this.frame_.invocationText() }
};
// Add script related information to the event if available.
var script = this.func().script();
if (script) {
o.body.sourceLine = this.sourceLine(),
o.body.sourceColumn = this.sourceColumn(),
o.body.sourceLineText = this.sourceLineText(),
o.body.script = MakeScriptObject_(script, false);
}
// Add an Array of break points hit if any.
if (this.breakPointsHit()) {
o.body.breakpoints = [];
for (var i = 0; i < this.breakPointsHit().length; i++) {
// Find the break point number. For break points originating from a
// script break point supply the script break point number.
var breakpoint = this.breakPointsHit()[i];
var script_break_point = breakpoint.script_break_point();
var number;
if (script_break_point) {
number = script_break_point.number();
} else {
number = breakpoint.number();
}
o.body.breakpoints.push(number);
}
}
return JSONStringify(ObjectToProtocolObject_(o));
};
function MakeExceptionEvent(break_id, exception, uncaught, promise) { function MakeExceptionEvent(break_id, exception, uncaught, promise) {
return new ExceptionEvent(break_id, exception, uncaught, promise); return new ExceptionEvent(break_id, exception, uncaught, promise);
} }
...@@ -958,6 +1003,32 @@ ExceptionEvent.prototype.sourceLineText = function() { ...@@ -958,6 +1003,32 @@ ExceptionEvent.prototype.sourceLineText = function() {
}; };
ExceptionEvent.prototype.toJSONProtocol = function() {
var o = new ProtocolMessage();
o.event = "exception";
o.body = { uncaught: this.uncaught_,
exception: MakeMirror(this.exception_)
};
// Exceptions might happen whithout any JavaScript frames.
if (this.exec_state_.frameCount() > 0) {
o.body.sourceLine = this.sourceLine();
o.body.sourceColumn = this.sourceColumn();
o.body.sourceLineText = this.sourceLineText();
// Add script information to the event if available.
var script = this.func().script();
if (script) {
o.body.script = MakeScriptObject_(script, false);
}
} else {
o.body.sourceLine = -1;
}
return o.toJSONProtocol();
};
function MakeCompileEvent(script, type) { function MakeCompileEvent(script, type) {
return new CompileEvent(script, type); return new CompileEvent(script, type);
} }
...@@ -979,6 +1050,27 @@ CompileEvent.prototype.script = function() { ...@@ -979,6 +1050,27 @@ CompileEvent.prototype.script = function() {
}; };
CompileEvent.prototype.toJSONProtocol = function() {
var o = new ProtocolMessage();
o.running = true;
switch (this.type_) {
case Debug.DebugEvent.BeforeCompile:
o.event = "beforeCompile";
break;
case Debug.DebugEvent.AfterCompile:
o.event = "afterCompile";
break;
case Debug.DebugEvent.CompileError:
o.event = "compileError";
break;
}
o.body = {};
o.body.script = this.script_;
return o.toJSONProtocol();
};
function MakeScriptObject_(script, include_source) { function MakeScriptObject_(script, include_source) {
var o = { id: script.id(), var o = { id: script.id(),
name: script.name(), name: script.name(),
...@@ -1016,11 +1108,1273 @@ AsyncTaskEvent.prototype.id = function() { ...@@ -1016,11 +1108,1273 @@ AsyncTaskEvent.prototype.id = function() {
return this.id_; return this.id_;
} }
function DebugCommandProcessor(exec_state, opt_is_running) {
this.exec_state_ = exec_state;
this.running_ = opt_is_running || false;
}
DebugCommandProcessor.prototype.processDebugRequest = function (request) {
return this.processDebugJSONRequest(request);
};
function ProtocolMessage(request) {
// Update sequence number.
this.seq = next_response_seq++;
if (request) {
// If message is based on a request this is a response. Fill the initial
// response from the request.
this.type = 'response';
this.request_seq = request.seq;
this.command = request.command;
} else {
// If message is not based on a request it is a dabugger generated event.
this.type = 'event';
}
this.success = true;
// Handler may set this field to control debugger state.
this.running = UNDEFINED;
}
ProtocolMessage.prototype.setOption = function(name, value) {
if (!this.options_) {
this.options_ = {};
}
this.options_[name] = value;
};
ProtocolMessage.prototype.failed = function(message, opt_details) {
this.success = false;
this.message = message;
if (IS_OBJECT(opt_details)) {
this.error_details = opt_details;
}
};
ProtocolMessage.prototype.toJSONProtocol = function() {
// Encode the protocol header.
var json = {};
json.seq= this.seq;
if (this.request_seq) {
json.request_seq = this.request_seq;
}
json.type = this.type;
if (this.event) {
json.event = this.event;
}
if (this.command) {
json.command = this.command;
}
if (this.success) {
json.success = this.success;
} else {
json.success = false;
}
if (this.body) {
// Encode the body part.
var bodyJson;
var serializer = MakeMirrorSerializer(true, this.options_);
if (this.body instanceof Mirror) {
bodyJson = serializer.serializeValue(this.body);
} else if (this.body instanceof GlobalArray) {
bodyJson = [];
for (var i = 0; i < this.body.length; i++) {
if (this.body[i] instanceof Mirror) {
bodyJson.push(serializer.serializeValue(this.body[i]));
} else {
bodyJson.push(ObjectToProtocolObject_(this.body[i], serializer));
}
}
} else {
bodyJson = ObjectToProtocolObject_(this.body, serializer);
}
json.body = bodyJson;
json.refs = serializer.serializeReferencedObjects();
}
if (this.message) {
json.message = this.message;
}
if (this.error_details) {
json.error_details = this.error_details;
}
json.running = this.running;
return JSONStringify(json);
};
DebugCommandProcessor.prototype.createResponse = function(request) {
return new ProtocolMessage(request);
};
DebugCommandProcessor.prototype.processDebugJSONRequest = function(
json_request) {
var request; // Current request.
var response; // Generated response.
try {
try {
// Convert the JSON string to an object.
request = JSONParse(json_request);
// Create an initial response.
response = this.createResponse(request);
if (!request.type) {
throw %make_error(kDebugger, 'Type not specified');
}
if (request.type != 'request') {
throw %make_error(kDebugger,
"Illegal type '" + request.type + "' in request");
}
if (!request.command) {
throw %make_error(kDebugger, 'Command not specified');
}
if (request.arguments) {
var args = request.arguments;
// TODO(yurys): remove request.arguments.compactFormat check once
// ChromeDevTools are switched to 'inlineRefs'
if (args.inlineRefs || args.compactFormat) {
response.setOption('inlineRefs', true);
}
if (!IS_UNDEFINED(args.maxStringLength)) {
response.setOption('maxStringLength', args.maxStringLength);
}
}
var key = request.command.toLowerCase();
var handler = DebugCommandProcessor.prototype.dispatch_[key];
if (IS_FUNCTION(handler)) {
%_Call(handler, this, request, response);
} else {
throw %make_error(kDebugger,
'Unknown command "' + request.command + '" in request');
}
} catch (e) {
// If there is no response object created one (without command).
if (!response) {
response = this.createResponse();
}
response.success = false;
response.message = TO_STRING(e);
}
// Return the response as a JSON encoded string.
try {
if (!IS_UNDEFINED(response.running)) {
// Response controls running state.
this.running_ = response.running;
}
response.running = this.running_;
return response.toJSONProtocol();
} catch (e) {
// Failed to generate response - return generic error.
return '{"seq":' + response.seq + ',' +
'"request_seq":' + request.seq + ',' +
'"type":"response",' +
'"success":false,' +
'"message":"Internal error: ' + TO_STRING(e) + '"}';
}
} catch (e) {
// Failed in one of the catch blocks above - most generic error.
return '{"seq":0,"type":"response","success":false,"message":"Internal error"}';
}
};
DebugCommandProcessor.prototype.continueRequest_ = function(request, response) {
// Check for arguments for continue.
if (request.arguments) {
var action = Debug.StepAction.StepIn;
// Pull out arguments.
var stepaction = request.arguments.stepaction;
// Get the stepaction argument.
if (stepaction) {
if (stepaction == 'in') {
action = Debug.StepAction.StepIn;
} else if (stepaction == 'next') {
action = Debug.StepAction.StepNext;
} else if (stepaction == 'out') {
action = Debug.StepAction.StepOut;
} else {
throw %make_error(kDebugger,
'Invalid stepaction argument "' + stepaction + '".');
}
}
// Set up the VM for stepping.
this.exec_state_.prepareStep(action);
}
// VM should be running after executing this request.
response.running = true;
};
DebugCommandProcessor.prototype.breakRequest_ = function(request, response) {
// Ignore as break command does not do anything when broken.
};
DebugCommandProcessor.prototype.setBreakPointRequest_ =
function(request, response) {
// Check for legal request.
if (!request.arguments) {
response.failed('Missing arguments');
return;
}
// Pull out arguments.
var type = request.arguments.type;
var target = request.arguments.target;
var line = request.arguments.line;
var column = request.arguments.column;
var enabled = IS_UNDEFINED(request.arguments.enabled) ?
true : request.arguments.enabled;
var condition = request.arguments.condition;
var groupId = request.arguments.groupId;
// Check for legal arguments.
if (!type || IS_UNDEFINED(target)) {
response.failed('Missing argument "type" or "target"');
return;
}
// Either function or script break point.
var break_point_number;
if (type == 'function') {
// Handle function break point.
if (!IS_STRING(target)) {
response.failed('Argument "target" is not a string value');
return;
}
var f;
try {
// Find the function through a global evaluate.
f = this.exec_state_.evaluateGlobal(target).value();
} catch (e) {
response.failed('Error: "' + TO_STRING(e) +
'" evaluating "' + target + '"');
return;
}
if (!IS_FUNCTION(f)) {
response.failed('"' + target + '" does not evaluate to a function');
return;
}
// Set function break point.
break_point_number = Debug.setBreakPoint(f, line, column, condition);
} else if (type == 'handle') {
// Find the object pointed by the specified handle.
var handle = ParseInt(target, 10);
var mirror = LookupMirror(handle);
if (!mirror) {
return response.failed('Object #' + handle + '# not found');
}
if (!mirror.isFunction()) {
return response.failed('Object #' + handle + '# is not a function');
}
// Set function break point.
break_point_number = Debug.setBreakPoint(mirror.value(),
line, column, condition);
} else if (type == 'script') {
// set script break point.
break_point_number =
Debug.setScriptBreakPointByName(target, line, column, condition,
groupId);
} else if (type == 'scriptId') {
break_point_number =
Debug.setScriptBreakPointById(target, line, column, condition, groupId);
} else if (type == 'scriptRegExp') {
break_point_number =
Debug.setScriptBreakPointByRegExp(target, line, column, condition,
groupId);
} else {
response.failed('Illegal type "' + type + '"');
return;
}
// Set additional break point properties.
var break_point = Debug.findBreakPoint(break_point_number);
if (!enabled) {
Debug.disableBreakPoint(break_point_number);
}
// Add the break point number to the response.
response.body = { type: type,
breakpoint: break_point_number };
// Add break point information to the response.
if (break_point instanceof ScriptBreakPoint) {
if (break_point.type() == Debug.ScriptBreakPointType.ScriptId) {
response.body.type = 'scriptId';
response.body.script_id = break_point.script_id();
} else if (break_point.type() == Debug.ScriptBreakPointType.ScriptName) {
response.body.type = 'scriptName';
response.body.script_name = break_point.script_name();
} else if (break_point.type() == Debug.ScriptBreakPointType.ScriptRegExp) {
response.body.type = 'scriptRegExp';
response.body.script_regexp = break_point.script_regexp_object().source;
} else {
throw %make_error(kDebugger,
"Unexpected breakpoint type: " + break_point.type());
}
response.body.line = break_point.line();
response.body.column = break_point.column();
response.body.actual_locations = break_point.actual_locations();
} else {
response.body.type = 'function';
response.body.actual_locations = [break_point.actual_location];
}
};
DebugCommandProcessor.prototype.changeBreakPointRequest_ = function(
request, response) {
// Check for legal request.
if (!request.arguments) {
response.failed('Missing arguments');
return;
}
// Pull out arguments.
var break_point = TO_NUMBER(request.arguments.breakpoint);
var enabled = request.arguments.enabled;
var condition = request.arguments.condition;
// Check for legal arguments.
if (!break_point) {
response.failed('Missing argument "breakpoint"');
return;
}
// Change enabled state if supplied.
if (!IS_UNDEFINED(enabled)) {
if (enabled) {
Debug.enableBreakPoint(break_point);
} else {
Debug.disableBreakPoint(break_point);
}
}
// Change condition if supplied
if (!IS_UNDEFINED(condition)) {
Debug.changeBreakPointCondition(break_point, condition);
}
};
DebugCommandProcessor.prototype.clearBreakPointGroupRequest_ = function(
request, response) {
// Check for legal request.
if (!request.arguments) {
response.failed('Missing arguments');
return;
}
// Pull out arguments.
var group_id = request.arguments.groupId;
// Check for legal arguments.
if (!group_id) {
response.failed('Missing argument "groupId"');
return;
}
var cleared_break_points = [];
var new_script_break_points = [];
for (var i = 0; i < script_break_points.length; i++) {
var next_break_point = script_break_points[i];
if (next_break_point.groupId() == group_id) {
cleared_break_points.push(next_break_point.number());
next_break_point.clear();
} else {
new_script_break_points.push(next_break_point);
}
}
script_break_points = new_script_break_points;
// Add the cleared break point numbers to the response.
response.body = { breakpoints: cleared_break_points };
};
DebugCommandProcessor.prototype.clearBreakPointRequest_ = function(
request, response) {
// Check for legal request.
if (!request.arguments) {
response.failed('Missing arguments');
return;
}
// Pull out arguments.
var break_point = TO_NUMBER(request.arguments.breakpoint);
// Check for legal arguments.
if (!break_point) {
response.failed('Missing argument "breakpoint"');
return;
}
// Clear break point.
Debug.clearBreakPoint(break_point);
// Add the cleared break point number to the response.
response.body = { breakpoint: break_point };
};
DebugCommandProcessor.prototype.listBreakpointsRequest_ = function(
request, response) {
var array = [];
for (var i = 0; i < script_break_points.length; i++) {
var break_point = script_break_points[i];
var description = {
number: break_point.number(),
line: break_point.line(),
column: break_point.column(),
groupId: break_point.groupId(),
active: break_point.active(),
condition: break_point.condition(),
actual_locations: break_point.actual_locations()
};
if (break_point.type() == Debug.ScriptBreakPointType.ScriptId) {
description.type = 'scriptId';
description.script_id = break_point.script_id();
} else if (break_point.type() == Debug.ScriptBreakPointType.ScriptName) {
description.type = 'scriptName';
description.script_name = break_point.script_name();
} else if (break_point.type() == Debug.ScriptBreakPointType.ScriptRegExp) {
description.type = 'scriptRegExp';
description.script_regexp = break_point.script_regexp_object().source;
} else {
throw %make_error(kDebugger,
"Unexpected breakpoint type: " + break_point.type());
}
array.push(description);
}
response.body = {
breakpoints: array,
breakOnExceptions: Debug.isBreakOnException(),
breakOnUncaughtExceptions: Debug.isBreakOnUncaughtException()
};
};
DebugCommandProcessor.prototype.disconnectRequest_ =
function(request, response) {
Debug.disableAllBreakPoints();
this.continueRequest_(request, response);
};
DebugCommandProcessor.prototype.setExceptionBreakRequest_ =
function(request, response) {
// Check for legal request.
if (!request.arguments) {
response.failed('Missing arguments');
return;
}
// Pull out and check the 'type' argument:
var type = request.arguments.type;
if (!type) {
response.failed('Missing argument "type"');
return;
}
// Initialize the default value of enable:
var enabled;
if (type == 'all') {
enabled = !Debug.isBreakOnException();
} else if (type == 'uncaught') {
enabled = !Debug.isBreakOnUncaughtException();
}
// Pull out and check the 'enabled' argument if present:
if (!IS_UNDEFINED(request.arguments.enabled)) {
enabled = request.arguments.enabled;
if ((enabled != true) && (enabled != false)) {
response.failed('Illegal value for "enabled":"' + enabled + '"');
}
}
// Now set the exception break state:
if (type == 'all') {
%ChangeBreakOnException(Debug.ExceptionBreak.Caught, enabled);
} else if (type == 'uncaught') {
%ChangeBreakOnException(Debug.ExceptionBreak.Uncaught, enabled);
} else {
response.failed('Unknown "type":"' + type + '"');
}
// Add the cleared break point number to the response.
response.body = { 'type': type, 'enabled': enabled };
};
DebugCommandProcessor.prototype.backtraceRequest_ = function(
request, response) {
// Get the number of frames.
var total_frames = this.exec_state_.frameCount();
// Create simple response if there are no frames.
if (total_frames == 0) {
response.body = {
totalFrames: total_frames
};
return;
}
// Default frame range to include in backtrace.
var from_index = 0;
var to_index = kDefaultBacktraceLength;
// Get the range from the arguments.
if (request.arguments) {
if (request.arguments.fromFrame) {
from_index = request.arguments.fromFrame;
}
if (request.arguments.toFrame) {
to_index = request.arguments.toFrame;
}
if (request.arguments.bottom) {
var tmp_index = total_frames - from_index;
from_index = total_frames - to_index;
to_index = tmp_index;
}
if (from_index < 0 || to_index < 0) {
return response.failed('Invalid frame number');
}
}
// Adjust the index.
to_index = MathMin(total_frames, to_index);
if (to_index <= from_index) {
var error = 'Invalid frame range';
return response.failed(error);
}
// Create the response body.
var frames = [];
for (var i = from_index; i < to_index; i++) {
frames.push(this.exec_state_.frame(i));
}
response.body = {
fromFrame: from_index,
toFrame: to_index,
totalFrames: total_frames,
frames: frames
};
};
DebugCommandProcessor.prototype.frameRequest_ = function(request, response) {
// No frames no source.
if (this.exec_state_.frameCount() == 0) {
return response.failed('No frames');
}
// With no arguments just keep the selected frame.
if (request.arguments) {
var index = request.arguments.number;
if (index < 0 || this.exec_state_.frameCount() <= index) {
return response.failed('Invalid frame number');
}
this.exec_state_.setSelectedFrame(request.arguments.number);
}
response.body = this.exec_state_.frame();
};
DebugCommandProcessor.prototype.resolveFrameFromScopeDescription_ =
function(scope_description) {
// Get the frame for which the scope or scopes are requested.
// With no frameNumber argument use the currently selected frame.
if (scope_description && !IS_UNDEFINED(scope_description.frameNumber)) {
var frame_index = scope_description.frameNumber;
if (frame_index < 0 || this.exec_state_.frameCount() <= frame_index) {
throw %make_type_error(kDebuggerFrame);
}
return this.exec_state_.frame(frame_index);
} else {
return this.exec_state_.frame();
}
};
// Gets scope host object from request. It is either a function
// ('functionHandle' argument must be specified) or a stack frame
// ('frameNumber' may be specified and the current frame is taken by default).
DebugCommandProcessor.prototype.resolveScopeHolder_ =
function(scope_description) {
if (scope_description && "functionHandle" in scope_description) {
if (!IS_NUMBER(scope_description.functionHandle)) {
throw %make_error(kDebugger, 'Function handle must be a number');
}
var function_mirror = LookupMirror(scope_description.functionHandle);
if (!function_mirror) {
throw %make_error(kDebugger, 'Failed to find function object by handle');
}
if (!function_mirror.isFunction()) {
throw %make_error(kDebugger,
'Value of non-function type is found by handle');
}
return function_mirror;
} else {
// No frames no scopes.
if (this.exec_state_.frameCount() == 0) {
throw %make_error(kDebugger, 'No scopes');
}
// Get the frame for which the scopes are requested.
var frame = this.resolveFrameFromScopeDescription_(scope_description);
return frame;
}
}
DebugCommandProcessor.prototype.scopesRequest_ = function(request, response) {
var scope_holder = this.resolveScopeHolder_(request.arguments);
// Fill all scopes for this frame or function.
var total_scopes = scope_holder.scopeCount();
var scopes = [];
for (var i = 0; i < total_scopes; i++) {
scopes.push(scope_holder.scope(i));
}
response.body = {
fromScope: 0,
toScope: total_scopes,
totalScopes: total_scopes,
scopes: scopes
};
};
DebugCommandProcessor.prototype.scopeRequest_ = function(request, response) {
// Get the frame or function for which the scope is requested.
var scope_holder = this.resolveScopeHolder_(request.arguments);
// With no scope argument just return top scope.
var scope_index = 0;
if (request.arguments && !IS_UNDEFINED(request.arguments.number)) {
scope_index = TO_NUMBER(request.arguments.number);
if (scope_index < 0 || scope_holder.scopeCount() <= scope_index) {
return response.failed('Invalid scope number');
}
}
response.body = scope_holder.scope(scope_index);
};
// Reads value from protocol description. Description may be in form of type
// (for singletons), raw value (primitive types supported in JSON),
// string value description plus type (for primitive values) or handle id.
// Returns raw value or throws exception.
DebugCommandProcessor.resolveValue_ = function(value_description) {
if ("handle" in value_description) {
var value_mirror = LookupMirror(value_description.handle);
if (!value_mirror) {
throw %make_error(kDebugger, "Failed to resolve value by handle, ' #" +
value_description.handle + "# not found");
}
return value_mirror.value();
} else if ("stringDescription" in value_description) {
if (value_description.type == MirrorType.BOOLEAN_TYPE) {
return TO_BOOLEAN(value_description.stringDescription);
} else if (value_description.type == MirrorType.NUMBER_TYPE) {
return TO_NUMBER(value_description.stringDescription);
} if (value_description.type == MirrorType.STRING_TYPE) {
return TO_STRING(value_description.stringDescription);
} else {
throw %make_error(kDebugger, "Unknown type");
}
} else if ("value" in value_description) {
return value_description.value;
} else if (value_description.type == MirrorType.UNDEFINED_TYPE) {
return UNDEFINED;
} else if (value_description.type == MirrorType.NULL_TYPE) {
return null;
} else {
throw %make_error(kDebugger, "Failed to parse value description");
}
};
DebugCommandProcessor.prototype.setVariableValueRequest_ =
function(request, response) {
if (!request.arguments) {
response.failed('Missing arguments');
return;
}
if (IS_UNDEFINED(request.arguments.name)) {
response.failed('Missing variable name');
}
var variable_name = request.arguments.name;
var scope_description = request.arguments.scope;
// Get the frame or function for which the scope is requested.
var scope_holder = this.resolveScopeHolder_(scope_description);
if (IS_UNDEFINED(scope_description.number)) {
response.failed('Missing scope number');
}
var scope_index = TO_NUMBER(scope_description.number);
var scope = scope_holder.scope(scope_index);
var new_value =
DebugCommandProcessor.resolveValue_(request.arguments.newValue);
scope.setVariableValue(variable_name, new_value);
var new_value_mirror = MakeMirror(new_value);
response.body = {
newValue: new_value_mirror
};
};
DebugCommandProcessor.prototype.evaluateRequest_ = function(request, response) {
if (!request.arguments) {
return response.failed('Missing arguments');
}
// Pull out arguments.
var expression = request.arguments.expression;
var frame = request.arguments.frame;
var global = request.arguments.global;
// The expression argument could be an integer so we convert it to a
// string.
try {
expression = TO_STRING(expression);
} catch(e) {
return response.failed('Failed to convert expression argument to string');
}
// Check for legal arguments.
if (!IS_UNDEFINED(frame) && global) {
return response.failed('Arguments "frame" and "global" are exclusive');
}
// Global evaluate.
if (global) {
// Evaluate in the native context.
response.body = this.exec_state_.evaluateGlobal(expression);
return;
}
// No frames no evaluate in frame.
if (this.exec_state_.frameCount() == 0) {
return response.failed('No frames');
}
// Check whether a frame was specified.
if (!IS_UNDEFINED(frame)) {
var frame_number = TO_NUMBER(frame);
if (frame_number < 0 || frame_number >= this.exec_state_.frameCount()) {
return response.failed('Invalid frame "' + frame + '"');
}
// Evaluate in the specified frame.
response.body = this.exec_state_.frame(frame_number).evaluate(expression);
return;
} else {
// Evaluate in the selected frame.
response.body = this.exec_state_.frame().evaluate(expression);
return;
}
};
DebugCommandProcessor.prototype.lookupRequest_ = function(request, response) {
if (!request.arguments) {
return response.failed('Missing arguments');
}
// Pull out arguments.
var handles = request.arguments.handles;
// Check for legal arguments.
if (IS_UNDEFINED(handles)) {
return response.failed('Argument "handles" missing');
}
// Set 'includeSource' option for script lookup.
if (!IS_UNDEFINED(request.arguments.includeSource)) {
var includeSource = TO_BOOLEAN(request.arguments.includeSource);
response.setOption('includeSource', includeSource);
}
// Lookup handles.
var mirrors = {};
for (var i = 0; i < handles.length; i++) {
var handle = handles[i];
var mirror = LookupMirror(handle);
if (!mirror) {
return response.failed('Object #' + handle + '# not found');
}
mirrors[handle] = mirror;
}
response.body = mirrors;
};
DebugCommandProcessor.prototype.referencesRequest_ =
function(request, response) {
if (!request.arguments) {
return response.failed('Missing arguments');
}
// Pull out arguments.
var type = request.arguments.type;
var handle = request.arguments.handle;
// Check for legal arguments.
if (IS_UNDEFINED(type)) {
return response.failed('Argument "type" missing');
}
if (IS_UNDEFINED(handle)) {
return response.failed('Argument "handle" missing');
}
if (type != 'referencedBy' && type != 'constructedBy') {
return response.failed('Invalid type "' + type + '"');
}
// Lookup handle and return objects with references the object.
var mirror = LookupMirror(handle);
if (mirror) {
if (type == 'referencedBy') {
response.body = mirror.referencedBy();
} else {
response.body = mirror.constructedBy();
}
} else {
return response.failed('Object #' + handle + '# not found');
}
};
DebugCommandProcessor.prototype.sourceRequest_ = function(request, response) {
// No frames no source.
if (this.exec_state_.frameCount() == 0) {
return response.failed('No source');
}
var from_line;
var to_line;
var frame = this.exec_state_.frame();
if (request.arguments) {
// Pull out arguments.
from_line = request.arguments.fromLine;
to_line = request.arguments.toLine;
if (!IS_UNDEFINED(request.arguments.frame)) {
var frame_number = TO_NUMBER(request.arguments.frame);
if (frame_number < 0 || frame_number >= this.exec_state_.frameCount()) {
return response.failed('Invalid frame "' + frame + '"');
}
frame = this.exec_state_.frame(frame_number);
}
}
// Get the script selected.
var script = frame.func().script();
if (!script) {
return response.failed('No source');
}
var raw_script = script.value();
// Sanitize arguments and remove line offset.
var line_offset = raw_script.line_offset;
var line_count = %ScriptLineCount(raw_script);
from_line = IS_UNDEFINED(from_line) ? 0 : from_line - line_offset;
to_line = IS_UNDEFINED(to_line) ? line_count : to_line - line_offset;
if (from_line < 0) from_line = 0;
if (to_line > line_count) to_line = line_count;
if (from_line >= line_count || to_line < 0 || from_line > to_line) {
return response.failed('Invalid line interval');
}
// Fill in the response.
response.body = {};
response.body.fromLine = from_line + line_offset;
response.body.toLine = to_line + line_offset;
response.body.fromPosition = %ScriptLineStartPosition(raw_script, from_line);
response.body.toPosition =
(to_line == 0) ? 0 : %ScriptLineEndPosition(raw_script, to_line - 1);
response.body.totalLines = %ScriptLineCount(raw_script);
response.body.source = %_SubString(raw_script.source,
response.body.fromPosition,
response.body.toPosition);
};
DebugCommandProcessor.prototype.scriptsRequest_ = function(request, response) {
var types = ScriptTypeFlag(Debug.ScriptType.Normal);
var includeSource = false;
var idsToInclude = null;
if (request.arguments) {
// Pull out arguments.
if (!IS_UNDEFINED(request.arguments.types)) {
types = TO_NUMBER(request.arguments.types);
if (IsNaN(types) || types < 0) {
return response.failed('Invalid types "' +
request.arguments.types + '"');
}
}
if (!IS_UNDEFINED(request.arguments.includeSource)) {
includeSource = TO_BOOLEAN(request.arguments.includeSource);
response.setOption('includeSource', includeSource);
}
if (IS_ARRAY(request.arguments.ids)) {
idsToInclude = {};
var ids = request.arguments.ids;
for (var i = 0; i < ids.length; i++) {
idsToInclude[ids[i]] = true;
}
}
var filterStr = null;
var filterNum = null;
if (!IS_UNDEFINED(request.arguments.filter)) {
var num = TO_NUMBER(request.arguments.filter);
if (!IsNaN(num)) {
filterNum = num;
}
filterStr = request.arguments.filter;
}
}
// Collect all scripts in the heap.
var scripts = Debug.scripts();
response.body = [];
for (var i = 0; i < scripts.length; i++) {
if (idsToInclude && !idsToInclude[scripts[i].id]) {
continue;
}
if (filterStr || filterNum) {
var script = scripts[i];
var found = false;
if (filterNum && !found) {
if (script.id && script.id === filterNum) {
found = true;
}
}
if (filterStr && !found) {
if (script.name && script.name.indexOf(filterStr) >= 0) {
found = true;
}
}
if (!found) continue;
}
if (types & ScriptTypeFlag(scripts[i].type)) {
response.body.push(MakeMirror(scripts[i]));
}
}
};
DebugCommandProcessor.prototype.suspendRequest_ = function(request, response) {
response.running = false;
};
// TODO(5510): remove this.
DebugCommandProcessor.prototype.versionRequest_ = function(request, response) {
response.body = {
V8Version: %GetV8Version()
};
};
DebugCommandProcessor.prototype.changeLiveRequest_ = function(
request, response) {
if (!request.arguments) {
return response.failed('Missing arguments');
}
var script_id = request.arguments.script_id;
var preview_only = !!request.arguments.preview_only;
var the_script = scriptById(script_id);
if (!the_script) {
response.failed('Script not found');
return;
}
var change_log = new GlobalArray();
if (!IS_STRING(request.arguments.new_source)) {
throw "new_source argument expected";
}
var new_source = request.arguments.new_source;
var result_description;
try {
result_description = Debug.LiveEdit.SetScriptSource(the_script,
new_source, preview_only, change_log);
} catch (e) {
if (e instanceof Debug.LiveEdit.Failure && "details" in e) {
response.failed(e.message, e.details);
return;
}
throw e;
}
response.body = {change_log: change_log, result: result_description};
if (!preview_only && !this.running_ && result_description.stack_modified) {
response.body.stepin_recommended = true;
}
};
DebugCommandProcessor.prototype.restartFrameRequest_ = function(
request, response) {
if (!request.arguments) {
return response.failed('Missing arguments');
}
var frame = request.arguments.frame;
// No frames to evaluate in frame.
if (this.exec_state_.frameCount() == 0) {
return response.failed('No frames');
}
var frame_mirror;
// Check whether a frame was specified.
if (!IS_UNDEFINED(frame)) {
var frame_number = TO_NUMBER(frame);
if (frame_number < 0 || frame_number >= this.exec_state_.frameCount()) {
return response.failed('Invalid frame "' + frame + '"');
}
// Restart specified frame.
frame_mirror = this.exec_state_.frame(frame_number);
} else {
// Restart selected frame.
frame_mirror = this.exec_state_.frame();
}
var result_description = frame_mirror.restart();
response.body = {result: result_description};
};
DebugCommandProcessor.prototype.debuggerFlagsRequest_ = function(request,
response) {
// Check for legal request.
if (!request.arguments) {
response.failed('Missing arguments');
return;
}
// Pull out arguments.
var flags = request.arguments.flags;
response.body = { flags: [] };
if (!IS_UNDEFINED(flags)) {
for (var i = 0; i < flags.length; i++) {
var name = flags[i].name;
var debugger_flag = debugger_flags[name];
if (!debugger_flag) {
continue;
}
if ('value' in flags[i]) {
debugger_flag.setValue(flags[i].value);
}
response.body.flags.push({ name: name, value: debugger_flag.getValue() });
}
} else {
for (var name in debugger_flags) {
var value = debugger_flags[name].getValue();
response.body.flags.push({ name: name, value: value });
}
}
};
DebugCommandProcessor.prototype.v8FlagsRequest_ = function(request, response) {
var flags = request.arguments.flags;
if (!flags) flags = '';
%SetFlags(flags);
};
DebugCommandProcessor.prototype.gcRequest_ = function(request, response) {
var type = request.arguments.type;
if (!type) type = 'all';
var before = %GetHeapUsage();
%CollectGarbage(type);
var after = %GetHeapUsage();
response.body = { "before": before, "after": after };
};
DebugCommandProcessor.prototype.dispatch_ = (function() {
var proto = DebugCommandProcessor.prototype;
return {
"continue": proto.continueRequest_,
"break" : proto.breakRequest_,
"setbreakpoint" : proto.setBreakPointRequest_,
"changebreakpoint": proto.changeBreakPointRequest_,
"clearbreakpoint": proto.clearBreakPointRequest_,
"clearbreakpointgroup": proto.clearBreakPointGroupRequest_,
"disconnect": proto.disconnectRequest_,
"setexceptionbreak": proto.setExceptionBreakRequest_,
"listbreakpoints": proto.listBreakpointsRequest_,
"backtrace": proto.backtraceRequest_,
"frame": proto.frameRequest_,
"scopes": proto.scopesRequest_,
"scope": proto.scopeRequest_,
"setvariablevalue": proto.setVariableValueRequest_,
"evaluate": proto.evaluateRequest_,
"lookup": proto.lookupRequest_,
"references": proto.referencesRequest_,
"source": proto.sourceRequest_,
"scripts": proto.scriptsRequest_,
"suspend": proto.suspendRequest_,
"version": proto.versionRequest_,
"changelive": proto.changeLiveRequest_,
"restartframe": proto.restartFrameRequest_,
"flags": proto.debuggerFlagsRequest_,
"v8flag": proto.v8FlagsRequest_,
"gc": proto.gcRequest_,
};
})();
// Check whether the previously processed command caused the VM to become
// running.
DebugCommandProcessor.prototype.isRunning = function() {
return this.running_;
};
DebugCommandProcessor.prototype.systemBreak = function(cmd, args) {
return %SystemBreak();
};
/**
* Convert an Object to its debugger protocol representation. The representation
* may be serilized to a JSON object using JSON.stringify().
* This implementation simply runs through all string property names, converts
* each property value to a protocol value and adds the property to the result
* object. For type "object" the function will be called recursively. Note that
* circular structures will cause infinite recursion.
* @param {Object} object The object to format as protocol object.
* @param {MirrorSerializer} mirror_serializer The serializer to use if any
* mirror objects are encountered.
* @return {Object} Protocol object value.
*/
function ObjectToProtocolObject_(object, mirror_serializer) {
var content = {};
for (var key in object) {
// Only consider string keys.
if (typeof key == 'string') {
// Format the value based on its type.
var property_value_json = ValueToProtocolValue_(object[key],
mirror_serializer);
// Add the property if relevant.
if (!IS_UNDEFINED(property_value_json)) {
content[key] = property_value_json;
}
}
}
return content;
}
/**
* Convert an array to its debugger protocol representation. It will convert
* each array element to a protocol value.
* @param {Array} array The array to format as protocol array.
* @param {MirrorSerializer} mirror_serializer The serializer to use if any
* mirror objects are encountered.
* @return {Array} Protocol array value.
*/
function ArrayToProtocolArray_(array, mirror_serializer) {
var json = [];
for (var i = 0; i < array.length; i++) {
json.push(ValueToProtocolValue_(array[i], mirror_serializer));
}
return json;
}
/**
* Convert a value to its debugger protocol representation.
* @param {*} value The value to format as protocol value.
* @param {MirrorSerializer} mirror_serializer The serializer to use if any
* mirror objects are encountered.
* @return {*} Protocol value.
*/
function ValueToProtocolValue_(value, mirror_serializer) {
// Format the value based on its type.
var json;
switch (typeof value) {
case 'object':
if (value instanceof Mirror) {
json = mirror_serializer.serializeValue(value);
} else if (IS_ARRAY(value)){
json = ArrayToProtocolArray_(value, mirror_serializer);
} else {
json = ObjectToProtocolObject_(value, mirror_serializer);
}
break;
case 'boolean':
case 'string':
case 'number':
json = value;
break;
default:
json = null;
}
return json;
}
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// Exports // Exports
utils.InstallConstants(global, [ utils.InstallConstants(global, [
"Debug", Debug, "Debug", Debug,
"DebugCommandProcessor", DebugCommandProcessor,
"BreakEvent", BreakEvent, "BreakEvent", BreakEvent,
"CompileEvent", CompileEvent, "CompileEvent", CompileEvent,
"BreakPoint", BreakPoint, "BreakPoint", BreakPoint,
......
...@@ -2463,11 +2463,577 @@ ContextMirror.prototype.data = function() { ...@@ -2463,11 +2463,577 @@ ContextMirror.prototype.data = function() {
return this.data_; return this.data_;
}; };
/**
* Returns a mirror serializer
*
* @param {boolean} details Set to true to include details
* @param {Object} options Options comtrolling the serialization
* The following options can be set:
* includeSource: include ths full source of scripts
* @returns {MirrorSerializer} mirror serializer
*/
function MakeMirrorSerializer(details, options) {
return new JSONProtocolSerializer(details, options);
}
/**
* Object for serializing a mirror objects and its direct references.
* @param {boolean} details Indicates whether to include details for the mirror
* serialized
* @constructor
*/
function JSONProtocolSerializer(details, options) {
this.details_ = details;
this.options_ = options;
this.mirrors_ = [ ];
}
/**
* Returns a serialization of an object reference. The referenced object are
* added to the serialization state.
*
* @param {Mirror} mirror The mirror to serialize
* @returns {String} JSON serialization
*/
JSONProtocolSerializer.prototype.serializeReference = function(mirror) {
return this.serialize_(mirror, true, true);
};
/**
* Returns a serialization of an object value. The referenced objects are
* added to the serialization state.
*
* @param {Mirror} mirror The mirror to serialize
* @returns {String} JSON serialization
*/
JSONProtocolSerializer.prototype.serializeValue = function(mirror) {
var json = this.serialize_(mirror, false, true);
return json;
};
/**
* Returns a serialization of all the objects referenced.
*
* @param {Mirror} mirror The mirror to serialize.
* @returns {Array.<Object>} Array of the referenced objects converted to
* protcol objects.
*/
JSONProtocolSerializer.prototype.serializeReferencedObjects = function() {
// Collect the protocol representation of the referenced objects in an array.
var content = [];
// Get the number of referenced objects.
var count = this.mirrors_.length;
for (var i = 0; i < count; i++) {
content.push(this.serialize_(this.mirrors_[i], false, false));
}
return content;
};
JSONProtocolSerializer.prototype.includeSource_ = function() {
return this.options_ && this.options_.includeSource;
};
JSONProtocolSerializer.prototype.inlineRefs_ = function() {
return this.options_ && this.options_.inlineRefs;
};
JSONProtocolSerializer.prototype.maxStringLength_ = function() {
if (IS_UNDEFINED(this.options_) ||
IS_UNDEFINED(this.options_.maxStringLength)) {
return kMaxProtocolStringLength;
}
return this.options_.maxStringLength;
};
JSONProtocolSerializer.prototype.add_ = function(mirror) {
// If this mirror is already in the list just return.
for (var i = 0; i < this.mirrors_.length; i++) {
if (this.mirrors_[i] === mirror) {
return;
}
}
// Add the mirror to the list of mirrors to be serialized.
this.mirrors_.push(mirror);
};
/**
* Formats mirror object to protocol reference object with some data that can
* be used to display the value in debugger.
* @param {Mirror} mirror Mirror to serialize.
* @return {Object} Protocol reference object.
*/
JSONProtocolSerializer.prototype.serializeReferenceWithDisplayData_ =
function(mirror) {
var o = {};
o.ref = mirror.handle();
o.type = mirror.type();
switch (mirror.type()) {
case MirrorType.UNDEFINED_TYPE:
case MirrorType.NULL_TYPE:
case MirrorType.BOOLEAN_TYPE:
case MirrorType.NUMBER_TYPE:
o.value = mirror.value();
break;
case MirrorType.STRING_TYPE:
o.value = mirror.getTruncatedValue(this.maxStringLength_());
break;
case MirrorType.SYMBOL_TYPE:
o.description = mirror.description();
break;
case MirrorType.FUNCTION_TYPE:
o.name = mirror.name();
o.inferredName = mirror.inferredName();
if (mirror.script()) {
o.scriptId = mirror.script().id();
}
break;
case MirrorType.ERROR_TYPE:
case MirrorType.REGEXP_TYPE:
o.value = mirror.toText();
break;
case MirrorType.OBJECT_TYPE:
o.className = mirror.className();
break;
}
return o;
};
JSONProtocolSerializer.prototype.serialize_ = function(mirror, reference,
details) {
// If serializing a reference to a mirror just return the reference and add
// the mirror to the referenced mirrors.
if (reference &&
(mirror.isValue() || mirror.isScript() || mirror.isContext())) {
if (this.inlineRefs_() && mirror.isValue()) {
return this.serializeReferenceWithDisplayData_(mirror);
} else {
this.add_(mirror);
return {'ref' : mirror.handle()};
}
}
// Collect the JSON property/value pairs.
var content = {};
// Add the mirror handle.
if (mirror.isValue() || mirror.isScript() || mirror.isContext()) {
content.handle = mirror.handle();
}
// Always add the type.
content.type = mirror.type();
switch (mirror.type()) {
case MirrorType.UNDEFINED_TYPE:
case MirrorType.NULL_TYPE:
// Undefined and null are represented just by their type.
break;
case MirrorType.BOOLEAN_TYPE:
// Boolean values are simply represented by their value.
content.value = mirror.value();
break;
case MirrorType.NUMBER_TYPE:
// Number values are simply represented by their value.
content.value = NumberToJSON_(mirror.value());
break;
case MirrorType.STRING_TYPE:
// String values might have their value cropped to keep down size.
if (this.maxStringLength_() != -1 &&
mirror.length() > this.maxStringLength_()) {
var str = mirror.getTruncatedValue(this.maxStringLength_());
content.value = str;
content.fromIndex = 0;
content.toIndex = this.maxStringLength_();
} else {
content.value = mirror.value();
}
content.length = mirror.length();
break;
case MirrorType.SYMBOL_TYPE:
content.description = mirror.description();
break;
case MirrorType.OBJECT_TYPE:
case MirrorType.FUNCTION_TYPE:
case MirrorType.ERROR_TYPE:
case MirrorType.REGEXP_TYPE:
case MirrorType.PROMISE_TYPE:
case MirrorType.GENERATOR_TYPE:
// Add object representation.
this.serializeObject_(mirror, content, details);
break;
case MirrorType.PROPERTY_TYPE:
case MirrorType.INTERNAL_PROPERTY_TYPE:
throw %make_error(kDebugger,
'PropertyMirror cannot be serialized independently');
break;
case MirrorType.FRAME_TYPE:
// Add object representation.
this.serializeFrame_(mirror, content);
break;
case MirrorType.SCOPE_TYPE:
// Add object representation.
this.serializeScope_(mirror, content);
break;
case MirrorType.SCRIPT_TYPE:
// Script is represented by id, name and source attributes.
if (mirror.name()) {
content.name = mirror.name();
}
content.id = mirror.id();
content.lineOffset = mirror.lineOffset();
content.columnOffset = mirror.columnOffset();
content.lineCount = mirror.lineCount();
if (mirror.data()) {
content.data = mirror.data();
}
if (this.includeSource_()) {
content.source = mirror.source();
} else {
var sourceStart = mirror.source().substring(0, 80);
content.sourceStart = sourceStart;
}
content.sourceLength = mirror.source().length;
content.scriptType = mirror.scriptType();
content.compilationType = mirror.compilationType();
// For compilation type eval emit information on the script from which
// eval was called if a script is present.
if (mirror.compilationType() == 1 &&
mirror.evalFromScript()) {
content.evalFromScript =
this.serializeReference(mirror.evalFromScript());
var evalFromLocation = mirror.evalFromLocation();
if (evalFromLocation) {
content.evalFromLocation = { line: evalFromLocation.line,
column: evalFromLocation.column };
}
if (mirror.evalFromFunctionName()) {
content.evalFromFunctionName = mirror.evalFromFunctionName();
}
}
if (mirror.context()) {
content.context = this.serializeReference(mirror.context());
}
break;
case MirrorType.CONTEXT_TYPE:
content.data = mirror.data();
break;
}
// Always add the text representation.
content.text = mirror.toText();
// Create and return the JSON string.
return content;
};
/**
* Serialize object information to the following JSON format.
*
* {"className":"<class name>",
* "constructorFunction":{"ref":<number>},
* "protoObject":{"ref":<number>},
* "prototypeObject":{"ref":<number>},
* "namedInterceptor":<boolean>,
* "indexedInterceptor":<boolean>,
* "properties":[<properties>],
* "internalProperties":[<internal properties>]}
*/
JSONProtocolSerializer.prototype.serializeObject_ = function(mirror, content,
details) {
// Add general object properties.
content.className = mirror.className();
content.constructorFunction =
this.serializeReference(mirror.constructorFunction());
content.protoObject = this.serializeReference(mirror.protoObject());
content.prototypeObject = this.serializeReference(mirror.prototypeObject());
// Add flags to indicate whether there are interceptors.
if (mirror.hasNamedInterceptor()) {
content.namedInterceptor = true;
}
if (mirror.hasIndexedInterceptor()) {
content.indexedInterceptor = true;
}
if (mirror.isFunction()) {
// Add function specific properties.
content.name = mirror.name();
if (!IS_UNDEFINED(mirror.inferredName())) {
content.inferredName = mirror.inferredName();
}
content.resolved = mirror.resolved();
if (mirror.resolved()) {
content.source = mirror.source();
}
if (mirror.script()) {
content.script = this.serializeReference(mirror.script());
content.scriptId = mirror.script().id();
serializeLocationFields(mirror.sourceLocation(), content);
}
content.scopes = [];
for (var i = 0; i < mirror.scopeCount(); i++) {
var scope = mirror.scope(i);
content.scopes.push({
type: scope.scopeType(),
index: i
});
}
}
if (mirror.isGenerator()) {
// Add generator specific properties.
// Either 'running', 'closed', or 'suspended'.
content.status = mirror.status();
content.func = this.serializeReference(mirror.func())
content.receiver = this.serializeReference(mirror.receiver())
// If the generator is suspended, the content add line/column properties.
serializeLocationFields(mirror.sourceLocation(), content);
// TODO(wingo): Also serialize a reference to the context (scope chain).
}
if (mirror.isDate()) {
// Add date specific properties.
content.value = mirror.value();
}
if (mirror.isPromise()) {
// Add promise specific properties.
content.status = mirror.status();
content.promiseValue = this.serializeReference(mirror.promiseValue());
}
// Add actual properties - named properties followed by indexed properties.
var properties = mirror.propertyNames();
for (var i = 0; i < properties.length; i++) {
var propertyMirror = mirror.property(properties[i]);
properties[i] = this.serializeProperty_(propertyMirror);
if (details) {
this.add_(propertyMirror.value());
}
}
content.properties = properties;
var internalProperties = mirror.internalProperties();
if (internalProperties.length > 0) {
var ip = [];
for (var i = 0; i < internalProperties.length; i++) {
ip.push(this.serializeInternalProperty_(internalProperties[i]));
}
content.internalProperties = ip;
}
};
/**
* Serialize location information to the following JSON format:
*
* "position":"<position>",
* "line":"<line>",
* "column":"<column>",
*
* @param {SourceLocation} location The location to serialize, may be undefined.
*/
function serializeLocationFields (location, content) {
if (!location) {
return;
}
content.position = location.position;
var line = location.line;
if (!IS_UNDEFINED(line)) {
content.line = line;
}
var column = location.column;
if (!IS_UNDEFINED(column)) {
content.column = column;
}
}
/**
* Serialize property information to the following JSON format for building the
* array of properties.
*
* {"name":"<property name>",
* "attributes":<number>,
* "propertyType":<number>,
* "ref":<number>}
*
* If the attribute for the property is PropertyAttribute.None it is not added.
* Here are a couple of examples.
*
* {"name":"hello","propertyType":0,"ref":1}
* {"name":"length","attributes":7,"propertyType":3,"ref":2}
*
* @param {PropertyMirror} propertyMirror The property to serialize.
* @returns {Object} Protocol object representing the property.
*/
JSONProtocolSerializer.prototype.serializeProperty_ = function(propertyMirror) {
var result = {};
result.name = propertyMirror.name();
var propertyValue = propertyMirror.value();
if (this.inlineRefs_() && propertyValue.isValue()) {
result.value = this.serializeReferenceWithDisplayData_(propertyValue);
} else {
if (propertyMirror.attributes() != PropertyAttribute.None) {
result.attributes = propertyMirror.attributes();
}
result.propertyType = propertyMirror.propertyType();
result.ref = propertyValue.handle();
}
return result;
};
/**
* Serialize internal property information to the following JSON format for
* building the array of properties.
*
* {"name":"<property name>",
* "ref":<number>}
*
* {"name":"[[BoundThis]]","ref":117}
*
* @param {InternalPropertyMirror} propertyMirror The property to serialize.
* @returns {Object} Protocol object representing the property.
*/
JSONProtocolSerializer.prototype.serializeInternalProperty_ =
function(propertyMirror) {
var result = {};
result.name = propertyMirror.name();
var propertyValue = propertyMirror.value();
if (this.inlineRefs_() && propertyValue.isValue()) {
result.value = this.serializeReferenceWithDisplayData_(propertyValue);
} else {
result.ref = propertyValue.handle();
}
return result;
};
JSONProtocolSerializer.prototype.serializeFrame_ = function(mirror, content) {
content.index = mirror.index();
content.receiver = this.serializeReference(mirror.receiver());
var func = mirror.func();
content.func = this.serializeReference(func);
var script = func.script();
if (script) {
content.script = this.serializeReference(script);
}
content.constructCall = mirror.isConstructCall();
content.atReturn = mirror.isAtReturn();
if (mirror.isAtReturn()) {
content.returnValue = this.serializeReference(mirror.returnValue());
}
content.debuggerFrame = mirror.isDebuggerFrame();
var x = new GlobalArray(mirror.argumentCount());
for (var i = 0; i < mirror.argumentCount(); i++) {
var arg = {};
var argument_name = mirror.argumentName(i);
if (argument_name) {
arg.name = argument_name;
}
arg.value = this.serializeReference(mirror.argumentValue(i));
x[i] = arg;
}
content.arguments = x;
var x = new GlobalArray(mirror.localCount());
for (var i = 0; i < mirror.localCount(); i++) {
var local = {};
local.name = mirror.localName(i);
local.value = this.serializeReference(mirror.localValue(i));
x[i] = local;
}
content.locals = x;
serializeLocationFields(mirror.sourceLocation(), content);
var source_line_text = mirror.sourceLineText();
if (!IS_UNDEFINED(source_line_text)) {
content.sourceLineText = source_line_text;
}
content.scopes = [];
for (var i = 0; i < mirror.scopeCount(); i++) {
var scope = mirror.scope(i);
content.scopes.push({
type: scope.scopeType(),
index: i
});
}
};
JSONProtocolSerializer.prototype.serializeScope_ = function(mirror, content) {
content.index = mirror.scopeIndex();
content.frameIndex = mirror.frameIndex();
content.type = mirror.scopeType();
content.object = this.inlineRefs_() ?
this.serializeValue(mirror.scopeObject()) :
this.serializeReference(mirror.scopeObject());
};
/**
* Convert a number to a protocol value. For all finite numbers the number
* itself is returned. For non finite numbers NaN, Infinite and
* -Infinite the string representation "NaN", "Infinite" or "-Infinite"
* (not including the quotes) is returned.
*
* @param {number} value The number value to convert to a protocol value.
* @returns {number|string} Protocol value.
*/
function NumberToJSON_(value) {
if (IsNaN(value)) {
return 'NaN';
}
if (!NUMBER_IS_FINITE(value)) {
if (value > 0) {
return 'Infinity';
} else {
return '-Infinity';
}
}
return value;
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Exports // Exports
utils.InstallFunctions(global, DONT_ENUM, [ utils.InstallFunctions(global, DONT_ENUM, [
"MakeMirror", MakeMirror, "MakeMirror", MakeMirror,
"MakeMirrorSerializer", MakeMirrorSerializer,
"LookupMirror", LookupMirror, "LookupMirror", LookupMirror,
"ToggleMirrorCache", ToggleMirrorCache, "ToggleMirrorCache", ToggleMirrorCache,
"MirrorCacheIsEmpty", MirrorCacheIsEmpty, "MirrorCacheIsEmpty", MirrorCacheIsEmpty,
......
...@@ -469,7 +469,7 @@ Object* StackGuard::HandleInterrupts() { ...@@ -469,7 +469,7 @@ Object* StackGuard::HandleInterrupts() {
isolate_->heap()->HandleGCRequest(); isolate_->heap()->HandleGCRequest();
} }
if (CheckDebugBreak()) { if (CheckDebugBreak() || CheckDebugCommand()) {
isolate_->debug()->HandleDebugBreak(); isolate_->debug()->HandleDebugBreak();
} }
......
...@@ -86,11 +86,12 @@ class StackGuard final { ...@@ -86,11 +86,12 @@ class StackGuard final {
#define INTERRUPT_LIST(V) \ #define INTERRUPT_LIST(V) \
V(DEBUGBREAK, DebugBreak, 0) \ V(DEBUGBREAK, DebugBreak, 0) \
V(TERMINATE_EXECUTION, TerminateExecution, 1) \ V(DEBUGCOMMAND, DebugCommand, 1) \
V(GC_REQUEST, GC, 2) \ V(TERMINATE_EXECUTION, TerminateExecution, 2) \
V(INSTALL_CODE, InstallCode, 3) \ V(GC_REQUEST, GC, 3) \
V(API_INTERRUPT, ApiInterrupt, 4) \ V(INSTALL_CODE, InstallCode, 4) \
V(DEOPT_MARKED_ALLOCATION_SITES, DeoptMarkedAllocationSites, 5) V(API_INTERRUPT, ApiInterrupt, 5) \
V(DEOPT_MARKED_ALLOCATION_SITES, DeoptMarkedAllocationSites, 6)
#define V(NAME, Name, id) \ #define V(NAME, Name, id) \
inline bool Check##Name() { return CheckInterrupt(NAME); } \ inline bool Check##Name() { return CheckInterrupt(NAME); } \
......
...@@ -1249,6 +1249,28 @@ void Logger::HeapSampleItemEvent(const char* type, int number, int bytes) { ...@@ -1249,6 +1249,28 @@ void Logger::HeapSampleItemEvent(const char* type, int number, int bytes) {
} }
void Logger::DebugTag(const char* call_site_tag) {
if (!log_->IsEnabled() || !FLAG_log) return;
Log::MessageBuilder msg(log_);
msg.Append("debug-tag,%s", call_site_tag);
msg.WriteToLogFile();
}
void Logger::DebugEvent(const char* event_type, Vector<uint16_t> parameter) {
if (!log_->IsEnabled() || !FLAG_log) return;
StringBuilder s(parameter.length() + 1);
for (int i = 0; i < parameter.length(); ++i) {
s.AddCharacter(static_cast<char>(parameter[i]));
}
char* parameter_string = s.Finalize();
Log::MessageBuilder msg(log_);
msg.Append("debug-queue-event,%s,%15.3f,%s", event_type,
base::OS::TimeCurrentMillis(), parameter_string);
DeleteArray(parameter_string);
msg.WriteToLogFile();
}
void Logger::RuntimeCallTimerEvent() { void Logger::RuntimeCallTimerEvent() {
RuntimeCallStats* stats = isolate_->counters()->runtime_call_stats(); RuntimeCallStats* stats = isolate_->counters()->runtime_call_stats();
RuntimeCallTimer* timer = stats->current_timer(); RuntimeCallTimer* timer = stats->current_timer();
......
...@@ -136,6 +136,12 @@ class Logger : public CodeEventListener { ...@@ -136,6 +136,12 @@ class Logger : public CodeEventListener {
// object. // object.
void SuspectReadEvent(Name* name, Object* obj); void SuspectReadEvent(Name* name, Object* obj);
// Emits an event when a message is put on or read from a debugging queue.
// DebugTag lets us put a call-site specific label on the event.
void DebugTag(const char* call_site_tag);
void DebugEvent(const char* event_type, Vector<uint16_t> parameter);
// ==== Events logged by --log-api. ==== // ==== Events logged by --log-api. ====
void ApiSecurityCheck(); void ApiSecurityCheck();
void ApiNamedPropertyAccess(const char* tag, JSObject* holder, Object* name); void ApiNamedPropertyAccess(const char* tag, JSObject* holder, Object* name);
......
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