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,47 +3008,21 @@ 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)
{ {
// Step 1 (implicit). return AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Resolve, value, done);
// 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.
RootedObject resultObj(cx, CreateIterResultObject(cx, value, done));
if (!resultObj)
return false;
RootedValue resultValue(cx, ObjectValue(*resultObj));
// Step 7.
if (!ResolvePromiseInternal(cx, resultPromise, resultValue))
return false;
// Step 8.
if (!AsyncGeneratorResumeNext(cx, asyncGenObj))
return false;
// Step 9.
return true;
} }
// Async Iteration proposal 11.4.3.4. // Async Iteration proposal 11.4.3.4.
@ -3056,123 +3030,188 @@ MOZ_MUST_USE bool
js::AsyncGeneratorReject(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, js::AsyncGeneratorReject(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
HandleValue exception) HandleValue exception)
{ {
// Step 1 (implicit). return AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Reject, exception);
// 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. // Async Iteration proposal 11.4.3.5.
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 */)
{ {
// Step 1 (implicit). RootedValue valueOrException(cx, valueOrException_);
// Steps 2-3. while (true) {
MOZ_ASSERT(!asyncGenObj->isExecuting()); switch (kind) {
case ResumeNextKind::Enqueue:
// No further action required.
break;
case ResumeNextKind::Reject: {
// 11.4.3.4 AsyncGeneratorReject ( generator, exception )
HandleValue exception = valueOrException;
// Step 4. // Step 1 (implicit).
if (asyncGenObj->isAwaitingYieldReturn() || asyncGenObj->isAwaitingReturn())
return true;
// Steps 5-6. // Steps 2-3.
if (asyncGenObj->isQueueEmpty()) MOZ_ASSERT(!asyncGenObj->isQueueEmpty());
return true;
// Steps 7-8. // Step 4.
Rooted<AsyncGeneratorRequest*> request( Rooted<AsyncGeneratorRequest*> request(
cx, AsyncGeneratorObject::peekRequest(cx, asyncGenObj)); cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj));
if (!request) if (!request)
return false; return false;
// Step 9. // Step 5.
CompletionKind completionKind = request->completionKind(); RootedObject resultPromise(cx, request->promise());
// Step 10. asyncGenObj->cacheRequest(request);
if (completionKind != CompletionKind::Normal) {
// Step 10.a.
if (asyncGenObj->isSuspendedStart())
asyncGenObj->setCompleted();
// Step 10.b. // Step 6.
if (asyncGenObj->isCompleted()) { if (!RejectMaybeWrappedPromise(cx, resultPromise, exception))
RootedValue value(cx, request->completionValue()); return false;
// Step 10.b.i. // Steps 7-8.
if (completionKind == CompletionKind::Return) { break;
// Steps 10.b.i.1. }
asyncGenObj->setAwaitingReturn(); case ResumeNextKind::Resolve: {
// 11.4.3.3 AsyncGeneratorResolve ( generator, value, done )
HandleValue value = valueOrException;
// Steps 10.b.i.4-6 (reordered). // Step 1 (implicit).
RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled));
RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorResumeNextReturnRejected));
// Steps 10.b.i.2-3, 7-10. // Steps 2-3.
auto extra = [&](Handle<PromiseReactionRecord*> reaction) { MOZ_ASSERT(!asyncGenObj->isQueueEmpty());
reaction->setIsAsyncGenerator(asyncGenObj);
};
return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra);
}
// Step 10.b.ii.1. // Step 4.
MOZ_ASSERT(completionKind == CompletionKind::Throw); Rooted<AsyncGeneratorRequest*> request(
cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj));
if (!request)
return false;
// Steps 10.b.ii.2-3. // Step 5.
return AsyncGeneratorReject(cx, asyncGenObj, value); RootedObject resultPromise(cx, request->promise());
asyncGenObj->cacheRequest(request);
// Step 6.
RootedObject resultObj(cx, CreateIterResultObject(cx, value, done));
if (!resultObj)
return false;
RootedValue resultValue(cx, ObjectValue(*resultObj));
// Step 7.
if (!ResolvePromiseInternal(cx, resultPromise, resultValue))
return false;
// Steps 8-9.
break;
}
} }
} else if (asyncGenObj->isCompleted()) {
// Step 11. // Step 1 (implicit).
return AsyncGeneratorResolve(cx, asyncGenObj, UndefinedHandleValue, true);
// Steps 2-3.
MOZ_ASSERT(!asyncGenObj->isExecuting());
// Step 4.
if (asyncGenObj->isAwaitingYieldReturn() || asyncGenObj->isAwaitingReturn())
return true;
// Steps 5-6.
if (asyncGenObj->isQueueEmpty())
return true;
// Steps 7-8.
Rooted<AsyncGeneratorRequest*> request(
cx, AsyncGeneratorObject::peekRequest(cx, asyncGenObj));
if (!request)
return false;
// Step 9.
CompletionKind completionKind = request->completionKind();
// Step 10.
if (completionKind != CompletionKind::Normal) {
// Step 10.a.
if (asyncGenObj->isSuspendedStart())
asyncGenObj->setCompleted();
// Step 10.b.
if (asyncGenObj->isCompleted()) {
RootedValue value(cx, request->completionValue());
// Step 10.b.i.
if (completionKind == CompletionKind::Return) {
// Steps 10.b.i.1.
asyncGenObj->setAwaitingReturn();
// Steps 10.b.i.4-6 (reordered).
static constexpr int32_t ResumeNextReturnFulfilled =
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.
auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
reaction->setIsAsyncGenerator(asyncGenObj);
};
return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra);
}
// Step 10.b.ii.1.
MOZ_ASSERT(completionKind == CompletionKind::Throw);
// Steps 10.b.ii.2-3.
kind = ResumeNextKind::Reject;
valueOrException.set(value);
// |done| is unused for ResumeNextKind::Reject.
continue;
}
} else if (asyncGenObj->isCompleted()) {
// Step 11.
kind = ResumeNextKind::Resolve;
valueOrException.setUndefined();
done = true;
continue;
}
// Step 12.
MOZ_ASSERT(asyncGenObj->isSuspendedStart() || asyncGenObj->isSuspendedYield());
// Step 16 (reordered).
asyncGenObj->setExecuting();
RootedValue argument(cx, request->completionValue());
if (completionKind == CompletionKind::Return) {
// 11.4.3.7 AsyncGeneratorYield step 8.b-e.
// Since we don't have the place that handles return from yield
// inside the generator, handle the case here, with extra state
// State_AwaitingYieldReturn.
asyncGenObj->setAwaitingYieldReturn();
static constexpr int32_t YieldReturnAwaitedFulfilled =
PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled;
static constexpr int32_t YieldReturnAwaitedRejected =
PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected;
RootedValue onFulfilled(cx, Int32Value(YieldReturnAwaitedFulfilled));
RootedValue onRejected(cx, Int32Value(YieldReturnAwaitedRejected));
auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
reaction->setIsAsyncGenerator(asyncGenObj);
};
return InternalAwait(cx, argument, nullptr, onFulfilled, onRejected, extra);
}
// Steps 13-15, 17-21.
return AsyncGeneratorResume(cx, asyncGenObj, completionKind, argument);
} }
// Step 12.
MOZ_ASSERT(asyncGenObj->isSuspendedStart() || asyncGenObj->isSuspendedYield());
// Step 16 (reordered).
asyncGenObj->setExecuting();
RootedValue argument(cx, request->completionValue());
if (completionKind == CompletionKind::Return) {
// 11.4.3.7 AsyncGeneratorYield step 8.b-e.
// Since we don't have the place that handles return from yield
// inside the generator, handle the case here, with extra state
// State_AwaitingYieldReturn.
asyncGenObj->setAwaitingYieldReturn();
RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled));
RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected));
auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
reaction->setIsAsyncGenerator(asyncGenObj);
};
return InternalAwait(cx, argument, nullptr, onFulfilled, onRejected, extra);
}
// Steps 13-15, 17-21.
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();
}