Revision: 13202
Author:   [email protected]
Date:     Tue Dec 11 15:27:38 2012
Log: Issue 2399 part 2: In debugger allow modifying local variable values

Review URL: https://codereview.chromium.org/11412310
http://code.google.com/p/v8/source/detail?r=13202

Modified:
 /branches/bleeding_edge/src/frames.cc
 /branches/bleeding_edge/src/frames.h
 /branches/bleeding_edge/src/runtime.cc
 /branches/bleeding_edge/test/mjsunit/debug-set-variable-value.js

=======================================
--- /branches/bleeding_edge/src/frames.cc       Mon Dec 10 03:09:12 2012
+++ /branches/bleeding_edge/src/frames.cc       Tue Dec 11 15:27:38 2012
@@ -690,6 +690,11 @@
   // Visit the return address in the callee and incoming arguments.
   IteratePc(v, pc_address(), code);
 }
+
+
+void JavaScriptFrame::SetParameterValue(int index, Object* value) const {
+  Memory::Object_at(GetParameterSlot(index)) = value;
+}


 bool JavaScriptFrame::IsConstructor() const {
=======================================
--- /branches/bleeding_edge/src/frames.h        Mon Dec 10 03:09:12 2012
+++ /branches/bleeding_edge/src/frames.h        Tue Dec 11 15:27:38 2012
@@ -499,6 +499,9 @@
   inline int ComputeParametersCount() const {
     return GetNumberOfIncomingArguments();
   }
+
+  // Debugger access.
+  void SetParameterValue(int index, Object* value) const;

   // Check if this frame is a constructor frame invoked through 'new'.
   bool IsConstructor() const;
=======================================
--- /branches/bleeding_edge/src/runtime.cc      Tue Dec 11 06:55:23 2012
+++ /branches/bleeding_edge/src/runtime.cc      Tue Dec 11 15:27:38 2012
@@ -10796,6 +10796,92 @@
                                                  frame,
                                                  &frame_inspector);
 }
+
+
+// Set the context local variable value.
+static bool SetContextLocalValue(Isolate* isolate,
+                                 Handle<ScopeInfo> scope_info,
+                                 Handle<Context> context,
+                                 Handle<String> variable_name,
+                                 Handle<Object> new_value) {
+  for (int i = 0; i < scope_info->ContextLocalCount(); i++) {
+    Handle<String> next_name(scope_info->ContextLocalName(i));
+    if (variable_name->Equals(*next_name)) {
+      VariableMode mode;
+      InitializationFlag init_flag;
+      int context_index =
+          scope_info->ContextSlotIndex(*next_name, &mode, &init_flag);
+      context->set(context_index, *new_value);
+      return true;
+    }
+  }
+
+  return false;
+}
+
+
+static bool SetLocalVariableValue(Isolate* isolate,
+                                  JavaScriptFrame* frame,
+                                  int inlined_jsframe_index,
+                                  Handle<String> variable_name,
+                                  Handle<Object> new_value) {
+  if (inlined_jsframe_index != 0 || frame->is_optimized()) {
+    // Optimized frames are not supported.
+    return false;
+  }
+
+  Handle<JSFunction> function(JSFunction::cast(frame->function()));
+  Handle<SharedFunctionInfo> shared(function->shared());
+  Handle<ScopeInfo> scope_info(shared->scope_info());
+
+  // Parameters.
+  for (int i = 0; i < scope_info->ParameterCount(); ++i) {
+    if (scope_info->ParameterName(i)->Equals(*variable_name)) {
+      frame->SetParameterValue(i, *new_value);
+      return true;
+    }
+  }
+
+  // Stack locals.
+  for (int i = 0; i < scope_info->StackLocalCount(); ++i) {
+    if (scope_info->StackLocalName(i)->Equals(*variable_name)) {
+      frame->SetExpression(i, *new_value);
+      return true;
+    }
+  }
+
+  if (scope_info->HasContext()) {
+    // Context locals.
+    Handle<Context> frame_context(Context::cast(frame->context()));
+    Handle<Context> function_context(frame_context->declaration_context());
+    if (SetContextLocalValue(
+        isolate, scope_info, function_context, variable_name, new_value)) {
+      return true;
+    }
+
+    // Function context extension. These are variables introduced by eval.
+    if (function_context->closure() == *function) {
+      if (function_context->has_extension() &&
+          !function_context->IsNativeContext()) {
+ Handle<JSObject> ext(JSObject::cast(function_context->extension()));
+
+        if (ext->HasProperty(*variable_name)) {
+          // We don't expect this to do anything except replacing
+          // property value.
+          SetProperty(isolate,
+                      ext,
+                      variable_name,
+                      new_value,
+                      NONE,
+                      kNonStrictMode);
+          return true;
+        }
+      }
+    }
+  }
+
+  return false;
+}


 // Create a plain JSObject which materializes the closure content for the
