Revision: 11458
Author:   [email protected]
Date:     Thu Apr 26 13:16:53 2012
Log: Issue 2081: Expose function's (closure's) inner context in debugger.

This is against the correct branch (bleeding_edge).

Review URL: https://chromiumcodereview.appspot.com/10171003
http://code.google.com/p/v8/source/detail?r=11458

Added:
 /branches/bleeding_edge/test/mjsunit/debug-function-scopes.js
 /branches/bleeding_edge/test/mjsunit/harmony/debug-function-scopes.js
Modified:
 /branches/bleeding_edge/src/debug-debugger.js
 /branches/bleeding_edge/src/mirror-debugger.js
 /branches/bleeding_edge/src/runtime.cc
 /branches/bleeding_edge/src/runtime.h

=======================================
--- /dev/null
+++ /branches/bleeding_edge/test/mjsunit/debug-function-scopes.js Thu Apr 26 13:16:53 2012
@@ -0,0 +1,162 @@
+// Copyright 2012 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Flags: --expose-debug-as debug
+
+// Get the Debug object exposed from the debug context global object.
+var Debug = debug.Debug;
+
+function CheckScope(scope_mirror, scope_expectations, expected_scope_type) {
+  assertEquals(expected_scope_type, scope_mirror.scopeType());
+
+  var scope_object = scope_mirror.scopeObject().value();
+
+  for (var name in scope_expectations) {
+    var actual = scope_object[name];
+    var expected = scope_expectations[name];
+    assertEquals(expected, actual);
+  }
+}
+
+// A copy of the scope types from mirror-debugger.js.
+var ScopeType = { Global: 0,
+                  Local: 1,
+                  With: 2,
+                  Closure: 3,
+                  Catch: 4,
+                  Block: 5 };
+
+var f1 = (function F1(x) {
+  function F2(y) {
+    var z = x + y;
+    with ({w: 5, v: "Capybara"}) {
+      var F3 = function(a, b) {
+        function F4(p) {
+          return p + a + b + z + w + v.length;
+        }
+        return F4;
+      }
+      return F3(4, 5);
+    }
+  }
+  return F2(17);
+})(5);
+
+var mirror = Debug.MakeMirror(f1);
+
+assertEquals(5, mirror.scopeCount());
+
+CheckScope(mirror.scope(0), { a: 4, b: 5 }, ScopeType.Closure);
+CheckScope(mirror.scope(1), { w: 5, v: "Capybara" }, ScopeType.With);
+CheckScope(mirror.scope(2), { y: 17, z: 22 }, ScopeType.Closure);
+CheckScope(mirror.scope(3), { x: 5 }, ScopeType.Closure);
+CheckScope(mirror.scope(4), {}, ScopeType.Global);
+
+var f2 = function() { return 5; }
+
+var mirror = Debug.MakeMirror(f2);
+
+assertEquals(1, mirror.scopeCount());
+
+CheckScope(mirror.scope(0), {}, ScopeType.Global);
+
+var f3 = (function F1(invisible_parameter) {
+  var invisible1 = 1;
+  var visible1 = 10;
+  return (function F2() {
+    var invisible2 = 2;
+    return (function F3() {
+      var visible2 = 20;
+      var invisible2 = 3;
+      return (function () {return visible1 + visible2 + visible1a;});
+    })();
+  })();
+})(5);
+
+var mirror = Debug.MakeMirror(f3);
+
+assertEquals(3, mirror.scopeCount());
+
+CheckScope(mirror.scope(0), { visible2: 20 }, ScopeType.Closure);
+CheckScope(mirror.scope(1), { visible1: 10 }, ScopeType.Closure);
+CheckScope(mirror.scope(2), {}, ScopeType.Global);
+
+
+var f4 = (function One() {
+  try {
+    throw "I'm error 1";
+  } catch (e1) {
+    try {
+      throw "I'm error 2";
+    } catch (e2) {
+      return function GetError() {
+        return e1 + e2;
+      };
+    }
+  }
+})();
+
+var mirror = Debug.MakeMirror(f4);
+
+assertEquals(3, mirror.scopeCount());
+
+CheckScope(mirror.scope(0), { e2: "I'm error 2" }, ScopeType.Catch);
+CheckScope(mirror.scope(1), { e1: "I'm error 1" }, ScopeType.Catch);
+CheckScope(mirror.scope(2), {}, ScopeType.Global);
+
+
+var f5 = (function Raz(p1, p2) {
+  var p3 = p1 + p2;
+  return (function() {
+    var p4 = 20;
+    var p5 = 21;
+    var p6 = 22;
+    return eval("(function(p7){return p1 + p4 + p6 + p7})");
+  })();
+})(1,2);
+
+var mirror = Debug.MakeMirror(f5);
+
+assertEquals(3, mirror.scopeCount());
+
+CheckScope(mirror.scope(0), { p4: 20, p6: 22 }, ScopeType.Closure);
+CheckScope(mirror.scope(1), { p1: 1 }, ScopeType.Closure);
+CheckScope(mirror.scope(2), {}, ScopeType.Global);
+
+
+function CheckNoScopeVisible(f) {
+  var mirror = Debug.MakeMirror(f);
+  assertEquals(0, mirror.scopeCount());
+}
+
+CheckNoScopeVisible(Number);
+
+CheckNoScopeVisible(Function.toString);
+
+// This getter is known to be implemented as closure.
+CheckNoScopeVisible(new Error().__lookupGetter__("stack"));
+
=======================================
--- /dev/null
+++ /branches/bleeding_edge/test/mjsunit/harmony/debug-function-scopes.js Thu Apr 26 13:16:53 2012
@@ -0,0 +1,115 @@
+// Copyright 2012 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Flags: --expose-debug-as debug --harmony-scoping
+
+"use strict";
+
+// Get the Debug object exposed from the debug context global object.
+var Debug = debug.Debug;
+
+function CheckScope(scope_mirror, scope_expectations, expected_scope_type) {
+  assertEquals(expected_scope_type, scope_mirror.scopeType());
+
+  var scope_object = scope_mirror.scopeObject().value();
+
+  for (let name in scope_expectations) {
+    let actual = scope_object[name];
+    let expected = scope_expectations[name];
+    assertEquals(expected, actual);
+  }
+}
+
+// A copy of the scope types from mirror-debugger.js.
+var ScopeType = { Global: 0,
+                  Local: 1,
+                  With: 2,
+                  Closure: 3,
+                  Catch: 4,
+                  Block: 5 };
+
+var f1 = (function F1(x) {
+  function F2(y) {
+    var z = x + y;
+    {
+      var w =  5;
+      var v = "Capybara";
+      var F3 = function(a, b) {
+        function F4(p) {
+          return p + a + b + z + w + v.length;
+        }
+        return F4;
+      }
+      return F3(4, 5);
+    }
+  }
+  return F2(17);
+})(5);
+
+var mirror = Debug.MakeMirror(f1);
+
+assertEquals(4, mirror.scopeCount());
+
+CheckScope(mirror.scope(0), { a: 4, b: 5 }, ScopeType.Closure);
+CheckScope(mirror.scope(1), { z: 22, w: 5, v: "Capybara" }, ScopeType.Closure);
+CheckScope(mirror.scope(2), { x: 5 }, ScopeType.Closure);
+CheckScope(mirror.scope(3), {}, ScopeType.Global);
+
+var f2 = (function() {
+  var v1 = 3;
+  var v2 = 4;
+  let l0 = 0;
+  {
+    var v3 = 5;
+    let l1 = 6;
+    let l2 = 7;
+    {
+      var v4 = 8;
+      let l3 = 9;
+      {
+        var v5 = "Cat";
+        let l4 = 11;
+        var v6 = l4;
+        return function() {
+          return l0 + v1 + v3 + l2 + l3 + v6;
+        };
+      }
+    }
+  }
+})();
+
+var mirror = Debug.MakeMirror(f2);
+
+assertEquals(5, mirror.scopeCount());
+
+// Implementation artifact: l4 isn't used in closure, but still it is saved.
+CheckScope(mirror.scope(0), { l4: 11 }, ScopeType.Block);
+
+CheckScope(mirror.scope(1), { l3: 9 }, ScopeType.Block);
+CheckScope(mirror.scope(2), { l1: 6, l2: 7 }, ScopeType.Block);
+CheckScope(mirror.scope(3), { v1:3, l0: 0, v3: 5, v6: 11 }, ScopeType.Closure);
+CheckScope(mirror.scope(4), {}, ScopeType.Global);
=======================================
--- /branches/bleeding_edge/src/debug-debugger.js       Mon Feb 27 03:52:08 2012
+++ /branches/bleeding_edge/src/debug-debugger.js       Thu Apr 26 13:16:53 2012
@@ -1957,7 +1957,7 @@
   if (request.arguments && !IS_UNDEFINED(request.arguments.frameNumber)) {
     frame_index = request.arguments.frameNumber;
     if (frame_index < 0 || this.exec_state_.frameCount() <= frame_index) {
-      return response.failed('Invalid frame number');
+      throw new Error('Invalid frame number');
     }
     return this.exec_state_.frame(frame_index);
   } else {
@@ -1966,20 +1966,44 @@
 };


