Skip to content

Commit 966dc90

Browse files
legendecaskangwoosukeq
authored andcommitted
[builtins] Handle broken promises in AsyncGenerator.prototype.return
As ecma262 normative change tc39/ecma262#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 <[email protected]> Commit-Queue: Chengzhong Wu <[email protected]> Cr-Commit-Position: refs/heads/main@{#80066}
1 parent f25cd0c commit 966dc90

File tree

2 files changed

+147
-17
lines changed

2 files changed

+147
-17
lines changed

src/builtins/builtins-async-generator-gen.cc

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,17 @@ class AsyncGeneratorBuiltinsAssembler : public AsyncBuiltinsAssembler {
130130
// for AsyncGenerators.
131131
template <typename Descriptor>
132132
void AsyncGeneratorAwait(bool is_catchable);
133+
void AsyncGeneratorAwaitResume(
134+
TNode<Context> context,
135+
TNode<JSAsyncGeneratorObject> async_generator_object, TNode<Object> value,
136+
JSAsyncGeneratorObject::ResumeMode resume_mode);
133137
void AsyncGeneratorAwaitResumeClosure(
134138
TNode<Context> context, TNode<Object> value,
135139
JSAsyncGeneratorObject::ResumeMode resume_mode);
140+
void AsyncGeneratorReturnClosedReject(
141+
TNode<Context> context,
142+
TNode<JSAsyncGeneratorObject> async_generator_object,
143+
TNode<Object> value);
136144
};
137145

138146
// Shared implementation for the 3 Async Iterator protocol methods of Async
@@ -208,12 +216,10 @@ AsyncGeneratorBuiltinsAssembler::AllocateAsyncGeneratorRequest(
208216
return CAST(request);
209217
}
210218

