Bug 1147371: Implement IteratorClose for array destructuring

Issue #74
This commit is contained in:
janekptacijarabaci 2018-03-24 12:23:14 +01:00 • committed by Roy Tam
commit d36d40760a
7 changed files with 322 additions and 135 deletions

View file

@ -4947,7 +4947,7 @@ BytecodeEmitter::emitIteratorClose(Maybe<JumpTarget> yieldStarTryStart, bool all
template <typename InnerEmitter> template <typename InnerEmitter>
bool bool
BytecodeEmitter::wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter) BytecodeEmitter::wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter)
{ {
MOZ_ASSERT(this->stackDepth >= iterDepth); MOZ_ASSERT(this->stackDepth >= iterDepth);
@ -4956,7 +4956,7 @@ BytecodeEmitter::wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter em
return false; return false;
ptrdiff_t end = offset(); ptrdiff_t end = offset();
if (start != end) if (start != end)
return tryNoteList.append(JSTRY_ITERCLOSE, iterDepth, start, end); return tryNoteList.append(JSTRY_DESTRUCTURING_ITERCLOSE, iterDepth, start, end);
return true; return true;
} }
@ -5057,6 +5057,9 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
// Here's pseudo code for |let [a, b, , c=y, ...d] = x;| // 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 x, y;
// let a, b, c, d; // let a, b, c, d;
// let iter, lref, result, done, value; // stack values // let iter, lref, result, done, value; // stack values
@ -5064,7 +5067,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
// iter = x[Symbol.iterator](); // iter = x[Symbol.iterator]();
// //
// // ==== emitted by loop for a ==== // // ==== emitted by loop for a ====
// lref = GetReference(a); // lref = GetReference(a); // covered by trynote
// //
// result = iter.next(); // result = iter.next();
// done = result.done; // done = result.done;
@ -5074,10 +5077,10 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
// else // else
// value = result.value; // value = result.value;
// //
// SetOrInitialize(lref, value); // SetOrInitialize(lref, value); // covered by trynote
// //
// // ==== emitted by loop for b ==== // // ==== emitted by loop for b ====
// lref = GetReference(b); // lref = GetReference(b); // covered by trynote
// //
// if (done) { // if (done) {
// value = undefined; // value = undefined;
@ -5090,7 +5093,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
// value = result.value; // value = result.value;
// } // }
// //
// SetOrInitialize(lref, value); // SetOrInitialize(lref, value); // covered by trynote
// //
// // ==== emitted by loop for elision ==== // // ==== emitted by loop for elision ====
// if (done) { // if (done) {
@ -5105,7 +5108,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
// } // }
// //
// // ==== emitted by loop for c ==== // // ==== emitted by loop for c ====
// lref = GetReference(c); // lref = GetReference(c); // covered by trynote
// //
// if (done) { // if (done) {
// value = undefined; // value = undefined;
@ -5119,19 +5122,23 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
// } // }
// //
// if (value === undefined) // if (value === undefined)
// value = y; // value = y; // covered by trynote
// //
// SetOrInitialize(lref, value); // SetOrInitialize(lref, value); // covered by trynote
// //
// // ==== emitted by loop for d ==== // // ==== emitted by loop for d ====
// lref = GetReference(d); // lref = GetReference(d); // covered by trynote
// //
// if (done) // if (done)
// value = []; // value = [];
// else // else
// value = [...iter]; // 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 // Use an iterator to destructure the RHS, instead of index lookup. We
// must leave the *original* value on the stack. // must leave the *original* value on the stack.
@ -5140,25 +5147,61 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
if (!emitIterator()) // ... OBJ ITER if (!emitIterator()) // ... OBJ ITER
return false; 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) { for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
bool isHead = member == pattern->pn_head; bool isFirst = member == pattern->pn_head;
bool hasNext = !!member->pn_next; 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)) { if (member->isKind(PNK_SPREAD)) {
size_t emitted = 0;
if (!emitDestructuringLHSRef(member, &emitted)) // ... OBJ ITER ?DONE *LREF
return false;
IfThenElseEmitter ifThenElse(this); IfThenElseEmitter ifThenElse(this);
if (!isHead) { if (!isFirst) {
// If spread is not the first element of the pattern, // If spread is not the first element of the pattern,
// iterator can already be completed. // iterator can already be completed.
// ... OBJ ITER DONE *LREF // ... OBJ ITER *LREF DONE
if (emitted) {
if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE
return false;
}
if (!ifThenElse.emitIfElse()) // ... OBJ ITER *LREF if (!ifThenElse.emitIfElse()) // ... OBJ ITER *LREF
return false; return false;
@ -5181,13 +5224,22 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF ARRAY if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF ARRAY
return false; return false;
if (!isHead) { if (!isFirst) {
if (!ifThenElse.emitEnd()) if (!ifThenElse.emitEnd())
return false; return false;
MOZ_ASSERT(ifThenElse.pushed() == 1); 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; return false;
MOZ_ASSERT(!hasNext); MOZ_ASSERT(!hasNext);
@ -5195,63 +5247,30 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
} }
ParseNode* pndefault = nullptr; ParseNode* pndefault = nullptr;
ParseNode* subpattern = member; if (member->isKind(PNK_ASSIGN))
if (subpattern->isKind(PNK_ASSIGN)) { pndefault = member->pn_right;
pndefault = subpattern->pn_right;
subpattern = subpattern->pn_left;
}
bool isElision = subpattern->isKind(PNK_ELISION); MOZ_ASSERT(!member->isKind(PNK_SPREAD));
MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD));
size_t emitted = 0;
if (!isElision) {
if (!emitDestructuringLHSRef(subpattern, &emitted)) // ... OBJ ITER ?DONE *LREF
return false;
}
IfThenElseEmitter ifAlreadyDone(this); IfThenElseEmitter ifAlreadyDone(this);
if (!isHead) { if (!isFirst) {
// If this element is not the first element of the pattern, // ... OBJ ITER *LREF DONE
// iterator can already be completed. if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER *LREF
// ... 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
return false; return false;
if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER *LREF UNDEF
return false; return false;
if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER *LREF UNDEF
return false; 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; return false;
if (hasNext) { if (!ifAlreadyDone.emitElse()) // ... OBJ ITER *LREF
if (emitted) { return false;
if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE
return false;
}
if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF
return false;
}
} }
if (emitted) { if (emitted) {
@ -5268,59 +5287,74 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER *LREF RESULT DONE if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER *LREF RESULT DONE
return false; return false;
if (hasNext) { if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT DONE DONE
if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT DONE DONE return false;
return false; if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE
if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE return false;
return false;
}
IfThenElseEmitter ifDone(this); IfThenElseEmitter ifDone(this);
if (!ifDone.emitIfElse()) // ... OBJ ITER ?DONE *LREF RESULT if (!ifDone.emitIfElse()) // ... OBJ ITER DONE *LREF RESULT
return false; return false;
if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE *LREF if (!emit1(JSOP_POP)) // ... OBJ ITER DONE *LREF
return false; return false;
if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER DONE *LREF UNDEF
return false; return false;
if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER DONE *LREF UNDEF
return false; return false;
if (!ifDone.emitElse()) // ... OBJ ITER ?DONE *LREF RESULT if (!ifDone.emitElse()) // ... OBJ ITER DONE *LREF RESULT
return false; 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; return false;
if (!ifDone.emitEnd()) if (!ifDone.emitEnd())
return false; return false;
MOZ_ASSERT(ifDone.pushed() == 0); MOZ_ASSERT(ifDone.pushed() == 0);
if (!isHead) { if (!isFirst) {
if (!ifAlreadyDone.emitEnd()) if (!ifAlreadyDone.emitEnd())
return false; return false;
MOZ_ASSERT(ifAlreadyDone.pushed() == 1); MOZ_ASSERT(ifAlreadyDone.pushed() == 2);
} }
if (pndefault) { 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; return false;
} }
if (!isElision) { if (!isElision) {
if (!emitSetOrInitializeDestructuring(subpattern, auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) {
flav)) // ... OBJ ITER ?DONE return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); // ... OBJ ITER DONE
{ };
if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment))
return false; return false;
}
} else { } else {
if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE if (!emit1(JSOP_POP)) // ... OBJ ITER DONE
return false; 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 if (!emit1(JSOP_POP)) // ... OBJ
return false; return false;
if (!ifDone.emitElse()) // ... OBJ ITER
return false;
if (!emitIteratorClose()) // ... OBJ
return false;
if (!ifDone.emitEnd())
return false;
return true; return true;
} }

