[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:
parent
a7d6bf9662
commit
c779a08f7c
@ -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
|
||||
|
@ -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");
|
Loading…
Reference in New Issue
Block a user