Leaving a generator via an exception causes it to close

R=rossberg@chromium.org
BUG=v8:3096
LOG=Y

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

Cr-Commit-Position: refs/heads/master@{#25297}
git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@25297 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
wingo@igalia.com 2014-11-12 14:28:53 +00:00
parent e201eb9a9e
commit 757f400246
10 changed files with 112 additions and 136 deletions

View File

@ -2195,15 +2195,6 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
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 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
// 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 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
// 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());
}

View File

@ -4894,7 +4894,6 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
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 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
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 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
// 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 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
// 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());
}

View File

@ -20,8 +20,23 @@ function GeneratorObjectNext(value) {
['[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 @@ function GeneratorObjectThrow(exn) {
['[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() {

View File

@ -2125,15 +2125,6 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
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 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
// 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 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
// 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());
}

View File

@ -9,9 +9,8 @@ var kMessages = {
cyclic_proto: ["Cyclic __proto__ value"],
code_gen_from_strings: ["%0"],
constructor_special_method: ["Class constructor may not be an accessor"],
generator_running: ["Generator is already running"],
generator_finished: ["Generator has already finished"],
// TypeError
generator_running: ["Generator is already running"],
unexpected_token: ["Unexpected token ", "%0"],
unexpected_token_number: ["Unexpected number"],
unexpected_token_string: ["Unexpected string"],

View File

@ -135,16 +135,14 @@ RUNTIME_FUNCTION(Runtime_ResumeJSGeneratorObject) {
}
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();
}

View File

@ -477,7 +477,7 @@ namespace internal {
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) \

View File

@ -2157,15 +2157,6 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
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 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
// 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 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
// 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());
}

View File

@ -41,10 +41,7 @@ function assertIteratorIsClosed(iter) {
}
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() {

View File

@ -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);