211-
void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwaitResumeClosure(
212-
TNode<Context> context, TNode<Object> value,
219+
void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwaitResume(
220+
TNode<Context> context,
221+
TNode<JSAsyncGeneratorObject> async_generator_object, TNode<Object> value,
213222
JSAsyncGeneratorObject::ResumeMode resume_mode) {
214-
const TNode<JSAsyncGeneratorObject> async_generator_object =
215-
CAST(LoadContextElement(context, Context::EXTENSION_INDEX));
216-
217223
SetGeneratorNotAwaiting(async_generator_object);
218224

219225
CSA_SLOW_DCHECK(this, IsGeneratorSuspended(async_generator_object));
@@ -247,6 +253,16 @@ void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwaitResumeClosure(
247253
async_generator_object);
248254
}
249255

256+
void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwaitResumeClosure(
257+
TNode<Context> context, TNode<Object> value,
258+
JSAsyncGeneratorObject::ResumeMode resume_mode) {
259+
const TNode<JSAsyncGeneratorObject> async_generator_object =
260+
CAST(LoadContextElement(context, Context::EXTENSION_INDEX));
261+
262+
AsyncGeneratorAwaitResume(context, async_generator_object, value,
263+
resume_mode);
264+
}
265+
250266
template <typename Descriptor>
251267
void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwait(bool is_catchable) {
252268
auto async_generator_object =
@@ -319,6 +335,19 @@ AsyncGeneratorBuiltinsAssembler::TakeFirstAsyncGeneratorRequestFromQueue(
319335
StoreObjectField(generator, JSAsyncGeneratorObject::kQueueOffset, next);
320336
return request;
321337
}
338+
339+
void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorReturnClosedReject(
340+
TNode<Context> context, TNode<JSAsyncGeneratorObject> generator,
341+
TNode<Object> value) {
342+
SetGeneratorNotAwaiting(generator);
343+
344+
// https://tc39.github.io/proposal-async-iteration/
345+
// #async-generator-resume-next-return-processor-rejected step 2:
346+
// Return ! AsyncGeneratorReject(_F_.[[Generator]], _reason_).
347+
CallBuiltin(Builtin::kAsyncGeneratorReject, context, generator, value);
348+
349+
TailCallBuiltin(Builtin::kAsyncGeneratorResumeNext, context, generator);
350+
}
322351
} // namespace
323352

324353
// https://tc39.github.io/proposal-async-iteration/
@@ -611,7 +640,7 @@ TF_BUILTIN(AsyncGeneratorReturn, AsyncGeneratorBuiltinsAssembler) {
611640
// AsyncGeneratorReturn is called when resuming requests with "return" resume
612641
// modes. It is similar to AsyncGeneratorAwait(), but selects different
613642
// resolve/reject closures depending on whether or not the generator is marked
614-
// as closed.
643+
// as closed, and handles exception on Await explicitly.
615644
//
616645
// In particular, non-closed generators will resume the generator with either
617646
// "return" or "throw" resume modes, allowing finally blocks or catch blocks
@@ -624,7 +653,8 @@ TF_BUILTIN(AsyncGeneratorReturn, AsyncGeneratorBuiltinsAssembler) {
624653
// (per proposal-async-iteration/#sec-asyncgeneratorresumenext step 10.b.i)
625654
//
626655
// In all cases, the final step is to jump back to AsyncGeneratorResumeNext.
627-
const auto generator = Parameter<JSGeneratorObject>(Descriptor::kGenerator);
656+
const auto generator =
657+
Parameter<JSAsyncGeneratorObject>(Descriptor::kGenerator);
628658
const auto value = Parameter<Object>(Descriptor::kValue);
629659
const auto is_caught = Parameter<Oddball>(Descriptor::kIsCaught);
630660
const TNode<AsyncGeneratorRequest> req =
@@ -650,9 +680,34 @@ TF_BUILTIN(AsyncGeneratorReturn, AsyncGeneratorBuiltinsAssembler) {
650680
auto context = Parameter<Context>(Descriptor::kContext);
651681
const TNode<JSPromise> outer_promise =
652682
LoadPromiseFromAsyncGeneratorRequest(req);
653-
Await(context, generator, value, outer_promise, var_on_resolve.value(),
654-
var_on_reject.value(), is_caught);
655683

684+
Label done(this), await_exception(this, Label::kDeferred),
685+
closed_await_exception(this, Label::kDeferred);
686+
TVARIABLE(Object, var_exception);
687+
{
688+
compiler::ScopedExceptionHandler handler(this, &await_exception,
689+
&var_exception);
690+
691+
Await(context, generator, value, outer_promise, var_on_resolve.value(),
692+
var_on_reject.value(), is_caught);
693+
}
694+
Goto(&done);
695+
696+
BIND(&await_exception);
697+
{
698+
GotoIf(IsGeneratorStateClosed(state), &closed_await_exception);
699+
// Tail call to AsyncGeneratorResumeNext
700+
AsyncGeneratorAwaitResume(context, generator, var_exception.value(),
701+
JSGeneratorObject::kThrow);
702+
}
703+
704+
BIND(&closed_await_exception);
705+
{
706+
// Tail call to AsyncGeneratorResumeNext
707+
AsyncGeneratorReturnClosedReject(context, generator, var_exception.value());
708+
}
709+
710+
BIND(&done);
656711
Return(UndefinedConstant());
657712
}
658713

@@ -695,14 +750,7 @@ TF_BUILTIN(AsyncGeneratorReturnClosedRejectClosure,
695750
const TNode<JSAsyncGeneratorObject> generator =
696751
CAST(LoadContextElement(context, Context::EXTENSION_INDEX));
697752

698-
SetGeneratorNotAwaiting(generator);
699-
700-
// https://tc39.github.io/proposal-async-iteration/
701-
// #async-generator-resume-next-return-processor-rejected step 2:
702-
// Return ! AsyncGeneratorReject(_F_.[[Generator]], _reason_).
703-
CallBuiltin(Builtin::kAsyncGeneratorReject, context, generator, value);
704-
705-
TailCallBuiltin(Builtin::kAsyncGeneratorResumeNext, context, generator);
753+
AsyncGeneratorReturnClosedReject(context, generator, value);
706754
}
707755

708756
} // namespace internal
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2022 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flags: --allow-natives-syntax
6+
7+
d8.file.execute('test/mjsunit/test-async.js');
8+
9+
function getBrokenPromise() {
10+
let brokenPromise = Promise.resolve(42);
11+
Object.defineProperty(brokenPromise, 'constructor', {
12+
get: function () {
13+
throw new Error('broken promise');
14+
}
15+
});
16+
return brokenPromise;
17+
}
18+
19+
testAsync(test => {
20+
test.plan(1);
21+
22+
let gen = (async function* () { })();
23+
24+
gen.return(getBrokenPromise())
25+
.then(
26+
() => {
27+
test.unreachable();
28+
},
29+
(err) => {
30+
test.equals(err.message, 'broken promise');
31+
}
32+
);
33+
}, "close suspendedStart async generator");
34+
35+
testAsync(test => {
36+
test.plan(1);
37+
38+
let unblock;
39+
let blocking = new Promise(res => { unblock = res; });
40+
41+
let gen = (async function* (){ await blocking; })();
42+
gen.next();
43+
44+
gen.return(getBrokenPromise())
45+
.then(
46+
() => {
47+
test.unreachable();
48+
},
49+
(err) => {
50+
test.equals(err.message, 'broken promise');
51+
}
52+
);
53+
54+
unblock();
55+
}, "close blocked suspendedStart async generator");
56+
57+
testAsync(test => {
58+
test.plan(2);
59+
60+
let gen = (async function* (){
61+
try {
62+
yield 1;
63+
} catch (err) {
64+
test.equals(err.message, 'broken promise');
65+
return 2;
66+
}
67+
})();
68+
gen.next()
69+
.then(() => {
70+
try {
71+
return gen.return(getBrokenPromise())
72+
} catch {
73+
test.unreachable();
74+
}
75+
})
76+
.then(
77+
val => {
78+
test.equals(val, {done: true, value: 2});
79+
},
80+
test.unexpectedRejection()
81+
);
82+
}, "resume suspendedYield async generator with throw completion");

0 commit comments

Comments
 (0)