Implement generator mirror
authorwingo@igalia.com <wingo@igalia.com>
Thu, 18 Sep 2014 13:30:15 +0000 (13:30 +0000)
committerwingo@igalia.com <wingo@igalia.com>
Thu, 18 Sep 2014 13:30:15 +0000 (13:30 +0000)
R=yangguo@chromium.org, aandrey@chromium.org

BUG=v8:3292
LOG=N

Review URL: https://codereview.chromium.org/580823002

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@24043 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/mirror-debugger.js
src/runtime.cc
src/runtime.h
test/mjsunit/es6/generators-mirror.js [new file with mode: 0644]

index b13b3fa..c36d6fd 100644 (file)
@@ -87,6 +87,8 @@ function MakeMirror(value, opt_transient) {
     mirror = new SetMirror(value);
   } else if (ObjectIsPromise(value)) {
     mirror = new PromiseMirror(value);
+  } else if (IS_GENERATOR(value)) {
+    mirror = new GeneratorMirror(value);
   } else {
     mirror = new ObjectMirror(value, OBJECT_TYPE, opt_transient);
   }
@@ -161,6 +163,7 @@ var SCOPE_TYPE = 'scope';
 var PROMISE_TYPE = 'promise';
 var MAP_TYPE = 'map';
 var SET_TYPE = 'set';
+var GENERATOR_TYPE = 'generator';
 
 // Maximum length when sending strings through the JSON protocol.
 var kMaxProtocolStringLength = 80;
