Added a debugger call to run a JavaScript function in the debugger. When called the...
authorsgjesse@chromium.org <sgjesse@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 27 Nov 2008 08:01:27 +0000 (08:01 +0000)
committersgjesse@chromium.org <sgjesse@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 27 Nov 2008 08:01:27 +0000 (08:01 +0000)
This makes it possible to get information like current line number, current script resource, backtrace information etc. which is not part of the normal API.
Review URL: http://codereview.chromium.org/12472

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@854 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

include/v8-debug.h
src/api.cc
src/debug.cc
src/debug.h
test/cctest/test-debug.cc

index c12c086..ecc5ae5 100644 (file)
@@ -128,6 +128,26 @@ class EXPORT Debug {
   // Message based interface. The message protocol is JSON.
   static void SetMessageHandler(DebugMessageHandler handler, void* data = NULL);
   static void SendCommand(const uint16_t* command, int length);
+
+ /**
+  * Run a JavaScript function in the debugger.
+  * \param fun the function to call
+  * \param data passed as second argument to the function
+  * With this call the debugger is entered and the function specified is called
+  * with the execution state as the first argument. This makes it possible to
+  * get access to information otherwise not available during normal JavaScript
+  * execution e.g. details on stack frames. The following example show a
+  * JavaScript function which when passed to v8::Debug::Call will return the
+  * current line of JavaScript execution.
+  *
+  * \code
+  *   function frame_source_line(exec_state) {
+  *     return exec_state.frame(0).sourceLine();
+  *   }
+  * \endcode
+  */
+  static Handle<Value> Call(v8::Handle<v8::Function> fun,
+                            Handle<Value> data = Handle<Value>());
 };
 
 
index 017753b..f580399 100644 (file)
@@ -2902,6 +2902,26 @@ void Debug::SendCommand(const uint16_t* command, int length) {
 }
 
 
