Revision: 25297
Author:   [email protected]
Date:     Wed Nov 12 14:28:53 2014 UTC
Log:      Leaving a generator via an exception causes it to close

[email protected]
BUG=v8:3096
LOG=Y

Review URL: https://codereview.chromium.org/717123002
https://code.google.com/p/v8/source/detail?r=25297

Added:
 /branches/bleeding_edge/test/mjsunit/es6/generators-states.js
Modified:
 /branches/bleeding_edge/src/arm/full-codegen-arm.cc
 /branches/bleeding_edge/src/arm64/full-codegen-arm64.cc
 /branches/bleeding_edge/src/generator.js
 /branches/bleeding_edge/src/ia32/full-codegen-ia32.cc
 /branches/bleeding_edge/src/messages.js
 /branches/bleeding_edge/src/runtime/runtime-generator.cc
 /branches/bleeding_edge/src/runtime/runtime.h
 /branches/bleeding_edge/src/x64/full-codegen-x64.cc
 /branches/bleeding_edge/test/mjsunit/es6/generators-iteration.js

=======================================
--- /dev/null
+++ /branches/bleeding_edge/test/mjsunit/es6/generators-states.js Wed Nov 12 14:28:53 2014 UTC
@@ -0,0 +1,67 @@
+// 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.
+
+// Test generator states.
+
+function Foo() {}
+function Bar() {}
+
+function assertIteratorResult(value, done, result) {
+  assertEquals({ value: value, done: done}, result);
+}
+
+function assertIteratorIsClosed(iter) {
+  assertIteratorResult(undefined, true, iter.next());
+  // Next and throw on a closed iterator.
+  assertDoesNotThrow(function() { iter.next(); });
+  assertThrows(function() { iter.throw(new Bar); }, Bar);
+}
+
+var iter;
+function* nextGenerator() { yield iter.next(); }
+function* throwGenerator() { yield iter.throw(new Bar); }
+
+// Throw on a suspendedStart iterator.
+iter = nextGenerator();
+assertThrows(function() { iter.throw(new Foo) }, Foo)
+assertThrows(function() { iter.throw(new Foo) }, Foo)
+assertIteratorIsClosed(iter);
+
+// The same.
+iter = throwGenerator();
+assertThrows(function() { iter.throw(new Foo) }, Foo)
+assertThrows(function() { iter.throw(new Foo) }, Foo)
+assertIteratorIsClosed(iter);
+
+// Next on an executing iterator raises a TypeError.
+iter = nextGenerator();
+assertThrows(function() { iter.next() }, TypeError)
+assertIteratorIsClosed(iter);
+
+// Throw on an executing iterator raises a TypeError.
+iter = throwGenerator();
+assertThrows(function() { iter.next() }, TypeError)
+assertIteratorIsClosed(iter);
+
+// Next on an executing iterator doesn't change the state of the
+// generator.
+iter = (function* () {
+  try {
+    iter.next();
+    yield 1;
+  } catch (e) {
+    try {
+      // This next() should raise the same exception, because the first
+      // next() left the iter in the executing state.
+      iter.next();
+      yield 2;
+    } catch (e) {
+      yield 3;
+    }
+  }
+  yield 4;
+})();
+assertIteratorResult(3, false, iter.next());
+assertIteratorResult(4, false, iter.next());
+assertIteratorIsClosed(iter);
=======================================
--- /branches/bleeding_edge/src/arm/full-codegen-arm.cc Wed Nov 12 11:34:09 2014 UTC +++ /branches/bleeding_edge/src/arm/full-codegen-arm.cc Wed Nov 12 14:28:53 2014 UTC
@@ -2195,15 +2195,6 @@
   VisitForAccumulatorValue(value);
   __ pop(r1);

-  // Check generator state.
-  Label wrong_state, closed_state, done;
-  __ ldr(r3, FieldMemOperand(r1, JSGeneratorObject::kContinuationOffset));
-  STATIC_ASSERT(JSGeneratorObject::kGeneratorExecuting < 0);
-  STATIC_ASSERT(JSGeneratorObject::kGeneratorClosed == 0);
-  __ cmp(r3, Operand(Smi::FromInt(0)));
-  __ b(eq, &closed_state);
-  __ b(lt, &wrong_state);
-
   // Load suspended function and context.
   __ ldr(cp, FieldMemOperand(r1, JSGeneratorObject::kContextOffset));
   __ ldr(r4, FieldMemOperand(r1, JSGeneratorObject::kFunctionOffset));
@@ -2226,7 +2217,7 @@