-DebugCommandProcessor.prototype.scopesRequest_ = function(request, response) {
-  // No frames no scopes.
-  if (this.exec_state_.frameCount() == 0) {
-    return response.failed('No scopes');
-  }
-
-  // Get the frame for which the scopes are requested.
-  var frame = this.frameForScopeRequest_(request);
-
-  // Fill all scopes for this frame.
-  var total_scopes = frame.scopeCount();
+// 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.scopeHolderForScopeRequest_ =
+    function(request) {
+  if (request.arguments && "functionHandle" in request.arguments) {
+    if (!IS_NUMBER(request.arguments.functionHandle)) {
+      throw new Error('Function handle must be a number');
+    }
+    var function_mirror = LookupMirror(request.arguments.functionHandle);
+    if (!function_mirror) {
+      throw new Error('Failed to find function object by handle');
+    }
+    if (!function_mirror.isFunction()) {
+      throw new Error('Value of non-function type is found by handle');
+    }
+    return function_mirror;
+  } else {
+    // No frames no scopes.
+    if (this.exec_state_.frameCount() == 0) {
+      throw new Error('No scopes');
+    }
+
+    // Get the frame for which the scopes are requested.
+    var frame = this.frameForScopeRequest_(request);
+    return frame;
+  }
+}
+
+
+DebugCommandProcessor.prototype.scopesRequest_ = function(request, response) {
+  var scope_holder = this.scopeHolderForScopeRequest_(request);
+
+  // 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(frame.scope(i));
+    scopes.push(scope_holder.scope(i));
   }
   response.body = {
     fromScope: 0,
@@ -1991,24 +2015,19 @@


DebugCommandProcessor.prototype.scopeRequest_ = function(request, response) {
-  // No frames no scopes.
-  if (this.exec_state_.frameCount() == 0) {
-    return response.failed('No scopes');
-  }
-
-  // Get the frame for which the scope is requested.
-  var frame = this.frameForScopeRequest_(request);
+  // Get the frame or function for which the scope is requested.
+  var scope_holder = this.scopeHolderForScopeRequest_(request);

   // With no scope argument just return top scope.
   var scope_index = 0;
   if (request.arguments && !IS_UNDEFINED(request.arguments.number)) {
     scope_index = %ToNumber(request.arguments.number);
-    if (scope_index < 0 || frame.scopeCount() <= scope_index) {
+    if (scope_index < 0 || scope_holder.scopeCount() <= scope_index) {
       return response.failed('Invalid scope number');
     }
   }

-  response.body = frame.scope(scope_index);
+  response.body = scope_holder.scope(scope_index);
 };


=======================================
--- /branches/bleeding_edge/src/mirror-debugger.js      Fri Apr 20 10:08:01 2012
+++ /branches/bleeding_edge/src/mirror-debugger.js      Thu Apr 26 13:16:53 2012
@@ -913,6 +913,22 @@
 };


+FunctionMirror.prototype.scopeCount = function() {
+  if (this.resolved()) {
+    return %GetFunctionScopeCount(this.value());
+  } else {
+    return 0;
+  }
+};
+
+
+FunctionMirror.prototype.scope = function(index) {
+  if (this.resolved()) {
+    return new ScopeMirror(void 0, this, index);
+  }
+};
+
+
 FunctionMirror.prototype.toText = function() {
   return this.source();
 };
@@ -1589,7 +1605,7 @@


 FrameMirror.prototype.scope = function(index) {
-  return new ScopeMirror(this, index);
+  return new ScopeMirror(this, void 0, index);
 };


@@ -1752,39 +1768,54 @@
 var kScopeDetailsTypeIndex = 0;
 var kScopeDetailsObjectIndex = 1;

-function ScopeDetails(frame, index) {
-  this.break_id_ = frame.break_id_;
-  this.details_ = %GetScopeDetails(frame.break_id_,
-                                   frame.details_.frameId(),
-                                   frame.details_.inlinedFrameIndex(),
-                                   index);
+function ScopeDetails(frame, fun, index) {
+  if (frame) {
+    this.break_id_ = frame.break_id_;
+    this.details_ = %GetScopeDetails(frame.break_id_,
+                                     frame.details_.frameId(),
+                                     frame.details_.inlinedFrameIndex(),
+                                     index);
+  } else {
+    this.details_ = %GetFunctionScopeDetails(fun.value(), index);
+    this.break_id_ = undefined;
+  }
 }


 ScopeDetails.prototype.type = function() {
-  %CheckExecutionState(this.break_id_);
+  if (!IS_UNDEFINED(this.break_id_)) {
+    %CheckExecutionState(this.break_id_);
+  }
   return this.details_[kScopeDetailsTypeIndex];
 };


 ScopeDetails.prototype.object = function() {
-  %CheckExecutionState(this.break_id_);
+  if (!IS_UNDEFINED(this.break_id_)) {
+    %CheckExecutionState(this.break_id_);
+  }
   return this.details_[kScopeDetailsObjectIndex];
 };


 /**
- * Mirror object for scope.
+ * Mirror object for scope of frame or function. Either frame or function must
+ * be specified.
  * @param {FrameMirror} frame The frame this scope is a part of
+ * @param {FunctionMirror} function The function this scope is a part of
  * @param {number} index The scope index in the frame
  * @constructor
  * @extends Mirror
  */
-function ScopeMirror(frame, index) {
+function ScopeMirror(frame, function, index) {
   %_CallFunction(this, SCOPE_TYPE, Mirror);
-  this.frame_index_ = frame.index_;
+  if (frame) {
+    this.frame_index_ = frame.index_;
+  } else {
+    this.frame_index_ = undefined;
+  }
   this.scope_index_ = index;
-  this.details_ = new ScopeDetails(frame, index);
+  this.details_ = new ScopeDetails(frame, function, index);
 }
 inherits(ScopeMirror, Mirror);

@@ -2281,6 +2312,15 @@

       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
+      });
+    }
   }

   // Add date specific properties.
