Add scope chain information to the debugger.
authorsgjesse@chromium.org <sgjesse@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 12 Jun 2009 10:03:25 +0000 (10:03 +0000)
committersgjesse@chromium.org <sgjesse@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 12 Jun 2009 10:03:25 +0000 (10:03 +0000)
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

src/d8.js
src/debug-delay.js
src/mirror-delay.js
src/runtime.cc
src/runtime.h
test/mjsunit/debug-scopes.js [new file with mode: 0644]

index 42e31c5..2d52170 100644 (file)
--- a/src/d8.js
+++ b/src/d8.js
@@ -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);
index 0b0501f..21cd68a 100644 (file)
@@ -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');
index f5a12c7..060586d 100644 (file)
@@ -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
@@ -373,6 +392,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.
  */
 Mirror.prototype.allocateHandle_ = function() {
@@ -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
index d290d33..654ba4c 100644 (file)
@@ -6106,6 +6106,406 @@ static Object* Runtime_GetFrameDetails(Arguments args) {
 }
 
 
+// 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);
@@ -6568,54 +6968,17 @@ static Object* Runtime_DebugEvaluate(Arguments args) {
   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
@@ -6657,6 +7020,13 @@ static Object* Runtime_DebugEvaluate(Arguments args) {
       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;
 }
 
index 30bb7c5..a18882d 100644 (file)
@@ -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) \
diff --git a/test/mjsunit/debug-scopes.js b/test/mjsunit/debug-scopes.js
new file mode 100644 (file)
index 0000000..7b477e1
--- /dev/null
@@ -0,0 +1,660 @@
+// 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');