+Handle<Value> Debug::Call(v8::Handle<v8::Function> fun,
+                          v8::Handle<v8::Value> data) {
+  if (!i::V8::HasBeenSetup()) return Handle<Value>();
+  ON_BAILOUT("v8::Debug::Call()", return Handle<Value>());
+  i::Handle<i::Object> result;
+  EXCEPTION_PREAMBLE();
+  if (data.IsEmpty()) {
+    result = i::Debugger::Call(Utils::OpenHandle(*fun),
+                               i::Factory::undefined_value(),
+                               &has_pending_exception);
+  } else {
+    result = i::Debugger::Call(Utils::OpenHandle(*fun),
+                               Utils::OpenHandle(*data),
+                               &has_pending_exception);
+  }
+  EXCEPTION_BAILOUT_CHECK(Local<Value>());
+  return Utils::ToLocal(result);
+}
+
+
 namespace internal {
 
 
index ecb3b6c..6952d8b 100644 (file)
@@ -1650,6 +1650,30 @@ void Debugger::UpdateActiveDebugger() {
 }
 
 
+Handle<Object> Debugger::Call(Handle<JSFunction> fun,
+                              Handle<Object> data,
+                              bool* pending_exception) {
+  // Enter the debugger.
+  EnterDebugger debugger;
+  if (debugger.FailedToEnter() || !debugger.HasJavaScriptFrames()) {
+    return Factory::undefined_value();
+  }
+
+  // Create the execution state.
+  bool caught_exception = false;
+  Handle<Object> exec_state = MakeExecutionState(&caught_exception);
+  if (caught_exception) {
+    return Factory::undefined_value();
+  }
+
+  static const int kArgc = 2;
+  Object** argv[kArgc] = { exec_state.location(), data.location() };
+  Handle<Object> result = Execution::Call(fun, Factory::undefined_value(),
+                                          kArgc, argv, pending_exception);
+  return result;
+}
+
+
 DebugMessageThread::DebugMessageThread()
     : host_running_(true),
       command_queue_(kQueueInitialSize),
index 6833982..629cebe 100644 (file)
@@ -358,6 +358,10 @@ class Debugger {
   static void SendMessage(Vector<uint16_t> message);
   static void ProcessCommand(Vector<const uint16_t> command);
   static void UpdateActiveDebugger();
+  static Handle<Object> Call(Handle<JSFunction> fun,
+                             Handle<Object> data,
+                             bool* pending_exception);
+
   inline static bool EventActive(v8::DebugEvent event) {
     // Currently argument event is not used.
     return !Debugger::compiling_natives_ && Debugger::debugger_active_;
@@ -504,6 +508,9 @@ class EnterDebugger BASE_EMBEDDED {
   // Check whether the debugger could be entered.
   inline bool FailedToEnter() { return load_failed_; }
 
+  // Check whether there are any JavaScript frames on the stack.
+  inline bool HasJavaScriptFrames() { return set_; }
+
  private:
   JavaScriptFrameIterator it_;
   const bool set_;  // Was the break actually set?
index ea819e0..81ce21f 100644 (file)
@@ -3166,3 +3166,155 @@ TEST(SendCommandToUninitializedVM) {
   int dummy_length = AsciiToUtf16(dummy_command, dummy_buffer);
   v8::Debug::SendCommand(dummy_buffer, dummy_length);
 }
+
+
+// Source for The JavaScript function which returns the number of frames.
+static const char* frame_count_source =
+    "function frame_count(exec_state) {"
+    "  return exec_state.frameCount();"
+    "}";
+v8::Handle<v8::Function> frame_count;
+
+
+// Source for a JavaScript function which returns the source line for the top
+// frame.
+static const char* frame_source_line_source =
+    "function frame_source_line(exec_state) {"
+    "  return exec_state.frame(0).sourceLine();"
+    "}";
+v8::Handle<v8::Function> frame_source_line;
+
+
+// Source for a JavaScript function which returns the data parameter of a
+// function called in the context of the debugger. If no data parameter is
+// passed it throws an exception.
+static const char* debugger_call_with_data_source =
+    "function debugger_call_with_data(exec_state, data) {"
+    "  if (data) return data;"
+    "  throw 'No data!'"
+    "}";
+v8::Handle<v8::Function> debugger_call_with_data;
+
+
+// Source for a JavaScript function which returns the data parameter of a
+// function called in the context of the debugger. If no data parameter is
+// passed it throws an exception.
+static const char* debugger_call_with_closure_source =
+    "var x = 3;"
+    "function (exec_state) {"
+    "  if (exec_state.y) return x - 1;"
+    "  exec_state.y = x;"
+    "  return exec_state.y"
+    "}";
+v8::Handle<v8::Function> debugger_call_with_closure;
+
+// Function to retrieve the number of JavaScript frames by calling a JavaScript
+// in the debugger.
+static v8::Handle<v8::Value> CheckFrameCount(const v8::Arguments& args) {
+  CHECK(v8::Debug::Call(frame_count)->IsNumber());
+  CHECK_EQ(args[0]->Int32Value(),
+           v8::Debug::Call(frame_count)->Int32Value());
+  return v8::Undefined();
+}
+
+
+// Function to retrieve the source line of the top JavaScript frame by calling a
+// JavaScript function in the debugger.
+static v8::Handle<v8::Value> CheckSourceLine(const v8::Arguments& args) {
+  CHECK(v8::Debug::Call(frame_source_line)->IsNumber());
+  CHECK_EQ(args[0]->Int32Value(),
+           v8::Debug::Call(frame_source_line)->Int32Value());
+  return v8::Undefined();
+}
+
+
+// Function to test passing an additional parameter to a JavaScript function
+// called in the debugger. It also tests that functions called in the debugger
+// can throw exceptions.
+static v8::Handle<v8::Value> CheckDataParameter(const v8::Arguments& args) {
+  v8::Handle<v8::String> data = v8::String::New("Test");
+  CHECK(v8::Debug::Call(debugger_call_with_data, data)->IsString());
+
+  CHECK(v8::Debug::Call(debugger_call_with_data).IsEmpty());
+  CHECK(v8::Debug::Call(debugger_call_with_data).IsEmpty());
+
+  v8::TryCatch catcher;
+  v8::Debug::Call(debugger_call_with_data);
+  CHECK(catcher.HasCaught());
+  CHECK(catcher.Exception()->IsString());
+
+  return v8::Undefined();
+}
+
+
+// Function to test using a JavaScript with closure in the debugger.
+static v8::Handle<v8::Value> CheckClosure(const v8::Arguments& args) {
+  CHECK(v8::Debug::Call(debugger_call_with_closure)->IsNumber());
+  CHECK_EQ(3, v8::Debug::Call(debugger_call_with_closure)->Int32Value());
+  return v8::Undefined();
+}
+
+
+// Test functions called through the debugger.
+TEST(CallFunctionInDebugger) {
+  // Create and enter a context with the functions CheckFrameCount,
+  // CheckSourceLine and CheckDataParameter installed.
+  v8::HandleScope scope;
+  v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
+  global_template->Set(v8::String::New("CheckFrameCount"),
+                       v8::FunctionTemplate::New(CheckFrameCount));
+  global_template->Set(v8::String::New("CheckSourceLine"),
+                       v8::FunctionTemplate::New(CheckSourceLine));
+  global_template->Set(v8::String::New("CheckDataParameter"),
+                       v8::FunctionTemplate::New(CheckDataParameter));
+  global_template->Set(v8::String::New("CheckClosure"),
+                       v8::FunctionTemplate::New(CheckClosure));
+  v8::Handle<v8::Context> context = v8::Context::New(NULL, global_template);
+  v8::Context::Scope context_scope(context);
+
+  // Compile a function for checking the number of JavaScript frames.
+  v8::Script::Compile(v8::String::New(frame_count_source))->Run();
+  frame_count = v8::Local<v8::Function>::Cast(
+      context->Global()->Get(v8::String::New("frame_count")));
+
+  // Compile a function for returning the source line for the top frame.
+  v8::Script::Compile(v8::String::New(frame_source_line_source))->Run();
+  frame_source_line = v8::Local<v8::Function>::Cast(
+      context->Global()->Get(v8::String::New("frame_source_line")));
+
+  // Compile a function returning the data parameter.
+  v8::Script::Compile(v8::String::New(debugger_call_with_data_source))->Run();
+  debugger_call_with_data = v8::Local<v8::Function>::Cast(
+      context->Global()->Get(v8::String::New("debugger_call_with_data")));
+
+  // Compile a function capturing closure.
+  debugger_call_with_closure = v8::Local<v8::Function>::Cast(
+      v8::Script::Compile(
+          v8::String::New(debugger_call_with_closure_source))->Run());
+
+  // Calling a function through the debugger returns undefined if there are no
+  // JavaScript frames.
+  CHECK(v8::Debug::Call(frame_count)->IsUndefined());
+  CHECK(v8::Debug::Call(frame_source_line)->IsUndefined());
+  CHECK(v8::Debug::Call(debugger_call_with_data)->IsUndefined());
+
+  // Test that the number of frames can be retrieved.
+  v8::Script::Compile(v8::String::New("CheckFrameCount(1)"))->Run();
+  v8::Script::Compile(v8::String::New("function f() {"
+                                      "  CheckFrameCount(2);"
+                                      "}; f()"))->Run();
+
+  // Test that the source line can be retrieved.
+  v8::Script::Compile(v8::String::New("CheckSourceLine(0)"))->Run();
+  v8::Script::Compile(v8::String::New("function f() {\n"
+                                      "  CheckSourceLine(1)\n"
+                                      "  CheckSourceLine(2)\n"
+                                      "  CheckSourceLine(3)\n"
+                                      "}; f()"))->Run();
+
+  // Test that a parameter can be passed to a function called in the debugger.
+  v8::Script::Compile(v8::String::New("CheckDataParameter()"))->Run();
+
+  // Test that a function with closure can be run in the debugger.
+  v8::Script::Compile(v8::String::New("CheckClosure()"))->Run();
+}