diff options
author | janekptacijarabaci <janekptacijarabaci@seznam.cz> | 2018-03-24 12:09:30 +0100 |
---|---|---|
committer | janekptacijarabaci <janekptacijarabaci@seznam.cz> | 2018-03-24 12:09:30 +0100 |
commit | 7d753c1a8f22f85f6279a3c016034ce8f8e740f7 (patch) | |
tree | 7d3dba690a5689a04d4f2c2b544accaf8621634a /js/src/frontend | |
parent | 2a57d73c3b5304be3f9be51018a1bbee79f007e2 (diff) | |
download | UXP-7d753c1a8f22f85f6279a3c016034ce8f8e740f7.tar UXP-7d753c1a8f22f85f6279a3c016034ce8f8e740f7.tar.gz UXP-7d753c1a8f22f85f6279a3c016034ce8f8e740f7.tar.lz UXP-7d753c1a8f22f85f6279a3c016034ce8f8e740f7.tar.xz UXP-7d753c1a8f22f85f6279a3c016034ce8f8e740f7.zip |
Bug 1147371: Implement IteratorClose for for-of
Issue #74
Diffstat (limited to 'js/src/frontend')
-rw-r--r-- | js/src/frontend/BytecodeEmitter.cpp | 619 | ||||
-rw-r--r-- | js/src/frontend/BytecodeEmitter.h | 6 |
2 files changed, 461 insertions, 164 deletions
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index a4cfeb753..fb141e92d 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -58,6 +58,7 @@ using mozilla::Some; class BreakableControl; class LabelControl; class LoopControl; +class ForOfLoopControl; class TryFinallyControl; static bool @@ -152,6 +153,13 @@ BytecodeEmitter::NestableControl::is<LoopControl>() const template <> bool +BytecodeEmitter::NestableControl::is<ForOfLoopControl>() const +{ + return kind_ == StatementKind::ForOfLoop; +} + +template <> +bool BytecodeEmitter::NestableControl::is<TryFinallyControl>() const { return kind_ == StatementKind::Try || kind_ == StatementKind::Finally; @@ -270,6 +278,64 @@ 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_; @@ -1497,6 +1563,156 @@ BytecodeEmitter::TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, return true; } +class MOZ_STACK_CLASS IfThenElseEmitter +{ + BytecodeEmitter* bce_; + JumpList jumpAroundThen_; + JumpList jumpsAroundElse_; + unsigned noteIndex_; + int32_t thenDepth_; +#ifdef DEBUG + int32_t pushed_; + bool calculatedPushed_; +#endif + enum State { + Start, + If, + Cond, + IfElse, + Else, + End + }; + State state_; + + public: + explicit IfThenElseEmitter(BytecodeEmitter* bce) + : bce_(bce), + noteIndex_(-1), + thenDepth_(0), +#ifdef DEBUG + pushed_(0), + calculatedPushed_(false), +#endif + state_(Start) + {} + + ~IfThenElseEmitter() + {} + + private: + bool emitIf(State nextState) { + MOZ_ASSERT(state_ == Start || state_ == Else); + MOZ_ASSERT(nextState == If || nextState == IfElse || nextState == Cond); + + // Clear jumpAroundThen_ offset that points previous JSOP_IFEQ. + if (state_ == Else) + jumpAroundThen_ = JumpList(); + + // Emit an annotated branch-if-false around the then part. + SrcNoteType type = nextState == If ? SRC_IF : nextState == IfElse ? SRC_IF_ELSE : SRC_COND; + if (!bce_->newSrcNote(type, ¬eIndex_)) + return false; + if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_)) + return false; + + // To restore stack depth in else part, save depth of the then part. +#ifdef DEBUG + // If DEBUG, this is also necessary to calculate |pushed_|. + thenDepth_ = bce_->stackDepth; +#else + if (nextState == IfElse || nextState == Cond) + thenDepth_ = bce_->stackDepth; +#endif + state_ = nextState; + return true; + } + + public: + bool emitIf() { + return emitIf(If); + } + + bool emitCond() { + return emitIf(Cond); + } + + bool emitIfElse() { + return emitIf(IfElse); + } + + bool emitElse() { + MOZ_ASSERT(state_ == IfElse || state_ == Cond); + + calculateOrCheckPushed(); + + // Emit a jump from the end of our then part around the else part. The + // patchJumpsToTarget call at the bottom of this function will fix up + // the offset with jumpsAroundElse value. + if (!bce_->emitJump(JSOP_GOTO, &jumpsAroundElse_)) + return false; + + // Ensure the branch-if-false comes here, then emit the else. + if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) + return false; + + // Annotate SRC_IF_ELSE or SRC_COND with the offset from branch to + // jump, for IonMonkey's benefit. We can't just "back up" from the pc + // of the else clause, because we don't know whether an extended + // jump was required to leap from the end of the then clause over + // the else clause. + if (!bce_->setSrcNoteOffset(noteIndex_, 0, + jumpsAroundElse_.offset - jumpAroundThen_.offset)) + { + return false; + } + + // Restore stack depth of the then part. + bce_->stackDepth = thenDepth_; + state_ = Else; + return true; + } + + bool emitEnd() { + MOZ_ASSERT(state_ == If || state_ == Else); + + calculateOrCheckPushed(); + + if (state_ == If) { + // No else part, fixup the branch-if-false to come here. + if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) + return false; + } + + // Patch all the jumps around else parts. + if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_)) + return false; + + state_ = End; + return true; + } + + void calculateOrCheckPushed() { +#ifdef DEBUG + if (!calculatedPushed_) { + pushed_ = bce_->stackDepth - thenDepth_; + calculatedPushed_ = true; + } else { + MOZ_ASSERT(pushed_ == bce_->stackDepth - thenDepth_); + } +#endif + } + +#ifdef DEBUG + int32_t pushed() const { + return pushed_; + } + + int32_t popped() const { + return -pushed_; + } +#endif +}; + BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, Parser<FullParseHandler>* parser, SharedContext* sc, HandleScript script, Handle<LazyScript*> lazyScript, @@ -2013,22 +2229,43 @@ BytecodeEmitter::flushPops(int* npops) namespace { -class NonLocalExitControl { +class NonLocalExitControl +{ + public: + enum Kind + { + // IteratorClose is handled especially inside the exception unwinder. + Throw, + + // A 'continue' statement does not call IteratorClose for the loop it + // is continuing, i.e. excluding the target loop. + Continue, + + // A 'break' or 'return' statement does call IteratorClose for the + // loop it is breaking out of or returning from, i.e. including the + // target loop. + Break, + Return + }; + + private: BytecodeEmitter* bce_; const uint32_t savedScopeNoteIndex_; const int savedDepth_; uint32_t openScopeNoteIndex_; + Kind kind_; NonLocalExitControl(const NonLocalExitControl&) = delete; MOZ_MUST_USE bool leaveScope(BytecodeEmitter::EmitterScope* scope); public: - explicit NonLocalExitControl(BytecodeEmitter* bce) + NonLocalExitControl(BytecodeEmitter* bce, Kind kind) : bce_(bce), savedScopeNoteIndex_(bce->scopeNoteList.length()), savedDepth_(bce->stackDepth), - openScopeNoteIndex_(bce->innermostEmitterScope->noteIndex()) + openScopeNoteIndex_(bce->innermostEmitterScope->noteIndex()), + kind_(kind) { } ~NonLocalExitControl() { @@ -2075,6 +2312,14 @@ 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 + // bytecode inline. 'continue' statements do not call IteratorClose for + // the loop they are continuing. + bool emitIteratorClose = kind_ == Continue || kind_ == Break || kind_ == Return; + bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != Continue; auto flushPops = [&npops](BytecodeEmitter* bce) { if (npops && !bce->flushPops(&npops)) @@ -2114,15 +2359,29 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta } case StatementKind::ForOfLoop: - npops += 2; + // 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 + return false; + if (!bce_->emitIteratorClose()) // ... + return false; + } else { + if (!bce_->emit1(JSOP_POP)) // ... ITER + return false; + if (!bce_->emit1(JSOP_POP)) // ... + return false; + } break; case StatementKind::ForInLoop: - /* The iterator and the current value are on the stack. */ - npops += 1; - if (!flushPops(bce_)) + // The iterator and the current value are on the stack. + if (!bce_->emit1(JSOP_POP)) // ... ITER return false; - if (!bce_->emit1(JSOP_ENDITER)) + if (!bce_->emit1(JSOP_ENDITER)) // ... return false; break; @@ -2131,13 +2390,45 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta } } + 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 + return false; + } + EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope; for (; es != targetEmitterScope; es = es->enclosingInFrame()) { if (!leaveScope(es)) return false; } - return flushPops(bce_); + if (!flushPops(bce_)) + 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; } } // anonymous namespace @@ -2145,7 +2436,9 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta bool BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, SrcNoteType noteType) { - NonLocalExitControl nle(this); + NonLocalExitControl nle(this, noteType == SRC_CONTINUE + ? NonLocalExitControl::Continue + : NonLocalExitControl::Break); if (!nle.prepareForNonLocalJump(target)) return false; @@ -4530,6 +4823,144 @@ BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) } bool +BytecodeEmitter::emitIteratorClose(Maybe<JumpTarget> yieldStarTryStart, bool allowSelfHosted) +{ + MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, + ".close() on iterators is prohibited in self-hosted code because it " + "can run user-modifiable iteration code"); + + // Generate inline logic corresponding to IteratorClose (ES 7.4.6). + // + // Callers need to ensure that the iterator object is at the top of the + // stack. + + if (!emit1(JSOP_DUP)) // ... ITER ITER + return false; + + // Step 3. + // + // Get the "return" method. + if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) // ... ITER RET + return false; + + // Step 4. + // + // Do nothing if "return" is null or undefined. + IfThenElseEmitter ifReturnMethodIsDefined(this); + if (!emit1(JSOP_DUP)) // ... ITER RET RET + return false; + if (!emit1(JSOP_UNDEFINED)) // ... ITER RET RET UNDEFINED + return false; + if (!emit1(JSOP_NE)) // ... ITER RET ?NEQL + return false; + if (!ifReturnMethodIsDefined.emitIfElse()) + 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; + + // ES 14.4.13, yield * AssignmentExpression, step 5.c + // + // When emitting iterator.return() for yield* forced return, we need to + // pass the argument passed to Generator.prototype.return to the return + // method. + if (yieldStarTryStart) { + IfThenElseEmitter ifGeneratorClosing(this); + if (!emitDupAt(2)) // ... FTYPE FVALUE RET ITER FVALUE + return false; + if (!emit1(JSOP_ISGENCLOSING)) // ... FTYPE FVALUE RET ITER FVALUE CLOSING + return false; + if (!emit1(JSOP_SWAP)) // ... FTYPE FVALUE RET ITER CLOSING FVALUE + return false; + if (!emit1(JSOP_POP)) // ... FTYPE FVALUE RET ITER CLOSING + return false; + if (!ifGeneratorClosing.emitIfElse()) // ... FTYPE FVALUE RET ITER + return false; + + if (!emit1(JSOP_GETRVAL)) // ... FTYPE FVALUE RET ITER RVAL + return false; + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... FTYPE FVALUE RET ITER VALUE + return false; + if (!emitCall(JSOP_CALL, 1)) // ... FTYPE FVALUE RESULT + return false; + checkTypeSet(JSOP_CALL); + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... FTYPE FVALUE RESULT + return false; + + IfThenElseEmitter ifReturnDone(this); + if (!emit1(JSOP_DUP)) // ITER OLDRESULT FTYPE FVALUE RESULT RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE RESULT DONE + return false; + if (!ifReturnDone.emitIfElse()) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + if (!emit1(JSOP_DUP)) // ITER OLDRESULT FTYPE FVALUE RESULT RESULT + return false; + if (!emit1(JSOP_SETRVAL)) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + if (!ifReturnDone.emitElse()) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + int32_t savedDepth = this->stackDepth; + if (!emit2(JSOP_UNPICK, 3)) // ITER RESULT OLDRESULT FTYPE FVALUE + return false; + if (!emitUint16Operand(JSOP_POPN, 3)) // ITER RESULT + return false; + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_GOTO, *yieldStarTryStart, &beq, &breakTarget)) // ITER RESULT + return false; + this->stackDepth = savedDepth; + if (!ifReturnDone.emitEnd()) + return false; + + if (!ifGeneratorClosing.emitElse()) // ... FTYPE FVALUE RET ITER + return false; + if (!emitCall(JSOP_CALL, 0)) // ... FTYPE FVALUE RESULT + return false; + checkTypeSet(JSOP_CALL); + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... FTYPE FVALUE RESULT + return false; + + if (!ifGeneratorClosing.emitEnd()) + return false; + } else { + if (!emitCall(JSOP_CALL, 0)) // ... RESULT + return false; + checkTypeSet(JSOP_CALL); + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT + return false; + } + + if (!ifReturnMethodIsDefined.emitElse()) + return false; + if (!emit1(JSOP_POP)) // ... ITER + return false; + if (!ifReturnMethodIsDefined.emitEnd()) + return false; + + return emit1(JSOP_POP); // ... +} + +template <typename InnerEmitter> +bool +BytecodeEmitter::wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter) +{ + MOZ_ASSERT(this->stackDepth >= iterDepth); + + ptrdiff_t start = offset(); + if (!emitter(this)) + return false; + ptrdiff_t end = offset(); + if (start != end) + return tryNoteList.append(JSTRY_ITERCLOSE, iterDepth, start, end); + return true; +} + +bool BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { if (!emit1(JSOP_DUP)) // VALUE VALUE @@ -4617,156 +5048,6 @@ BytecodeEmitter::emitInitializerInBranch(ParseNode* initializer, ParseNode* patt return emitInitializer(initializer, pattern); } -class MOZ_STACK_CLASS IfThenElseEmitter -{ - BytecodeEmitter* bce_; - JumpList jumpAroundThen_; - JumpList jumpsAroundElse_; - unsigned noteIndex_; - int32_t thenDepth_; -#ifdef DEBUG - int32_t pushed_; - bool calculatedPushed_; -#endif - enum State { - Start, - If, - Cond, - IfElse, - Else, - End - }; - State state_; - - public: - explicit IfThenElseEmitter(BytecodeEmitter* bce) - : bce_(bce), - noteIndex_(-1), - thenDepth_(0), -#ifdef DEBUG - pushed_(0), - calculatedPushed_(false), -#endif - state_(Start) - {} - - ~IfThenElseEmitter() - {} - - private: - bool emitIf(State nextState) { - MOZ_ASSERT(state_ == Start || state_ == Else); - MOZ_ASSERT(nextState == If || nextState == IfElse || nextState == Cond); - - // Clear jumpAroundThen_ offset that points previous JSOP_IFEQ. - if (state_ == Else) - jumpAroundThen_ = JumpList(); - - // Emit an annotated branch-if-false around the then part. - SrcNoteType type = nextState == If ? SRC_IF : nextState == IfElse ? SRC_IF_ELSE : SRC_COND; - if (!bce_->newSrcNote(type, ¬eIndex_)) - return false; - if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_)) - return false; - - // To restore stack depth in else part, save depth of the then part. -#ifdef DEBUG - // If DEBUG, this is also necessary to calculate |pushed_|. - thenDepth_ = bce_->stackDepth; -#else - if (nextState == IfElse || nextState == Cond) - thenDepth_ = bce_->stackDepth; -#endif - state_ = nextState; - return true; - } - - public: - bool emitIf() { - return emitIf(If); - } - - bool emitCond() { - return emitIf(Cond); - } - - bool emitIfElse() { - return emitIf(IfElse); - } - - bool emitElse() { - MOZ_ASSERT(state_ == IfElse || state_ == Cond); - - calculateOrCheckPushed(); - - // Emit a jump from the end of our then part around the else part. The - // patchJumpsToTarget call at the bottom of this function will fix up - // the offset with jumpsAroundElse value. - if (!bce_->emitJump(JSOP_GOTO, &jumpsAroundElse_)) - return false; - - // Ensure the branch-if-false comes here, then emit the else. - if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) - return false; - - // Annotate SRC_IF_ELSE or SRC_COND with the offset from branch to - // jump, for IonMonkey's benefit. We can't just "back up" from the pc - // of the else clause, because we don't know whether an extended - // jump was required to leap from the end of the then clause over - // the else clause. - if (!bce_->setSrcNoteOffset(noteIndex_, 0, - jumpsAroundElse_.offset - jumpAroundThen_.offset)) - { - return false; - } - - // Restore stack depth of the then part. - bce_->stackDepth = thenDepth_; - state_ = Else; - return true; - } - - bool emitEnd() { - MOZ_ASSERT(state_ == If || state_ == Else); - - calculateOrCheckPushed(); - - if (state_ == If) { - // No else part, fixup the branch-if-false to come here. - if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) - return false; - } - - // Patch all the jumps around else parts. - if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_)) - return false; - - state_ = End; - return true; - } - - void calculateOrCheckPushed() { -#ifdef DEBUG - if (!calculatedPushed_) { - pushed_ = bce_->stackDepth - thenDepth_; - calculatedPushed_ = true; - } else { - MOZ_ASSERT(pushed_ == bce_->stackDepth - thenDepth_); - } -#endif - } - -#ifdef DEBUG - int32_t pushed() const { - return pushed_; - } - - int32_t popped() const { - return -pushed_; - } -#endif -}; - bool BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlavor flav) { @@ -5710,7 +5991,7 @@ BytecodeEmitter::emitCatch(ParseNode* pn) return false; { - NonLocalExitControl nle(this); + NonLocalExitControl nle(this, NonLocalExitControl::Throw); // Move exception back to cx->exception to prepare for // the next catch. @@ -6303,12 +6584,14 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte if (!emitIterator()) // ITER return false; + int32_t iterDepth = stackDepth; + // For-of loops have both the iterator and the value on the stack. Push // undefined to balance the stack. if (!emit1(JSOP_UNDEFINED)) // ITER RESULT return false; - LoopControl loopInfo(this, StatementKind::ForOfLoop); + ForOfLoopControl loopInfo(this, iterDepth); // Annotate so IonMonkey can find the loop-closing jump. unsigned noteIndex; @@ -6354,18 +6637,23 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte #endif // Emit code to assign result.value to the iteration variable. + // + // 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_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 return false; if (!emit1(JSOP_POP)) // ITER RESULT return false; - MOZ_ASSERT(this->stackDepth == loopDepth, + MOZ_ASSERT(stackDepth == loopDepth, "the stack must be balanced around the initializing " "operation"); @@ -6374,6 +6662,9 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte if (!emitTree(forBody)) // ITER RESULT return false; + if (!loopInfo.finishIterCloseTryNote(this)) + return false; + // Set offset for continues. loopInfo.continueTarget = { offset() }; @@ -7609,7 +7900,7 @@ BytecodeEmitter::emitReturn(ParseNode* pn) return false; } - NonLocalExitControl nle(this); + NonLocalExitControl nle(this, NonLocalExitControl::Return); if (!nle.prepareForNonLocalJumpToOutermost()) return false; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 066c06672..6b39069de 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -679,6 +679,12 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Pops iterator from the top of the stack. Pushes the result of |.next()| // onto the stack. MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, bool allowSelfHosted = false); + MOZ_MUST_USE bool emitIteratorClose( + mozilla::Maybe<JumpTarget> yieldStarTryStart = mozilla::Nothing(), + bool allowSelfHosted = false); + + template <typename InnerEmitter> + MOZ_MUST_USE bool wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter); // Check if the value on top of the stack is "undefined". If so, replace // that value on the stack with the value defined by |defaultExpr|. |