From 55eacd43a45cacbad13c13dda2e9253641540aa3 Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 22 Jan 2023 04:11:22 +0100 Subject: [PATCH] Issue #2089 - Turn AsyncGeneratorResumeNext recursion into iteration to avoid stack overflow Based-on: m-c 1416809 --- js/src/builtin/Promise.cpp | 305 ++++++++++-------- .../tests/auto-regress/bug1416809-1.js | 12 + .../tests/auto-regress/bug1416809-2.js | 12 + 3 files changed, 196 insertions(+), 133 deletions(-) create mode 100644 js/src/jit-test/tests/auto-regress/bug1416809-1.js create mode 100644 js/src/jit-test/tests/auto-regress/bug1416809-2.js diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 50c6c8f308..881eb6e774 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -3008,47 +3008,21 @@ js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind co return true; } +enum class ResumeNextKind { + Enqueue, Reject, Resolve +}; + static MOZ_MUST_USE bool -AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenObj); +AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenObj, + ResumeNextKind kind, HandleValue valueOrException = UndefinedHandleValue, + bool done = false); // Async Iteration proposal 11.4.3.3. MOZ_MUST_USE bool js::AsyncGeneratorResolve(JSContext* cx, Handle asyncGenObj, HandleValue value, bool done) { - // Step 1 (implicit). - - // Steps 2-3. - MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); - - // Step 4. - Rooted 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; + return AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Resolve, value, done); } // Async Iteration proposal 11.4.3.4. @@ -3056,123 +3030,188 @@ MOZ_MUST_USE bool js::AsyncGeneratorReject(JSContext* cx, Handle asyncGenObj, HandleValue exception) { - // Step 1 (implicit). - - // Steps 2-3. - MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); - - // Step 4. - Rooted 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; + return AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Reject, exception); } // Async Iteration proposal 11.4.3.5. static MOZ_MUST_USE bool -AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenObj) +AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenObj, + ResumeNextKind kind, + HandleValue valueOrException_ /* = UndefinedHandleValue */, + bool done /* = false */) { - // Step 1 (implicit). + RootedValue valueOrException(cx, valueOrException_); - // Steps 2-3. - MOZ_ASSERT(!asyncGenObj->isExecuting()); + 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 4. - if (asyncGenObj->isAwaitingYieldReturn() || asyncGenObj->isAwaitingReturn()) - return true; + // Step 1 (implicit). - // Steps 5-6. - if (asyncGenObj->isQueueEmpty()) - return true; + // Steps 2-3. + MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); - // Steps 7-8. - Rooted request( - cx, AsyncGeneratorObject::peekRequest(cx, asyncGenObj)); - if (!request) - return false; + // Step 4. + Rooted request( + cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj)); + if (!request) + return false; - // Step 9. - CompletionKind completionKind = request->completionKind(); + // Step 5. + RootedObject resultPromise(cx, request->promise()); - // Step 10. - if (completionKind != CompletionKind::Normal) { - // Step 10.a. - if (asyncGenObj->isSuspendedStart()) - asyncGenObj->setCompleted(); + asyncGenObj->cacheRequest(request); - // Step 10.b. - if (asyncGenObj->isCompleted()) { - RootedValue value(cx, request->completionValue()); + // Step 6. + if (!RejectMaybeWrappedPromise(cx, resultPromise, exception)) + return false; - // Step 10.b.i. - if (completionKind == CompletionKind::Return) { - // Steps 10.b.i.1. - asyncGenObj->setAwaitingReturn(); + // Steps 7-8. + break; + } + case ResumeNextKind::Resolve: { + // 11.4.3.3 AsyncGeneratorResolve ( generator, value, done ) + HandleValue value = valueOrException; - // Steps 10.b.i.4-6 (reordered). - RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled)); - RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorResumeNextReturnRejected)); + // Step 1 (implicit). - // Steps 10.b.i.2-3, 7-10. - auto extra = [&](Handle reaction) { - reaction->setIsAsyncGenerator(asyncGenObj); - }; - return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra); - } + // Steps 2-3. + MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); - // Step 10.b.ii.1. - MOZ_ASSERT(completionKind == CompletionKind::Throw); + // Step 4. + Rooted request( + cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj)); + if (!request) + return false; - // Steps 10.b.ii.2-3. - return AsyncGeneratorReject(cx, asyncGenObj, value); + // 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; + + // Steps 8-9. + break; + } } - } else if (asyncGenObj->isCompleted()) { - // Step 11. - return AsyncGeneratorResolve(cx, asyncGenObj, UndefinedHandleValue, true); + + // Step 1 (implicit). + + // 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 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 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 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 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. @@ -3221,7 +3260,7 @@ js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, // Step 7. if (!asyncGenObj->isExecuting()) { // Step 8. - if (!AsyncGeneratorResumeNext(cx, asyncGenObj)) + if (!AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Enqueue)) return false; } diff --git a/js/src/jit-test/tests/auto-regress/bug1416809-1.js b/js/src/jit-test/tests/auto-regress/bug1416809-1.js new file mode 100644 index 0000000000..067943575b --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/bug1416809-1.js @@ -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(); +} diff --git a/js/src/jit-test/tests/auto-regress/bug1416809-2.js b/js/src/jit-test/tests/auto-regress/bug1416809-2.js new file mode 100644 index 0000000000..95b20ec607 --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/bug1416809-2.js @@ -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(); +}