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
This commit is contained in:
janekptacijarabaci 2018-03-25 16:22:38 +02:00 committed by Roy Tam
commit 444483bf81
11 changed files with 378 additions and 189 deletions

View file

@ -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_))
if (!flushPops(bce_))
return false;
if (!bce_->emit1(JSOP_POP)) // ... ITER
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");
// Perform the loop body.
ParseNode* forBody = forOfLoop->pn_right;
if (!emitTree(forBody)) // ITER RESULT
// 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;
if (!loopInfo.finishIterCloseTryNote(this))
// Perform the loop body.
ParseNode* forBody = forOfLoop->pn_right;
if (!emitTree(forBody)) // ITER RESULT UNDEF
return false;
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_DUP)) // ITER ITER
if (!emit1(JSOP_POP)) // ITER UNDEF
return false;
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 (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE
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 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

View file

@ -473,6 +473,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter
// Helper to emit JSOP_CHECKISOBJ.
MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind);
// Helper to emit JSOP_CHECKISCALLABLE.
MOZ_MUST_USE bool emitCheckIsCallable(CheckIsCallableKind kind);
// Emit a bytecode followed by an uint16 immediate operand stored in
// big-endian order.
MOZ_MUST_USE bool emitUint16Operand(JSOp op, uint32_t operand);
@ -679,7 +682,8 @@ 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(bool allowSelfHosted = false);
MOZ_MUST_USE bool emitIteratorClose(CompletionKind completionKind = CompletionKind::Normal,
bool allowSelfHosted = false);
template <typename InnerEmitter>
MOZ_MUST_USE bool wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth,

View file

@ -0,0 +1,15 @@
// |jit-test| error:ReferenceError
// for-of should close iterator even if the exception is once caught by the
// debugger.
var g = newGlobal();
g.parent = this;
g.eval("new Debugger(parent).onExceptionUnwind = function () { };");
// jsfunfuzz-generated
for (var x of []) {};
for (var l of [0]) {
for (var y = 0; y < 1; y++) {
g2;
}
}

View file

@ -509,16 +509,10 @@ HasLiveStackValueAtDepth(JSScript* script, jsbytecode* pc, uint32_t stackDepth)
break;
case JSTRY_FOR_OF:
// For-of loops have both the iterator and the result object on
// stack. The iterator is below the result object.
if (stackDepth == tn->stackDepth - 1)
return true;
break;
case JSTRY_ITERCLOSE:
// Code that need to call IteratorClose have the iterator on the
// stack.
if (stackDepth == tn->stackDepth)
// For-of loops have the iterator, the result object, and the value
// of the result object on stack. The iterator is below the result
// object and the value.
if (stackDepth == tn->stackDepth - 2)
return true;
break;

View file

@ -331,7 +331,6 @@ static void
CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, JSTryNote* tn)
{
MOZ_ASSERT(tn->kind == JSTRY_FOR_IN ||
tn->kind == JSTRY_ITERCLOSE ||
tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE);
bool isDestructuring = tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE;
@ -442,7 +441,6 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx
switch (tn->kind) {
case JSTRY_FOR_IN:
case JSTRY_ITERCLOSE:
case JSTRY_DESTRUCTURING_ITERCLOSE:
MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN,
JSOp(*(script->main() + tn->start + tn->length)) == JSOP_ENDITER);
@ -637,19 +635,6 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen
break;
}
case JSTRY_ITERCLOSE: {
uint8_t* framePointer;
uint8_t* stackPointer;
BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer);
Value iterValue(*reinterpret_cast<Value*>(stackPointer));
RootedObject iterObject(cx, &iterValue.toObject());
if (!IteratorCloseForException(cx, iterObject)) {
SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
return false;
}
break;
}
case JSTRY_DESTRUCTURING_ITERCLOSE: {
uint8_t* framePointer;
uint8_t* stackPointer;

View file

@ -6622,5 +6622,14 @@ SetGetPerformanceGroupsCallback(JSContext*, GetGroupsCallback, void*);
} /* namespace js */
namespace js {
enum class CompletionKind {
Normal,
Return,
Throw
};
} /* namespace js */
#endif /* jsapi_h */

View file

@ -85,7 +85,6 @@ enum JSTryNoteKind {
JSTRY_FOR_IN,
JSTRY_FOR_OF,
JSTRY_LOOP,
JSTRY_ITERCLOSE,
JSTRY_DESTRUCTURING_ITERCLOSE
};

View file

@ -2599,8 +2599,6 @@ TryNoteName(JSTryNoteKind kind)
return "for-of";
case JSTRY_LOOP:
return "loop";
case JSTRY_ITERCLOSE:
return "iterclose";
case JSTRY_DESTRUCTURING_ITERCLOSE:
return "dstr-iterclose";
}

View file

@ -1189,17 +1189,6 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs)
break;
}
case JSTRY_ITERCLOSE: {
// The iterator object is at the top of the stack.
Value* sp = regs.spForStackDepth(tn->stackDepth);
RootedObject iterObject(cx, &sp[-1].toObject());
if (!IteratorCloseForException(cx, iterObject)) {
SettleOnTryNote(cx, tn, ei, regs);
return ErrorReturnContinuation;
}
break;
}
case JSTRY_DESTRUCTURING_ITERCLOSE: {
// Whether the destructuring iterator is done is at the top of the
// stack. The iterator object is second from the top.
@ -1892,7 +1881,6 @@ CASE(JSOP_UNUSED192)
CASE(JSOP_UNUSED209)
CASE(JSOP_UNUSED210)
CASE(JSOP_UNUSED211)
CASE(JSOP_UNUSED219)
CASE(JSOP_UNUSED220)
CASE(JSOP_UNUSED221)
CASE(JSOP_UNUSED222)
@ -2637,6 +2625,15 @@ CASE(JSOP_CHECKISOBJ)
}
END_CASE(JSOP_CHECKISOBJ)
CASE(JSOP_CHECKISCALLABLE)
{
if (!IsCallable(REGS.sp[-1])) {
MOZ_ALWAYS_FALSE(ThrowCheckIsCallable(cx, CheckIsCallableKind(GET_UINT8(REGS.pc))));
goto error;
}
}
END_CASE(JSOP_CHECKISCALLABLE)
CASE(JSOP_CHECKTHIS)
{
if (REGS.sp[-1].isMagic(JS_UNINITIALIZED_LEXICAL)) {
@ -5094,6 +5091,19 @@ js::ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind)
return false;
}
bool
js::ThrowCheckIsCallable(JSContext* cx, CheckIsCallableKind kind)
{
switch (kind) {
case CheckIsCallableKind::IteratorReturn:
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_RETURN_NOT_CALLABLE);
break;
default:
MOZ_CRASH("Unknown kind");
}
return false;
}
bool
js::ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame)
{

View file

@ -570,6 +570,13 @@ enum class CheckIsObjectKind : uint8_t {
bool
ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind);
enum class CheckIsCallableKind : uint8_t {
IteratorReturn
};
bool
ThrowCheckIsCallable(JSContext* cx, CheckIsCallableKind kind);
bool
ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame);

View file

@ -2201,7 +2201,16 @@
*/ \
macro(JSOP_HOLE, 218, "hole", NULL, 1, 0, 1, JOF_BYTE) \
\
macro(JSOP_UNUSED219, 219,"unused219", NULL, 1, 0, 0, JOF_BYTE) \
/*
* Checks that the top value on the stack is callable, and throws a
* TypeError if not. The operand 'kind' is used only to generate an
* appropriate error message.
* Category: Statements
* Type: Function
* Operands: uint8_t kind
* Stack: result => result, callable
*/ \
macro(JSOP_CHECKISCALLABLE, 219, "checkiscallable", NULL, 2, 1, 1, JOF_UINT8) \
macro(JSOP_UNUSED220, 220,"unused220", NULL, 1, 0, 0, JOF_BYTE) \
macro(JSOP_UNUSED221, 221,"unused221", NULL, 1, 0, 0, JOF_BYTE) \
macro(JSOP_UNUSED222, 222,"unused222", NULL, 1, 0, 0, JOF_BYTE) \