// Enter a new JavaScript frame, and initialize its slots as they were when
   // the generator was suspended.
-  Label resume_frame;
+  Label resume_frame, done;
   __ bind(&push_frame);
   __ bl(&resume_frame);
   __ jmp(&done);
@@ -2286,26 +2277,6 @@
   // Not reached: the runtime call returns elsewhere.
   __ stop("not-reached");

-  // Reach here when generator is closed.
-  __ bind(&closed_state);
-  if (resume_mode == JSGeneratorObject::NEXT) {
-    // Return completed iterator result when generator is closed.
-    __ LoadRoot(r2, Heap::kUndefinedValueRootIndex);
-    __ push(r2);
-    // Pop value from top-of-stack slot; box result into result register.
-    EmitCreateIteratorResult(true);
-  } else {
-    // Throw the provided value.
-    __ push(r0);
-    __ CallRuntime(Runtime::kThrow, 1);
-  }
-  __ jmp(&done);
-
-  // Throw error if we attempt to operate on a running generator.
-  __ bind(&wrong_state);
-  __ push(r1);
-  __ CallRuntime(Runtime::kThrowGeneratorStateError, 1);
-
   __ bind(&done);
   context()->Plug(result_register());
 }
=======================================
--- /branches/bleeding_edge/src/arm64/full-codegen-arm64.cc Wed Nov 12 11:34:09 2014 UTC +++ /branches/bleeding_edge/src/arm64/full-codegen-arm64.cc Wed Nov 12 14:28:53 2014 UTC
@@ -4894,7 +4894,6 @@
     Expression *value,
     JSGeneratorObject::ResumeMode resume_mode) {
   ASM_LOCATION("FullCodeGenerator::EmitGeneratorResume");
-  Register value_reg = x0;
   Register generator_object = x1;
   Register the_hole = x2;
   Register operand_stack_size = w3;
@@ -4908,15 +4907,6 @@
   VisitForAccumulatorValue(value);
   __ Pop(generator_object);

-  // Check generator state.
-  Label wrong_state, closed_state, done;
-  __ Ldr(x10, FieldMemOperand(generator_object,
-                              JSGeneratorObject::kContinuationOffset));
-  STATIC_ASSERT(JSGeneratorObject::kGeneratorExecuting < 0);
-  STATIC_ASSERT(JSGeneratorObject::kGeneratorClosed == 0);
-  __ CompareAndBranch(x10, Smi::FromInt(0), eq, &closed_state);
-  __ CompareAndBranch(x10, Smi::FromInt(0), lt, &wrong_state);
-
   // Load suspended function and context.
   __ Ldr(cp, FieldMemOperand(generator_object,
                              JSGeneratorObject::kContextOffset));
@@ -4942,7 +4932,7 @@

// Enter a new JavaScript frame, and initialize its slots as they were when
   // the generator was suspended.
-  Label resume_frame;
+  Label resume_frame, done;
   __ Bl(&resume_frame);
   __ B(&done);

@@ -4987,26 +4977,6 @@
   // Not reached: the runtime call returns elsewhere.
   __ Unreachable();

-  // Reach here when generator is closed.
-  __ Bind(&closed_state);
-  if (resume_mode == JSGeneratorObject::NEXT) {
-    // Return completed iterator result when generator is closed.
-    __ LoadRoot(x10, Heap::kUndefinedValueRootIndex);
-    __ Push(x10);
-    // Pop value from top-of-stack slot; box result into result register.
-    EmitCreateIteratorResult(true);
-  } else {
-    // Throw the provided value.
-    __ Push(value_reg);
-    __ CallRuntime(Runtime::kThrow, 1);
-  }
-  __ B(&done);
-
-  // Throw error if we attempt to operate on a running generator.
-  __ Bind(&wrong_state);
-  __ Push(generator_object);
-  __ CallRuntime(Runtime::kThrowGeneratorStateError, 1);
-
   __ Bind(&done);
   context()->Plug(result_register());
 }
=======================================
--- /branches/bleeding_edge/src/generator.js    Wed Nov 12 10:06:19 2014 UTC
+++ /branches/bleeding_edge/src/generator.js    Wed Nov 12 14:28:53 2014 UTC
@@ -20,8 +20,23 @@
                         ['[Generator].prototype.next', this]);
   }