View file

@ -684,7 +684,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter
bool allowSelfHosted = false); bool allowSelfHosted = false);
template <typename InnerEmitter> 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 // 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|. // that value on the stack with the value defined by |defaultExpr|.

View file

@ -330,28 +330,44 @@ NumArgAndLocalSlots(const InlineFrameIterator& frame)
static void static void
CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, JSTryNote* tn) CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, JSTryNote* tn)
{ {
MOZ_ASSERT(tn->kind == JSTRY_FOR_IN || tn->kind == JSTRY_ITERCLOSE); MOZ_ASSERT(tn->kind == JSTRY_FOR_IN ||
MOZ_ASSERT(tn->stackDepth > 0); 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(); 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 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++) for (unsigned i = 0; i < skipSlots; i++)
si.skip(); si.skip();
Value v = si.read(); 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 (cx->isExceptionPending()) {
if (tn->kind == JSTRY_FOR_IN) if (tn->kind == JSTRY_FOR_IN)
UnwindIteratorForException(cx, obj); UnwindIteratorForException(cx, iterObject);
else else
IteratorCloseForException(cx, obj); IteratorCloseForException(cx, iterObject);
} else { } else {
UnwindIteratorForUncatchableException(cx, obj); UnwindIteratorForUncatchableException(cx, iterObject);
} }
} }
@ -426,14 +442,12 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx
switch (tn->kind) { switch (tn->kind) {
case JSTRY_FOR_IN: case JSTRY_FOR_IN:
case JSTRY_ITERCLOSE: { case JSTRY_ITERCLOSE:
case JSTRY_DESTRUCTURING_ITERCLOSE:
MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN,
JSOp(*(script->main() + tn->start + tn->length)) == JSOP_ENDITER); JSOp(*(script->main() + tn->start + tn->length)) == JSOP_ENDITER);
MOZ_ASSERT(tn->stackDepth > 0);
CloseLiveIteratorIon(cx, frame, tn); CloseLiveIteratorIon(cx, frame, tn);
break; break;
}
case JSTRY_FOR_OF: case JSTRY_FOR_OF:
case JSTRY_LOOP: case JSTRY_LOOP:
@ -607,28 +621,52 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen
return true; 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: { case JSTRY_ITERCLOSE: {
uint8_t* framePointer; uint8_t* framePointer;
uint8_t* stackPointer; uint8_t* stackPointer;
BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer); BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer);
Value iterValue(*(reinterpret_cast<Value*>(stackPointer))); Value iterValue(*reinterpret_cast<Value*>(stackPointer));
RootedObject iterObject(cx, &iterValue.toObject()); RootedObject iterObject(cx, &iterValue.toObject());
bool ok; if (!IteratorCloseForException(cx, iterObject)) {
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.
SettleOnTryNote(cx, tn, frame, ei, rfe, pc); SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, **pc == JSOP_ENDITER);
return false; return false;
} }
break; 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_FOR_OF:
case JSTRY_LOOP: case JSTRY_LOOP:
break; break;

