[builtins] Handle broken promises in AsyncGenerator.prototype.return

As ecma262 normative change https://github.com/tc39/ecma262/pull/2683,
exception thrown on PromiseResolve the broken promises need to be caught
and use it to reject the promise returned by
`AsyncGenerator.prototype.return`.

AsyncGeneratorReturn didn't handle the exception thrown by Await. This
CL add an exception handler around it and pass through the caught
exception to the returned promise and resume the generator by
AsyncGeneratorAwaitResume if the generator is not closed, otherwise
reject the promise by AsyncGeneratorReject and drain the queue.

Bug: v8:12770
Change-Id: Ic3cac4ce36a6d8ecfeb5d7d762a37a2e0524831c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3581158
Reviewed-by: Shu-yu Guo <syg@chromium.org>
Commit-Queue: Chengzhong Wu <legendecas@gmail.com>
Cr-Commit-Position: refs/heads/main@{#80066}
This commit is contained in:
legendecas 2022-04-20 21:46:33 +08:00 committed by V8 LUCI CQ
parent a7d6bf9662
commit c779a08f7c
2 changed files with 147 additions and 17 deletions

View File

@ -130,9 +130,17 @@ class AsyncGeneratorBuiltinsAssembler : public AsyncBuiltinsAssembler {
// for AsyncGenerators.
template <typename Descriptor>
void AsyncGeneratorAwait(bool is_catchable);
void AsyncGeneratorAwaitResume(
TNode<Context> context,
TNode<JSAsyncGeneratorObject> async_generator_object, TNode<Object> value,
JSAsyncGeneratorObject::ResumeMode resume_mode);
void AsyncGeneratorAwaitResumeClosure(
TNode<Context> context, TNode<Object> value,
JSAsyncGeneratorObject::ResumeMode resume_mode);
void AsyncGeneratorReturnClosedReject(
TNode<Context> context,
TNode<JSAsyncGeneratorObject> async_generator_object,
TNode<Object> value);
};
// Shared implementation for the 3 Async Iterator protocol methods of Async
@ -208,12 +216,10 @@ AsyncGeneratorBuiltinsAssembler::AllocateAsyncGeneratorRequest(
return CAST(request);
}
void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwaitResumeClosure(
TNode<Context> context, TNode<Object> value,
void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwaitResume(
TNode<Context> context,
TNode<JSAsyncGeneratorObject> async_generator_object, TNode<Object> value,
JSAsyncGeneratorObject::ResumeMode resume_mode) {
const TNode<JSAsyncGeneratorObject> async_generator_object =
CAST(LoadContextElement(context, Context::EXTENSION_INDEX));
SetGeneratorNotAwaiting(async_generator_object);
CSA_SLOW_DCHECK(this, IsGeneratorSuspended(async_generator_object));
@ -247,6 +253,16 @@ void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwaitResumeClosure(
async_generator_object);
}
void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwaitResumeClosure(
TNode<Context> context, TNode<Object> value,
JSAsyncGeneratorObject::ResumeMode resume_mode) {
const TNode<JSAsyncGeneratorObject> async_generator_object =
CAST(LoadContextElement(context, Context::EXTENSION_INDEX));
AsyncGeneratorAwaitResume(context, async_generator_object, value,
resume_mode);
}
template <typename Descriptor>
void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwait(bool is_catchable) {
auto async_generator_object =
@ -319,6 +335,19 @@ AsyncGeneratorBuiltinsAssembler::TakeFirstAsyncGeneratorRequestFromQueue(
StoreObjectField(generator, JSAsyncGeneratorObject::kQueueOffset, next);
return request;
}
void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorReturnClosedReject(
TNode<Context> context, TNode<JSAsyncGeneratorObject> generator,
TNode<Object> value) {
SetGeneratorNotAwaiting(generator);
// https://tc39.github.io/proposal-async-iteration/
// #async-generator-resume-next-return-processor-rejected step 2:
// Return ! AsyncGeneratorReject(_F_.[[Generator]], _reason_).
CallBuiltin(Builtin::kAsyncGeneratorReject, context, generator, value);
TailCallBuiltin(Builtin::kAsyncGeneratorResumeNext, context, generator);
}
} // namespace
// https://tc39.github.io/proposal-async-iteration/
@ -611,7 +640,7 @@ TF_BUILTIN(AsyncGeneratorReturn, AsyncGeneratorBuiltinsAssembler) {
// AsyncGeneratorReturn is called when resuming requests with "return" resume
// modes. It is similar to AsyncGeneratorAwait(), but selects different
// resolve/reject closures depending on whether or not the generator is marked
// as closed.
// as closed, and handles exception on Await explicitly.
//
// In particular, non-closed generators will resume the generator with either
// "return" or "throw" resume modes, allowing finally blocks or catch blocks
@ -624,7 +653,8 @@ TF_BUILTIN(AsyncGeneratorReturn, AsyncGeneratorBuiltinsAssembler) {
// (per proposal-async-iteration/#sec-asyncgeneratorresumenext step 10.b.i)
//
// In all cases, the final step is to jump back to AsyncGeneratorResumeNext.
const auto generator = Parameter<JSGeneratorObject>(Descriptor::kGenerator);
const auto generator =
Parameter<JSAsyncGeneratorObject>(Descriptor::kGenerator);
const auto value = Parameter<Object>(Descriptor::kValue);
const auto is_caught = Parameter<Oddball>(Descriptor::kIsCaught);
const TNode<AsyncGeneratorRequest> req =
@ -650,9 +680,34 @@ TF_BUILTIN(AsyncGeneratorReturn, AsyncGeneratorBuiltinsAssembler) {
auto context = Parameter<Context>(Descriptor::kContext);
const TNode<JSPromise> outer_promise =
LoadPromiseFromAsyncGeneratorRequest(req);
Await(context, generator, value, outer_promise, var_on_resolve.value(),
var_on_reject.value(), is_caught);
Label done(this), await_exception(this, Label::kDeferred),
closed_await_exception(this, Label::kDeferred);
TVARIABLE(Object, var_exception);
{
compiler::ScopedExceptionHandler handler(this, &await_exception,
&var_exception);
Await(context, generator, value, outer_promise, var_on_resolve.value(),
var_on_reject.value(), is_caught);
}
Goto(&done);
BIND(&await_exception);
{
GotoIf(IsGeneratorStateClosed(state), &closed_await_exception);
// Tail call to AsyncGeneratorResumeNext
AsyncGeneratorAwaitResume(context, generator, var_exception.value(),
JSGeneratorObject::kThrow);
}
BIND(&closed_await_exception);
{
// Tail call to AsyncGeneratorResumeNext
AsyncGeneratorReturnClosedReject(context, generator, var_exception.value());
}
BIND(&done);
Return(UndefinedConstant());
}
@ -695,14 +750,7 @@ TF_BUILTIN(AsyncGeneratorReturnClosedRejectClosure,
const TNode<JSAsyncGeneratorObject> generator =
CAST(LoadContextElement(context, Context::EXTENSION_INDEX));
SetGeneratorNotAwaiting(generator);
// https://tc39.github.io/proposal-async-iteration/
// #async-generator-resume-next-return-processor-rejected step 2:
// Return ! AsyncGeneratorReject(_F_.[[Generator]], _reason_).
CallBuiltin(Builtin::kAsyncGeneratorReject, context, generator, value);
TailCallBuiltin(Builtin::kAsyncGeneratorResumeNext, context, generator);
AsyncGeneratorReturnClosedReject(context, generator, value);
}
} // namespace internal

View File

@ -0,0 +1,82 @@
// Copyright 2022 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: --allow-natives-syntax
d8.file.execute('test/mjsunit/test-async.js');
function getBrokenPromise() {
let brokenPromise = Promise.resolve(42);
Object.defineProperty(brokenPromise, 'constructor', {
get: function () {
throw new Error('broken promise');
}
});
return brokenPromise;
}
testAsync(test => {
test.plan(1);
let gen = (async function* () { })();
gen.return(getBrokenPromise())
.then(
() => {
test.unreachable();
},
(err) => {
test.equals(err.message, 'broken promise');
}
);
}, "close suspendedStart async generator");
testAsync(test => {
test.plan(1);
let unblock;
let blocking = new Promise(res => { unblock = res; });
let gen = (async function* (){ await blocking; })();
gen.next();
gen.return(getBrokenPromise())
.then(
() => {
test.unreachable();
},
(err) => {
test.equals(err.message, 'broken promise');
}
);
unblock();
}, "close blocked suspendedStart async generator");
testAsync(test => {
test.plan(2);
let gen = (async function* (){
try {
yield 1;
} catch (err) {
test.equals(err.message, 'broken promise');
return 2;
}
})();
gen.next()
.then(() => {
try {
return gen.return(getBrokenPromise())
} catch {
test.unreachable();
}
})
.then(
val => {
test.equals(val, {done: true, value: 2});
},
test.unexpectedRejection()
);
}, "resume suspendedYield async generator with throw completion");