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 = {
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);
// 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.
};
+// 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.
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');
function formatHandleReference_(value) {
- return '#' + value.handle() + '#';
+ if (value.handle() >= 0) {
+ return '#' + value.handle() + '#';
+ } else {
+ return '#Transient#';
+ }
}
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);
}
+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}
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 {
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);
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') {
// 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');
}
};
+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');
Date;
+// Handle id counters.
var next_handle_ = 0;
+var next_transient_handle_ = -1;
+
+// Mirror cache.
var mirror_cache_ = [];
+
/**
* Clear the mirror handle cache.
*/
* 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;
+ }
}
}
} 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;
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;
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
/**
+ * 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.
*/
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) + ">";
* 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);
/**
* 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);
PropertyMirror.prototype.value = function() {
- return MakeMirror(this.value_);
+ return MakeMirror(this.value_, false);
}
if (this.hasGetter()) {
return MakeMirror(this.getter_);
} else {
- return new UndefinedMirror();
+ return GetUndefinedMirror();
}
}
if (this.hasSetter()) {
return MakeMirror(this.setter_);
} else {
- return new UndefinedMirror();
+ return GetUndefinedMirror();
}
}
}
+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
};
+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));
}
+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
return o;
};
+
JSONProtocolSerializer.prototype.serialize_ = function(mirror, reference,
details) {
// If serializing a reference to a mirror just return the reference and add
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()) {
}
+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
}
+// Copy all the context locals into an object used to materialize a scope.
+static void CopyContextLocalsToScopeObject(Handle<Code> code,
+ ScopeInfo<>& scope_info,
+ Handle<Context> context,
+ Handle<JSObject> scope_object) {
+ // Fill all context locals to the context extension.
+ for (int i = Context::MIN_CONTEXT_SLOTS;
+ i < scope_info.number_of_context_slots();
+ i++) {
+ int context_index =
+ ScopeInfo<>::ContextSlotIndex(*code,
+ *scope_info.context_slot_name(i),
+ NULL);
+
+ // Don't include the arguments shadow (.arguments) context variable.
+ if (*scope_info.context_slot_name(i) != Heap::arguments_shadow_symbol()) {
+ SetProperty(scope_object,
+ scope_info.context_slot_name(i),
+ Handle<Object>(context->get(context_index)), NONE);
+ }
+ }
+}
+
+
+// Create a plain JSObject which materializes the local scope for the specified
+// frame.
+static Handle<JSObject> MaterializeLocalScope(JavaScriptFrame* frame) {
+ Handle<JSFunction> function(JSFunction::cast(frame->function()));
+ Handle<Code> code(function->code());
+ ScopeInfo<> scope_info(*code);
+
+ // Allocate and initialize a JSObject with all the arguments, stack locals
+ // heap locals and extension properties of the debugged function.
+ Handle<JSObject> local_scope = Factory::NewJSObject(Top::object_function());
+
+ // First fill all parameters.
+ for (int i = 0; i < scope_info.number_of_parameters(); ++i) {
+ SetProperty(local_scope,
+ scope_info.parameter_name(i),
+ Handle<Object>(frame->GetParameter(i)), NONE);
+ }
+
+ // Second fill all stack locals.
+ for (int i = 0; i < scope_info.number_of_stack_slots(); i++) {
+ SetProperty(local_scope,
+ scope_info.stack_slot_name(i),
+ Handle<Object>(frame->GetExpression(i)), NONE);
+ }
+
+ // Third fill all context locals.
+ Handle<Context> frame_context(Context::cast(frame->context()));
+ Handle<Context> function_context(frame_context->fcontext());
+ CopyContextLocalsToScopeObject(code, scope_info,
+ function_context, local_scope);
+
+ // Finally copy any properties from the function context extension. This will
+ // be variables introduced by eval.
+ if (function_context->closure() == *function) {
+ if (function_context->has_extension() &&
+ !function_context->IsGlobalContext()) {
+ Handle<JSObject> ext(JSObject::cast(function_context->extension()));
+ Handle<FixedArray> keys = GetKeysInFixedArrayFor(ext);
+ for (int i = 0; i < keys->length(); i++) {
+ // Names of variables introduced by eval are strings.
+ ASSERT(keys->get(i)->IsString());
+ Handle<String> key(String::cast(keys->get(i)));
+ SetProperty(local_scope, key, GetProperty(ext, key), NONE);
+ }
+ }
+ }
+ return local_scope;
+}
+
+
+// Create a plain JSObject which materializes the closure content for the
+// context.
+static Handle<JSObject> MaterializeClosure(Handle<Context> context) {
+ ASSERT(context->is_function_context());
+
+ Handle<Code> code(context->closure()->code());
+ ScopeInfo<> scope_info(*code);
+
+ // Allocate and initialize a JSObject with all the content of theis function
+ // closure.
+ Handle<JSObject> closure_scope = Factory::NewJSObject(Top::object_function());
+
+ // Check whether the arguments shadow object exists.
+ int arguments_shadow_index =
+ ScopeInfo<>::ContextSlotIndex(*code,
+ Heap::arguments_shadow_symbol(),
+ NULL);
+ if (arguments_shadow_index >= 0) {
+ // In this case all the arguments are available in the arguments shadow
+ // object.
+ Handle<JSObject> arguments_shadow(
+ JSObject::cast(context->get(arguments_shadow_index)));
+ for (int i = 0; i < scope_info.number_of_parameters(); ++i) {
+ SetProperty(closure_scope,
+ scope_info.parameter_name(i),
+ Handle<Object>(arguments_shadow->GetElement(i)), NONE);
+ }
+ }
+
+ // Fill all context locals to the context extension.
+ CopyContextLocalsToScopeObject(code, scope_info, context, closure_scope);
+
+ // Finally copy any properties from the function context extension. This will
+ // be variables introduced by eval.
+ if (context->has_extension()) {
+ Handle<JSObject> ext(JSObject::cast(context->extension()));
+ Handle<FixedArray> keys = GetKeysInFixedArrayFor(ext);
+ for (int i = 0; i < keys->length(); i++) {
+ // Names of variables introduced by eval are strings.
+ ASSERT(keys->get(i)->IsString());
+ Handle<String> key(String::cast(keys->get(i)));
+ SetProperty(closure_scope, key, GetProperty(ext, key), NONE);
+ }
+ }
+
+ return closure_scope;
+}
+
+
+// Iterate over the actual scopes visible from a stack frame. All scopes are
+// backed by an actual context except the local scope, which is inserted
+// "artifically" in the context chain.
+class ScopeIterator {
+ public:
+ enum ScopeType {
+ ScopeTypeGlobal = 0,
+ ScopeTypeLocal,
+ ScopeTypeWith,
+ ScopeTypeClosure
+ };
+
+ explicit ScopeIterator(JavaScriptFrame* frame)
+ : frame_(frame),
+ function_(JSFunction::cast(frame->function())),
+ context_(Context::cast(frame->context())),
+ local_done_(false),
+ at_local_(false) {
+
+ // Check whether the first scope is actually a local scope.
+ if (context_->IsGlobalContext()) {
+ // If there is a stack slot for .result then this local scope has been
+ // created for evaluating top level code and it is not a real local scope.
+ // Checking for the existence of .result seems fragile, but the scope info
+ // saved with the code object does not otherwise have that information.
+ Handle<Code> code(function_->code());
+ int index = ScopeInfo<>::StackSlotIndex(*code, Heap::result_symbol());
+ at_local_ = index < 0;
+ } else if (context_->is_function_context()) {
+ at_local_ = true;
+ }
+ }
+
+ // More scopes?
+ bool Done() { return context_.is_null(); }
+
+ // Move to the next scope.
+ void Next() {
+ // If at a local scope mark the local scope as passed.
+ if (at_local_) {
+ at_local_ = false;
+ local_done_ = true;
+
+ // If the current context is not associated with the local scope the
+ // current context is the next real scope, so don't move to the next
+ // context in this case.
+ if (context_->closure() != *function_) {
+ return;
+ }
+ }
+
+ // The global scope is always the last in the chain.
+ if (context_->IsGlobalContext()) {
+ context_ = Handle<Context>();
+ return;
+ }
+
+ // Move to the next context.
+ if (context_->is_function_context()) {
+ context_ = Handle<Context>(Context::cast(context_->closure()->context()));
+ } else {
+ context_ = Handle<Context>(context_->previous());
+ }
+
+ // If passing the local scope indicate that the current scope is now the
+ // local scope.
+ if (!local_done_ &&
+ (context_->IsGlobalContext() || (context_->is_function_context()))) {
+ at_local_ = true;
+ }
+ }
+
+ // Return the type of the current scope.
+ int Type() {
+ if (at_local_) {
+ return ScopeTypeLocal;
+ }
+ if (context_->IsGlobalContext()) {
+ ASSERT(context_->global()->IsGlobalObject());
+ return ScopeTypeGlobal;
+ }
+ if (context_->is_function_context()) {
+ return ScopeTypeClosure;
+ }
+ ASSERT(context_->has_extension());
+ ASSERT(!context_->extension()->IsJSContextExtensionObject());
+ return ScopeTypeWith;
+ }
+
+ // Return the JavaScript object with the content of the current scope.
+ Handle<JSObject> ScopeObject() {
+ switch (Type()) {
+ case ScopeIterator::ScopeTypeGlobal:
+ return Handle<JSObject>(CurrentContext()->global());
+ break;
+ case ScopeIterator::ScopeTypeLocal:
+ // Materialize the content of the local scope into a JSObject.
+ return MaterializeLocalScope(frame_);
+ break;
+ case ScopeIterator::ScopeTypeWith:
+ // Return the with object.
+ return Handle<JSObject>(CurrentContext()->extension());
+ break;
+ case ScopeIterator::ScopeTypeClosure:
+ // Materialize the content of the closure scope into a JSObject.
+ return MaterializeClosure(CurrentContext());
+ break;
+ default:
+ UNREACHABLE();
+ return Handle<JSObject>();
+ }
+ }
+
+ // Return the context for this scope. For the local context there might not
+ // be an actual context.
+ Handle<Context> CurrentContext() {
+ if (at_local_ && context_->closure() != *function_) {
+ return Handle<Context>();
+ }
+ return context_;
+ }
+
+#ifdef DEBUG
+ // Debug print of the content of the current scope.
+ void DebugPrint() {
+ switch (Type()) {
+ case ScopeIterator::ScopeTypeGlobal:
+ PrintF("Global:\n");
+ CurrentContext()->Print();
+ break;
+
+ case ScopeIterator::ScopeTypeLocal: {
+ PrintF("Local:\n");
+ Handle<Code> code(function_->code());
+ ScopeInfo<> scope_info(*code);
+ scope_info.Print();
+ if (!CurrentContext().is_null()) {
+ CurrentContext()->Print();
+ if (CurrentContext()->has_extension()) {
+ Handle<JSObject> extension =
+ Handle<JSObject>(CurrentContext()->extension());
+ if (extension->IsJSContextExtensionObject()) {
+ extension->Print();
+ }
+ }
+ }
+ break;
+ }
+
+ case ScopeIterator::ScopeTypeWith: {
+ PrintF("With:\n");
+ Handle<JSObject> extension =
+ Handle<JSObject>(CurrentContext()->extension());
+ extension->Print();
+ break;
+ }
+
+ case ScopeIterator::ScopeTypeClosure: {
+ PrintF("Closure:\n");
+ CurrentContext()->Print();
+ if (CurrentContext()->has_extension()) {
+ Handle<JSObject> extension =
+ Handle<JSObject>(CurrentContext()->extension());
+ if (extension->IsJSContextExtensionObject()) {
+ extension->Print();
+ }
+ }
+ break;
+ }
+
+ default:
+ UNREACHABLE();
+ }
+ PrintF("\n");
+ }
+#endif
+
+ private:
+ JavaScriptFrame* frame_;
+ Handle<JSFunction> function_;
+ Handle<Context> context_;
+ bool local_done_;
+ bool at_local_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ScopeIterator);
+};
+
+
+static Object* Runtime_GetScopeCount(Arguments args) {
+ HandleScope scope;
+ ASSERT(args.length() == 2);
+
+ // Check arguments.
+ Object* check = Runtime_CheckExecutionState(args);
+ if (check->IsFailure()) return check;
+ CONVERT_CHECKED(Smi, wrapped_id, args[1]);
+
+ // Get the frame where the debugging is performed.
+ StackFrame::Id id = UnwrapFrameId(wrapped_id);
+ JavaScriptFrameIterator it(id);
+ JavaScriptFrame* frame = it.frame();
+
+ // Count the visible scopes.
+ int n = 0;
+ for (ScopeIterator it(frame); !it.Done(); it.Next()) {
+ n++;
+ }
+
+ return Smi::FromInt(n);
+}
+
+
+static const int kScopeDetailsTypeIndex = 0;
+static const int kScopeDetailsObjectIndex = 1;
+static const int kScopeDetailsSize = 2;
+
+// Return an array with scope details
+// args[0]: number: break id
+// args[1]: number: frame index
+// args[2]: number: scope index
+//
+// The array returned contains the following information:
+// 0: Scope type
+// 1: Scope object
+static Object* Runtime_GetScopeDetails(Arguments args) {
+ HandleScope scope;
+ ASSERT(args.length() == 3);
+
+ // Check arguments.
+ Object* check = Runtime_CheckExecutionState(args);
+ if (check->IsFailure()) return check;
+ CONVERT_CHECKED(Smi, wrapped_id, args[1]);
+ CONVERT_NUMBER_CHECKED(int, index, Int32, args[2]);
+
+ // Get the frame where the debugging is performed.
+ StackFrame::Id id = UnwrapFrameId(wrapped_id);
+ JavaScriptFrameIterator frame_it(id);
+ JavaScriptFrame* frame = frame_it.frame();
+
+ // Find the requested scope.
+ int n = 0;
+ ScopeIterator it(frame);
+ for (; !it.Done() && n < index; it.Next()) {
+ n++;
+ }
+ if (it.Done()) {
+ return Heap::undefined_value();
+ }
+
+ // Calculate the size of the result.
+ int details_size = kScopeDetailsSize;
+ Handle<FixedArray> details = Factory::NewFixedArray(details_size);
+
+ // Fill in scope details.
+ details->set(kScopeDetailsTypeIndex, Smi::FromInt(it.Type()));
+ details->set(kScopeDetailsObjectIndex, *it.ScopeObject());
+
+ return *Factory::NewJSArrayWithElements(details);
+}
+
+
+static Object* Runtime_DebugPrintScopes(Arguments args) {
+ HandleScope scope;
+ ASSERT(args.length() == 0);
+
+#ifdef DEBUG
+ // Print the scopes for the top frame.
+ StackFrameLocator locator;
+ JavaScriptFrame* frame = locator.FindJavaScriptFrame(0);
+ for (ScopeIterator it(frame); !it.Done(); it.Next()) {
+ it.DebugPrint();
+ }
+#endif
+ return Heap::undefined_value();
+}
+
+
static Object* Runtime_GetCFrames(Arguments args) {
HandleScope scope;
ASSERT(args.length() == 1);
ASSERT(go_between_sinfo.number_of_context_slots() == 0);
#endif
- // Allocate and initialize a context extension object with all the
- // arguments, stack locals heap locals and extension properties of the
- // debugged function.
- Handle<JSObject> context_ext = Factory::NewJSObject(Top::object_function());
- // First fill all parameters to the context extension.
- for (int i = 0; i < sinfo.number_of_parameters(); ++i) {
- SetProperty(context_ext,
- sinfo.parameter_name(i),
- Handle<Object>(frame->GetParameter(i)), NONE);
- }
- // Second fill all stack locals to the context extension.
- for (int i = 0; i < sinfo.number_of_stack_slots(); i++) {
- SetProperty(context_ext,
- sinfo.stack_slot_name(i),
- Handle<Object>(frame->GetExpression(i)), NONE);
- }
- // Third fill all context locals to the context extension.
- Handle<Context> frame_context(Context::cast(frame->context()));
- Handle<Context> function_context(frame_context->fcontext());
- for (int i = Context::MIN_CONTEXT_SLOTS;
- i < sinfo.number_of_context_slots();
- ++i) {
- int context_index =
- ScopeInfo<>::ContextSlotIndex(*code, *sinfo.context_slot_name(i), NULL);
- SetProperty(context_ext,
- sinfo.context_slot_name(i),
- Handle<Object>(function_context->get(context_index)), NONE);
- }
- // Finally copy any properties from the function context extension. This will
- // be variables introduced by eval.
- if (function_context->has_extension() &&
- !function_context->IsGlobalContext()) {
- Handle<JSObject> ext(JSObject::cast(function_context->extension()));
- Handle<FixedArray> keys = GetKeysInFixedArrayFor(ext);
- for (int i = 0; i < keys->length(); i++) {
- // Names of variables introduced by eval are strings.
- ASSERT(keys->get(i)->IsString());
- Handle<String> key(String::cast(keys->get(i)));
- SetProperty(context_ext, key, GetProperty(ext, key), NONE);
- }
- }
+ // Materialize the content of the local scope into a JSObject.
+ Handle<JSObject> local_scope = MaterializeLocalScope(frame);
// Allocate a new context for the debug evaluation and set the extension
// object build.
Handle<Context> context =
Factory::NewFunctionContext(Context::MIN_CONTEXT_SLOTS, go_between);
- context->set_extension(*context_ext);
+ context->set_extension(*local_scope);
// Copy any with contexts present and chain them in front of this context.
+ Handle<Context> frame_context(Context::cast(frame->context()));
+ Handle<Context> function_context(frame_context->fcontext());
context = CopyWithContextChain(frame_context, context);
// Wrap the evaluation statement in a new function compiled in the newly
Execution::Call(Handle<JSFunction>::cast(evaluation_function), receiver,
argc, argv, &has_pending_exception);
if (has_pending_exception) return Failure::Exception();
+
+ // Skip the global proxy as it has no properties and always delegates to the
+ // real global object.
+ if (result->IsJSGlobalProxy()) {
+ result = Handle<JSObject>(JSObject::cast(result->GetPrototype()));
+ }
+
return *result;
}
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) \
--- /dev/null
+// Copyright 2008 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
+// The functions used for testing backtraces. They are at the top to make the
+// testing of source line/column easier.
+
+
+// Get the Debug object exposed from the debug context global object.
+Debug = debug.Debug
+
+var name;
+var listener_delegate;
+var listener_called;
+var exception;
+var begin_test_count = 0;
+var end_test_count = 0;
+var break_count = 0;
+
+
+// Debug event listener which delegates.
+function listener(event, exec_state, event_data, data) {
+ try {
+ if (event == Debug.DebugEvent.Break) {
+ break_count++;
+ listener_called = true;
+ listener_delegate(exec_state)
+ }
+ } catch (e) {
+ exception = e;
+ }
+}
+
+// Add the debug event listener.
+Debug.setListener(listener);
+
+
+// Initialize for a noew test.
+function BeginTest(name) {
+ test_name = name;
+ listener_delegate = null;
+ listener_called = false;
+ exception = null;
+ begin_test_count++;
+}
+
+
+// Check result of a test.
+function EndTest() {
+ assertTrue(listener_called, "listerner not called for " + test_name);
+ assertNull(exception, test_name)
+ end_test_count++;
+}
+
+
+// Check that the scope chain contains the expected types of scopes.
+function CheckScopeChain(scopes, exec_state) {
+ assertEquals(scopes.length, exec_state.frame().scopeCount());
+ for (var i = 0; i < scopes.length; i++) {
+ var scope = exec_state.frame().scope(i);
+ assertTrue(scope.isScope());
+ assertEquals(scopes[i], scope.scopeType());
+
+ // Check the global object when hitting the global scope.
+ if (scopes[i] == debug.ScopeType.Global) {
+ assertEquals(this, scope.scopeObject().value());
+ }
+ }
+
+ // Get the debug command processor.
+ var dcp = exec_state.debugCommandProcessor();
+
+ // Send a scopes request and check the result.
+ var json;
+ request_json = '{"seq":0,"type":"request","command":"scopes"}'
+ var response_json = dcp.processDebugJSONRequest(request_json);
+ var response = JSON.parse(response_json);
+ assertEquals(scopes.length, response.body.scopes.length);
+ for (var i = 0; i < scopes.length; i++) {
+ assertEquals(i, response.body.scopes[i].index);
+ assertEquals(scopes[i], response.body.scopes[i].type);
+ if (scopes[i] == debug.ScopeType.Local ||
+ scopes[i] == debug.ScopeType.Closure) {
+ assertTrue(response.body.scopes[i].object.ref < 0);
+ } else {
+ assertTrue(response.body.scopes[i].object.ref >= 0);
+ }
+ var found = false;
+ for (var j = 0; j < response.refs.length && !found; j++) {
+ found = response.refs[j].handle == response.body.scopes[i].object.ref;
+ }
+ assertTrue(found, "Scope object " + response.body.scopes[i].object.ref + " not found");
+ }
+}
+
+
+// Check that the content of the scope is as expected. For functions just check
+// that there is a function.
+function CheckScopeContent(content, number, exec_state) {
+ var scope = exec_state.frame().scope(number)
+ var count = 0;
+ for (var p in content) {
+ var property_mirror = scope.scopeObject().property(p);
+ assertFalse(property_mirror.isUndefined(), 'property ' + p + ' not found in scope');
+ if (typeof(content[p]) === 'function') {
+ assertTrue(property_mirror.value().isFunction());
+ } else {
+ assertEquals(content[p], property_mirror.value().value(), 'property ' + p + ' has unexpected value');
+ }
+ count++;
+ }
+
+ // 'arguments' and might be exposed in the local and closure scope. Just
+ // ignore this.
+ var scope_size = scope.scopeObject().properties().length;
+ if (!scope.scopeObject().property('arguments').isUndefined()) {
+ scope_size--;
+ }
+ if (count != scope_size) {
+ print('Names found in scope:');
+ var names = scope.scopeObject().propertyNames();
+ for (var i = 0; i < names.length; i++) {
+ print(names[i]);
+ }
+ }
+ assertEquals(count, scope_size);
+
+ // Get the debug command processor.
+ var dcp = exec_state.debugCommandProcessor();
+
+ // Send a scope request for information on a single scope and check the
+ // result.
+ request_json = '{"seq":0,"type":"request","command":"scope","arguments":{"number":'
+ request_json += scope.scopeIndex();
+ request_json += '}}'
+ var response_json = dcp.processDebugJSONRequest(request_json);
+ var response = JSON.parse(response_json);
+ assertEquals(scope.scopeType(), response.body.type);
+ assertEquals(number, response.body.index);
+ if (scope.scopeType() == debug.ScopeType.Local ||
+ scope.scopeType() == debug.ScopeType.Closure) {
+ assertTrue(response.body.object.ref < 0);
+ } else {
+ assertTrue(response.body.object.ref >= 0);
+ }
+ var found = false;
+ for (var i = 0; i < response.refs.length && !found; i++) {
+ found = response.refs[i].handle == response.body.object.ref;
+ }
+ assertTrue(found, "Scope object " + response.body.object.ref + " not found");
+}
+
+
+// Simple empty local scope.
+BeginTest("Local 1");
+
+function local_1() {
+ debugger;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({}, 0, exec_state);
+}
+local_1()
+EndTest();
+
+
+// Local scope with a parameter.
+BeginTest("Local 2");
+
+function local_2(a) {
+ debugger;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1}, 0, exec_state);
+}
+local_2(1)
+EndTest();
+
+
+// Local scope with a parameter and a local variable.
+BeginTest("Local 3");
+
+function local_3(a) {
+ var x = 3;
+ debugger;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1,x:3}, 0, exec_state);
+}
+local_3(1)
+EndTest();
+
+
+// Local scope with parameters and local variables.
+BeginTest("Local 4");
+
+function local_4(a, b) {
+ var x = 3;
+ var y = 4;
+ debugger;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1,b:2,x:3,y:4}, 0, exec_state);
+}
+local_4(1, 2)
+EndTest();
+
+
+// Empty local scope with use of eval.
+BeginTest("Local 5");
+
+function local_5() {
+ eval('');
+ debugger;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({}, 0, exec_state);
+}
+local_5()
+EndTest();
+
+
+// Local introducing local variable using eval.
+BeginTest("Local 6");
+
+function local_6() {
+ eval('var i = 5');
+ debugger;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({i:5}, 0, exec_state);
+}
+local_6()
+EndTest();
+
+
+// Local scope with parameters, local variables and local variable introduced
+// using eval.
+BeginTest("Local 7");
+
+function local_7(a, b) {
+ var x = 3;
+ var y = 4;
+ eval('var i = 5');
+ eval('var j = 6');
+ debugger;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1,b:2,x:3,y:4,i:5,j:6}, 0, exec_state);
+}
+local_7(1, 2)
+EndTest();
+
+
+// Single empty with block.
+BeginTest("With 1");
+
+function with_1() {
+ with({}) {
+ debugger;
+ }
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.With,
+ debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({}, 0, exec_state);
+}
+with_1()
+EndTest();
+
+
+// Nested empty with blocks.
+BeginTest("With 2");
+
+function with_2() {
+ with({}) {
+ with({}) {
+ debugger;
+ }
+ }
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.With,
+ debug.ScopeType.With,
+ debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({}, 0, exec_state);
+ CheckScopeContent({}, 1, exec_state);
+}
+with_2()
+EndTest();
+
+
+// With block using an in-place object literal.
+BeginTest("With 3");
+
+function with_3() {
+ with({a:1,b:2}) {
+ debugger;
+ }
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.With,
+ debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1,b:2}, 0, exec_state);
+}
+with_3()
+EndTest();
+
+
+// Nested with blocks using in-place object literals.
+BeginTest("With 4");
+
+function with_4() {
+ with({a:1,b:2}) {
+ with({a:2,b:1}) {
+ debugger;
+ }
+ }
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.With,
+ debug.ScopeType.With,
+ debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:2,b:1}, 0, exec_state);
+ CheckScopeContent({a:1,b:2}, 1, exec_state);
+}
+with_4()
+EndTest();
+
+
+// Nested with blocks using existing object.
+BeginTest("With 5");
+
+var with_object = {c:3,d:4};
+function with_5() {
+ with(with_object) {
+ with(with_object) {
+ debugger;
+ }
+ }
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.With,
+ debug.ScopeType.With,
+ debug.ScopeType.Local,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent(with_object, 0, exec_state);
+ CheckScopeContent(with_object, 1, exec_state);
+ assertEquals(exec_state.frame().scope(0).scopeObject(), exec_state.frame().scope(1).scopeObject());
+ assertEquals(with_object, exec_state.frame().scope(1).scopeObject().value());
+}
+with_5()
+EndTest();
+
+
+// Simple closure formed by returning an inner function referering the outer
+// functions arguments.
+BeginTest("Closure 1");
+
+function closure_1(a) {
+ function f() {
+ debugger;
+ return a;
+ };
+ return f;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Closure,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1}, 1, exec_state);
+}
+closure_1(1)()
+EndTest();
+
+
+// Simple closure formed by returning an inner function referering the outer
+// functions arguments. Due to VM optimizations parts of the actual closure is
+// missing from the debugger information.
+BeginTest("Closure 2");
+
+function closure_2(a, b) {
+ var x = a + 2;
+ var y = b + 2;
+ function f() {
+ debugger;
+ return a + x;
+ };
+ return f;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Closure,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1,x:3}, 1, exec_state);
+}
+closure_2(1, 2)()
+EndTest();
+
+
+// Simple closure formed by returning an inner function referering the outer
+// functions arguments. Using all arguments and locals from the outer function
+// in the inner function makes these part of the debugger information on the
+// closure.
+BeginTest("Closure 3");
+
+function closure_3(a, b) {
+ var x = a + 2;
+ var y = b + 2;
+ function f() {
+ debugger;
+ return a + b + x + y;
+ };
+ return f;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Closure,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1,b:2,x:3,y:4}, 1, exec_state);
+}
+closure_3(1, 2)()
+EndTest();
+
+
+
+// Simple closure formed by returning an inner function referering the outer
+// functions arguments. Using all arguments and locals from the outer function
+// in the inner function makes these part of the debugger information on the
+// closure. Use the inner function as well...
+BeginTest("Closure 4");
+
+function closure_4(a, b) {
+ var x = a + 2;
+ var y = b + 2;
+ function f() {
+ debugger;
+ if (f) {
+ return a + b + x + y;
+ }
+ };
+ return f;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Closure,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1,b:2,x:3,y:4,f:function(){}}, 1, exec_state);
+}
+closure_4(1, 2)()
+EndTest();
+
+
+
+// Simple closure formed by returning an inner function referering the outer
+// functions arguments. In the presence of eval all arguments and locals
+// (including the inner function itself) from the outer function becomes part of
+// the debugger infformation on the closure.
+BeginTest("Closure 5");
+
+function closure_5(a, b) {
+ var x = 3;
+ var y = 4;
+ function f() {
+ eval('');
+ debugger;
+ return 1;
+ };
+ return f;
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Closure,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1,b:2,x:3,y:4,f:function(){}}, 1, exec_state);
+}
+closure_5(1, 2)()
+EndTest();
+
+
+// Two closures. Due to optimizations only the parts actually used are provided
+// through the debugger information.
+BeginTest("Closure 6");
+function closure_6(a, b) {
+ function f(a, b) {
+ var x = 3;
+ var y = 4;
+ return function() {
+ var x = 3;
+ var y = 4;
+ debugger;
+ some_global = a;
+ return f;
+ }
+ }
+ return f(a, b);
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Closure,
+ debug.ScopeType.Closure,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({a:1}, 1, exec_state);
+ CheckScopeContent({f:function(){}}, 2, exec_state);
+}
+closure_6(1, 2)()
+EndTest();
+
+
+// Two closures. In the presence of eval all information is provided as the
+// compiler cannot determine which parts are used.
+BeginTest("Closure 7");
+function closure_7(a, b) {
+ var x = 3;
+ var y = 4;
+ eval('var i = 5');
+ eval('var j = 6');
+ function f(a, b) {
+ var x = 3;
+ var y = 4;
+ eval('var i = 5');
+ eval('var j = 6');
+ return function() {
+ debugger;
+ some_global = a;
+ return f;
+ }
+ }
+ return f(a, b);
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Local,
+ debug.ScopeType.Closure,
+ debug.ScopeType.Closure,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({}, 0, exec_state);
+ CheckScopeContent({a:1,b:2,x:3,y:4,i:5,j:6}, 1, exec_state);
+ CheckScopeContent({a:1,b:2,x:3,y:4,i:5,j:6,f:function(){}}, 2, exec_state);
+}
+closure_7(1, 2)()
+EndTest();
+
+
+// Test a mixture of scopes.
+BeginTest("The full monty");
+function the_full_monty(a, b) {
+ var x = 3;
+ var y = 4;
+ eval('var i = 5');
+ eval('var j = 6');
+ function f(a, b) {
+ var x = 9;
+ var y = 10;
+ eval('var i = 11');
+ eval('var j = 12');
+ with ({j:13}){
+ return function() {
+ var x = 14;
+ with ({a:15}) {
+ with ({b:16}) {
+ debugger;
+ some_global = a;
+ return f;
+ }
+ }
+ }
+ }
+ }
+ return f(a, b);
+}
+
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.With,
+ debug.ScopeType.With,
+ debug.ScopeType.Local,
+ debug.ScopeType.With,
+ debug.ScopeType.Closure,
+ debug.ScopeType.Closure,
+ debug.ScopeType.Global], exec_state);
+ CheckScopeContent({b:16}, 0, exec_state);
+ CheckScopeContent({a:15}, 1, exec_state);
+ CheckScopeContent({x:14}, 2, exec_state);
+ CheckScopeContent({j:13}, 3, exec_state);
+ CheckScopeContent({a:1,b:2,x:9,y:10,i:11,j:12}, 4, exec_state);
+ CheckScopeContent({a:1,b:2,x:3,y:4,i:5,j:6,f:function(){}}, 5, exec_state);
+}
+the_full_monty(1, 2)()
+EndTest();
+
+// Test global scope.
+BeginTest("Global");
+listener_delegate = function(exec_state) {
+ CheckScopeChain([debug.ScopeType.Global], exec_state);
+}
+debugger;
+EndTest();
+
+assertEquals(begin_test_count, break_count, 'one or more tests did not enter the debugger');
+assertEquals(begin_test_count, end_test_count, 'one or more tests did not have its result checked');