@@ -10858,19 +10944,9 @@
   Handle<ScopeInfo> scope_info(shared->scope_info());

   // Context locals to the context extension.
-  for (int i = 0; i < scope_info->ContextLocalCount(); i++) {
-    Handle<String> next_name(scope_info->ContextLocalName(i));
-    if (variable_name->Equals(*next_name)) {
-      VariableMode mode;
-      InitializationFlag init_flag;
-      int context_index =
-          scope_info->ContextSlotIndex(*next_name, &mode, &init_flag);
-      if (context_index < 0) {
-        return false;
-      }
-      context->set(context_index, *new_value);
-      return true;
-    }
+  if (SetContextLocalValue(
+          isolate, scope_info, context, variable_name, new_value)) {
+    return true;
   }

   // Properties from the function context extension. This will
@@ -10913,6 +10989,20 @@
       Handle<JSObject>());
   return catch_scope;
 }
+
+
+static bool SetCatchVariableValue(Isolate* isolate,
+                                  Handle<Context> context,
+                                  Handle<String> variable_name,
+                                  Handle<Object> new_value) {
+  ASSERT(context->IsCatchContext());
+  Handle<String> name(String::cast(context->extension()));
+  if (!name->Equals(*variable_name)) {
+    return false;
+  }
+  context->set(Context::THROWN_OBJECT_INDEX, *new_value);
+  return true;
+}


// Create a plain JSObject which materializes the block scope for the specified
@@ -11180,13 +11270,13 @@
       case ScopeIterator::ScopeTypeGlobal:
         break;
       case ScopeIterator::ScopeTypeLocal:
-        // TODO(2399): implement.
-        break;
+ return SetLocalVariableValue(isolate_, frame_, inlined_jsframe_index_,
+            variable_name, new_value);
       case ScopeIterator::ScopeTypeWith:
         break;
       case ScopeIterator::ScopeTypeCatch:
-        // TODO(2399): implement.
-        break;
+        return SetCatchVariableValue(isolate_, CurrentContext(),
+            variable_name, new_value);
       case ScopeIterator::ScopeTypeClosure:
         return SetClosureVariableValue(isolate_, CurrentContext(),
             variable_name, new_value);
=======================================
--- /branches/bleeding_edge/test/mjsunit/debug-set-variable-value.js Mon Dec 3 12:29:29 2012 +++ /branches/bleeding_edge/test/mjsunit/debug-set-variable-value.js Tue Dec 11 15:27:38 2012
@@ -34,8 +34,10 @@
 // A variable 'variable_name' must be initialized before debugger statement
// and returned after the statement. The test will alter variable value when
 // on debugger statement and check that returned value reflects the change.
-function RunPauseTest(scope_number, variable_name, expected_new_value, fun) {
-  var old_value = fun();
+function RunPauseTest(scope_number, expected_old_result, variable_name,
+    new_value, expected_new_result, fun) {
+  var actual_old_result = fun();
+  assertEquals(expected_old_result, actual_old_result);

   var listener_delegate;
   var listener_called = false;
@@ -43,7 +45,7 @@

   function listener_delegate(exec_state) {
     var scope = exec_state.frame(0).scope(scope_number);
-    scope.setVariableValue(variable_name, expected_new_value);
+    scope.setVariableValue(variable_name, new_value);
   }

   function listener(event, exec_state, event_data, data) {
@@ -62,102 +64,201 @@

   var actual_new_value;
   try {
-    actual_new_value = fun();
+    actual_new_result = fun();
   } finally {
     Debug.setListener(null);
   }

   if (exception != null) {
-   assertUnreachable("Exception: " + exception);
+   assertUnreachable("Exception in listener\n" + exception.stack);
   }
   assertTrue(listener_called);

-  assertTrue(old_value != actual_new_value);
-  assertTrue(expected_new_value == actual_new_value);
+  assertEquals(expected_new_result, actual_new_result);
 }

 // Accepts a closure 'fun' that returns a variable from it's outer scope.
// The test changes the value of variable via the handle to function and checks
 // that the return value changed accordingly.
-function RunClosureTest(scope_number, variable_name, expected_new_value, fun) {
-  var old_value = fun();
+function RunClosureTest(scope_number, expected_old_result, variable_name,
+    new_value, expected_new_result, fun) {
+  var actual_old_result = fun();
+  assertEquals(expected_old_result, actual_old_result);

   var fun_mirror = Debug.MakeMirror(fun);

   var scope = fun_mirror.scope(scope_number);
-  scope.setVariableValue(variable_name, expected_new_value);
+  scope.setVariableValue(variable_name, new_value);

-  var actual_new_value = fun();
+  var actual_new_result = fun();

-  assertTrue(old_value != actual_new_value);
-  assertTrue(expected_new_value == actual_new_value);
+  assertEquals(expected_new_result, actual_new_result);
 }

-// Test changing variable value when in pause
-RunPauseTest(1, 'v1', 5, (function Factory() {
-  var v1 = 'cat';
+
+function ClosureTestCase(scope_index, old_result, variable_name, new_value,
+    new_result, success_expected, factory) {
+  this.scope_index_ = scope_index;
+  this.old_result_ = old_result;
+  this.variable_name_ = variable_name;
+  this.new_value_ = new_value;
+  this.new_result_ = new_result;
+  this.success_expected_ = success_expected;
+  this.factory_ = factory;
+}
+
+ClosureTestCase.prototype.run_pause_test = function() {
+  var th = this;
+  var fun = this.factory_(true);
+  this.run_and_catch_(function() {
+    RunPauseTest(th.scope_index_ + 1, th.old_result_, th.variable_name_,
+        th.new_value_, th.new_result_, fun);
+  });
+}
+
+ClosureTestCase.prototype.run_closure_test = function() {
+  var th = this;
+  var fun = this.factory_(false);
+  this.run_and_catch_(function() {
+    RunClosureTest(th.scope_index_, th.old_result_, th.variable_name_,
+        th.new_value_, th.new_result_, fun);
+  });
+}
+
+ClosureTestCase.prototype.run_and_catch_ = function(runnable) {
+  if (this.success_expected_) {
+    runnable();
+  } else {
+    assertThrows(runnable);
+  }
+}
+
+
+// Test scopes visible from closures.
+
+var closure_test_cases = [
+  new ClosureTestCase(0, 'cat', 'v1', 5, 5, true,
+      function Factory(debug_stop) {
+    var v1 = 'cat';
+    return function() {
+      if (debug_stop) debugger;
+      return v1;
+    }
+  }),
+
+  new ClosureTestCase(0, 4, 't', 7, 9, true, function Factory(debug_stop) {
+    var t = 2;
+    var r = eval("t");
+    return function() {
+      if (debug_stop) debugger;
+      return r + t;
+    }
+  }),
+
+ new ClosureTestCase(0, 6, 't', 10, 13, true, function Factory(debug_stop) {
+    var t = 2;
+    var r = eval("t = 3");
+    return function() {
+      if (debug_stop) debugger;
+      return r + t;
+    }
+  }),
+
+  new ClosureTestCase(0, 17, 's', 'Bird', 'Bird', true,
+      function Factory(debug_stop) {
+    eval("var s = 17");
+    return function() {
+      if (debug_stop) debugger;
+      return s;
+    }
+  }),
+
+  new ClosureTestCase(2, 'capybara', 'foo', 77, 77, true,
+      function Factory(debug_stop) {
+    var foo = "capybara";
+    return (function() {
+      var bar = "fish";
+      try {
+        throw {name: "test exception"};
+      } catch (e) {
+        return function() {
+          if (debug_stop) debugger;
+          bar = "beast";
+          return foo;
+        }
+      }
+    })();
+  }),
+
+  new ClosureTestCase(0, 'AlphaBeta', 'eee', 5, '5Beta', true,
+      function Factory(debug_stop) {
+    var foo = "Beta";
+    return (function() {
+      var bar = "fish";
+      try {
+        throw "Alpha";
+      } catch (eee) {
+        return function() {
+          if (debug_stop) debugger;
+          return eee + foo;
+        }
+      }
+    })();
+  })
+];
+
+for (var i = 0; i < closure_test_cases.length; i++) {
+  closure_test_cases[i].run_pause_test();
+}
+
+for (var i = 0; i < closure_test_cases.length; i++) {
+  closure_test_cases[i].run_closure_test();
+}
+
+
+// Test local scope.
+
+RunPauseTest(0, 'HelloYou', 'u', 'We', 'HelloWe', (function Factory() {
   return function() {
+    var u = "You";
+    var v = "Hello";
     debugger;
-    return v1;
+    return v + u;
   }
 })());

-RunPauseTest(1, 'v2', 11, (function Factory(v2) {
+RunPauseTest(0, 'Helloworld', 'p', 'GoodBye', 'HelloGoodBye',
+    (function Factory() {
+  function H(p) {
+    var v = "Hello";
+    debugger;
+    return v + p;
+  }
   return function() {
-    debugger;
-    return v2;
+    return H("world");
   }
-})('dog'));
-
-RunPauseTest(3, 'foo', 77, (function Factory() {
-  var foo = "capybara";
-  return (function() {
-    var bar = "fish";
-    try {
-      throw {name: "test exception"};
-    } catch (e) {
-      return function() {
-        debugger;
-        bar = "beast";
-        return foo;
-      }
-    }
-  })();
 })());

-
-
-// Test changing variable value in closure by handle
-RunClosureTest(0, 'v1', 5, (function Factory() {
-  var v1 = 'cat';
+RunPauseTest(0, 'mouse', 'v1', 'dog', 'dog', (function Factory() {
   return function() {
+    var v1 = 'cat';
+    eval("v1 = 'mouse'");
+    debugger;
     return v1;
   }
 })());

-RunClosureTest(0, 'v2', 11, (function Factory(v2) {
+RunPauseTest(0, 'mouse', 'v1', 'dog', 'dog', (function Factory() {
   return function() {
-    return v2;
+    eval("var v1 = 'mouse'");
+    debugger;
+    return v1;
   }
-})('dog'));
-
-RunClosureTest(2, 'foo', 77, (function Factory() {
-  var foo = "capybara";
-  return (function() {
-    var bar = "fish";
-    try {
-      throw {name: "test exception"};
-    } catch (e) {
-      return function() {
-        bar = "beast";
-        return foo;
-      }
-    }
-  })();
 })());


 // Test value description protocol JSON
+
assertEquals(true, Debug.TestApi.CommandProcessorResolveValue({value: true}));

assertSame(null, Debug.TestApi.CommandProcessorResolveValue({type: "null"}));
@@ -173,4 +274,3 @@
     {handle: Debug.MakeMirror(Number).handle()}));
 assertSame(RunClosureTest, Debug.TestApi.CommandProcessorResolveValue(
     {handle: Debug.MakeMirror(RunClosureTest).handle()}));
-

--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev

Reply via email to