diff options
author | janekptacijarabaci <janekptacijarabaci@seznam.cz> | 2018-03-25 16:22:38 +0200 |
---|---|---|
committer | janekptacijarabaci <janekptacijarabaci@seznam.cz> | 2018-03-25 16:22:38 +0200 |
commit | b311e8fa718e2a32805c25847781cc8a6a0541bd (patch) | |
tree | 458018bc532e6a56878c901424cdf2ddc5595694 /js/src/frontend/BytecodeEmitter.cpp | |
parent | 2818e8dedb4a9a6cf40688ad42bf2be05e2f26d2 (diff) | |
download | UXP-b311e8fa718e2a32805c25847781cc8a6a0541bd.tar UXP-b311e8fa718e2a32805c25847781cc8a6a0541bd.tar.gz UXP-b311e8fa718e2a32805c25847781cc8a6a0541bd.tar.lz UXP-b311e8fa718e2a32805c25847781cc8a6a0541bd.tar.xz UXP-b311e8fa718e2a32805c25847781cc8a6a0541bd.zip |
Bug 1342553, Bug 1343072, Bug 1344753 (details in the description)
Bug 1342553 - Part 0.1: Use try-catch for IteratorClose in for-of
Bug 1343072 - Update HasLiveStackValueAtDepth to follow the change in
JSTRY_FOR_OF
Bug 1344753 - Update for-of stack depth in
ControlFlowGenerator::processWhileOrForInLoop
Issue #74
Diffstat (limited to 'js/src/frontend/BytecodeEmitter.cpp')
-rw-r--r-- | js/src/frontend/BytecodeEmitter.cpp | 453 |
1 files changed, 306 insertions, 147 deletions
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 4a60f1cf1..4d6ff6305 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -244,9 +244,9 @@ class LoopControl : public BreakableControl loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1; int loopSlots; - if (loopKind == StatementKind::Spread) + if (loopKind == StatementKind::Spread || loopKind == StatementKind::ForOfLoop) loopSlots = 3; - else if (loopKind == StatementKind::ForInLoop || loopKind == StatementKind::ForOfLoop) + else if (loopKind == StatementKind::ForInLoop) loopSlots = 2; else loopSlots = 0; @@ -278,64 +278,6 @@ class LoopControl : public BreakableControl } }; -class ForOfLoopControl : public LoopControl -{ - // The stack depth of the iterator. - int32_t iterDepth_; - - // for-of loops, when throwing from non-iterator code (i.e. from the body - // or from evaluating the LHS of the loop condition), need to call - // IteratorClose. If IteratorClose itself throws, we must not re-call - // IteratorClose. Since non-local jumps like break and return call - // IteratorClose, whenever a non-local jump is emitted, we must terminate - // the current JSTRY_ITERCLOSE note to skip the non-local jump code, then - // start a new one. - // - // Visually, - // - // for (x of y) { - // ... instantiate ForOfLoopControl - // ... + <-- iterCloseTryStart_ points to right before - // ... assignment to loop variable - // ... ^ - // ... | - // if (...) v - // + call finishIterCloseTryNote before |break| - // above range is noted with JSTRY_ITERCLOSE - // - // break; <-- break and IteratorClose are not inside - // JSTRY_ITERCLOSE note - // - // call startNewIterCloseTryNote after |break| - // + <-- next iterCloseTryStart_ points here - // ... | - // ... ~ - // } - ptrdiff_t iterCloseTryStart_; - - public: - ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth) - : LoopControl(bce, StatementKind::ForOfLoop), - iterDepth_(iterDepth), - iterCloseTryStart_(-1) - { - MOZ_ASSERT(bce->stackDepth >= iterDepth); - } - - MOZ_MUST_USE bool finishIterCloseTryNote(BytecodeEmitter* bce) { - ptrdiff_t end = bce->offset(); - MOZ_ASSERT(end >= iterCloseTryStart_); - if (end != iterCloseTryStart_) - return bce->tryNoteList.append(JSTRY_ITERCLOSE, iterDepth_, iterCloseTryStart_, end); - return true; - } - - void startNewIterCloseTryNote(BytecodeEmitter* bce) { - MOZ_ASSERT(bce->offset() > iterCloseTryStart_); - iterCloseTryStart_ = bce->offset(); - } -}; - class TryFinallyControl : public BytecodeEmitter::NestableControl { bool emittingSubroutine_; @@ -2025,6 +1967,144 @@ class MOZ_STACK_CLASS IfThenElseEmitter #endif }; +class ForOfLoopControl : public LoopControl +{ + // The stack depth of the iterator. + int32_t iterDepth_; + + // for-of loops, when throwing from non-iterator code (i.e. from the body + // or from evaluating the LHS of the loop condition), need to call + // IteratorClose. This is done by enclosing non-iterator code with + // try-catch and call IteratorClose in `catch` block. + // If IteratorClose itself throws, we must not re-call IteratorClose. Since + // non-local jumps like break and return call IteratorClose, whenever a + // non-local jump is emitted, we must tell catch block not to perform + // IteratorClose. + // + // for (x of y) { + // // Operations for iterator (IteratorNext etc) are outside of + // // try-block. + // try { + // ... + // if (...) { + // // Before non-local jump, clear iterator on the stack to tell + // // catch block not to perform IteratorClose. + // tmpIterator = iterator; + // iterator = undefined; + // IteratorClose(tmpIterator, { break }); + // break; + // } + // ... + // } catch (e) { + // // Just throw again when iterator is cleared by non-local jump. + // if (iterator === undefined) + // throw e; + // IteratorClose(iterator, { throw, e }); + // } + // } + Maybe<TryEmitter> tryCatch_; + + bool allowSelfHosted_; + + public: + ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted) + : LoopControl(bce, StatementKind::ForOfLoop), + iterDepth_(iterDepth), + allowSelfHosted_(allowSelfHosted) + { + } + + bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) { + tryCatch_.emplace(bce, TryEmitter::TryCatch, TryEmitter::DontUseRetVal); + + if (!tryCatch_->emitTry()) + return false; + return true; + } + + bool emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) { + if (!tryCatch_->emitCatch()) // ITER ... + return false; + + if (!bce->emit1(JSOP_EXCEPTION)) // ITER ... EXCEPTION + return false; + unsigned slotFromTop = bce->stackDepth - iterDepth_; + if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER + return false; + + // If ITER is undefined, it means the exception is thrown by + // IteratorClose for non-local jump, and we should't perform + // IteratorClose again here. + if (!bce->emit1(JSOP_UNDEFINED)) // ITER ... EXCEPTION ITER UNDEF + return false; + if (!bce->emit1(JSOP_STRICTNE)) // ITER ... EXCEPTION NE + return false; + + IfThenElseEmitter ifIteratorIsNotClosed(bce); + if (!ifIteratorIsNotClosed.emitIf()) // ITER ... EXCEPTION + return false; + + MOZ_ASSERT(slotFromTop == unsigned(bce->stackDepth - iterDepth_)); + if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER + return false; + if (!emitIteratorClose(bce, CompletionKind::Throw)) // ITER ... EXCEPTION + return false; + + if (!ifIteratorIsNotClosed.emitEnd()) // ITER ... EXCEPTION + return false; + + if (!bce->emit1(JSOP_THROW)) // ITER ... + return false; + + if (!tryCatch_->emitEnd()) + return false; + + tryCatch_.reset(); + return true; + } + + bool emitIteratorClose(BytecodeEmitter* bce, + CompletionKind completionKind = CompletionKind::Normal) { + return bce->emitIteratorClose(completionKind, allowSelfHosted_); + } + + bool emitPrepareForNonLocalJump(BytecodeEmitter* bce, bool isTarget) { + // Pop unnecessary values from the stack. Effectively this means + // leaving try-catch block. However, the performing IteratorClose can + // reach the depth for try-catch, and effectively re-enter the + // try-catch block. + if (!bce->emit1(JSOP_POP)) // ITER RESULT + return false; + if (!bce->emit1(JSOP_POP)) // ITER + return false; + + // Clear ITER slot on the stack to tell catch block to avoid performing + // IteratorClose again. + if (!bce->emit1(JSOP_UNDEFINED)) // ITER UNDEF + return false; + if (!bce->emit1(JSOP_SWAP)) // UNDEF ITER + return false; + + if (!emitIteratorClose(bce)) // UNDEF + return false; + + if (isTarget) { + // At the level of the target block, there's bytecode after the + // loop that will pop the iterator and the value, so push + // undefineds to balance the stack. + if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF + return false; + if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF UNDEF + return false; + } else { + if (!bce->emit1(JSOP_POP)) // + return false; + } + + return true; + } +}; + BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, Parser<FullParseHandler>* parser, SharedContext* sc, HandleScript script, Handle<LazyScript*> lazyScript, @@ -2383,6 +2463,12 @@ BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) return emit2(JSOP_CHECKISOBJ, uint8_t(kind)); } +bool +BytecodeEmitter::emitCheckIsCallable(CheckIsCallableKind kind) +{ + return emit2(JSOP_CHECKISCALLABLE, uint8_t(kind)); +} + static inline unsigned LengthOfSetLine(unsigned line) { @@ -2624,10 +2710,8 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta EmitterScope* es = bce_->innermostEmitterScope; int npops = 0; - bool hasForOfLoopsWithIteratorClose = false; - // IteratorClose is handled specially in the exception unwinder. For - // 'continue', 'break', and 'return' statements, emit IteratorClose + // For 'continue', 'break', and 'return' statements, emit IteratorClose // bytecode inline. 'continue' statements do not call IteratorClose for // the loop they are continuing. bool emitIteratorClose = kind_ == Continue || kind_ == Break || kind_ == Return; @@ -2664,31 +2748,22 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta } else { if (!flushPops(bce_)) return false; - if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs)) + if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs)) // ... return false; } break; } case StatementKind::ForOfLoop: - if (!flushPops(bce_)) - return false; - - // The iterator and the current value are on the stack. - // if (emitIteratorClose) { - hasForOfLoopsWithIteratorClose = true; - if (!control->as<ForOfLoopControl>().finishIterCloseTryNote(bce_)) - return false; - if (!bce_->emit1(JSOP_POP)) // ... ITER + if (!flushPops(bce_)) return false; - if (!bce_->emitIteratorClose()) // ... + + ForOfLoopControl& loopinfo = control->as<ForOfLoopControl>(); + if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ false)) // ... return false; } else { - if (!bce_->emit1(JSOP_POP)) // ... ITER - return false; - if (!bce_->emit1(JSOP_POP)) // ... - return false; + npops += 3; } break; @@ -2711,18 +2786,9 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta if (!flushPops(bce_)) return false; - if (target && target->is<ForOfLoopControl>() && emitIteratorCloseAtTarget) { - hasForOfLoopsWithIteratorClose = true; - if (!target->as<ForOfLoopControl>().finishIterCloseTryNote(bce_)) - return false; - - // The iterator and the current value are on the stack. At the level - // of the target block, there's bytecode after the loop that will pop - // the iterator and the value, so duplicate the iterator and call - // IteratorClose. - if (!bce_->emitDupAt(1)) // ... ITER RESULT ITER - return false; - if (!bce_->emitIteratorClose()) // ... ITER RESULT + if (target && emitIteratorCloseAtTarget && target->is<ForOfLoopControl>()) { + ForOfLoopControl& loopinfo = target->as<ForOfLoopControl>(); + if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ true)) // ... UNDEF UNDEF UNDEF return false; } @@ -2732,20 +2798,6 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta return false; } - // See comment in ForOfLoopControl. - if (hasForOfLoopsWithIteratorClose) { - for (NestableControl* control = bce_->innermostNestableControl; - control != target; - control = control->enclosing()) - { - if (control->is<ForOfLoopControl>()) - control->as<ForOfLoopControl>().startNewIterCloseTryNote(bce_); - } - - if (target && target->is<ForOfLoopControl>() && emitIteratorCloseAtTarget) - target->as<ForOfLoopControl>().startNewIterCloseTryNote(bce_); - } - return true; } @@ -5120,7 +5172,7 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri } bool -BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) +BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted /* = false */) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".next() iteration is prohibited in self-hosted code because it " @@ -5141,7 +5193,8 @@ BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) } bool -BytecodeEmitter::emitIteratorClose(bool allowSelfHosted) +BytecodeEmitter::emitIteratorClose(CompletionKind completionKind /* = CompletionKind::Normal */, + bool allowSelfHosted /* = false */) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".close() on iterators is prohibited in self-hosted code because it " @@ -5174,17 +5227,86 @@ BytecodeEmitter::emitIteratorClose(bool allowSelfHosted) if (!ifReturnMethodIsDefined.emitIfElse()) return false; + if (completionKind == CompletionKind::Throw) { + // 7.4.6 IteratorClose ( iterator, completion ) + // ... + // 3. Let return be ? GetMethod(iterator, "return"). + // 4. If return is undefined, return Completion(completion). + // 5. Let innerResult be Call(return, iterator, « »). + // 6. If completion.[[Type]] is throw, return Completion(completion). + // 7. If innerResult.[[Type]] is throw, return + // Completion(innerResult). + // + // For CompletionKind::Normal case, JSOP_CALL for step 5 checks if RET + // is callable, and throws if not. Since step 6 doesn't match and + // error handling in step 3 and step 7 can be merged. + // + // For CompletionKind::Throw case, an error thrown by JSOP_CALL for + // step 5 is ignored by try-catch. So we should check if RET is + // callable here, outside of try-catch, and the throw immediately if + // not. + CheckIsCallableKind kind = CheckIsCallableKind::IteratorReturn; + if (!emitCheckIsCallable(kind)) // ... ITER RET + return false; + } + // Steps 5, 8. // // Call "return" if it is not undefined or null, and check that it returns // an Object. if (!emit1(JSOP_SWAP)) // ... RET ITER return false; - if (!emitCall(JSOP_CALL, 0)) // ... RESULT + + Maybe<TryEmitter> tryCatch; + + if (completionKind == CompletionKind::Throw) { + tryCatch.emplace(this, TryEmitter::TryCatch, TryEmitter::DontUseRetVal, + TryEmitter::DontUseControl); + + // Mutate stack to balance stack for try-catch. + if (!emit1(JSOP_UNDEFINED)) // ... RET ITER UNDEF + return false; + if (!tryCatch->emitTry()) // ... RET ITER UNDEF + return false; + if (!emitDupAt(2)) // ... RET ITER UNDEF RET + return false; + if (!emitDupAt(2)) // ... RET ITER UNDEF RET ITER + return false; + } + + if (!emitCall(JSOP_CALL, 0)) // ... ... RESULT return false; checkTypeSet(JSOP_CALL); - if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT - return false; + + if (completionKind == CompletionKind::Throw) { + if (!emit1(JSOP_SWAP)) // ... RET ITER RESULT UNDEF + return false; + if (!emit1(JSOP_POP)) // ... RET ITER RESULT + return false; + + if (!tryCatch->emitCatch()) // ... RET ITER RESULT + return false; + + // Just ignore the exception thrown by call. + if (!emit1(JSOP_EXCEPTION)) // ... RET ITER RESULT EXC + return false; + if (!emit1(JSOP_POP)) // ... RET ITER RESULT + return false; + + if (!tryCatch->emitEnd()) // ... RET ITER RESULT + return false; + + // Restore stack. + if (!emit2(JSOP_UNPICK, 2)) // ... RESULT RET ITER + return false; + if (!emit1(JSOP_POP)) // ... RESULT RET + return false; + if (!emit1(JSOP_POP)) // ... RESULT + return false; + } else { + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT + return false; + } if (!ifReturnMethodIsDefined.emitElse()) return false; @@ -6749,8 +6871,19 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte MOZ_ASSERT(forOfHead->isKind(PNK_FOROF)); MOZ_ASSERT(forOfHead->isArity(PN_TERNARY)); - // Evaluate the expression being iterated. ParseNode* forHeadExpr = forOfHead->pn_kid3; + + // Certain builtins (e.g. Array.from) are implemented in self-hosting + // as for-of loops. + bool allowSelfHostedIter = false; + if (emitterMode == BytecodeEmitter::SelfHosting && + forHeadExpr->isKind(PNK_CALL) && + forHeadExpr->pn_head->name() == cx->names().allowContentIter) + { + allowSelfHostedIter = true; + } + + // Evaluate the expression being iterated. if (!emitTree(forHeadExpr)) // ITERABLE return false; if (!emitIterator()) // ITER @@ -6758,12 +6891,14 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte int32_t iterDepth = stackDepth; - // For-of loops have both the iterator and the value on the stack. Push - // undefined to balance the stack. + // For-of loops have both the iterator, the result, and the result.value + // on the stack. Push undefineds to balance the stack. if (!emit1(JSOP_UNDEFINED)) // ITER RESULT return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF + return false; - ForOfLoopControl loopInfo(this, iterDepth); + ForOfLoopControl loopInfo(this, iterDepth, allowSelfHostedIter); // Annotate so IonMonkey can find the loop-closing jump. unsigned noteIndex; @@ -6771,11 +6906,11 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte return false; JumpList initialJump; - if (!emitJump(JSOP_GOTO, &initialJump)) // ITER RESULT + if (!emitJump(JSOP_GOTO, &initialJump)) // ITER RESULT UNDEF return false; JumpTarget top{ -1 }; - if (!emitLoopHead(nullptr, &top)) // ITER RESULT + if (!emitLoopHead(nullptr, &top)) // ITER RESULT UNDEF return false; // If the loop had an escaping lexical declaration, replace the current @@ -6792,7 +6927,7 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); if (headLexicalEmitterScope->hasEnvironment()) { - if (!emit1(JSOP_RECREATELEXICALENV)) // ITER RESULT + if (!emit1(JSOP_RECREATELEXICALENV)) // ITER RESULT UNDEF return false; } @@ -6812,61 +6947,66 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte // // Note that ES 13.7.5.13, step 5.c says getting result.value does not // call IteratorClose, so start JSTRY_ITERCLOSE after the GETPROP. + if (!emit1(JSOP_POP)) // ITER RESULT + return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE return false; - loopInfo.startNewIterCloseTryNote(this); - - if (!emitInitializeForInOrOfTarget(forOfHead)) // ITER RESULT VALUE + if (!loopInfo.emitBeginCodeNeedingIteratorClose(this)) return false; - if (!emit1(JSOP_POP)) // ITER RESULT + if (!emitInitializeForInOrOfTarget(forOfHead)) // ITER RESULT VALUE return false; MOZ_ASSERT(stackDepth == loopDepth, "the stack must be balanced around the initializing " "operation"); + // Remove VALUE from the stack to release it. + if (!emit1(JSOP_POP)) // ITER RESULT + return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF + return false; + // Perform the loop body. ParseNode* forBody = forOfLoop->pn_right; - if (!emitTree(forBody)) // ITER RESULT + if (!emitTree(forBody)) // ITER RESULT UNDEF return false; - if (!loopInfo.finishIterCloseTryNote(this)) + MOZ_ASSERT(stackDepth == loopDepth, + "the stack must be balanced around the for-of body"); + + if (!loopInfo.emitEndCodeNeedingIteratorClose(this)) return false; // Set offset for continues. loopInfo.continueTarget = { offset() }; - if (!emitLoopEntry(forHeadExpr, initialJump)) // ITER RESULT + if (!emitLoopEntry(forHeadExpr, initialJump)) // ITER RESULT UNDEF return false; - if (!emit1(JSOP_POP)) // ITER + if (!emit1(JSOP_SWAP)) // ITER UNDEF RESULT + return false; + if (!emit1(JSOP_POP)) // ITER UNDEF return false; - if (!emit1(JSOP_DUP)) // ITER ITER + if (!emitDupAt(1)) // ITER UNDEF ITER return false; - // Certain builtins (e.g. Array.from) are implemented in self-hosting - // as for-of loops. - bool allowSelfHostedIter = false; - if (emitterMode == BytecodeEmitter::SelfHosting && - forHeadExpr->isKind(PNK_CALL) && - forHeadExpr->pn_head->name() == cx->names().allowContentIter) - { - allowSelfHostedIter = true; - } + if (!emitIteratorNext(forOfHead, allowSelfHostedIter)) // ITER UNDEF RESULT + return false; - if (!emitIteratorNext(forOfHead, allowSelfHostedIter)) // ITER RESULT + if (!emit1(JSOP_SWAP)) // ITER RESULT UNDEF return false; - if (!emit1(JSOP_DUP)) // ITER RESULT RESULT + + if (!emitDupAt(1)) // ITER RESULT UNDEF RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT UNDEF DONE return false; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) - return false; // ITER RESULT + return false; // ITER RESULT UNDEF MOZ_ASSERT(this->stackDepth == loopDepth); } @@ -6881,7 +7021,7 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset)) return false; - return emitUint16Operand(JSOP_POPN, 2); // + return emitUint16Operand(JSOP_POPN, 3); // } bool @@ -7291,6 +7431,8 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) // Push a dummy result so that we properly enter iteration midstream. if (!emit1(JSOP_UNDEFINED)) // ITER RESULT return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT VALUE + return false; // Enter the block before the loop body, after evaluating the obj. // Initialize let bindings with undefined when entering, as the name @@ -7328,42 +7470,59 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) #endif // Emit code to assign result.value to the iteration variable. + if (!emit1(JSOP_POP)) // ITER RESULT + return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE return false; + + // Notice: Comprehension for-of doesn't perform IteratorClose, since it's + // not in the spec. + if (!emitAssignment(loopVariableName, JSOP_NOP, nullptr)) // ITER RESULT VALUE return false; + + // Remove VALUE from the stack to release it. if (!emit1(JSOP_POP)) // ITER RESULT return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF + return false; // The stack should be balanced around the assignment opcode sequence. MOZ_ASSERT(this->stackDepth == loopDepth); // Emit code for the loop body. - if (!emitTree(forBody)) + if (!emitTree(forBody)) // ITER RESULT UNDEF return false; + // The stack should be balanced around the assignment opcode sequence. + MOZ_ASSERT(this->stackDepth == loopDepth); + // Set offset for continues. loopInfo.continueTarget = { offset() }; if (!emitLoopEntry(forHeadExpr, jmp)) return false; - if (!emit1(JSOP_POP)) // ITER + if (!emit1(JSOP_SWAP)) // ITER UNDEF RESULT return false; - if (!emit1(JSOP_DUP)) // ITER ITER + if (!emit1(JSOP_POP)) // ITER UNDEF return false; - if (!emitIteratorNext(forHead)) // ITER RESULT + if (!emitDupAt(1)) // ITER UNDEF ITER return false; - if (!emit1(JSOP_DUP)) // ITER RESULT RESULT + if (!emitIteratorNext(forHead)) // ITER UNDEF RESULT + return false; + if (!emit1(JSOP_SWAP)) // ITER RESULT UNDEF + return false; + if (!emitDupAt(1)) // ITER RESULT UNDEF RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT UNDEF DONE return false; JumpList beq; JumpTarget breakTarget{ -1 }; - if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER RESULT + if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER RESULT UNDEF return false; MOZ_ASSERT(this->stackDepth == loopDepth); @@ -7385,7 +7544,7 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) } // Pop the result and the iter. - return emitUint16Operand(JSOP_POPN, 2); // + return emitUint16Operand(JSOP_POPN, 3); // } bool |