View file

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

View file

@ -2585,12 +2585,28 @@ Notes(JSContext* cx, unsigned argc, Value* vp)
return true; return true;
} }
JS_STATIC_ASSERT(JSTRY_CATCH == 0); static const char*
JS_STATIC_ASSERT(JSTRY_FINALLY == 1); TryNoteName(JSTryNoteKind kind)
JS_STATIC_ASSERT(JSTRY_FOR_IN == 2); {
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", MOZ_CRASH("Bad JSTryNoteKind");
"iterclose" }; }
static MOZ_MUST_USE bool static MOZ_MUST_USE bool
TryNotes(JSContext* cx, HandleScript script, Sprinter* sp) TryNotes(JSContext* cx, HandleScript script, Sprinter* sp)
@ -2598,17 +2614,16 @@ TryNotes(JSContext* cx, HandleScript script, Sprinter* sp)
if (!script->hasTrynotes()) if (!script->hasTrynotes())
return true; 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; return false;
JSTryNote* tn = script->trynotes()->vector; JSTryNote* tn = script->trynotes()->vector;
JSTryNote* tnlimit = tn + script->trynotes()->length; JSTryNote* tnlimit = tn + script->trynotes()->length;
do { do {
MOZ_ASSERT(tn->kind < ArrayLength(TryNoteNames));
uint32_t startOff = script->pcToOffset(script->main()) + tn->start; uint32_t startOff = script->pcToOffset(script->main()) + tn->start;
if (!sp->jsprintf(" %-9s %6u %8u %8u\n", if (!sp->jsprintf(" %-14s %6u %8u %8u\n",
TryNoteNames[tn->kind], tn->stackDepth, TryNoteName(static_cast<JSTryNoteKind>(tn->kind)),
startOff, startOff + tn->length)) tn->stackDepth, startOff, startOff + tn->length))
{ {
return false; return false;
} }

View file

@ -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);

View file

@ -1171,19 +1171,13 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs)
SettleOnTryNote(cx, tn, ei, regs); SettleOnTryNote(cx, tn, ei, regs);
return FinallyContinuation; return FinallyContinuation;
case JSTRY_FOR_IN: case JSTRY_FOR_IN: {
case JSTRY_ITERCLOSE: {
/* This is similar to JSOP_ENDITER in the interpreter loop. */ /* This is similar to JSOP_ENDITER in the interpreter loop. */
DebugOnly<jsbytecode*> pc = regs.fp()->script()->main() + tn->start + tn->length; 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); Value* sp = regs.spForStackDepth(tn->stackDepth);
RootedObject obj(cx, &sp[-1].toObject()); RootedObject obj(cx, &sp[-1].toObject());
bool ok; if (!UnwindIteratorForException(cx, obj)) {
if (tn->kind == JSTRY_FOR_IN)
ok = UnwindIteratorForException(cx, obj);
else
ok = IteratorCloseForException(cx, obj);
if (!ok) {
// We should only settle on the note only if // We should only settle on the note only if
// UnwindIteratorForException itself threw, as // UnwindIteratorForException itself threw, as
// onExceptionUnwind should be called anew with the new // onExceptionUnwind should be called anew with the new
@ -1195,6 +1189,33 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs)
break; 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_FOR_OF:
case JSTRY_LOOP: case JSTRY_LOOP:
break; break;