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