=======================================
--- /branches/bleeding_edge/src/runtime.cc      Thu Apr 26 06:44:18 2012
+++ /branches/bleeding_edge/src/runtime.cc      Thu Apr 26 13:16:53 2012
@@ -11018,10 +11018,10 @@
 }


-// Iterate over the actual scopes visible from a stack frame. The iteration
-// proceeds from the innermost visible nested scope outwards. All scopes are
-// backed by an actual context except the local scope, which is inserted
-// "artificially" in the context chain.
+// Iterate over the actual scopes visible from a stack frame or from a closure.
+// The iteration proceeds from the innermost visible nested scope outwards.
+// All scopes are backed by an actual context except the local scope,
+// which is inserted "artificially" in the context chain.
 class ScopeIterator {
  public:
   enum ScopeType {
@@ -11121,6 +11121,18 @@
       }
     }
   }
+
+  ScopeIterator(Isolate* isolate,
+                Handle<JSFunction> function)
+    : isolate_(isolate),
+      frame_(NULL),
+      inlined_jsframe_index_(0),
+      function_(function),
+      context_(function->context()) {
+    if (function->IsBuiltin()) {
+      context_ = Handle<Context>();
+    }
+  }

   // More scopes?
   bool Done() { return context_.is_null(); }
@@ -11341,6 +11353,22 @@
 static const int kScopeDetailsTypeIndex = 0;
 static const int kScopeDetailsObjectIndex = 1;
 static const int kScopeDetailsSize = 2;
