Issue #2089 - Turn AsyncGeneratorResumeNext recursion into iteration to avoid stack overflow

Based-on: m-c 1416809
This commit is contained in:
Martok 2023-01-22 04:11:22 +01:00 • committed by roytam1
commit 55eacd43a4
3 changed files with 188 additions and 125 deletions

View file

@ -3008,14 +3008,76 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co
return true; return true;
} }
enum class ResumeNextKind {
Enqueue, Reject, Resolve
};
static MOZ_MUST_USE bool static MOZ_MUST_USE bool
AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj); AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
ResumeNextKind kind, HandleValue valueOrException = UndefinedHandleValue,
bool done = false);
// Async Iteration proposal 11.4.3.3. // Async Iteration proposal 11.4.3.3.
MOZ_MUST_USE bool MOZ_MUST_USE bool
js::AsyncGeneratorResolve(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, js::AsyncGeneratorResolve(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
HandleValue value, bool done) HandleValue value, bool done)
{ {
return AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Resolve, value, done);
}
// Async Iteration proposal 11.4.3.4.
MOZ_MUST_USE bool
js::AsyncGeneratorReject(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
HandleValue exception)
{
return AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Reject, exception);
}
// Async Iteration proposal 11.4.3.5.
static MOZ_MUST_USE bool
AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
ResumeNextKind kind,
HandleValue valueOrException_ /* = UndefinedHandleValue */,
bool done /* = false */)
{
RootedValue valueOrException(cx, valueOrException_);
while (true) {
switch (kind) {
case ResumeNextKind::Enqueue:
// No further action required.
break;
case ResumeNextKind::Reject: {
// 11.4.3.4 AsyncGeneratorReject ( generator, exception )
HandleValue exception = valueOrException;
// Step 1 (implicit).
// Steps 2-3.
MOZ_ASSERT(!asyncGenObj->isQueueEmpty());
// Step 4.
Rooted<AsyncGeneratorRequest*> request(
cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj));
if (!request)
return false;
// Step 5.
RootedObject resultPromise(cx, request->promise());
asyncGenObj->cacheRequest(request);
// Step 6.
if (!RejectMaybeWrappedPromise(cx, resultPromise, exception))
return false;
// Steps 7-8.
break;
}
case ResumeNextKind::Resolve: {
// 11.4.3.3 AsyncGeneratorResolve ( generator, value, done )
HandleValue value = valueOrException;
// Step 1 (implicit). // Step 1 (implicit).
// Steps 2-3. // Steps 2-3.
@ -3043,51 +3105,11 @@ js::AsyncGeneratorResolve(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenO
if (!ResolvePromiseInternal(cx, resultPromise, resultValue)) if (!ResolvePromiseInternal(cx, resultPromise, resultValue))
return false; return false;
// Step 8. // Steps 8-9.
if (!AsyncGeneratorResumeNext(cx, asyncGenObj)) break;
return false; }
}
// Step 9.
return true;
}
// Async Iteration proposal 11.4.3.4.
MOZ_MUST_USE bool
js::AsyncGeneratorReject(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
HandleValue exception)
{
// Step 1 (implicit).
// Steps 2-3.
MOZ_ASSERT(!asyncGenObj->isQueueEmpty());
// Step 4.
Rooted<AsyncGeneratorRequest*> request(
cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj));
if (!request)
return false;
// Step 5.
RootedObject resultPromise(cx, request->promise());
asyncGenObj->cacheRequest(request);
// Step 6.
if (!RejectMaybeWrappedPromise(cx, resultPromise, exception))
return false;
// Step 7.
if (!AsyncGeneratorResumeNext(cx, asyncGenObj))
return false;
// Step 8.
return true;
}
// Async Iteration proposal 11.4.3.5.
static MOZ_MUST_USE bool
AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj)
{
// Step 1 (implicit). // Step 1 (implicit).
// Steps 2-3. // Steps 2-3.
@ -3126,8 +3148,13 @@ AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenOb
asyncGenObj->setAwaitingReturn(); asyncGenObj->setAwaitingReturn();
// Steps 10.b.i.4-6 (reordered). // Steps 10.b.i.4-6 (reordered).
RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled)); static constexpr int32_t ResumeNextReturnFulfilled =
RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorResumeNextReturnRejected)); PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled;
static constexpr int32_t ResumeNextReturnRejected =
PromiseHandlerAsyncGeneratorResumeNextReturnRejected;
RootedValue onFulfilled(cx, Int32Value(ResumeNextReturnFulfilled));
RootedValue onRejected(cx, Int32Value(ResumeNextReturnRejected));
// Steps 10.b.i.2-3, 7-10. // Steps 10.b.i.2-3, 7-10.
auto extra = [&](Handle<PromiseReactionRecord*> reaction) { auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
@ -3140,11 +3167,17 @@ AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenOb
MOZ_ASSERT(completionKind == CompletionKind::Throw); MOZ_ASSERT(completionKind == CompletionKind::Throw);
// Steps 10.b.ii.2-3. // Steps 10.b.ii.2-3.
return AsyncGeneratorReject(cx, asyncGenObj, value); kind = ResumeNextKind::Reject;
valueOrException.set(value);
// |done| is unused for ResumeNextKind::Reject.
continue;
} }
} else if (asyncGenObj->isCompleted()) { } else if (asyncGenObj->isCompleted()) {
// Step 11. // Step 11.
return AsyncGeneratorResolve(cx, asyncGenObj, UndefinedHandleValue, true); kind = ResumeNextKind::Resolve;
valueOrException.setUndefined();
done = true;
continue;
} }
// Step 12. // Step 12.
@ -3162,8 +3195,13 @@ AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenOb
// State_AwaitingYieldReturn. // State_AwaitingYieldReturn.
asyncGenObj->setAwaitingYieldReturn(); asyncGenObj->setAwaitingYieldReturn();
RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled)); static constexpr int32_t YieldReturnAwaitedFulfilled =
RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected)); PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled;
static constexpr int32_t YieldReturnAwaitedRejected =
PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected;
RootedValue onFulfilled(cx, Int32Value(YieldReturnAwaitedFulfilled));
RootedValue onRejected(cx, Int32Value(YieldReturnAwaitedRejected));
auto extra = [&](Handle<PromiseReactionRecord*> reaction) { auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
reaction->setIsAsyncGenerator(asyncGenObj); reaction->setIsAsyncGenerator(asyncGenObj);
@ -3173,6 +3211,7 @@ AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenOb
// Steps 13-15, 17-21. // Steps 13-15, 17-21.
return AsyncGeneratorResume(cx, asyncGenObj, completionKind, argument); return AsyncGeneratorResume(cx, asyncGenObj, completionKind, argument);
}
} }
// Async Iteration proposal 11.4.3.6. // Async Iteration proposal 11.4.3.6.
@ -3221,7 +3260,7 @@ js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal,
// Step 7. // Step 7.
if (!asyncGenObj->isExecuting()) { if (!asyncGenObj->isExecuting()) {
// Step 8. // Step 8.
if (!AsyncGeneratorResumeNext(cx, asyncGenObj)) if (!AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Enqueue))
return false; return false;
} }

View file

@ -0,0 +1,12 @@
// https://tc39.github.io/proposal-async-iteration
// Recursion between:
// 11.4.3.3 AsyncGeneratorResolve, step 8
// 11.4.3.5 AsyncGeneratorResumeNext, step 11.
var asyncIter = async function*(){ yield; }();
asyncIter.next();
for (var i = 0; i < 20000; i++) {
asyncIter.next();
}

View file

@ -0,0 +1,12 @@
// https://tc39.github.io/proposal-async-iteration
// Recursion between:
// 11.4.3.4 AsyncGeneratorReject, step 7.
// 11.4.3.5 AsyncGeneratorResumeNext, step 10.b.ii.2.
var asyncIter = async function*(){ yield; }();
asyncIter.next();
for (var i = 0; i < 20000; i++) {
asyncIter.throw();
}