@@ -214,6 +217,7 @@ var ScopeType = { Global: 0,
 //         - PromiseMirror
 //         - MapMirror
 //         - SetMirror
+//         - GeneratorMirror
 //     - PropertyMirror
 //     - InternalPropertyMirror
 //     - FrameMirror
@@ -371,6 +375,15 @@ Mirror.prototype.isPromise = function() {
 
 
 /**
+ * Check whether the mirror reflects a generator object.
+ * @returns {boolean} True if the mirror reflects a generator object
+ */
+Mirror.prototype.isGenerator = function() {
+  return this instanceof GeneratorMirror;
+};
+
+
+/**
  * Check whether the mirror reflects a property.
  * @returns {boolean} True if the mirror reflects a property
  */
@@ -986,8 +999,8 @@ FunctionMirror.prototype.script = function() {
  * @return {Number or undefined} in-script position for the function
  */
 FunctionMirror.prototype.sourcePosition_ = function() {
-  // Return script if function is resolved. Otherwise just fall through
-  // to return undefined.
+  // Return position if function is resolved. Otherwise just fall
+  // through to return undefined.
   if (this.resolved()) {
     return %FunctionGetScriptSourcePosition(this.value_);
   }
@@ -1352,6 +1365,66 @@ SetMirror.prototype.values = function() {
 
 
 /**
+ * Mirror object for a Generator object.
+ * @param {Object} data The Generator object
+ * @constructor
+ * @extends Mirror
+ */
+function GeneratorMirror(value) {
+  %_CallFunction(this, value, GENERATOR_TYPE, ObjectMirror);
+}
+inherits(GeneratorMirror, ObjectMirror);
+
+
+GeneratorMirror.prototype.status = function() {
+  var continuation = %GeneratorGetContinuation(this.value_);
+  if (continuation < 0) return "running";
+  if (continuation == 0) return "closed";
+  return "suspended";
+};
+
+
+GeneratorMirror.prototype.sourcePosition_ = function() {
+  return %GeneratorGetSourcePosition(this.value_);
+};
+
+
+GeneratorMirror.prototype.sourceLocation = function() {
+  var pos = this.sourcePosition_();
+  if (!IS_UNDEFINED(pos)) {
+    var script = this.func().script();
+    if (script) {
+      return script.locationFromPosition(pos, true);
+    }
+  }
+};
+
+
+GeneratorMirror.prototype.func = function() {
+  if (!this.func_) {
+    this.func_ = MakeMirror(%GeneratorGetFunction(this.value_));
+  }
+  return this.func_;
+};
+
+
+GeneratorMirror.prototype.context = function() {
+  if (!this.context_) {
+    this.context_ = new ContextMirror(%GeneratorGetContext(this.value_));
+  }
+  return this.context_;
+};
+
+
+GeneratorMirror.prototype.receiver = function() {
+  if (!this.receiver_) {
+    this.receiver_ = MakeMirror(%GeneratorGetReceiver(this.value_));
+  }
+  return this.receiver_;
+};
+
+
+/**
  * Base mirror object for properties.
  * @param {ObjectMirror} mirror The mirror object having this property
  * @param {string} name The name of the property
@@ -2539,6 +2612,7 @@ JSONProtocolSerializer.prototype.serialize_ = function(mirror, reference,
     case ERROR_TYPE:
     case REGEXP_TYPE:
     case PROMISE_TYPE:
+    case GENERATOR_TYPE:
       // Add object representation.
       this.serializeObject_(mirror, content, details);
       break;
@@ -2668,6 +2742,21 @@ JSONProtocolSerializer.prototype.serializeObject_ = function(mirror, content,
     }
   }
 
+  if (mirror.isGenerator()) {
+    // Add generator specific properties.
+
+    // Either 'running', 'closed', or 'suspended'.
+    content.status = mirror.status();
+
+    content.func = this.serializeReference(mirror.func())
+    content.receiver = this.serializeReference(mirror.receiver())
+
+    // If the generator is suspended, the content add line/column properties.
+    serializeLocationFields(mirror.sourceLocation(), content);
+
+    // TODO(wingo): Also serialize a reference to the context (scope chain).
+  }
+
   if (mirror.isDate()) {
     // Add date specific properties.
     content.value = mirror.value();
index 83cbfd1..d5cd2a8 100644 (file)
@@ -14627,6 +14627,65 @@ RUNTIME_FUNCTION(Runtime_GetV8Version) {
 }
 
 
+// Returns function of generator activation.
+RUNTIME_FUNCTION(Runtime_GeneratorGetFunction) {
+  HandleScope scope(isolate);
+  DCHECK(args.length() == 1);
+  CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, generator, 0);
+
+  return generator->function();
+}
+
+
+// Returns context of generator activation.
+RUNTIME_FUNCTION(Runtime_GeneratorGetContext) {
+  HandleScope scope(isolate);
+  DCHECK(args.length() == 1);
+  CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, generator, 0);
+
+  return generator->context();
+}
+
+
+// Returns receiver of generator activation.
+RUNTIME_FUNCTION(Runtime_GeneratorGetReceiver) {
+  HandleScope scope(isolate);
+  DCHECK(args.length() == 1);
+  CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, generator, 0);
+
+  return generator->receiver();
+}
+
+
+// Returns generator continuation as a PC offset, or the magic -1 or 0 values.
+RUNTIME_FUNCTION(Runtime_GeneratorGetContinuation) {
+  HandleScope scope(isolate);
+  DCHECK(args.length() == 1);
+  CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, generator, 0);
+
+  return Smi::FromInt(generator->continuation());
+}
+
+
+RUNTIME_FUNCTION(Runtime_GeneratorGetSourcePosition) {
+  HandleScope scope(isolate);
+  DCHECK(args.length() == 1);
+  CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, generator, 0);
+
+  if (generator->is_suspended()) {
+    Handle<Code> code(generator->function()->code(), isolate);
+    int offset = generator->continuation();
+
+    RUNTIME_ASSERT(0 <= offset && offset < code->Size());
+    Address pc = code->address() + offset;
+
+    return Smi::FromInt(code->SourcePosition(pc));
+  }
+
+  return isolate->heap()->undefined_value();
+}
+
+
 RUNTIME_FUNCTION(Runtime_Abort) {
   SealHandleScope shs(isolate);
   DCHECK(args.length() == 1);
index cfff6f0..603745e 100644 (file)
@@ -210,6 +210,11 @@ namespace internal {
   F(GetScript, 1, 1)                                                  \
   F(CollectStackTrace, 2, 1)                                          \
   F(GetV8Version, 0, 1)                                               \
+  F(GeneratorGetFunction, 1, 1)                                       \
+  F(GeneratorGetContext, 1, 1)                                        \
+  F(GeneratorGetReceiver, 1, 1)                                       \
+  F(GeneratorGetContinuation, 1, 1)                                   \
+  F(GeneratorGetSourcePosition, 1, 1)                                 \
                                                                       \
   F(SetCode, 2, 1)                                                    \
                                                                       \
diff --git a/test/mjsunit/es6/generators-mirror.js b/test/mjsunit/es6/generators-mirror.js
new file mode 100644 (file)
index 0000000..6925285
--- /dev/null
@@ -0,0 +1,84 @@
+// Copyright 2014 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --expose-debug-as debug
+// Test the mirror object for functions.
+
+function *generator(f) {
+  "use strict";
+  yield;
+  f();
+  yield;
+}
+
+function MirrorRefCache(json_refs) {
+  var tmp = eval('(' + json_refs + ')');
+  this.refs_ = [];
+  for (var i = 0; i < tmp.length; i++) {
+    this.refs_[tmp[i].handle] = tmp[i];
+  }
+}
+
+MirrorRefCache.prototype.lookup = function(handle) {
+  return this.refs_[handle];
+}
+
+function TestGeneratorMirror(g, test) {
+  // Create mirror and JSON representation.
+  var mirror = debug.MakeMirror(g);
+  var serializer = debug.MakeMirrorSerializer();
+  var json = JSON.stringify(serializer.serializeValue(mirror));
+  var refs = new MirrorRefCache(
+      JSON.stringify(serializer.serializeReferencedObjects()));
+
+  // Check the mirror hierachy.
+  assertTrue(mirror instanceof debug.Mirror);
+  assertTrue(mirror instanceof debug.ValueMirror);
+  assertTrue(mirror instanceof debug.ObjectMirror);
+  assertTrue(mirror instanceof debug.GeneratorMirror);
+
+  // Check the mirror properties.
+  assertTrue(mirror.isGenerator());
+  assertEquals('generator', mirror.type());
+  assertFalse(mirror.isPrimitive());
+  assertEquals('Generator', mirror.className());
+
+  assertTrue(mirror.receiver().isUndefined());
+  assertEquals(generator, mirror.func().value());
+
+  test(mirror);
+}
+
+var iter = generator(function () {
+  assertEquals('running', debug.MakeMirror(iter).status());
+})
+
+// Note that line numbers are 0-based, not 1-based.
+function assertSourceLocation(loc, line, column) {
+  assertEquals(line, loc.line);
+  assertEquals(column, loc.column);
+}
+
+TestGeneratorMirror(iter, function (mirror) {
+  assertEquals('suspended', mirror.status())
+  assertSourceLocation(mirror.sourceLocation(), 7, 19);
+});
+
+iter.next();
+TestGeneratorMirror(iter, function (mirror) {
+  assertEquals('suspended', mirror.status())
+  assertSourceLocation(mirror.sourceLocation(), 9, 2);
+});
+
+iter.next();
+TestGeneratorMirror(iter, function (mirror) {
+  assertEquals('suspended', mirror.status())
+  assertSourceLocation(mirror.sourceLocation(), 11, 2);
+});
+
+iter.next();
+TestGeneratorMirror(iter, function (mirror) {
+  assertEquals('closed', mirror.status())
+  assertEquals(undefined, mirror.sourceLocation());
+});