+
+
+static MaybeObject* MaterializeScopeDetails(Isolate* isolate,
+    ScopeIterator* it) {
+  // Calculate the size of the result.
+  int details_size = kScopeDetailsSize;
+ Handle<FixedArray> details = isolate->factory()->NewFixedArray(details_size);
+
+  // Fill in scope details.
+  details->set(kScopeDetailsTypeIndex, Smi::FromInt(it->Type()));
+  Handle<JSObject> scope_object = it->ScopeObject();
+  RETURN_IF_EMPTY_HANDLE(isolate, scope_object);
+  details->set(kScopeDetailsObjectIndex, *scope_object);
+
+  return *isolate->factory()->NewJSArrayWithElements(details);
+}

 // Return an array with scope details
 // args[0]: number: break id
@@ -11379,18 +11407,46 @@
   if (it.Done()) {
     return isolate->heap()->undefined_value();
   }
-
-  // Calculate the size of the result.
-  int details_size = kScopeDetailsSize;
- Handle<FixedArray> details = isolate->factory()->NewFixedArray(details_size);
-
-  // Fill in scope details.
-  details->set(kScopeDetailsTypeIndex, Smi::FromInt(it.Type()));
-  Handle<JSObject> scope_object = it.ScopeObject();
-  RETURN_IF_EMPTY_HANDLE(isolate, scope_object);
-  details->set(kScopeDetailsObjectIndex, *scope_object);
-
-  return *isolate->factory()->NewJSArrayWithElements(details);
+  return MaterializeScopeDetails(isolate, &it);
+}
+
+
+RUNTIME_FUNCTION(MaybeObject*, Runtime_GetFunctionScopeCount) {
+  HandleScope scope(isolate);
+  ASSERT(args.length() == 1);
+
+  // Check arguments.
+  CONVERT_ARG_HANDLE_CHECKED(JSFunction, fun, 0);
+
+  // Count the visible scopes.
+  int n = 0;
+  for (ScopeIterator it(isolate, fun); !it.Done(); it.Next()) {
+    n++;
+  }
+
+  return Smi::FromInt(n);
+}
+
+
+RUNTIME_FUNCTION(MaybeObject*, Runtime_GetFunctionScopeDetails) {
+  HandleScope scope(isolate);
+  ASSERT(args.length() == 2);
+
+  // Check arguments.
+  CONVERT_ARG_HANDLE_CHECKED(JSFunction, fun, 0);
+  CONVERT_NUMBER_CHECKED(int, index, Int32, args[1]);
+
+  // Find the requested scope.
+  int n = 0;
+  ScopeIterator it(isolate, fun);
+  for (; !it.Done() && n < index; it.Next()) {
+    n++;
+  }
+  if (it.Done()) {
+    return isolate->heap()->undefined_value();
+  }
+
+  return MaterializeScopeDetails(isolate, &it);
 }


=======================================
--- /branches/bleeding_edge/src/runtime.h       Fri Apr 20 07:12:49 2012
+++ /branches/bleeding_edge/src/runtime.h       Thu Apr 26 13:16:53 2012
@@ -404,6 +404,8 @@
   F(GetFrameDetails, 2, 1) \
   F(GetScopeCount, 2, 1) \
   F(GetScopeDetails, 4, 1) \
+  F(GetFunctionScopeCount, 1, 1) \
+  F(GetFunctionScopeDetails, 2, 1) \
   F(DebugPrintScopes, 0, 1) \
   F(GetThreadCount, 1, 1) \
   F(GetThreadDetails, 2, 1) \

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

Reply via email to