-  if (DEBUG_IS_ACTIVE) %DebugPrepareStepInIfStepping(this);
-  return %_GeneratorNext(this, value);
+  var continuation = %GeneratorGetContinuation(this);
+  if (continuation > 0) {
+    // Generator is suspended.
+    if (DEBUG_IS_ACTIVE) %DebugPrepareStepInIfStepping(this);
+    try {
+      return %_GeneratorNext(this, value);
+    } catch (e) {
+      %GeneratorClose(this);
+      throw e;
+    }
+  } else if (continuation == 0) {
+    // Generator is already closed.
+    return { value: void 0, done: true };
+  } else {
+    // Generator is running.
+    throw MakeTypeError('generator_running', []);
+  }
 }

 function GeneratorObjectThrow(exn) {
@@ -30,7 +45,22 @@
                         ['[Generator].prototype.throw', this]);
   }

-  return %_GeneratorThrow(this, exn);
+  var continuation = %GeneratorGetContinuation(this);
+  if (continuation > 0) {
+    // Generator is suspended.
+    try {
+      return %_GeneratorThrow(this, exn);
+    } catch (e) {
+      %GeneratorClose(this);
+      throw e;
+    }
+  } else if (continuation == 0) {
+    // Generator is already closed.
+    throw exn;
+  } else {
+    // Generator is running.
+    throw MakeTypeError('generator_running', []);
+  }
 }

 function GeneratorObjectIterator() {
=======================================
--- /branches/bleeding_edge/src/ia32/full-codegen-ia32.cc Wed Nov 12 11:34:09 2014 UTC +++ /branches/bleeding_edge/src/ia32/full-codegen-ia32.cc Wed Nov 12 14:28:53 2014 UTC
@@ -2125,15 +2125,6 @@
   VisitForAccumulatorValue(value);
   __ pop(ebx);

-  // Check generator state.
-  Label wrong_state, closed_state, done;
-  STATIC_ASSERT(JSGeneratorObject::kGeneratorExecuting < 0);
-  STATIC_ASSERT(JSGeneratorObject::kGeneratorClosed == 0);
-  __ cmp(FieldOperand(ebx, JSGeneratorObject::kContinuationOffset),
-         Immediate(Smi::FromInt(0)));
-  __ j(equal, &closed_state);
-  __ j(less, &wrong_state);
-
   // Load suspended function and context.
   __ mov(esi, FieldOperand(ebx, JSGeneratorObject::kContextOffset));
   __ mov(edi, FieldOperand(ebx, JSGeneratorObject::kFunctionOffset));
@@ -2155,7 +2146,7 @@

// Enter a new JavaScript frame, and initialize its slots as they were when
   // the generator was suspended.
-  Label resume_frame;
+  Label resume_frame, done;
   __ bind(&push_frame);
   __ call(&resume_frame);
   __ jmp(&done);
@@ -2202,25 +2193,6 @@
   // Not reached: the runtime call returns elsewhere.
   __ Abort(kGeneratorFailedToResume);

-  // Reach here when generator is closed.
-  __ bind(&closed_state);
-  if (resume_mode == JSGeneratorObject::NEXT) {
-    // Return completed iterator result when generator is closed.
-    __ push(Immediate(isolate()->factory()->undefined_value()));
-    // Pop value from top-of-stack slot; box result into result register.
-    EmitCreateIteratorResult(true);
-  } else {
-    // Throw the provided value.
-    __ push(eax);
-    __ CallRuntime(Runtime::kThrow, 1);
-  }
-  __ jmp(&done);
-
-  // Throw error if we attempt to operate on a running generator.
-  __ bind(&wrong_state);
-  __ push(ebx);
-  __ CallRuntime(Runtime::kThrowGeneratorStateError, 1);
-
   __ bind(&done);
   context()->Plug(result_register());
 }
=======================================
--- /branches/bleeding_edge/src/messages.js     Wed Nov 12 10:06:19 2014 UTC
+++ /branches/bleeding_edge/src/messages.js     Wed Nov 12 14:28:53 2014 UTC
@@ -9,9 +9,8 @@
   cyclic_proto:                  ["Cyclic __proto__ value"],
   code_gen_from_strings:         ["%0"],
constructor_special_method: ["Class constructor may not be an accessor"],
+  // TypeError
   generator_running:             ["Generator is already running"],
-  generator_finished:            ["Generator has already finished"],
-  // TypeError
   unexpected_token:              ["Unexpected token ", "%0"],
   unexpected_token_number:       ["Unexpected number"],
   unexpected_token_string:       ["Unexpected string"],
=======================================
--- /branches/bleeding_edge/src/runtime/runtime-generator.cc Mon Oct 20 12:07:45 2014 UTC +++ /branches/bleeding_edge/src/runtime/runtime-generator.cc Wed Nov 12 14:28:53 2014 UTC
@@ -135,16 +135,14 @@
 }


