Commit 88261439 authored by sgjesse@chromium.org's avatar sgjesse@chromium.org

Add scope chain information to the debugger.

For each frame it is now possible to request information on the scope chain. Each scope in the chain can have one of the types local, global, with and closure. For scopes of type global and with the mirror for the actual global or with object is available. For scopes of type local and closure a plain JavaScript object with the materialized content of the scope is created and its mirror is returned. Depending on the level of possible optimization the content of the materialized local and closure scopes might only contain the names which are actually used.

To iterate the scope chain an iterator ScopeIterator have been added which can provide the type of each scope for each part of the chain. This iterator creates an artificial local scope whenever that is present as the context chain does not include the local scope.

To avoid caching the mirror objects for the materialized the local and closure scopes transient mirrors have been added. They have negative handles and cannot be retrieved by subsequent lookup calls. Their content is part of a single response.

For debugging purposes an additional runtime function DebugPrintScopes is been added.

Added commands 'scopes' and 'scope' to the developer shell and fixed the dir command.

BUG=none
TEST=test/mjsunit/debug-scopes.js
Review URL: http://codereview.chromium.org/123021

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@2149 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent d62f77fd
......@@ -98,6 +98,13 @@ Debug.ScriptCompilationType = { Host: 0,
JSON: 2 };
// The different types of scopes matching constants runtime.cc.
Debug.ScopeType = { Global: 0,
Local: 1,
With: 2,
Closure: 3 };
// Current debug state.
const kNoFrame = -1;
Debug.State = {
......@@ -295,6 +302,14 @@ function DebugRequest(cmd_line) {
this.request_ = this.frameCommandToJSONRequest_(args);
break;
case 'scopes':
this.request_ = this.scopesCommandToJSONRequest_(args);
break;
case 'scope':
this.request_ = this.scopeCommandToJSONRequest_(args);
break;
case 'print':
case 'p':
this.request_ = this.printCommandToJSONRequest_(args);
......@@ -394,13 +409,17 @@ DebugRequest.prototype.createRequest = function(command) {
// Create a JSON request for the evaluation command.
DebugRequest.prototype.makeEvaluateJSONRequest_ = function(expression) {
// Global varaible used to store whether a handle was requested.
lookup_handle = null;
// Check if the expression is a handle id in the form #<handle>#.
var handle_match = expression.match(/^#([0-9]*)#$/);
if (handle_match) {
// Remember the handle requested in a global variable.
lookup_handle = parseInt(handle_match[1]);
// Build a lookup request.
var request = this.createRequest('lookup');
request.arguments = {};
request.arguments.handle = parseInt(handle_match[1]);
request.arguments.handles = [ lookup_handle ];
return request.toJSONProtocol();
} else {
// Build an evaluate request.
......@@ -559,6 +578,27 @@ DebugRequest.prototype.frameCommandToJSONRequest_ = function(args) {
};
// Create a JSON request for the scopes command.
DebugRequest.prototype.scopesCommandToJSONRequest_ = function(args) {
// Build a scopes request from the text command.
var request = this.createRequest('scopes');
return request.toJSONProtocol();
};
// Create a JSON request for the scope command.
DebugRequest.prototype.scopeCommandToJSONRequest_ = function(args) {
// Build a scope request from the text command.
var request = this.createRequest('scope');
args = args.split(/\s*[ ]+\s*/g);
if (args.length > 0 && args[0].length > 0) {
request.arguments = {};
request.arguments.number = args[0];
}
return request.toJSONProtocol();
};
// Create a JSON request for the print command.
DebugRequest.prototype.printCommandToJSONRequest_ = function(args) {
// Build an evaluate request from the text command.
......@@ -783,8 +823,11 @@ DebugRequest.prototype.helpCommand_ = function(args) {
print('clear <breakpoint #>');
print('backtrace [n] | [-n] | [from to]');
print('frame <frame #>');
print('scopes');
print('scope <scope #>');
print('step [in | next | out| min [step count]]');
print('print <expression>');
print('dir <expression>');
print('source [from line [num lines]]');
print('scripts');
print('continue');
......@@ -794,7 +837,11 @@ DebugRequest.prototype.helpCommand_ = function(args) {
function formatHandleReference_(value) {
return '#' + value.handle() + '#';
if (value.handle() >= 0) {
return '#' + value.handle() + '#';
} else {
return '#Transient#';
}
}
......@@ -818,10 +865,14 @@ function formatObject_(value, include_properties) {
result += value.propertyName(i);
result += ': ';
var property_value = value.propertyValue(i);
if (property_value && property_value.type()) {
result += property_value.type();
} else {
if (property_value instanceof ProtocolReference) {
result += '<no type>';
} else {
if (property_value && property_value.type()) {
result += property_value.type();
} else {
result += '<no type>';
}
}
result += ' ';
result += formatHandleReference_(property_value);
......@@ -832,6 +883,33 @@ function formatObject_(value, include_properties) {
}
function formatScope_(scope) {
var result = '';
var index = scope.index;
result += '#' + (index <= 9 ? '0' : '') + index;
result += ' ';
switch (scope.type) {
case Debug.ScopeType.Global:
result += 'Global, ';
result += '#' + scope.object.ref + '#';
break;
case Debug.ScopeType.Local:
result += 'Local';
break;
case Debug.ScopeType.With:
result += 'With, ';
result += '#' + scope.object.ref + '#';
break;
case Debug.ScopeType.Closure:
result += 'Closure';
break;
default:
result += 'UNKNOWN';
}
return result;
}
// Convert a JSON response to text for display in a text based debugger.
function DebugResponseDetails(response) {
details = {text:'', running:false}
......@@ -881,12 +959,41 @@ function DebugResponseDetails(response) {
Debug.State.currentFrame = body.index;
break;
case 'scopes':
if (body.totalScopes == 0) {
result = '(no scopes)';
} else {
result = 'Scopes #' + body.fromScope + ' to #' +
(body.toScope - 1) + ' of ' + body.totalScopes + '\n';
for (i = 0; i < body.scopes.length; i++) {
if (i != 0) {
result += '\n';
}
result += formatScope_(body.scopes[i]);
}
}
details.text = result;
break;
case 'scope':
result += formatScope_(body);
result += '\n';
var scope_object_value = response.lookup(body.object.ref);
result += formatObject_(scope_object_value, true);
details.text = result;
break;
case 'evaluate':
case 'lookup':
if (last_cmd == 'p' || last_cmd == 'print') {
result = body.text;
} else {
var value = response.bodyValue();
var value;
if (lookup_handle) {
value = response.bodyValue(lookup_handle);
} else {
value = response.bodyValue();
}
if (value.isObject()) {
result += formatObject_(value, true);
} else {
......@@ -1103,7 +1210,7 @@ ProtocolPackage.prototype.body = function() {
ProtocolPackage.prototype.bodyValue = function(index) {
if (index) {
if (index != null) {
return new ProtocolValue(this.packet_.body[index], this);
} else {
return new ProtocolValue(this.packet_.body, this);
......
......@@ -1208,6 +1208,10 @@ DebugCommandProcessor.prototype.processDebugJSONRequest = function(json_request)
this.backtraceRequest_(request, response);
} else if (request.command == 'frame') {
this.frameRequest_(request, response);
} else if (request.command == 'scopes') {
this.scopesRequest_(request, response);
} else if (request.command == 'scope') {
this.scopeRequest_(request, response);
} else if (request.command == 'evaluate') {
this.evaluateRequest_(request, response);
} else if (request.command == 'lookup') {
......@@ -1540,7 +1544,7 @@ DebugCommandProcessor.prototype.frameRequest_ = function(request, response) {
// With no arguments just keep the selected frame.
if (request.arguments) {
index = request.arguments.number;
var index = request.arguments.number;
if (index < 0 || this.exec_state_.frameCount() <= index) {
return response.failed('Invalid frame number');
}
......@@ -1551,6 +1555,67 @@ DebugCommandProcessor.prototype.frameRequest_ = function(request, response) {
};
DebugCommandProcessor.prototype.frameForScopeRequest_ = function(request) {
// Get the frame for which the scope or scopes are requested. With no frameNumber
// argument use the currently selected frame.
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');
}
return this.exec_state_.frame(frame_index);
} else {
return this.exec_state_.frame();
}
}
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();
var scopes = [];
for (var i = 0; i < total_scopes; i++) {
scopes.push(frame.scope(i));
}
response.body = {
fromScope: 0,
toScope: total_scopes,
totalScopes: total_scopes,
scopes: scopes
}
};
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);
// 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) {
return response.failed('Invalid scope number');
}
}
response.body = frame.scope(scope_index);
};
DebugCommandProcessor.prototype.evaluateRequest_ = function(request, response) {
if (!request.arguments) {
return response.failed('Missing arguments');
......
......@@ -34,9 +34,14 @@ RegExp;
Date;
// Handle id counters.
var next_handle_ = 0;
var next_transient_handle_ = -1;
// Mirror cache.
var mirror_cache_ = [];
/**
* Clear the mirror handle cache.
*/
......@@ -50,19 +55,25 @@ function ClearMirrorCache() {
* Returns the mirror for a specified value or object.
*
* @param {value or Object} value the value or object to retreive the mirror for
* @param {boolean} transient indicate whether this object is transient and
* should not be added to the mirror cache. The default is not transient.
* @returns {Mirror} the mirror reflects the passed value or object
*/
function MakeMirror(value) {
function MakeMirror(value, opt_transient) {
var mirror;
for (id in mirror_cache_) {
mirror = mirror_cache_[id];
if (mirror.value() === value) {
return mirror;
}
// Special check for NaN as NaN == NaN is false.
if (mirror.isNumber() && isNaN(mirror.value()) &&
typeof value == 'number' && isNaN(value)) {
return mirror;
// Look for non transient mirrors in the mirror cache.
if (!opt_transient) {
for (id in mirror_cache_) {
mirror = mirror_cache_[id];
if (mirror.value() === value) {
return mirror;
}
// Special check for NaN as NaN == NaN is false.
if (mirror.isNumber() && isNaN(mirror.value()) &&
typeof value == 'number' && isNaN(value)) {
return mirror;
}
}
}
......@@ -89,7 +100,7 @@ function MakeMirror(value) {
} else if (IS_SCRIPT(value)) {
mirror = new ScriptMirror(value);
} else {
mirror = new ObjectMirror(value);
mirror = new ObjectMirror(value, OBJECT_TYPE, opt_transient);
}
mirror_cache_[mirror.handle()] = mirror;
......@@ -155,6 +166,7 @@ const PROPERTY_TYPE = 'property';
const FRAME_TYPE = 'frame';
const SCRIPT_TYPE = 'script';
const CONTEXT_TYPE = 'context';
const SCOPE_TYPE = 'scope';
// Maximum length when sending strings through the JSON protocol.
const kMaxProtocolStringLength = 80;
......@@ -185,6 +197,13 @@ PropertyAttribute.DontEnum = DONT_ENUM;
PropertyAttribute.DontDelete = DONT_DELETE;
// A copy of the scope types from runtime.cc.
ScopeType = { Global: 0,
Local: 1,
With: 2,
Closure: 3 };
// Mirror hierarchy:
// - Mirror
// - ValueMirror
......@@ -372,6 +391,15 @@ Mirror.prototype.isContext = function() {
}
/**
* Check whether the mirror reflects a scope.
* @returns {boolean} True if the mirror reflects a scope
*/
Mirror.prototype.isScope = function() {
return this instanceof ScopeMirror;
}
/**
* Allocate a handle id for this object.
*/
......@@ -380,6 +408,15 @@ Mirror.prototype.allocateHandle_ = function() {
}
/**
* Allocate a transient handle id for this object. Transient handles are
* negative.
*/
Mirror.prototype.allocateTransientHandle_ = function() {
this.handle_ = next_transient_handle_--;
}
Mirror.prototype.toText = function() {
// Simpel to text which is used when on specialization in subclass.
return "#<" + builtins.GetInstanceName(this.constructor.name) + ">";
......@@ -390,13 +427,19 @@ Mirror.prototype.toText = function() {
* Base class for all value mirror objects.
* @param {string} type The type of the mirror
* @param {value} value The value reflected by this mirror
* @param {boolean} transient indicate whether this object is transient with a
* transient handle
* @constructor
* @extends Mirror
*/
function ValueMirror(type, value) {
function ValueMirror(type, value, transient) {
Mirror.call(this, type);
this.value_ = value;
this.allocateHandle_();
if (!transient) {
this.allocateHandle_();
} else {
this.allocateTransientHandle_();
}
}
inherits(ValueMirror, Mirror);
......@@ -525,11 +568,13 @@ StringMirror.prototype.toText = function() {
/**
* Mirror object for objects.
* @param {object} value The object reflected by this mirror
* @param {boolean} transient indicate whether this object is transient with a
* transient handle
* @constructor
* @extends ValueMirror
*/
function ObjectMirror(value, type) {
ValueMirror.call(this, type || OBJECT_TYPE, value);
function ObjectMirror(value, type, transient) {
ValueMirror.call(this, type || OBJECT_TYPE, value, transient);
}
inherits(ObjectMirror, ValueMirror);
......@@ -1080,7 +1125,7 @@ PropertyMirror.prototype.isIndexed = function() {
PropertyMirror.prototype.value = function() {
return MakeMirror(this.value_);
return MakeMirror(this.value_, false);
}
......@@ -1135,7 +1180,7 @@ PropertyMirror.prototype.getter = function() {
if (this.hasGetter()) {
return MakeMirror(this.getter_);
} else {
return new UndefinedMirror();
return GetUndefinedMirror();
}
}
......@@ -1149,7 +1194,7 @@ PropertyMirror.prototype.setter = function() {
if (this.hasSetter()) {
return MakeMirror(this.setter_);
} else {
return new UndefinedMirror();
return GetUndefinedMirror();
}
}
......@@ -1294,6 +1339,11 @@ FrameDetails.prototype.localValue = function(index) {
}
FrameDetails.prototype.scopeCount = function() {
return %GetScopeCount(this.break_id_, this.frameId());
}
/**
* Mirror object for stack frames.
* @param {number} break_id The break id in the VM for which this frame is
......@@ -1419,6 +1469,16 @@ FrameMirror.prototype.sourceLineText = function() {
};
FrameMirror.prototype.scopeCount = function() {
return this.details_.scopeCount();
};
FrameMirror.prototype.scope = function(index) {
return new ScopeMirror(this, index);
};
FrameMirror.prototype.evaluate = function(source, disable_break) {
var result = %DebugEvaluate(this.break_id_, this.details_.frameId(),
source, Boolean(disable_break));
......@@ -1562,6 +1622,70 @@ FrameMirror.prototype.toText = function(opt_locals) {
}
const kScopeDetailsTypeIndex = 0;
const kScopeDetailsObjectIndex = 1;
function ScopeDetails(frame, index) {
this.break_id_ = frame.break_id_;
this.details_ = %GetScopeDetails(frame.break_id_,
frame.details_.frameId(),
index);
}
ScopeDetails.prototype.type = function() {
%CheckExecutionState(this.break_id_);
return this.details_[kScopeDetailsTypeIndex];
}
ScopeDetails.prototype.object = function() {
%CheckExecutionState(this.break_id_);
return this.details_[kScopeDetailsObjectIndex];
}
/**
* Mirror object for scope.
* @param {FrameMirror} frame The frame this scope is a part of
* @param {number} index The scope index in the frame
* @constructor
* @extends Mirror
*/
function ScopeMirror(frame, index) {
Mirror.call(this, SCOPE_TYPE);
this.frame_index_ = frame.index_;
this.scope_index_ = index;
this.details_ = new ScopeDetails(frame, index);
}
inherits(ScopeMirror, Mirror);
ScopeMirror.prototype.frameIndex = function() {
return this.frame_index_;
};
ScopeMirror.prototype.scopeIndex = function() {
return this.scope_index_;
};
ScopeMirror.prototype.scopeType = function() {
return this.details_.type();
};
ScopeMirror.prototype.scopeObject = function() {
// For local and closure scopes create a transient mirror as these objects are
// created on the fly materializing the local or closure scopes and
// therefore will not preserve identity.
var transient = this.scopeType() == ScopeType.Local ||
this.scopeType() == ScopeType.Closure;
return MakeMirror(this.details_.object(), transient);
};
/**
* Mirror object for script source.
* @param {Script} script The script object
......@@ -1829,6 +1953,7 @@ JSONProtocolSerializer.prototype.serializeReferenceWithDisplayData_ =
return o;
};
JSONProtocolSerializer.prototype.serialize_ = function(mirror, reference,
details) {
// If serializing a reference to a mirror just return the reference and add
......@@ -1900,6 +2025,11 @@ JSONProtocolSerializer.prototype.serialize_ = function(mirror, reference,
this.serializeFrame_(mirror, content);
break;
case SCOPE_TYPE:
// Add object representation.
this.serializeScope_(mirror, content);
break;
case SCRIPT_TYPE:
// Script is represented by id, name and source attributes.
if (mirror.name()) {
......@@ -2102,6 +2232,14 @@ JSONProtocolSerializer.prototype.serializeFrame_ = function(mirror, content) {
}
JSONProtocolSerializer.prototype.serializeScope_ = function(mirror, content) {
content.index = mirror.scopeIndex();
content.frameIndex = mirror.frameIndex();
content.type = mirror.scopeType();
content.object = this.serializeReference(mirror.scopeObject());
}
/**
* Convert a number to a protocol value. For all finite numbers the number
* itself is returned. For non finite numbers NaN, Infinite and
......
This diff is collapsed.
......@@ -288,6 +288,9 @@ namespace internal {
F(CheckExecutionState, 1) \
F(GetFrameCount, 1) \
F(GetFrameDetails, 2) \
F(GetScopeCount, 2) \
F(GetScopeDetails, 3) \
F(DebugPrintScopes, 0) \
F(GetCFrames, 1) \
F(GetThreadCount, 1) \
F(GetThreadDetails, 2) \
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment