зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1147371 - Implement IteratorClose for array destructuring. (r=arai)
Since result.done is always needed now, always emit the code that pushes it on the stack. For throwing, like for-of, IteratorClose is only called when non-iterator code throws. Unlike for-of, both the iterator object and the done boolean value are on the stack for the trynote. IteratorClose is only called when !done.
This commit is contained in:
Родитель
a40ddf408f
Коммит
9c2567d79e
|
@ -4955,7 +4955,7 @@ BytecodeEmitter::emitIteratorClose(Maybe<JumpTarget> yieldStarTryStart, bool all
|
|||
|
||||
template <typename InnerEmitter>
|
||||
bool
|
||||
BytecodeEmitter::wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter)
|
||||
BytecodeEmitter::wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter)
|
||||
{
|
||||
MOZ_ASSERT(this->stackDepth >= iterDepth);
|
||||
|
||||
|
@ -4964,7 +4964,7 @@ BytecodeEmitter::wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter em
|
|||
return false;
|
||||
ptrdiff_t end = offset();
|
||||
if (start != end)
|
||||
return tryNoteList.append(JSTRY_ITERCLOSE, iterDepth, start, end);
|
||||
return tryNoteList.append(JSTRY_DESTRUCTURING_ITERCLOSE, iterDepth, start, end);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -5065,6 +5065,9 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
|
||||
// Here's pseudo code for |let [a, b, , c=y, ...d] = x;|
|
||||
//
|
||||
// Lines that are annotated "covered by trynote" mean that upon throwing
|
||||
// an exception, IteratorClose is called on iter only if done is false.
|
||||
//
|
||||
// let x, y;
|
||||
// let a, b, c, d;
|
||||
// let iter, lref, result, done, value; // stack values
|
||||
|
@ -5072,7 +5075,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
// iter = x[Symbol.iterator]();
|
||||
//
|
||||
// // ==== emitted by loop for a ====
|
||||
// lref = GetReference(a);
|
||||
// lref = GetReference(a); // covered by trynote
|
||||
//
|
||||
// result = iter.next();
|
||||
// done = result.done;
|
||||
|
@ -5082,10 +5085,10 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
// else
|
||||
// value = result.value;
|
||||
//
|
||||
// SetOrInitialize(lref, value);
|
||||
// SetOrInitialize(lref, value); // covered by trynote
|
||||
//
|
||||
// // ==== emitted by loop for b ====
|
||||
// lref = GetReference(b);
|
||||
// lref = GetReference(b); // covered by trynote
|
||||
//
|
||||
// if (done) {
|
||||
// value = undefined;
|
||||
|
@ -5098,7 +5101,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
// value = result.value;
|
||||
// }
|
||||
//
|
||||
// SetOrInitialize(lref, value);
|
||||
// SetOrInitialize(lref, value); // covered by trynote
|
||||
//
|
||||
// // ==== emitted by loop for elision ====
|
||||
// if (done) {
|
||||
|
@ -5113,7 +5116,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
// }
|
||||
//
|
||||
// // ==== emitted by loop for c ====
|
||||
// lref = GetReference(c);
|
||||
// lref = GetReference(c); // covered by trynote
|
||||
//
|
||||
// if (done) {
|
||||
// value = undefined;
|
||||
|
@ -5127,19 +5130,23 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
// }
|
||||
//
|
||||
// if (value === undefined)
|
||||
// value = y;
|
||||
// value = y; // covered by trynote
|
||||
//
|
||||
// SetOrInitialize(lref, value);
|
||||
// SetOrInitialize(lref, value); // covered by trynote
|
||||
//
|
||||
// // ==== emitted by loop for d ====
|
||||
// lref = GetReference(d);
|
||||
// lref = GetReference(d); // covered by trynote
|
||||
//
|
||||
// if (done)
|
||||
// value = [];
|
||||
// else
|
||||
// value = [...iter];
|
||||
//
|
||||
// SetOrInitialize(lref, value);
|
||||
// SetOrInitialize(lref, value); // covered by trynote
|
||||
//
|
||||
// // === emitted after loop ===
|
||||
// if (!done)
|
||||
// IteratorClose(iter);
|
||||
|
||||
// Use an iterator to destructure the RHS, instead of index lookup. We
|
||||
// must leave the *original* value on the stack.
|
||||
|
@ -5148,25 +5155,61 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
if (!emitIterator()) // ... OBJ ITER
|
||||
return false;
|
||||
|
||||
// For an empty pattern [], call IteratorClose unconditionally. Nothing
|
||||
// else needs to be done.
|
||||
if (!pattern->pn_head)
|
||||
return emitIteratorClose(); // ... OBJ
|
||||
|
||||
// Push an initial FALSE value for DONE.
|
||||
if (!emit1(JSOP_FALSE)) // ... OBJ ITER FALSE
|
||||
return false;
|
||||
|
||||
// JSTRY_DESTRUCTURING_ITERCLOSE expects the iterator and the done value
|
||||
// to be the second to top and the top of the stack, respectively.
|
||||
// IteratorClose is called upon exception only if done is false.
|
||||
int32_t tryNoteDepth = stackDepth;
|
||||
|
||||
for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
|
||||
bool isHead = member == pattern->pn_head;
|
||||
bool hasNext = !!member->pn_next;
|
||||
bool isFirst = member == pattern->pn_head;
|
||||
DebugOnly<bool> hasNext = !!member->pn_next;
|
||||
|
||||
size_t emitted = 0;
|
||||
|
||||
// Spec requires LHS reference to be evaluated first.
|
||||
ParseNode* lhsPattern = member;
|
||||
if (lhsPattern->isKind(PNK_ASSIGN))
|
||||
lhsPattern = lhsPattern->pn_left;
|
||||
|
||||
bool isElision = lhsPattern->isKind(PNK_ELISION);
|
||||
if (!isElision) {
|
||||
auto emitLHSRef = [lhsPattern, &emitted](BytecodeEmitter* bce) {
|
||||
return bce->emitDestructuringLHSRef(lhsPattern, &emitted); // ... OBJ ITER DONE *LREF
|
||||
};
|
||||
if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitLHSRef))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pick the DONE value to the top of the stack.
|
||||
if (emitted) {
|
||||
if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isFirst) {
|
||||
// If this element is the first, DONE is always FALSE, so pop it.
|
||||
//
|
||||
// Non-first elements should emit if-else depending on the
|
||||
// member pattern, below.
|
||||
if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF
|
||||
return false;
|
||||
}
|
||||
|
||||
if (member->isKind(PNK_SPREAD)) {
|
||||
size_t emitted = 0;
|
||||
if (!emitDestructuringLHSRef(member, &emitted)) // ... OBJ ITER ?DONE *LREF
|
||||
return false;
|
||||
|
||||
IfThenElseEmitter ifThenElse(this);
|
||||
if (!isHead) {
|
||||
if (!isFirst) {
|
||||
// If spread is not the first element of the pattern,
|
||||
// iterator can already be completed.
|
||||
// ... OBJ ITER DONE *LREF
|
||||
if (emitted) {
|
||||
if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE
|
||||
return false;
|
||||
}
|
||||
|
||||
// ... OBJ ITER *LREF DONE
|
||||
if (!ifThenElse.emitIfElse()) // ... OBJ ITER *LREF
|
||||
return false;
|
||||
|
||||
|
@ -5189,13 +5232,22 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF ARRAY
|
||||
return false;
|
||||
|
||||
if (!isHead) {
|
||||
if (!isFirst) {
|
||||
if (!ifThenElse.emitEnd())
|
||||
return false;
|
||||
MOZ_ASSERT(ifThenElse.pushed() == 1);
|
||||
}
|
||||
|
||||
if (!emitSetOrInitializeDestructuring(member, flav)) // ... OBJ ITER
|
||||
// At this point the iterator is done. Unpick a TRUE value for DONE above ITER.
|
||||
if (!emit1(JSOP_TRUE)) // ... OBJ ITER *LREF ARRAY TRUE
|
||||
return false;
|
||||
if (!emit2(JSOP_UNPICK, emitted + 1)) // ... OBJ ITER TRUE *LREF ARRAY
|
||||
return false;
|
||||
|
||||
auto emitAssignment = [member, flav](BytecodeEmitter* bce) {
|
||||
return bce->emitSetOrInitializeDestructuring(member, flav); // ... OBJ ITER TRUE
|
||||
};
|
||||
if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment))
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(!hasNext);
|
||||
|
@ -5203,63 +5255,30 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
}
|
||||
|
||||
ParseNode* pndefault = nullptr;
|
||||
ParseNode* subpattern = member;
|
||||
if (subpattern->isKind(PNK_ASSIGN)) {
|
||||
pndefault = subpattern->pn_right;
|
||||
subpattern = subpattern->pn_left;
|
||||
}
|
||||
if (member->isKind(PNK_ASSIGN))
|
||||
pndefault = member->pn_right;
|
||||
|
||||
bool isElision = subpattern->isKind(PNK_ELISION);
|
||||
|
||||
MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD));
|
||||
|
||||
size_t emitted = 0;
|
||||
if (!isElision) {
|
||||
if (!emitDestructuringLHSRef(subpattern, &emitted)) // ... OBJ ITER ?DONE *LREF
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(!member->isKind(PNK_SPREAD));
|
||||
|
||||
IfThenElseEmitter ifAlreadyDone(this);
|
||||
if (!isHead) {
|
||||
// If this element is not the first element of the pattern,
|
||||
// iterator can already be completed.
|
||||
// ... OBJ ITER DONE *LREF
|
||||
if (emitted) {
|
||||
if (hasNext) {
|
||||
if (!emitDupAt(emitted)) // ... OBJ ITER DONE *LREF DONE
|
||||
return false;
|
||||
} else {
|
||||
if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (hasNext) {
|
||||
// The position of LREF in the following stack comment
|
||||
// isn't accurate for the operation, but it's equivalent
|
||||
// since LREF is nothing
|
||||
if (!emit1(JSOP_DUP)) // ... OBJ ITER DONE *LREF DONE
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER ?DONE *LREF
|
||||
if (!isFirst) {
|
||||
// ... OBJ ITER *LREF DONE
|
||||
if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER *LREF
|
||||
return false;
|
||||
|
||||
if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF
|
||||
if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER *LREF UNDEF
|
||||
return false;
|
||||
if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF
|
||||
if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER *LREF UNDEF
|
||||
return false;
|
||||
|
||||
if (!ifAlreadyDone.emitElse()) // ... OBJ ITER ?DONE *LREF
|
||||
// The iterator is done. Unpick a TRUE value for DONE above ITER.
|
||||
if (!emit1(JSOP_TRUE)) // ... OBJ ITER *LREF UNDEF TRUE
|
||||
return false;
|
||||
if (!emit2(JSOP_UNPICK, emitted + 1)) // ... OBJ ITER TRUE *LREF UNDEF
|
||||
return false;
|
||||
|
||||
if (hasNext) {
|
||||
if (emitted) {
|
||||
if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE
|
||||
return false;
|
||||
}
|
||||
if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF
|
||||
return false;
|
||||
}
|
||||
if (!ifAlreadyDone.emitElse()) // ... OBJ ITER *LREF
|
||||
return false;
|
||||
}
|
||||
|
||||
if (emitted) {
|
||||
|
@ -5276,59 +5295,74 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER *LREF RESULT DONE
|
||||
return false;
|
||||
|
||||
if (hasNext) {
|
||||
if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT DONE DONE
|
||||
return false;
|
||||
if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE
|
||||
return false;
|
||||
}
|
||||
if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT DONE DONE
|
||||
return false;
|
||||
if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE
|
||||
return false;
|
||||
|
||||
IfThenElseEmitter ifDone(this);
|
||||
if (!ifDone.emitIfElse()) // ... OBJ ITER ?DONE *LREF RESULT
|
||||
if (!ifDone.emitIfElse()) // ... OBJ ITER DONE *LREF RESULT
|
||||
return false;
|
||||
|
||||
if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE *LREF
|
||||
if (!emit1(JSOP_POP)) // ... OBJ ITER DONE *LREF
|
||||
return false;
|
||||
if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF
|
||||
if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER DONE *LREF UNDEF
|
||||
return false;
|
||||
if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF
|
||||
if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER DONE *LREF UNDEF
|
||||
return false;
|
||||
|
||||
if (!ifDone.emitElse()) // ... OBJ ITER ?DONE *LREF RESULT
|
||||
if (!ifDone.emitElse()) // ... OBJ ITER DONE *LREF RESULT
|
||||
return false;
|
||||
|
||||
if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER ?DONE *LREF VALUE
|
||||
if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER DONE *LREF VALUE
|
||||
return false;
|
||||
|
||||
if (!ifDone.emitEnd())
|
||||
return false;
|
||||
MOZ_ASSERT(ifDone.pushed() == 0);
|
||||
|
||||
if (!isHead) {
|
||||
if (!isFirst) {
|
||||
if (!ifAlreadyDone.emitEnd())
|
||||
return false;
|
||||
MOZ_ASSERT(ifAlreadyDone.pushed() == 1);
|
||||
MOZ_ASSERT(ifAlreadyDone.pushed() == 2);
|
||||
}
|
||||
|
||||
if (pndefault) {
|
||||
if (!emitDefault(pndefault, subpattern)) // ... OBJ ITER ?DONE *LREF VALUE
|
||||
auto emitDefault = [pndefault, lhsPattern](BytecodeEmitter* bce) {
|
||||
return bce->emitDefault(pndefault, lhsPattern); // ... OBJ ITER DONE *LREF VALUE
|
||||
};
|
||||
|
||||
if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitDefault))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isElision) {
|
||||
if (!emitSetOrInitializeDestructuring(subpattern,
|
||||
flav)) // ... OBJ ITER ?DONE
|
||||
{
|
||||
auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) {
|
||||
return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); // ... OBJ ITER DONE
|
||||
};
|
||||
|
||||
if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment))
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE
|
||||
if (!emit1(JSOP_POP)) // ... OBJ ITER DONE
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The last DONE value is on top of the stack. If not DONE, call
|
||||
// IteratorClose.
|
||||
// ... OBJ ITER DONE
|
||||
IfThenElseEmitter ifDone(this);
|
||||
if (!ifDone.emitIfElse()) // ... OBJ ITER
|
||||
return false;
|
||||
if (!emit1(JSOP_POP)) // ... OBJ
|
||||
return false;
|
||||
if (!ifDone.emitElse()) // ... OBJ ITER
|
||||
return false;
|
||||
if (!emitIteratorClose()) // ... OBJ
|
||||
return false;
|
||||
if (!ifDone.emitEnd())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -684,7 +684,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
|||
bool allowSelfHosted = false);
|
||||
|
||||
template <typename InnerEmitter>
|
||||
MOZ_MUST_USE bool wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter);
|
||||
MOZ_MUST_USE bool wrapWithDestructuringIteratorCloseTryNote(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|.
|
||||
|
|
|
@ -330,28 +330,44 @@ NumArgAndLocalSlots(const InlineFrameIterator& frame)
|
|||
static void
|
||||
CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, JSTryNote* tn)
|
||||
{
|
||||
MOZ_ASSERT(tn->kind == JSTRY_FOR_IN || tn->kind == JSTRY_ITERCLOSE);
|
||||
MOZ_ASSERT(tn->stackDepth > 0);
|
||||
MOZ_ASSERT(tn->kind == JSTRY_FOR_IN ||
|
||||
tn->kind == JSTRY_ITERCLOSE ||
|
||||
tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE);
|
||||
|
||||
bool isDestructuring = tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE;
|
||||
MOZ_ASSERT_IF(!isDestructuring, tn->stackDepth > 0);
|
||||
MOZ_ASSERT_IF(isDestructuring, tn->stackDepth > 1);
|
||||
|
||||
SnapshotIterator si = frame.snapshotIterator();
|
||||
|
||||
// Skip stack slots until we reach the iterator object.
|
||||
// Skip stack slots until we reach the iterator object on the stack. For
|
||||
// the destructuring case, we also need to get the "done" value.
|
||||
uint32_t stackSlot = tn->stackDepth;
|
||||
uint32_t skipSlots = NumArgAndLocalSlots(frame) + stackSlot - 1;
|
||||
uint32_t adjust = isDestructuring ? 2 : 1;
|
||||
uint32_t skipSlots = NumArgAndLocalSlots(frame) + stackSlot - adjust;
|
||||
|
||||
for (unsigned i = 0; i < skipSlots; i++)
|
||||
si.skip();
|
||||
|
||||
Value v = si.read();
|
||||
RootedObject obj(cx, &v.toObject());
|
||||
RootedObject iterObject(cx, &v.toObject());
|
||||
|
||||
if (isDestructuring) {
|
||||
Value v = si.read();
|
||||
MOZ_ASSERT(v.isBoolean());
|
||||
// Do not call IteratorClose if the destructuring iterator is already
|
||||
// done.
|
||||
if (v.isTrue())
|
||||
return;
|
||||
}
|
||||
|
||||
if (cx->isExceptionPending()) {
|
||||
if (tn->kind == JSTRY_FOR_IN)
|
||||
UnwindIteratorForException(cx, obj);
|
||||
UnwindIteratorForException(cx, iterObject);
|
||||
else
|
||||
IteratorCloseForException(cx, obj);
|
||||
IteratorCloseForException(cx, iterObject);
|
||||
} else {
|
||||
UnwindIteratorForUncatchableException(cx, obj);
|
||||
UnwindIteratorForUncatchableException(cx, iterObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -426,13 +442,12 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx
|
|||
|
||||
switch (tn->kind) {
|
||||
case JSTRY_FOR_IN:
|
||||
case JSTRY_ITERCLOSE: {
|
||||
case JSTRY_ITERCLOSE:
|
||||
case JSTRY_DESTRUCTURING_ITERCLOSE:
|
||||
MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN,
|
||||
JSOp(*(script->main() + tn->start + tn->length)) == JSOP_ENDITER);
|
||||
MOZ_ASSERT(tn->stackDepth > 0);
|
||||
CloseLiveIteratorIon(cx, frame, tn);
|
||||
break;
|
||||
}
|
||||
|
||||
case JSTRY_FOR_OF:
|
||||
case JSTRY_LOOP:
|
||||
|
@ -606,28 +621,52 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen
|
|||
return true;
|
||||
}
|
||||
|
||||
case JSTRY_FOR_IN:
|
||||
case JSTRY_FOR_IN: {
|
||||
uint8_t* framePointer;
|
||||
uint8_t* stackPointer;
|
||||
BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer);
|
||||
Value iterValue(*reinterpret_cast<Value*>(stackPointer));
|
||||
RootedObject iterObject(cx, &iterValue.toObject());
|
||||
if (!UnwindIteratorForException(cx, iterObject)) {
|
||||
// See comment in the JSTRY_FOR_IN case in Interpreter.cpp's
|
||||
// ProcessTryNotes.
|
||||
SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
|
||||
MOZ_ASSERT(**pc == JSOP_ENDITER);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JSTRY_ITERCLOSE: {
|
||||
uint8_t* framePointer;
|
||||
uint8_t* stackPointer;
|
||||
BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer);
|
||||
Value iterValue(*(reinterpret_cast<Value*>(stackPointer)));
|
||||
Value iterValue(*reinterpret_cast<Value*>(stackPointer));
|
||||
RootedObject iterObject(cx, &iterValue.toObject());
|
||||
bool ok;
|
||||
if (tn->kind == JSTRY_FOR_IN)
|
||||
ok = UnwindIteratorForException(cx, iterObject);
|
||||
else
|
||||
ok = IteratorCloseForException(cx, iterObject);
|
||||
if (!ok) {
|
||||
// See comment in the JSTRY_FOR_IN case in Interpreter.cpp's
|
||||
// ProcessTryNotes.
|
||||
if (!IteratorCloseForException(cx, iterObject)) {
|
||||
SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
|
||||
MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, **pc == JSOP_ENDITER);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JSTRY_DESTRUCTURING_ITERCLOSE: {
|
||||
uint8_t* framePointer;
|
||||
uint8_t* stackPointer;
|
||||
BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer);
|
||||
Value doneValue(*(reinterpret_cast<Value*>(stackPointer)));
|
||||
MOZ_ASSERT(doneValue.isBoolean());
|
||||
if (doneValue.isFalse()) {
|
||||
Value iterValue(*(reinterpret_cast<Value*>(stackPointer) + 1));
|
||||
RootedObject iterObject(cx, &iterValue.toObject());
|
||||
if (!IteratorCloseForException(cx, iterObject)) {
|
||||
SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JSTRY_FOR_OF:
|
||||
case JSTRY_LOOP:
|
||||
break;
|
||||
|
|
|
@ -85,7 +85,8 @@ enum JSTryNoteKind {
|
|||
JSTRY_FOR_IN,
|
||||
JSTRY_FOR_OF,
|
||||
JSTRY_LOOP,
|
||||
JSTRY_ITERCLOSE
|
||||
JSTRY_ITERCLOSE,
|
||||
JSTRY_DESTRUCTURING_ITERCLOSE
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -2589,12 +2589,28 @@ Notes(JSContext* cx, unsigned argc, Value* vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
JS_STATIC_ASSERT(JSTRY_CATCH == 0);
|
||||
JS_STATIC_ASSERT(JSTRY_FINALLY == 1);
|
||||
JS_STATIC_ASSERT(JSTRY_FOR_IN == 2);
|
||||
static const char*
|
||||
TryNoteName(JSTryNoteKind kind)
|
||||
{
|
||||
switch (kind) {
|
||||
case JSTRY_CATCH:
|
||||
return "catch";
|
||||
case JSTRY_FINALLY:
|
||||
return "finally";
|
||||
case JSTRY_FOR_IN:
|
||||
return "for-in";
|
||||
case JSTRY_FOR_OF:
|
||||
return "for-of";
|
||||
case JSTRY_LOOP:
|
||||
return "loop";
|
||||
case JSTRY_ITERCLOSE:
|
||||
return "iterclose";
|
||||
case JSTRY_DESTRUCTURING_ITERCLOSE:
|
||||
return "dstr-iterclose";
|
||||
}
|
||||
|
||||
static const char* const TryNoteNames[] = { "catch", "finally", "for-in", "for-of", "loop",
|
||||
"iterclose" };
|
||||
MOZ_CRASH("Bad JSTryNoteKind");
|
||||
}
|
||||
|
||||
static MOZ_MUST_USE bool
|
||||
TryNotes(JSContext* cx, HandleScript script, Sprinter* sp)
|
||||
|
@ -2602,17 +2618,16 @@ TryNotes(JSContext* cx, HandleScript script, Sprinter* sp)
|
|||
if (!script->hasTrynotes())
|
||||
return true;
|
||||
|
||||
if (sp->put("\nException table:\nkind stack start end\n") < 0)
|
||||
if (sp->put("\nException table:\nkind stack start end\n") < 0)
|
||||
return false;
|
||||
|
||||
JSTryNote* tn = script->trynotes()->vector;
|
||||
JSTryNote* tnlimit = tn + script->trynotes()->length;
|
||||
do {
|
||||
MOZ_ASSERT(tn->kind < ArrayLength(TryNoteNames));
|
||||
uint32_t startOff = script->pcToOffset(script->main()) + tn->start;
|
||||
if (!sp->jsprintf(" %-9s %6u %8u %8u\n",
|
||||
TryNoteNames[tn->kind], tn->stackDepth,
|
||||
startOff, startOff + tn->length))
|
||||
if (!sp->jsprintf(" %-14s %6u %8u %8u\n",
|
||||
TryNoteName(static_cast<JSTryNoteKind>(tn->kind)),
|
||||
tn->stackDepth, startOff, startOff + tn->length))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
// Tests that IteratorClose is called in array destructuring patterns.
|
||||
|
||||
function test() {
|
||||
var returnCalled = 0;
|
||||
var returnCalledExpected = 0;
|
||||
var iterable = {};
|
||||
|
||||
// empty [] calls IteratorClose regardless of "done" on the result.
|
||||
iterable[Symbol.iterator] = makeIterator({
|
||||
next: function() {
|
||||
return { done: true };
|
||||
},
|
||||
ret: function() {
|
||||
returnCalled++;
|
||||
return {};
|
||||
}
|
||||
});
|
||||
var [] = iterable;
|
||||
assertEq(returnCalled, ++returnCalledExpected);
|
||||
|
||||
iterable[Symbol.iterator] = makeIterator({
|
||||
ret: function() {
|
||||
returnCalled++;
|
||||
return {};
|
||||
}
|
||||
});
|
||||
var [] = iterable;
|
||||
assertEq(returnCalled, ++returnCalledExpected);
|
||||
|
||||
// Non-empty destructuring calls IteratorClose if iterator is not done by
|
||||
// the end of destructuring.
|
||||
var [a,b] = iterable;
|
||||
assertEq(returnCalled, ++returnCalledExpected);
|
||||
var [c,] = iterable;
|
||||
assertEq(returnCalled, ++returnCalledExpected);
|
||||
|
||||
// throw in lhs ref calls IteratorClose
|
||||
function throwlhs() {
|
||||
throw "in lhs";
|
||||
}
|
||||
assertThrowsValue(function() {
|
||||
0, [...{}[throwlhs()]] = iterable;
|
||||
}, "in lhs");
|
||||
assertEq(returnCalled, ++returnCalledExpected);
|
||||
|
||||
// throw in iter.next doesn't call IteratorClose
|
||||
iterable[Symbol.iterator] = makeIterator({
|
||||
next: function() {
|
||||
throw "in next";
|
||||
},
|
||||
ret: function() {
|
||||
returnCalled++;
|
||||
return {};
|
||||
}
|
||||
});
|
||||
assertThrowsValue(function() {
|
||||
var [d] = iterable;
|
||||
}, "in next");
|
||||
assertEq(returnCalled, returnCalledExpected);
|
||||
|
||||
// "return" must return an Object.
|
||||
iterable[Symbol.iterator] = makeIterator({
|
||||
ret: function() {
|
||||
returnCalled++;
|
||||
return 42;
|
||||
}
|
||||
});
|
||||
assertThrowsInstanceOf(function() {
|
||||
var [] = iterable;
|
||||
}, TypeError);
|
||||
assertEq(returnCalled, ++returnCalledExpected);
|
||||
}
|
||||
|
||||
test();
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
|
@ -1174,19 +1174,13 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs)
|
|||
SettleOnTryNote(cx, tn, ei, regs);
|
||||
return FinallyContinuation;
|
||||
|
||||
case JSTRY_FOR_IN:
|
||||
case JSTRY_ITERCLOSE: {
|
||||
case JSTRY_FOR_IN: {
|
||||
/* This is similar to JSOP_ENDITER in the interpreter loop. */
|
||||
DebugOnly<jsbytecode*> pc = regs.fp()->script()->main() + tn->start + tn->length;
|
||||
MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, JSOp(*pc) == JSOP_ENDITER);
|
||||
MOZ_ASSERT(JSOp(*pc) == JSOP_ENDITER);
|
||||
Value* sp = regs.spForStackDepth(tn->stackDepth);
|
||||
RootedObject obj(cx, &sp[-1].toObject());
|
||||
bool ok;
|
||||
if (tn->kind == JSTRY_FOR_IN)
|
||||
ok = UnwindIteratorForException(cx, obj);
|
||||
else
|
||||
ok = IteratorCloseForException(cx, obj);
|
||||
if (!ok) {
|
||||
if (!UnwindIteratorForException(cx, obj)) {
|
||||
// We should only settle on the note only if
|
||||
// UnwindIteratorForException itself threw, as
|
||||
// onExceptionUnwind should be called anew with the new
|
||||
|
@ -1198,6 +1192,33 @@ 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.
|
||||
MOZ_ASSERT(tn->stackDepth > 1);
|
||||
Value* sp = regs.spForStackDepth(tn->stackDepth);
|
||||
MOZ_ASSERT(sp[-1].isBoolean());
|
||||
if (sp[-1].isFalse()) {
|
||||
RootedObject iterObject(cx, &sp[-2].toObject());
|
||||
if (!IteratorCloseForException(cx, iterObject)) {
|
||||
SettleOnTryNote(cx, tn, ei, regs);
|
||||
return ErrorReturnContinuation;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JSTRY_FOR_OF:
|
||||
case JSTRY_LOOP:
|
||||
break;
|
||||
|
|
Загрузка…
Ссылка в новой задаче