-RUNTIME_FUNCTION(Runtime_ThrowGeneratorStateError) {
+RUNTIME_FUNCTION(Runtime_GeneratorClose) {
   HandleScope scope(isolate);
   DCHECK(args.length() == 1);
   CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, generator, 0);
-  int continuation = generator->continuation();
-  const char* message = continuation == JSGeneratorObject::kGeneratorClosed
-                            ? "generator_finished"
-                            : "generator_running";
-  Vector<Handle<Object> > argv = HandleVector<Object>(NULL, 0);
-  THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewError(message, argv));
+
+  generator->set_continuation(JSGeneratorObject::kGeneratorClosed);
+
+  return isolate->heap()->undefined_value();
 }


=======================================
--- /branches/bleeding_edge/src/runtime/runtime.h Wed Nov 12 11:34:09 2014 UTC +++ /branches/bleeding_edge/src/runtime/runtime.h Wed Nov 12 14:28:53 2014 UTC
@@ -477,7 +477,7 @@
   F(CreateJSGeneratorObject, 0, 1)                           \
   F(SuspendJSGeneratorObject, 1, 1)                          \
   F(ResumeJSGeneratorObject, 3, 1)                           \
-  F(ThrowGeneratorStateError, 1, 1)                          \
+  F(GeneratorClose, 1, 1)                                    \
                                                              \
   /* Arrays */                                               \
   F(ArrayConstructor, -1, 1)                                 \
=======================================
--- /branches/bleeding_edge/src/x64/full-codegen-x64.cc Wed Nov 12 11:34:09 2014 UTC +++ /branches/bleeding_edge/src/x64/full-codegen-x64.cc Wed Nov 12 14:28:53 2014 UTC
@@ -2157,15 +2157,6 @@
   VisitForAccumulatorValue(value);
   __ Pop(rbx);

-  // Check generator state.
-  Label wrong_state, closed_state, done;
-  STATIC_ASSERT(JSGeneratorObject::kGeneratorExecuting < 0);
-  STATIC_ASSERT(JSGeneratorObject::kGeneratorClosed == 0);
-  __ SmiCompare(FieldOperand(rbx, JSGeneratorObject::kContinuationOffset),
-                Smi::FromInt(0));
-  __ j(equal, &closed_state);
-  __ j(less, &wrong_state);
-
   // Load suspended function and context.
   __ movp(rsi, FieldOperand(rbx, JSGeneratorObject::kContextOffset));
   __ movp(rdi, FieldOperand(rbx, JSGeneratorObject::kFunctionOffset));
@@ -2187,7 +2178,7 @@

// Enter a new JavaScript frame, and initialize its slots as they were when
   // the generator was suspended.
-  Label resume_frame;
+  Label resume_frame, done;
   __ bind(&push_frame);
   __ call(&resume_frame);
   __ jmp(&done);
@@ -2234,25 +2225,6 @@
   // Not reached: the runtime call returns elsewhere.
   __ Abort(kGeneratorFailedToResume);

-  // Reach here when generator is closed.
-  __ bind(&closed_state);
-  if (resume_mode == JSGeneratorObject::NEXT) {
-    // Return completed iterator result when generator is closed.
-    __ PushRoot(Heap::kUndefinedValueRootIndex);
-    // Pop value from top-of-stack slot; box result into result register.
-    EmitCreateIteratorResult(true);
-  } else {
-    // Throw the provided value.
-    __ Push(rax);
-    __ CallRuntime(Runtime::kThrow, 1);
-  }
-  __ jmp(&done);
-
-  // Throw error if we attempt to operate on a running generator.
-  __ bind(&wrong_state);
-  __ Push(rbx);
-  __ CallRuntime(Runtime::kThrowGeneratorStateError, 1);
-
   __ bind(&done);
   context()->Plug(result_register());
 }
=======================================
--- /branches/bleeding_edge/test/mjsunit/es6/generators-iteration.js Tue Sep 16 12:30:39 2014 UTC +++ /branches/bleeding_edge/test/mjsunit/es6/generators-iteration.js Wed Nov 12 14:28:53 2014 UTC
@@ -41,10 +41,7 @@
 }

 function assertThrownIteratorIsClosed(iter) {
-  // TODO(yusukesuzuki): Since status of a thrown generator is "executing",
-  // following tests are failed.
-  // https://code.google.com/p/v8/issues/detail?id=3096
-  // assertIteratorIsClosed(iter);
+  assertIteratorIsClosed(iter);
 }

 function TestGeneratorResultPrototype() {

--
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
--- You received this message because you are subscribed to the Google Groups "v8-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to