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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@11458 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
peter.rybin@gmail.com 2012-04-26 20:16:53 +00:00
parent 94e5a8dfcd
commit 569eba39f5
6 changed files with 439 additions and 45 deletions

View File

@ -1957,7 +1957,7 @@ DebugCommandProcessor.prototype.frameForScopeRequest_ = function(request) {
if (request.arguments && !IS_UNDEFINED(request.arguments.frameNumber)) { if (request.arguments && !IS_UNDEFINED(request.arguments.frameNumber)) {
frame_index = request.arguments.frameNumber; frame_index = request.arguments.frameNumber;
if (frame_index < 0 || this.exec_state_.frameCount() <= frame_index) { 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); return this.exec_state_.frame(frame_index);
} else { } else {
@ -1966,20 +1966,44 @@ DebugCommandProcessor.prototype.frameForScopeRequest_ = function(request) {
}; };
DebugCommandProcessor.prototype.scopesRequest_ = function(request, response) { // 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. // No frames no scopes.
if (this.exec_state_.frameCount() == 0) { if (this.exec_state_.frameCount() == 0) {
return response.failed('No scopes'); throw new Error('No scopes');
} }
// Get the frame for which the scopes are requested. // Get the frame for which the scopes are requested.
var frame = this.frameForScopeRequest_(request); var frame = this.frameForScopeRequest_(request);
return frame;
}
}
// Fill all scopes for this frame.
var total_scopes = frame.scopeCount(); 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 = []; var scopes = [];
for (var i = 0; i < total_scopes; i++) { for (var i = 0; i < total_scopes; i++) {
scopes.push(frame.scope(i)); scopes.push(scope_holder.scope(i));
} }
response.body = { response.body = {
fromScope: 0, fromScope: 0,
@ -1991,24 +2015,19 @@ DebugCommandProcessor.prototype.scopesRequest_ = function(request, response) {
DebugCommandProcessor.prototype.scopeRequest_ = function(request, response) { DebugCommandProcessor.prototype.scopeRequest_ = function(request, response) {
// No frames no scopes. // Get the frame or function for which the scope is requested.
if (this.exec_state_.frameCount() == 0) { var scope_holder = this.scopeHolderForScopeRequest_(request);
return response.failed('No scopes');
}
// Get the frame for which the scope is requested.
var frame = this.frameForScopeRequest_(request);
// With no scope argument just return top scope. // With no scope argument just return top scope.
var scope_index = 0; var scope_index = 0;
if (request.arguments && !IS_UNDEFINED(request.arguments.number)) { if (request.arguments && !IS_UNDEFINED(request.arguments.number)) {
scope_index = %ToNumber(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'); return response.failed('Invalid scope number');
} }
} }
response.body = frame.scope(scope_index); response.body = scope_holder.scope(scope_index);
}; };

View File

@ -913,6 +913,22 @@ FunctionMirror.prototype.constructedBy = function(opt_max_instances) {
}; };
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() { FunctionMirror.prototype.toText = function() {
return this.source(); return this.source();
}; };
@ -1589,7 +1605,7 @@ FrameMirror.prototype.scopeCount = function() {
FrameMirror.prototype.scope = function(index) { FrameMirror.prototype.scope = function(index) {
return new ScopeMirror(this, index); return new ScopeMirror(this, void 0, index);
}; };
@ -1752,39 +1768,54 @@ FrameMirror.prototype.toText = function(opt_locals) {
var kScopeDetailsTypeIndex = 0; var kScopeDetailsTypeIndex = 0;
var kScopeDetailsObjectIndex = 1; var kScopeDetailsObjectIndex = 1;
function ScopeDetails(frame, index) { function ScopeDetails(frame, fun, index) {
if (frame) {
this.break_id_ = frame.break_id_; this.break_id_ = frame.break_id_;
this.details_ = %GetScopeDetails(frame.break_id_, this.details_ = %GetScopeDetails(frame.break_id_,
frame.details_.frameId(), frame.details_.frameId(),
frame.details_.inlinedFrameIndex(), frame.details_.inlinedFrameIndex(),
index); index);
} else {
this.details_ = %GetFunctionScopeDetails(fun.value(), index);
this.break_id_ = undefined;
}
} }
ScopeDetails.prototype.type = function() { ScopeDetails.prototype.type = function() {
if (!IS_UNDEFINED(this.break_id_)) {
%CheckExecutionState(this.break_id_); %CheckExecutionState(this.break_id_);
}
return this.details_[kScopeDetailsTypeIndex]; return this.details_[kScopeDetailsTypeIndex];
}; };
ScopeDetails.prototype.object = function() { ScopeDetails.prototype.object = function() {
if (!IS_UNDEFINED(this.break_id_)) {
%CheckExecutionState(this.break_id_); %CheckExecutionState(this.break_id_);
}
return this.details_[kScopeDetailsObjectIndex]; 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 {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 * @param {number} index The scope index in the frame
* @constructor * @constructor
* @extends Mirror * @extends Mirror
*/ */
function ScopeMirror(frame, index) { function ScopeMirror(frame, function, index) {
%_CallFunction(this, SCOPE_TYPE, Mirror); %_CallFunction(this, SCOPE_TYPE, Mirror);
if (frame) {
this.frame_index_ = frame.index_; this.frame_index_ = frame.index_;
} else {
this.frame_index_ = undefined;
}
this.scope_index_ = index; this.scope_index_ = index;
this.details_ = new ScopeDetails(frame, index); this.details_ = new ScopeDetails(frame, function, index);
} }
inherits(ScopeMirror, Mirror); inherits(ScopeMirror, Mirror);
@ -2281,6 +2312,15 @@ JSONProtocolSerializer.prototype.serializeObject_ = function(mirror, content,
serializeLocationFields(mirror.sourceLocation(), content); 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. // Add date specific properties.

View File

@ -11018,10 +11018,10 @@ static Handle<JSObject> MaterializeModuleScope(
} }
// Iterate over the actual scopes visible from a stack frame. The iteration // Iterate over the actual scopes visible from a stack frame or from a closure.
// proceeds from the innermost visible nested scope outwards. All scopes are // The iteration proceeds from the innermost visible nested scope outwards.
// backed by an actual context except the local scope, which is inserted // All scopes are backed by an actual context except the local scope,
// "artificially" in the context chain. // which is inserted "artificially" in the context chain.
class ScopeIterator { class ScopeIterator {
public: public:
enum ScopeType { enum ScopeType {
@ -11122,6 +11122,18 @@ class ScopeIterator {
} }
} }
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? // More scopes?
bool Done() { return context_.is_null(); } bool Done() { return context_.is_null(); }
@ -11342,6 +11354,22 @@ static const int kScopeDetailsTypeIndex = 0;
static const int kScopeDetailsObjectIndex = 1; static const int kScopeDetailsObjectIndex = 1;
static const int kScopeDetailsSize = 2; 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 // Return an array with scope details
// args[0]: number: break id // args[0]: number: break id
// args[1]: number: frame index // args[1]: number: frame index
@ -11379,18 +11407,46 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_GetScopeDetails) {
if (it.Done()) { if (it.Done()) {
return isolate->heap()->undefined_value(); return isolate->heap()->undefined_value();
} }
return MaterializeScopeDetails(isolate, &it);
}
// Calculate the size of the result.
int details_size = kScopeDetailsSize;
Handle<FixedArray> details = isolate->factory()->NewFixedArray(details_size);
// Fill in scope details. RUNTIME_FUNCTION(MaybeObject*, Runtime_GetFunctionScopeCount) {
details->set(kScopeDetailsTypeIndex, Smi::FromInt(it.Type())); HandleScope scope(isolate);
Handle<JSObject> scope_object = it.ScopeObject(); ASSERT(args.length() == 1);
RETURN_IF_EMPTY_HANDLE(isolate, scope_object);
details->set(kScopeDetailsObjectIndex, *scope_object);
return *isolate->factory()->NewJSArrayWithElements(details); // 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);
} }

View File

@ -404,6 +404,8 @@ namespace internal {
F(GetFrameDetails, 2, 1) \ F(GetFrameDetails, 2, 1) \
F(GetScopeCount, 2, 1) \ F(GetScopeCount, 2, 1) \
F(GetScopeDetails, 4, 1) \ F(GetScopeDetails, 4, 1) \
F(GetFunctionScopeCount, 1, 1) \
F(GetFunctionScopeDetails, 2, 1) \
F(DebugPrintScopes, 0, 1) \ F(DebugPrintScopes, 0, 1) \
F(GetThreadCount, 1, 1) \ F(GetThreadCount, 1, 1) \
F(GetThreadDetails, 2, 1) \ F(GetThreadDetails, 2, 1) \

View File

@ -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"));

View File

@ -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);