diff options
Diffstat (limited to 'js/src')
60 files changed, 4370 insertions, 977 deletions
diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js index 5ab0b71be..45f90a7b8 100644 --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -803,54 +803,32 @@ function ArrayFrom(items, mapfn=undefined, thisArg=undefined) { // Steps 5.a-c. var A = IsConstructor(C) ? new C() : []; - // Step 5.c. - var iterator = GetIterator(items, usingIterator); - // Step 5.d. var k = 0; - // Step 5.e. - // These steps cannot be implemented using a for-of loop. - // See <https://bugs.ecmascript.org/show_bug.cgi?id=2883>. - while (true) { + // Step 5.c, 5.e. + var iteratorWrapper = { [std_iterator]() { return GetIterator(items, usingIterator); } }; + for (var nextValue of allowContentIter(iteratorWrapper)) { // Step 5.e.i. // Disabled for performance reason. We won't hit this case on // normal array, since _DefineDataProperty will throw before it. // We could hit this when |A| is a proxy and it ignores // |_DefineDataProperty|, but it happens only after too long loop. /* - if (k >= 0x1fffffffffffff) { - IteratorCloseThrow(iterator); + if (k >= 0x1fffffffffffff) ThrowTypeError(JSMSG_TOO_LONG_ARRAY); - } */ - // Step 5.e.iii. - var next = callContentFunction(iterator.next, iterator); - if (!IsObject(next)) - ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE); - - // Step 5.e.iv. - if (next.done) { - A.length = k; - return A; - } - - // Steps 5.e.v. - var nextValue = next.value; - // Steps 5.e.vi-vii. - try { - var mappedValue = mapping ? callContentFunction(mapfn, thisArg, nextValue, k) : nextValue; - - // Steps 5.e.ii (reordered), 5.e.viii. - _DefineDataProperty(A, k++, mappedValue); - } catch (e) { - // Steps 5.e.vi.2, 5.e.ix. - IteratorCloseThrow(iterator); - throw e; - } + var mappedValue = mapping ? callContentFunction(mapfn, thisArg, nextValue, k) : nextValue; + + // Steps 5.e.ii (reordered), 5.e.viii. + _DefineDataProperty(A, k++, mappedValue); } + + // Step 5.e.iv. + A.length = k; + return A; } // Step 7. diff --git a/js/src/builtin/Classes.js b/js/src/builtin/Classes.js index d0f20b5fd..24841d605 100644 --- a/js/src/builtin/Classes.js +++ b/js/src/builtin/Classes.js @@ -5,7 +5,7 @@ var DefaultDerivedClassConstructor = class extends null { constructor(...args) { - super(...allowContentSpread(args)); + super(...allowContentIter(args)); } }; MakeDefaultConstructor(DefaultDerivedClassConstructor); diff --git a/js/src/builtin/Map.js b/js/src/builtin/Map.js index 27a12bfff..580629a13 100644 --- a/js/src/builtin/Map.js +++ b/js/src/builtin/Map.js @@ -14,44 +14,14 @@ function MapConstructorInit(iterable) { if (!IsCallable(adder)) ThrowTypeError(JSMSG_NOT_FUNCTION, typeof adder); - // Step 6.c. - var iterFn = iterable[std_iterator]; - if (!IsCallable(iterFn)) - ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, iterable)); - - var iter = callContentFunction(iterFn, iterable); - if (!IsObject(iter)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof iter); - - // Step 7 (not applicable). - - // Step 8. - while (true) { - // Step 8.a. - var next = callContentFunction(iter.next, iter); - if (!IsObject(next)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof next); - - // Step 8.b. - if (next.done) - return; - - // Step 8.c. - var nextItem = next.value; - + // Steps 6.c-8. + for (var nextItem of allowContentIter(iterable)) { // Step 8.d. - if (!IsObject(nextItem)) { - IteratorCloseThrow(iter); + if (!IsObject(nextItem)) ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "Map"); - } // Steps 8.e-j. - try { - callContentFunction(adder, map, nextItem[0], nextItem[1]); - } catch (e) { - IteratorCloseThrow(iter); - throw e; - } + callContentFunction(adder, map, nextItem[0], nextItem[1]); } } diff --git a/js/src/builtin/Set.js b/js/src/builtin/Set.js index c61a49ef8..9af6cf8d1 100644 --- a/js/src/builtin/Set.js +++ b/js/src/builtin/Set.js @@ -14,39 +14,9 @@ function SetConstructorInit(iterable) { if (!IsCallable(adder)) ThrowTypeError(JSMSG_NOT_FUNCTION, typeof adder); - // Step 6.c. - var iterFn = iterable[std_iterator]; - if (!IsCallable(iterFn)) - ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, iterable)); - - var iter = callContentFunction(iterFn, iterable); - if (!IsObject(iter)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof iter); - - // Step 7 (not applicable). - - // Step 8. - while (true) { - // Step 8.a. - var next = callContentFunction(iter.next, iter); - if (!IsObject(next)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof next); - - // Step 8.b. - if (next.done) - return; - - // Step 8.c. - var nextValue = next.value; - - // Steps 8.d-e. - try { - callContentFunction(adder, set, nextValue); - } catch (e) { - IteratorCloseThrow(iter); - throw e; - } - } + // Steps 6.c-8. + for (var nextValue of allowContentIter(iterable)) + callContentFunction(adder, set, nextValue); } /* ES6 20121122 draft 15.16.4.6. */ diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index 4a3f38365..a2205dc92 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -1428,7 +1428,7 @@ function TypedArrayStaticFrom(source, mapfn = undefined, thisArg = undefined) { // 22.2.2.1.1 IterableToList, step 4.a. var next = callContentFunction(iterator.next, iterator); if (!IsObject(next)) - ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE); + ThrowTypeError(JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "next"); // 22.2.2.1.1 IterableToList, step 4.b. if (next.done) @@ -1555,7 +1555,7 @@ function IterableToList(items, method) { // Step 4.a. var next = callContentFunction(iterator.next, iterator); if (!IsObject(next)) - ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE); + ThrowTypeError(JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "next"); // Step 4.b. if (next.done) diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index 3145b4d51..c73bc5e7f 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -164,29 +164,6 @@ function GetIterator(obj, method) { return iterator; } -// ES2017 draft rev 7.4.6. -// When completion.[[Type]] is throw. -function IteratorCloseThrow(iter) { - // Steps 1-2 (implicit) - - // Step 3. - var returnMethod = GetMethod(iter, "return"); - - // Step 4 (done in caller). - if (returnMethod === undefined) - return; - - try { - // Step 5. - callContentFunction(returnMethod, iter); - } catch (e) { - } - - // Step 6 (done in caller). - - // Steps 7-9 (skipped). -} - var _builtinCtorsCache = {__proto__: null}; function GetBuiltinConstructor(builtinName) { diff --git a/js/src/builtin/WeakMap.js b/js/src/builtin/WeakMap.js index 708be8424..6755b7a7b 100644 --- a/js/src/builtin/WeakMap.js +++ b/js/src/builtin/WeakMap.js @@ -14,43 +14,13 @@ function WeakMapConstructorInit(iterable) { if (!IsCallable(adder)) ThrowTypeError(JSMSG_NOT_FUNCTION, typeof adder); - // Step 6.c. - var iterFn = iterable[std_iterator]; - if (!IsCallable(iterFn)) - ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, iterable)); - - var iter = callContentFunction(iterFn, iterable); - if (!IsObject(iter)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof iter); - - // Step 7 (not applicable). - - // Step 8. - while (true) { - // Step 8.a. - var next = callContentFunction(iter.next, iter); - if (!IsObject(next)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof next); - - // Step 8.b. - if (next.done) - return; - - // Step 8.c. - var nextItem = next.value; - + // Steps 6.c-8. + for (var nextItem of allowContentIter(iterable)) { // Step 8.d. - if (!IsObject(nextItem)) { - IteratorCloseThrow(iter); + if (!IsObject(nextItem)) ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "WeakMap"); - } // Steps 8.e-j. - try { - callContentFunction(adder, map, nextItem[0], nextItem[1]); - } catch (e) { - IteratorCloseThrow(iter); - throw e; - } + callContentFunction(adder, map, nextItem[0], nextItem[1]); } } diff --git a/js/src/builtin/WeakSet.js b/js/src/builtin/WeakSet.js index 8589f9dc6..b16b4634d 100644 --- a/js/src/builtin/WeakSet.js +++ b/js/src/builtin/WeakSet.js @@ -14,39 +14,9 @@ function WeakSetConstructorInit(iterable) { if (!IsCallable(adder)) ThrowTypeError(JSMSG_NOT_FUNCTION, typeof adder); - // Step 6.c. - var iterFn = iterable[std_iterator]; - if (!IsCallable(iterFn)) - ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, iterable)); - - var iter = callContentFunction(iterFn, iterable); - if (!IsObject(iter)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof iter); - - // Step 7 (not applicable). - - // Step 8. - while (true) { - // Step 8.a. - var next = callContentFunction(iter.next, iter); - if (!IsObject(next)) - ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof next); - - // Step 8.b. - if (next.done) - return; - - // Step 8.c. - var nextValue = next.value; - - // Steps 8.d-e. - try { - callContentFunction(adder, set, nextValue); - } catch (e) { - IteratorCloseThrow(iter); - throw e; - } - } + // Steps 6.c-8. + for (var nextValue of allowContentIter(iterable)) + callContentFunction(adder, set, nextValue); } // 23.4.3.1 diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index acf734794..c7c615ccf 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; @@ -236,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; @@ -1497,6 +1505,659 @@ BytecodeEmitter::TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, return true; } +class MOZ_STACK_CLASS TryEmitter +{ + public: + enum Kind { + TryCatch, + TryCatchFinally, + TryFinally + }; + enum ShouldUseRetVal { + UseRetVal, + DontUseRetVal + }; + enum ShouldUseControl { + UseControl, + DontUseControl, + }; + + private: + BytecodeEmitter* bce_; + Kind kind_; + ShouldUseRetVal retValKind_; + + // Track jumps-over-catches and gosubs-to-finally for later fixup. + // + // When a finally block is active, non-local jumps (including + // jumps-over-catches) result in a GOSUB being written into the bytecode + // stream and fixed-up later. + // + // If ShouldUseControl is DontUseControl, all that handling is skipped. + // DontUseControl is used by yield* and the internal try-catch around + // IteratorClose. These internal uses must: + // * have only one catch block + // * have no catch guard + // * have JSOP_GOTO at the end of catch-block + // * have no non-local-jump + // * don't use finally block for normal completion of try-block and + // catch-block + // + // Additionally, a finally block may be emitted when ShouldUseControl is + // DontUseControl, even if the kind is not TryCatchFinally or TryFinally, + // because GOSUBs are not emitted. This internal use shares the + // requirements as above. + Maybe<TryFinallyControl> controlInfo_; + + int depth_; + unsigned noteIndex_; + ptrdiff_t tryStart_; + JumpList catchAndFinallyJump_; + JumpTarget tryEnd_; + JumpTarget finallyStart_; + + enum State { + Start, + Try, + TryEnd, + Catch, + CatchEnd, + Finally, + FinallyEnd, + End + }; + State state_; + + bool hasCatch() const { + return kind_ == TryCatch || kind_ == TryCatchFinally; + } + bool hasFinally() const { + return kind_ == TryCatchFinally || kind_ == TryFinally; + } + + public: + TryEmitter(BytecodeEmitter* bce, Kind kind, ShouldUseRetVal retValKind = UseRetVal, + ShouldUseControl controlKind = UseControl) + : bce_(bce), + kind_(kind), + retValKind_(retValKind), + depth_(0), + noteIndex_(0), + tryStart_(0), + state_(Start) + { + if (controlKind == UseControl) + controlInfo_.emplace(bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try); + finallyStart_.offset = 0; + } + + bool emitJumpOverCatchAndFinally() { + if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) + return false; + return true; + } + + bool emitTry() { + MOZ_ASSERT(state_ == Start); + + // Since an exception can be thrown at any place inside the try block, + // we need to restore the stack and the scope chain before we transfer + // the control to the exception handler. + // + // For that we store in a try note associated with the catch or + // finally block the stack depth upon the try entry. The interpreter + // uses this depth to properly unwind the stack and the scope chain. + depth_ = bce_->stackDepth; + + // Record the try location, then emit the try block. + if (!bce_->newSrcNote(SRC_TRY, ¬eIndex_)) + return false; + if (!bce_->emit1(JSOP_TRY)) + return false; + tryStart_ = bce_->offset(); + + state_ = Try; + return true; + } + + private: + bool emitTryEnd() { + MOZ_ASSERT(state_ == Try); + MOZ_ASSERT(depth_ == bce_->stackDepth); + + // GOSUB to finally, if present. + if (hasFinally() && controlInfo_) { + if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs)) + return false; + } + + // Source note points to the jump at the end of the try block. + if (!bce_->setSrcNoteOffset(noteIndex_, 0, bce_->offset() - tryStart_ + JSOP_TRY_LENGTH)) + return false; + + // Emit jump over catch and/or finally. + if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) + return false; + + if (!bce_->emitJumpTarget(&tryEnd_)) + return false; + + return true; + } + + public: + bool emitCatch() { + if (state_ == Try) { + if (!emitTryEnd()) + return false; + } else { + MOZ_ASSERT(state_ == Catch); + if (!emitCatchEnd(true)) + return false; + } + + MOZ_ASSERT(bce_->stackDepth == depth_); + + if (retValKind_ == UseRetVal) { + // Clear the frame's return value that might have been set by the + // try block: + // + // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1 + if (!bce_->emit1(JSOP_UNDEFINED)) + return false; + if (!bce_->emit1(JSOP_SETRVAL)) + return false; + } + + state_ = Catch; + return true; + } + + private: + bool emitCatchEnd(bool hasNext) { + MOZ_ASSERT(state_ == Catch); + + if (!controlInfo_) + return true; + + // gosub <finally>, if required. + if (hasFinally()) { + if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs)) + return false; + MOZ_ASSERT(bce_->stackDepth == depth_); + } + + // Jump over the remaining catch blocks. This will get fixed + // up to jump to after catch/finally. + if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) + return false; + + // If this catch block had a guard clause, patch the guard jump to + // come here. + if (controlInfo_->guardJump.offset != -1) { + if (!bce_->emitJumpTargetAndPatch(controlInfo_->guardJump)) + return false; + controlInfo_->guardJump.offset = -1; + + // If this catch block is the last one, rethrow, delegating + // execution of any finally block to the exception handler. + if (!hasNext) { + if (!bce_->emit1(JSOP_EXCEPTION)) + return false; + if (!bce_->emit1(JSOP_THROW)) + return false; + } + } + + return true; + } + + public: + bool emitFinally(Maybe<uint32_t> finallyPos = Nothing()) { + // If we are using controlInfo_ (i.e., emitting a syntactic try + // blocks), we must have specified up front if there will be a finally + // close. For internal try blocks, like those emitted for yield* and + // IteratorClose inside for-of loops, we can emitFinally even without + // specifying up front, since the internal try blocks emit no GOSUBs. + if (!controlInfo_) { + if (kind_ == TryCatch) + kind_ = TryCatchFinally; + } else { + MOZ_ASSERT(hasFinally()); + } + + if (state_ == Try) { + if (!emitTryEnd()) + return false; + } else { + MOZ_ASSERT(state_ == Catch); + if (!emitCatchEnd(false)) + return false; + } + + MOZ_ASSERT(bce_->stackDepth == depth_); + + if (!bce_->emitJumpTarget(&finallyStart_)) + return false; + + if (controlInfo_) { + // Fix up the gosubs that might have been emitted before non-local + // jumps to the finally code. + bce_->patchJumpsToTarget(controlInfo_->gosubs, finallyStart_); + + // Indicate that we're emitting a subroutine body. + controlInfo_->setEmittingSubroutine(); + } + if (finallyPos) { + if (!bce_->updateSourceCoordNotes(finallyPos.value())) + return false; + } + if (!bce_->emit1(JSOP_FINALLY)) + return false; + + if (retValKind_ == UseRetVal) { + if (!bce_->emit1(JSOP_GETRVAL)) + return false; + + // Clear the frame's return value to make break/continue return + // correct value even if there's no other statement before them: + // + // eval("x: try { 1 } finally { break x; }"); // undefined, not 1 + if (!bce_->emit1(JSOP_UNDEFINED)) + return false; + if (!bce_->emit1(JSOP_SETRVAL)) + return false; + } + + state_ = Finally; + return true; + } + + private: + bool emitFinallyEnd() { + MOZ_ASSERT(state_ == Finally); + + if (retValKind_ == UseRetVal) { + if (!bce_->emit1(JSOP_SETRVAL)) + return false; + } + + if (!bce_->emit1(JSOP_RETSUB)) + return false; + + bce_->hasTryFinally = true; + return true; + } + + public: + bool emitEnd() { + if (state_ == Catch) { + MOZ_ASSERT(!hasFinally()); + if (!emitCatchEnd(false)) + return false; + } else { + MOZ_ASSERT(state_ == Finally); + MOZ_ASSERT(hasFinally()); + if (!emitFinallyEnd()) + return false; + } + + MOZ_ASSERT(bce_->stackDepth == depth_); + + // ReconstructPCStack needs a NOP here to mark the end of the last + // catch block. + if (!bce_->emit1(JSOP_NOP)) + return false; + + // Fix up the end-of-try/catch jumps to come here. + if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_)) + return false; + + // Add the try note last, to let post-order give us the right ordering + // (first to last for a given nesting level, inner to outer by level). + if (hasCatch()) { + if (!bce_->tryNoteList.append(JSTRY_CATCH, depth_, tryStart_, tryEnd_.offset)) + return false; + } + + // If we've got a finally, mark try+catch region with additional + // trynote to catch exceptions (re)thrown from a catch block or + // for the try{}finally{} case. + if (hasFinally()) { + if (!bce_->tryNoteList.append(JSTRY_FINALLY, depth_, tryStart_, finallyStart_.offset)) + return false; + } + + state_ = End; + 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 +}; + +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_; + + // Used to track if any yields were emitted between calls to to + // emitBeginCodeNeedingIteratorClose and emitEndCodeNeedingIteratorClose. + uint32_t numYieldsAtBeginCodeNeedingIterClose_; + + bool allowSelfHosted_; + + public: + ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted) + : LoopControl(bce, StatementKind::ForOfLoop), + iterDepth_(iterDepth), + numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX), + allowSelfHosted_(allowSelfHosted) + { + } + + bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) { + tryCatch_.emplace(bce, TryEmitter::TryCatch, TryEmitter::DontUseRetVal, + TryEmitter::DontUseControl); + + if (!tryCatch_->emitTry()) + return false; + + MOZ_ASSERT(numYieldsAtBeginCodeNeedingIterClose_ == UINT32_MAX); + numYieldsAtBeginCodeNeedingIterClose_ = bce->yieldOffsetList.numYields; + + 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 any yields were emitted, then this for-of loop is inside a star + // generator and must handle the case of Generator.return. Like in + // yield*, it is handled with a finally block. + uint32_t numYieldsEmitted = bce->yieldOffsetList.numYields; + if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) { + if (!tryCatch_->emitFinally()) + return false; + + IfThenElseEmitter ifGeneratorClosing(bce); + if (!bce->emit1(JSOP_ISGENCLOSING)) // ITER ... FTYPE FVALUE CLOSING + return false; + if (!ifGeneratorClosing.emitIf()) // ITER ... FTYPE FVALUE + return false; + if (!bce->emitDupAt(slotFromTop + 1)) // ITER ... FTYPE FVALUE ITER + return false; + if (!emitIteratorClose(bce, CompletionKind::Normal)) // ITER ... FTYPE FVALUE + return false; + if (!ifGeneratorClosing.emitEnd()) // ITER ... FTYPE FVALUE + return false; + } + + if (!tryCatch_->emitEnd()) + return false; + + tryCatch_.reset(); + numYieldsAtBeginCodeNeedingIterClose_ = UINT32_MAX; + + return true; + } + + bool emitIteratorClose(BytecodeEmitter* bce, + CompletionKind completionKind = CompletionKind::Normal) { + ptrdiff_t start = bce->offset(); + if (!bce->emitIteratorClose(completionKind, allowSelfHosted_)) + return false; + ptrdiff_t end = bce->offset(); + return bce->tryNoteList.append(JSTRY_FOR_OF_ITERCLOSE, 0, start, end); + } + + 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, @@ -1855,6 +2516,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) { @@ -2000,35 +2667,45 @@ BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand) return true; } -bool -BytecodeEmitter::flushPops(int* npops) -{ - MOZ_ASSERT(*npops != 0); - if (!emitUint16Operand(JSOP_POPN, *npops)) - return false; - - *npops = 0; - return true; -} - 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() { @@ -2076,9 +2753,16 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta EmitterScope* es = bce_->innermostEmitterScope; int npops = 0; + // 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)) + if (npops && !bce->emitUint16Operand(JSOP_POPN, npops)) return false; + npops = 0; return true; }; @@ -2107,22 +2791,33 @@ 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: - npops += 2; + if (emitIteratorClose) { + if (!flushPops(bce_)) + return false; + + ForOfLoopControl& loopinfo = control->as<ForOfLoopControl>(); + if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ false)) // ... + return false; + } else { + npops += 3; + } break; case StatementKind::ForInLoop: - /* The iterator and the current value are on the stack. */ - npops += 1; if (!flushPops(bce_)) return false; - if (!bce_->emit1(JSOP_ENDITER)) + + // The iterator and the current value are on the stack. + if (!bce_->emit1(JSOP_POP)) // ... ITER + return false; + if (!bce_->emit1(JSOP_ENDITER)) // ... return false; break; @@ -2131,13 +2826,22 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta } } + if (!flushPops(bce_)) + return false; + + if (target && emitIteratorCloseAtTarget && target->is<ForOfLoopControl>()) { + ForOfLoopControl& loopinfo = target->as<ForOfLoopControl>(); + if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ true)) // ... UNDEF UNDEF UNDEF + return false; + } + EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope; for (; es != targetEmitterScope; es = es->enclosingInFrame()) { if (!leaveScope(es)) return false; } - return flushPops(bce_); + return true; } } // anonymous namespace @@ -2145,7 +2849,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; @@ -4091,6 +4797,11 @@ BytecodeEmitter::emitYieldOp(JSOp op) return false; } + if (op == JSOP_YIELD) + yieldOffsetList.numYields++; + else + yieldOffsetList.numAwaits++; + SET_UINT24(code(off), yieldIndex); if (!yieldOffsetList.append(offset())) @@ -4325,7 +5036,69 @@ BytecodeEmitter::emitDestructuringDeclsWithEmitter(ParseNode* pattern, NameEmitt } bool -BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav) +BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, size_t* emitted) +{ + *emitted = 0; + + if (target->isKind(PNK_SPREAD)) + target = target->pn_kid; + else if (target->isKind(PNK_ASSIGN)) + target = target->pn_left; + + // No need to recur into PNK_ARRAY and PNK_OBJECT subpatterns here, since + // emitSetOrInitializeDestructuring does the recursion when setting or + // initializing value. Getting reference doesn't recur. + if (target->isKind(PNK_NAME) || target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)) + return true; + +#ifdef DEBUG + int depth = stackDepth; +#endif + + switch (target->getKind()) { + case PNK_DOT: { + if (target->as<PropertyAccess>().isSuper()) { + if (!emitSuperPropLHS(&target->as<PropertyAccess>().expression())) + return false; + *emitted = 2; + } else { + if (!emitTree(target->pn_expr)) + return false; + *emitted = 1; + } + break; + } + + case PNK_ELEM: { + if (target->as<PropertyByValue>().isSuper()) { + if (!emitSuperElemOperands(target, EmitElemOption::Ref)) + return false; + *emitted = 3; + } else { + if (!emitElemOperands(target, EmitElemOption::Ref)) + return false; + *emitted = 2; + } + break; + } + + case PNK_CALL: + MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget " + "rejects function calls as assignment " + "targets in destructuring assignments"); + break; + + default: + MOZ_CRASH("emitDestructuringLHSRef: bad lhs kind"); + } + + MOZ_ASSERT(stackDepth == depth + int(*emitted)); + + return true; +} + +bool +BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav) { // Now emit the lvalue opcode sequence. If the lvalue is a nested // destructuring initialiser-form, call ourselves to handle it, then pop @@ -4401,44 +5174,28 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla } case PNK_DOT: { - // See the (PNK_NAME, JSOP_SETNAME) case above. - // - // In `a.x = b`, `a` is evaluated first, then `b`, then a - // JSOP_SETPROP instruction. - // - // In `[a.x] = [b]`, per spec, `b` is evaluated before `a`. Then we - // need a property set -- but the operands are on the stack in the - // wrong order for JSOP_SETPROP, so we have to add a JSOP_SWAP. + // The reference is already pushed by emitDestructuringLHSRef. JSOp setOp; - if (target->as<PropertyAccess>().isSuper()) { - if (!emitSuperPropLHS(&target->as<PropertyAccess>().expression())) - return false; - if (!emit2(JSOP_PICK, 2)) - return false; + if (target->as<PropertyAccess>().isSuper()) setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER; - } else { - if (!emitTree(target->pn_expr)) - return false; - if (!emit1(JSOP_SWAP)) - return false; + else setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; - } if (!emitAtomOp(target, setOp)) return false; break; } case PNK_ELEM: { - // See the comment at `case PNK_DOT:` above. This case, - // `[a[x]] = [b]`, is handled much the same way. The JSOP_SWAP - // is emitted by emitElemOperands. + // The reference is already pushed by emitDestructuringLHSRef. if (target->as<PropertyByValue>().isSuper()) { JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER; - if (!emitSuperElemOp(target, setOp)) + // emitDestructuringLHSRef already did emitSuperElemOperands + // part of emitSuperElemOp. Perform remaining part here. + if (!emitElemOpBase(setOp)) return false; } else { JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; - if (!emitElemOp(target, setOp)) + if (!emitElemOpBase(setOp)) return false; } break; @@ -4451,7 +5208,7 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla break; default: - MOZ_CRASH("emitDestructuringLHS: bad lhs kind"); + MOZ_CRASH("emitSetOrInitializeDestructuring: bad lhs kind"); } // Pop the assigned value. @@ -4463,14 +5220,7 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla } bool -BytecodeEmitter::emitDestructuringLHSInBranch(ParseNode* target, DestructuringFlavor flav) -{ - TDZCheckCache tdzCache(this); - return emitDestructuringLHS(target, flav); -} - -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 " @@ -4491,6 +5241,155 @@ BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) } bool +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 " + "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; + + 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; + + 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 (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; + if (!emit1(JSOP_POP)) // ... ITER + return false; + if (!ifReturnMethodIsDefined.emitEnd()) + return false; + + return emit1(JSOP_POP); // ... +} + +template <typename InnerEmitter> +bool +BytecodeEmitter::wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter) +{ + MOZ_ASSERT(this->stackDepth >= iterDepth); + + // Pad a nop at the beginning of the bytecode covered by the trynote so + // that when unwinding environments, we may unwind to the scope + // corresponding to the pc *before* the start, in case the first bytecode + // emitted by |emitter| is the start of an inner scope. See comment above + // UnwindEnvironmentToTryPc. + if (!emit1(JSOP_TRY_DESTRUCTURING_ITERCLOSE)) + return false; + + ptrdiff_t start = offset(); + if (!emitter(this)) + return false; + ptrdiff_t end = offset(); + if (start != end) + return tryNoteList.append(JSTRY_DESTRUCTURING_ITERCLOSE, iterDepth, start, end); + return true; +} + +bool BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { if (!emit1(JSOP_DUP)) // VALUE VALUE @@ -4578,156 +5477,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) { @@ -4737,240 +5486,304 @@ 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 tmp, done, iter, result; // stack values + // let iter, lref, result, done, value; // stack values // // iter = x[Symbol.iterator](); // // // ==== emitted by loop for a ==== + // lref = GetReference(a); // covered by trynote + // // result = iter.next(); // done = result.done; // - // if (done) { - // a = undefined; - // - // result = undefined; - // done = true; - // } else { - // a = result.value; + // if (done) + // value = undefined; + // else + // value = result.value; // - // // Do next element's .next() and .done access here - // result = iter.next(); - // done = result.done; - // } + // SetOrInitialize(lref, value); // covered by trynote // // // ==== emitted by loop for b ==== - // if (done) { - // b = undefined; + // lref = GetReference(b); // covered by trynote // - // result = undefined; - // done = true; + // if (done) { + // value = undefined; // } else { - // b = result.value; - // // result = iter.next(); // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; // } // + // SetOrInitialize(lref, value); // covered by trynote + // // // ==== emitted by loop for elision ==== // if (done) { - // result = undefined - // done = true + // value = undefined; // } else { - // result.value; - // // result = iter.next(); // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; // } // // // ==== emitted by loop for c ==== + // lref = GetReference(c); // covered by trynote + // // if (done) { - // c = y; + // value = undefined; // } else { - // tmp = result.value; - // if (tmp === undefined) - // tmp = y; - // c = tmp; - // - // // Don't do next element's .next() and .done access if - // // this is the last non-spread element. + // result = iter.next(); + // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; // } // + // if (value === undefined) + // value = y; // covered by trynote + // + // SetOrInitialize(lref, value); // covered by trynote + // // // ==== emitted by loop for d ==== - // if (done) { - // // Assing empty array when completed - // d = []; - // } else { - // d = [...iter]; - // } + // lref = GetReference(d); // covered by trynote + // + // if (done) + // value = []; + // else + // value = [...iter]; + // + // 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. - */ + // Use an iterator to destructure the RHS, instead of index lookup. We + // must leave the *original* value on the stack. if (!emit1(JSOP_DUP)) // ... OBJ OBJ return false; - if (!emitIterator()) // ... OBJ? ITER + if (!emitIterator()) // ... OBJ ITER return false; - bool needToPopIterator = true; + + // 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 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)) { IfThenElseEmitter ifThenElse(this); - if (!isHead) { + if (!isFirst) { // If spread is not the first element of the pattern, // iterator can already be completed. - if (!ifThenElse.emitIfElse()) // ... OBJ? ITER + // ... OBJ ITER *LREF DONE + if (!ifThenElse.emitIfElse()) // ... OBJ ITER *LREF return false; - if (!emit1(JSOP_POP)) // ... OBJ? - return false; - if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ARRAY + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER *LREF ARRAY return false; - if (!emitDestructuringLHSInBranch(member, flav)) // ... OBJ? - return false; - - if (!ifThenElse.emitElse()) // ... OBJ? ITER + if (!ifThenElse.emitElse()) // ... OBJ ITER *LREF return false; } // If iterator is not completed, create a new array with the rest // of the iterator. - if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ITER ARRAY + if (!emitDupAt(emitted)) // ... OBJ ITER *LREF ITER return false; - if (!emitNumberOp(0)) // ... OBJ? ITER ARRAY INDEX + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER *LREF ITER ARRAY return false; - if (!emitSpread()) // ... OBJ? ARRAY INDEX + if (!emitNumberOp(0)) // ... OBJ ITER *LREF ITER ARRAY INDEX return false; - if (!emit1(JSOP_POP)) // ... OBJ? ARRAY + if (!emitSpread()) // ... OBJ ITER *LREF ARRAY INDEX return false; - if (!emitDestructuringLHSInBranch(member, flav)) // ... OBJ? + if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF ARRAY return false; - if (!isHead) { + if (!isFirst) { if (!ifThenElse.emitEnd()) return false; - MOZ_ASSERT(ifThenElse.popped() == 1); + MOZ_ASSERT(ifThenElse.pushed() == 1); } - needToPopIterator = false; - MOZ_ASSERT(!member->pn_next); + + // 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); break; } 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); - bool hasNextNonSpread = member->pn_next && !member->pn_next->isKind(PNK_SPREAD); - bool hasNextSpread = member->pn_next && member->pn_next->isKind(PNK_SPREAD); + MOZ_ASSERT(!member->isKind(PNK_SPREAD)); - MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD)); + IfThenElseEmitter ifAlreadyDone(this); + if (!isFirst) { + // ... OBJ ITER *LREF DONE + if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER *LREF + return false; - auto emitNext = [pattern](ExclusiveContext* cx, BytecodeEmitter* bce) { - if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER ITER + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER *LREF UNDEF return false; - if (!bce->emitIteratorNext(pattern)) // ... OBJ? ITER RESULT + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER *LREF UNDEF return false; - if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER RESULT RESULT + + // The iterator is done. Unpick a TRUE value for DONE above ITER. + if (!emit1(JSOP_TRUE)) // ... OBJ ITER *LREF UNDEF TRUE return false; - if (!bce->emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ? ITER RESULT DONE? + if (!emit2(JSOP_UNPICK, emitted + 1)) // ... OBJ ITER TRUE *LREF UNDEF return false; - return true; - }; - if (isHead) { - if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE? + if (!ifAlreadyDone.emitElse()) // ... OBJ ITER *LREF return false; } - IfThenElseEmitter ifThenElse(this); - if (!ifThenElse.emitIfElse()) // ... OBJ? ITER RESULT - return false; - - if (!emit1(JSOP_POP)) // ... OBJ? ITER - return false; - if (pndefault) { - // Emit only pndefault tree here, as undefined check in emitDefault - // should always be true. - if (!emitInitializerInBranch(pndefault, subpattern)) // ... OBJ? ITER VALUE + if (emitted) { + if (!emitDupAt(emitted)) // ... OBJ ITER *LREF ITER return false; } else { - if (!isElision) { - if (!emit1(JSOP_UNDEFINED)) // ... OBJ? ITER UNDEFINED - return false; - if (!emit1(JSOP_NOP_DESTRUCTURING)) - return false; - } - } - if (!isElision) { - if (!emitDestructuringLHSInBranch(subpattern, flav)) // ... OBJ? ITER - return false; - } else if (pndefault) { - if (!emit1(JSOP_POP)) // ... OBJ? ITER + if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF ITER return false; } + if (!emitIteratorNext(pattern)) // ... OBJ ITER *LREF RESULT + return false; + if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER *LREF RESULT DONE + return false; - // Setup next element's result when the iterator is done. - if (hasNextNonSpread) { - if (!emit1(JSOP_UNDEFINED)) // ... OBJ? ITER RESULT - return false; - if (!emit1(JSOP_NOP_DESTRUCTURING)) - return false; - if (!emit1(JSOP_TRUE)) // ... OBJ? ITER RESULT DONE? - return false; - } else if (hasNextSpread) { - if (!emit1(JSOP_TRUE)) // ... OBJ? ITER 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 + return false; - if (!ifThenElse.emitElse()) // ... OBJ? ITER RESULT + if (!emit1(JSOP_POP)) // ... OBJ ITER DONE *LREF + return false; + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER DONE *LREF UNDEF + return false; + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER DONE *LREF UNDEF return false; - if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ? ITER VALUE + if (!ifDone.emitElse()) // ... OBJ ITER DONE *LREF RESULT return false; - if (pndefault) { - if (!emitDefault(pndefault, subpattern)) // ... OBJ? ITER 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 (!isFirst) { + if (!ifAlreadyDone.emitEnd()) return false; + MOZ_ASSERT(ifAlreadyDone.pushed() == 2); } - if (!isElision) { - if (!emitDestructuringLHSInBranch(subpattern, flav)) // ... OBJ? ITER - return false; - } else { - if (!emit1(JSOP_POP)) // ... OBJ? ITER + if (pndefault) { + auto emitDefault = [pndefault, lhsPattern](BytecodeEmitter* bce) { + return bce->emitDefault(pndefault, lhsPattern); // ... OBJ ITER DONE *LREF VALUE + }; + + if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitDefault)) return false; } - // Setup next element's result when the iterator is not done. - if (hasNextNonSpread) { - if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE? + if (!isElision) { + auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) { + return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); // ... OBJ ITER DONE + }; + + if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment)) return false; - } else if (hasNextSpread) { - if (!emit1(JSOP_FALSE)) // ... OBJ? ITER DONE? + } else { + if (!emit1(JSOP_POP)) // ... OBJ ITER DONE return false; } - - if (!ifThenElse.emitEnd()) - return false; - if (hasNextNonSpread) - MOZ_ASSERT(ifThenElse.pushed() == 1); - else if (hasNextSpread) - MOZ_ASSERT(ifThenElse.pushed() == 0); - else - MOZ_ASSERT(ifThenElse.popped() == 1); } - if (needToPopIterator) { - if (!emit1(JSOP_POP)) // ... OBJ? - 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; } @@ -4994,27 +5807,43 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla return false; for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { - // Duplicate the value being destructured to use as a reference base. - if (!emit1(JSOP_DUP)) // ... RHS RHS + ParseNode* subpattern; + if (member->isKind(PNK_MUTATEPROTO)) + subpattern = member->pn_kid; + else + subpattern = member->pn_right; + ParseNode* lhs = subpattern; + if (lhs->isKind(PNK_ASSIGN)) + lhs = lhs->pn_left; + + size_t emitted; + if (!emitDestructuringLHSRef(lhs, &emitted)) // ... RHS *LREF return false; + // Duplicate the value being destructured to use as a reference base. + if (emitted) { + if (!emitDupAt(emitted)) // ... RHS *LREF RHS + return false; + } else { + if (!emit1(JSOP_DUP)) // ... RHS RHS + return false; + } + // Now push the property name currently being matched, which is the // current property name "label" on the left of a colon in the object // initialiser. bool needsGetElem = true; - ParseNode* subpattern; if (member->isKind(PNK_MUTATEPROTO)) { - if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... RHS PROP + if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... RHS *LREF PROP return false; needsGetElem = false; - subpattern = member->pn_kid; } else { MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND)); ParseNode* key = member->pn_left; if (key->isKind(PNK_NUMBER)) { - if (!emitNumberOp(key->pn_dval)) // ... RHS RHS KEY + if (!emitNumberOp(key->pn_dval)) // ... RHS *LREF RHS KEY return false; } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { PropertyName* name = key->pn_atom->asPropertyName(); @@ -5024,33 +5853,30 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla // as indexes for simplification of downstream analysis. jsid id = NameToId(name); if (id != IdToTypeId(id)) { - if (!emitTree(key)) // ... RHS RHS KEY + if (!emitTree(key)) // ... RHS *LREF RHS KEY return false; } else { - if (!emitAtomOp(name, JSOP_GETPROP)) // ...RHS PROP + if (!emitAtomOp(name, JSOP_GETPROP)) // ... RHS *LREF PROP return false; needsGetElem = false; } } else { - if (!emitComputedPropertyName(key)) // ... RHS RHS KEY + if (!emitComputedPropertyName(key)) // ... RHS *LREF RHS KEY return false; } - - subpattern = member->pn_right; } // Get the property value if not done already. - if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS PROP + if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS *LREF PROP return false; if (subpattern->isKind(PNK_ASSIGN)) { - if (!emitDefault(subpattern->pn_right, subpattern->pn_left)) + if (!emitDefault(subpattern->pn_right, lhs)) // ... RHS *LREF VALUE return false; - subpattern = subpattern->pn_left; } - // Destructure PROP per this member's subpattern. - if (!emitDestructuringLHS(subpattern, flav)) + // Destructure PROP per this member's lhs. + if (!emitSetOrInitializeDestructuring(subpattern, flav)) // ... RHS return false; } @@ -5628,7 +6454,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. @@ -5662,57 +6488,28 @@ BytecodeEmitter::emitCatch(ParseNode* pn) MOZ_NEVER_INLINE bool BytecodeEmitter::emitTry(ParseNode* pn) { - // Track jumps-over-catches and gosubs-to-finally for later fixup. - // - // When a finally block is active, non-local jumps (including - // jumps-over-catches) result in a GOSUB being written into the bytecode - // stream and fixed-up later. - // - TryFinallyControl controlInfo(this, pn->pn_kid3 ? StatementKind::Finally : StatementKind::Try); - - // Since an exception can be thrown at any place inside the try block, - // we need to restore the stack and the scope chain before we transfer - // the control to the exception handler. - // - // For that we store in a try note associated with the catch or - // finally block the stack depth upon the try entry. The interpreter - // uses this depth to properly unwind the stack and the scope chain. - // - int depth = stackDepth; - - // Record the try location, then emit the try block. - unsigned noteIndex; - if (!newSrcNote(SRC_TRY, ¬eIndex)) - return false; - if (!emit1(JSOP_TRY)) - return false; - - ptrdiff_t tryStart = offset(); - if (!emitTree(pn->pn_kid1)) - return false; - MOZ_ASSERT(depth == stackDepth); + ParseNode* catchList = pn->pn_kid2; + ParseNode* finallyNode = pn->pn_kid3; - // GOSUB to finally, if present. - if (pn->pn_kid3) { - if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs)) - return false; + TryEmitter::Kind kind; + if (catchList) { + if (finallyNode) + kind = TryEmitter::TryCatchFinally; + else + kind = TryEmitter::TryCatch; + } else { + MOZ_ASSERT(finallyNode); + kind = TryEmitter::TryFinally; } + TryEmitter tryCatch(this, kind); - // Source note points to the jump at the end of the try block. - if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart + JSOP_TRY_LENGTH)) - return false; - - // Emit jump over catch and/or finally. - JumpList catchJump; - if (!emitJump(JSOP_GOTO, &catchJump)) + if (!tryCatch.emitTry()) return false; - JumpTarget tryEnd; - if (!emitJumpTarget(&tryEnd)) + if (!emitTree(pn->pn_kid1)) return false; // If this try has a catch block, emit it. - ParseNode* catchList = pn->pn_kid2; if (catchList) { MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST)); @@ -5742,110 +6539,26 @@ BytecodeEmitter::emitTry(ParseNode* pn) // capturing exceptions thrown from catch{} blocks. // for (ParseNode* pn3 = catchList->pn_head; pn3; pn3 = pn3->pn_next) { - MOZ_ASSERT(this->stackDepth == depth); - - // Clear the frame's return value that might have been set by the - // try block: - // - // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1 - if (!emit1(JSOP_UNDEFINED)) - return false; - if (!emit1(JSOP_SETRVAL)) + if (!tryCatch.emitCatch()) return false; // Emit the lexical scope and catch body. MOZ_ASSERT(pn3->isKind(PNK_LEXICALSCOPE)); if (!emitTree(pn3)) return false; - - // gosub <finally>, if required. - if (pn->pn_kid3) { - if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs)) - return false; - MOZ_ASSERT(this->stackDepth == depth); - } - - // Jump over the remaining catch blocks. This will get fixed - // up to jump to after catch/finally. - if (!emitJump(JSOP_GOTO, &catchJump)) - return false; - - // If this catch block had a guard clause, patch the guard jump to - // come here. - if (controlInfo.guardJump.offset != -1) { - if (!emitJumpTargetAndPatch(controlInfo.guardJump)) - return false; - controlInfo.guardJump.offset = -1; - - // If this catch block is the last one, rethrow, delegating - // execution of any finally block to the exception handler. - if (!pn3->pn_next) { - if (!emit1(JSOP_EXCEPTION)) - return false; - if (!emit1(JSOP_THROW)) - return false; - } - } } } - MOZ_ASSERT(this->stackDepth == depth); - // Emit the finally handler, if there is one. - JumpTarget finallyStart{ 0 }; - if (pn->pn_kid3) { - if (!emitJumpTarget(&finallyStart)) + if (finallyNode) { + if (!tryCatch.emitFinally(Some(finallyNode->pn_pos.begin))) return false; - // Fix up the gosubs that might have been emitted before non-local - // jumps to the finally code. - patchJumpsToTarget(controlInfo.gosubs, finallyStart); - - // Indicate that we're emitting a subroutine body. - controlInfo.setEmittingSubroutine(); - if (!updateSourceCoordNotes(pn->pn_kid3->pn_pos.begin)) + if (!emitTree(finallyNode)) return false; - if (!emit1(JSOP_FINALLY)) - return false; - if (!emit1(JSOP_GETRVAL)) - return false; - - // Clear the frame's return value to make break/continue return - // correct value even if there's no other statement before them: - // - // eval("x: try { 1 } finally { break x; }"); // undefined, not 1 - if (!emit1(JSOP_UNDEFINED)) - return false; - if (!emit1(JSOP_SETRVAL)) - return false; - - if (!emitTree(pn->pn_kid3)) - return false; - if (!emit1(JSOP_SETRVAL)) - return false; - if (!emit1(JSOP_RETSUB)) - return false; - hasTryFinally = true; - MOZ_ASSERT(this->stackDepth == depth); } - // ReconstructPCStack needs a NOP here to mark the end of the last catch block. - if (!emit1(JSOP_NOP)) - return false; - - // Fix up the end-of-try/catch jumps to come here. - if (!emitJumpTargetAndPatch(catchJump)) - return false; - - // Add the try note last, to let post-order give us the right ordering - // (first to last for a given nesting level, inner to outer by level). - if (catchList && !tryNoteList.append(JSTRY_CATCH, depth, tryStart, tryEnd.offset)) - return false; - - // If we've got a finally, mark try+catch region with additional - // trynote to catch exceptions (re)thrown from a catch block or - // for the try{}finally{} case. - if (pn->pn_kid3 && !tryNoteList.append(JSTRY_FINALLY, depth, tryStart, finallyStart.offset)) + if (!tryCatch.emitEnd()) return false; return true; @@ -6118,7 +6831,7 @@ BytecodeEmitter::emitSpread(bool allowSelfHosted) return false; if (!emit1(JSOP_DUP)) // ITER ARR I RESULT RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER ARR I RESULT DONE? + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER ARR I RESULT DONE return false; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER ARR I RESULT @@ -6214,19 +6927,34 @@ 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 return false; - // For-of loops have both the iterator and the value on the stack. Push - // undefined to balance the stack. + int32_t iterDepth = stackDepth; + + // 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; - LoopControl loopInfo(this, StatementKind::ForOfLoop); + ForOfLoopControl loopInfo(this, iterDepth, allowSelfHostedIter); // Annotate so IonMonkey can find the loop-closing jump. unsigned noteIndex; @@ -6234,11 +6962,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 @@ -6255,7 +6983,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; } @@ -6272,46 +7000,69 @@ 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_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; - 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(this->stackDepth == loopDepth, + MOZ_ASSERT(stackDepth == loopDepth, "the stack must be balanced around the initializing " "operation"); + // Remove VALUE from the stack to release it. + if (!emit1(JSOP_POP)) // ITER RESULT + return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF + return false; + // Perform the loop body. ParseNode* forBody = forOfLoop->pn_right; - if (!emitTree(forBody)) // ITER RESULT + if (!emitTree(forBody)) // ITER RESULT UNDEF + return false; + + 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; - if (!emitIteratorNext(forOfHead)) // ITER RESULT + if (!emitIteratorNext(forOfHead, allowSelfHostedIter)) // ITER UNDEF RESULT return false; - if (!emit1(JSOP_DUP)) // ITER RESULT RESULT + + if (!emit1(JSOP_SWAP)) // ITER RESULT UNDEF + return false; + + if (!emitDupAt(1)) // ITER RESULT UNDEF RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE? + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT UNDEF DONE return false; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) - return false; // ITER RESULT + return false; // ITER RESULT UNDEF MOZ_ASSERT(this->stackDepth == loopDepth); } @@ -6326,7 +7077,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 @@ -6736,6 +7487,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 @@ -6773,42 +7526,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); @@ -6830,7 +7600,7 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) } // Pop the result and the iter. - return emitUint16Operand(JSOP_POPN, 2); // + return emitUint16Operand(JSOP_POPN, 3); // } bool @@ -6975,14 +7745,13 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) RootedFunction fun(cx, funbox->function()); RootedAtom name(cx, fun->explicitName()); MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript()); - MOZ_ASSERT_IF(pn->isOp(JSOP_FUNWITHPROTO), needsProto); /* * Set the |wasEmitted| flag in the funbox once the function has been * emitted. Function definitions that need hoisting to the top of the * function will be seen by emitFunction in two places. */ - if (funbox->wasEmitted && pn->functionIsHoisted()) { + if (funbox->wasEmitted) { // Annex B block-scoped functions are hoisted like any other // block-scoped function to the top of their scope. When their // definitions are seen for the second time, we need to emit the @@ -7111,7 +7880,7 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) } if (needsProto) { - MOZ_ASSERT(pn->getOp() == JSOP_FUNWITHPROTO || pn->getOp() == JSOP_LAMBDA); + MOZ_ASSERT(pn->getOp() == JSOP_LAMBDA); pn->setOp(JSOP_FUNWITHPROTO); } @@ -7528,7 +8297,7 @@ BytecodeEmitter::emitReturn(ParseNode* pn) return false; } - NonLocalExitControl nle(this); + NonLocalExitControl nle(this, NonLocalExitControl::Return); if (!nle.prepareForNonLocalJumpToOutermost()) return false; @@ -7598,110 +8367,208 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) MOZ_ASSERT(sc->isFunctionBox()); MOZ_ASSERT(sc->asFunctionBox()->isStarGenerator()); - if (!emitTree(iter)) // ITERABLE + if (!emitTree(iter)) // ITERABLE return false; - if (!emitIterator()) // ITER + if (!emitIterator()) // ITER return false; // Initial send value is undefined. - if (!emit1(JSOP_UNDEFINED)) // ITER RECEIVED + if (!emit1(JSOP_UNDEFINED)) // ITER RECEIVED return false; - int depth = stackDepth; - MOZ_ASSERT(depth >= 2); + int32_t savedDepthTemp; + int32_t startDepth = stackDepth; + MOZ_ASSERT(startDepth >= 2); - JumpList send; - if (!emitJump(JSOP_GOTO, &send)) // goto send + TryEmitter tryCatch(this, TryEmitter::TryCatchFinally, TryEmitter::DontUseRetVal, + TryEmitter::DontUseControl); + if (!tryCatch.emitJumpOverCatchAndFinally()) // ITER RESULT return false; - // Try prologue. // ITER RESULT - unsigned noteIndex; - if (!newSrcNote(SRC_TRY, ¬eIndex)) - return false; JumpTarget tryStart{ offset() }; - if (!emit1(JSOP_TRY)) // tryStart: + if (!tryCatch.emitTry()) // ITER RESULT return false; - MOZ_ASSERT(this->stackDepth == depth); + + MOZ_ASSERT(this->stackDepth == startDepth); // Load the generator object. - if (!emitTree(gen)) // ITER RESULT GENOBJ + if (!emitTree(gen)) // ITER RESULT GENOBJ return false; // Yield RESULT as-is, without re-boxing. - if (!emitYieldOp(JSOP_YIELD)) // ITER RECEIVED + if (!emitYieldOp(JSOP_YIELD)) // ITER RECEIVED return false; - // Try epilogue. - if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart.offset)) - return false; - if (!emitJump(JSOP_GOTO, &send)) // goto send + if (!tryCatch.emitCatch()) // ITER RESULT return false; - JumpTarget tryEnd; - if (!emitJumpTarget(&tryEnd)) // tryEnd: + stackDepth = startDepth; // ITER RESULT + if (!emit1(JSOP_EXCEPTION)) // ITER RESULT EXCEPTION + return false; + if (!emitDupAt(2)) // ITER RESULT EXCEPTION ITER + return false; + if (!emit1(JSOP_DUP)) // ITER RESULT EXCEPTION ITER ITER + return false; + if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) // ITER RESULT EXCEPTION ITER THROW + return false; + if (!emit1(JSOP_DUP)) // ITER RESULT EXCEPTION ITER THROW THROW + return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT EXCEPTION ITER THROW THROW UNDEFINED + return false; + if (!emit1(JSOP_EQ)) // ITER RESULT EXCEPTION ITER THROW ?EQL return false; - // Catch location. - stackDepth = uint32_t(depth); // ITER RESULT - if (!emit1(JSOP_POP)) // ITER + IfThenElseEmitter ifThrowMethodIsNotDefined(this); + if (!ifThrowMethodIsNotDefined.emitIf()) // ITER RESULT EXCEPTION ITER THROW + return false; + savedDepthTemp = stackDepth; + if (!emit1(JSOP_POP)) // ITER RESULT EXCEPTION ITER return false; - // THROW? = 'throw' in ITER - if (!emit1(JSOP_EXCEPTION)) // ITER EXCEPTION + // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.2 + // + // If the iterator does not have a "throw" method, it calls IteratorClose + // and then throws a TypeError. + if (!emitIteratorClose()) // ITER RESULT EXCEPTION + return false; + if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_ITERATOR_NO_THROW)) // throw return false; - if (!emit1(JSOP_SWAP)) // EXCEPTION ITER + stackDepth = savedDepthTemp; + if (!ifThrowMethodIsNotDefined.emitEnd()) // ITER OLDRESULT EXCEPTION ITER THROW return false; - if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER + // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.4. + // RESULT = ITER.throw(EXCEPTION) // ITER OLDRESULT EXCEPTION ITER THROW + if (!emit1(JSOP_SWAP)) // ITER OLDRESULT EXCEPTION THROW ITER return false; - if (!emitAtomOp(cx->names().throw_, JSOP_STRING)) // EXCEPTION ITER ITER "throw" + if (!emit2(JSOP_PICK, 2)) // ITER OLDRESULT THROW ITER EXCEPTION return false; - if (!emit1(JSOP_SWAP)) // EXCEPTION ITER "throw" ITER + if (!emitCall(JSOP_CALL, 1, iter)) // ITER OLDRESULT RESULT return false; - if (!emit1(JSOP_IN)) // EXCEPTION ITER THROW? + checkTypeSet(JSOP_CALL); + if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) // ITER OLDRESULT RESULT return false; - // if (THROW?) goto delegate - JumpList checkThrow; - if (!emitJump(JSOP_IFNE, &checkThrow)) // EXCEPTION ITER + if (!emit1(JSOP_SWAP)) // ITER RESULT OLDRESULT return false; - if (!emit1(JSOP_POP)) // EXCEPTION + if (!emit1(JSOP_POP)) // ITER RESULT return false; - if (!emit1(JSOP_THROW)) // throw EXCEPTION + MOZ_ASSERT(this->stackDepth == startDepth); + JumpList checkResult; + // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.ii. + // + // Note that there is no GOSUB to the finally block here. If the iterator has a + // "throw" method, it does not perform IteratorClose. + if (!emitJump(JSOP_GOTO, &checkResult)) // goto checkResult return false; - if (!emitJumpTargetAndPatch(checkThrow)) // delegate: + if (!tryCatch.emitFinally()) + return false; + + // ES 14.4.13, yield * AssignmentExpression, step 5.c + // + // Call iterator.return() for receiving a "forced return" completion from + // the generator. + + IfThenElseEmitter ifGeneratorClosing(this); + if (!emit1(JSOP_ISGENCLOSING)) // ITER RESULT FTYPE FVALUE CLOSING return false; - // RESULT = ITER.throw(EXCEPTION) // EXCEPTION ITER - stackDepth = uint32_t(depth); - if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER + if (!ifGeneratorClosing.emitIf()) // ITER RESULT FTYPE FVALUE return false; - if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER ITER + + // Step ii. + // + // Get the "return" method. + if (!emitDupAt(3)) // ITER RESULT FTYPE FVALUE ITER return false; - if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) // EXCEPTION ITER ITER THROW + if (!emit1(JSOP_DUP)) // ITER RESULT FTYPE FVALUE ITER ITER return false; - if (!emit1(JSOP_SWAP)) // EXCEPTION ITER THROW ITER + if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) // ITER RESULT FTYPE FVALUE ITER RET return false; - if (!emit2(JSOP_PICK, 3)) // ITER THROW ITER EXCEPTION + + // Step iii. + // + // Do nothing if "return" is undefined. + IfThenElseEmitter ifReturnMethodIsDefined(this); + if (!emit1(JSOP_DUP)) // ITER RESULT FTYPE FVALUE ITER RET RET return false; - if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT FTYPE FVALUE ITER RET RET UNDEFINED + return false; + if (!emit1(JSOP_NE)) // ITER RESULT FTYPE FVALUE ITER RET ?NEQL + return false; + + // Step iv. + // + // Call "return" with the argument passed to Generator.prototype.return, + // which is currently in rval.value. + if (!ifReturnMethodIsDefined.emitIfElse()) // ITER OLDRESULT FTYPE FVALUE ITER RET + return false; + if (!emit1(JSOP_SWAP)) // ITER OLDRESULT FTYPE FVALUE RET ITER + return false; + if (!emit1(JSOP_GETRVAL)) // ITER OLDRESULT FTYPE FVALUE RET ITER RVAL + return false; + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE RET ITER VALUE + return false; + if (!emitCall(JSOP_CALL, 1)) // ITER OLDRESULT FTYPE FVALUE RESULT return false; checkTypeSet(JSOP_CALL); - MOZ_ASSERT(this->stackDepth == depth); - JumpList checkResult; - if (!emitJump(JSOP_GOTO, &checkResult)) // goto checkResult + + // Step v. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ITER OLDRESULT FTYPE FVALUE RESULT return false; - // Catch epilogue. + // Steps vi-viii. + // + // Check if the returned object from iterator.return() is done. If not, + // continuing yielding. + 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 (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE VALUE + return false; + if (!emitPrepareIteratorResult()) // ITER OLDRESULT FTYPE FVALUE VALUE RESULT + return false; + if (!emit1(JSOP_SWAP)) // ITER OLDRESULT FTYPE FVALUE RESULT VALUE + return false; + if (!emitFinishIteratorResult(true)) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + if (!emit1(JSOP_SETRVAL)) // ITER OLDRESULT FTYPE FVALUE + return false; + savedDepthTemp = this->stackDepth; + if (!ifReturnDone.emitElse()) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + if (!emit2(JSOP_UNPICK, 3)) // ITER RESULT OLDRESULT FTYPE FVALUE + return false; + if (!emitUint16Operand(JSOP_POPN, 3)) // ITER RESULT + return false; + { + // goto tryStart; + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_GOTO, tryStart, &beq, &breakTarget)) // ITER RESULT + return false; + } + this->stackDepth = savedDepthTemp; + if (!ifReturnDone.emitEnd()) + return false; - // This is a peace offering to ReconstructPCStack. See the note in EmitTry. - if (!emit1(JSOP_NOP)) + if (!ifReturnMethodIsDefined.emitElse()) // ITER RESULT FTYPE FVALUE ITER RET + return false; + if (!emit1(JSOP_POP)) // ITER RESULT FTYPE FVALUE ITER + return false; + if (!emit1(JSOP_POP)) // ITER RESULT FTYPE FVALUE return false; - if (!tryNoteList.append(JSTRY_CATCH, depth, tryStart.offset + JSOP_TRY_LENGTH, tryEnd.offset)) + if (!ifReturnMethodIsDefined.emitEnd()) return false; - // After the try/catch block: send the received value to the iterator. - if (!emitJumpTargetAndPatch(send)) // send: + if (!ifGeneratorClosing.emitEnd()) return false; - // Send location. + if (!tryCatch.emitEnd()) + return false; + + // After the try-catch-finally block: send the received value to the iterator. // result = iter.next(received) // ITER RECEIVED if (!emit1(JSOP_SWAP)) // RECEIVED ITER return false; @@ -7720,7 +8587,7 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ITER RESULT return false; checkTypeSet(JSOP_CALL); - MOZ_ASSERT(this->stackDepth == depth); + MOZ_ASSERT(this->stackDepth == startDepth); if (!emitJumpTargetAndPatch(checkResult)) // checkResult: return false; @@ -7731,10 +8598,12 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE return false; // if (!DONE) goto tryStart; - JumpList beq; - JumpTarget breakTarget{ -1 }; - if (!emitBackwardJump(JSOP_IFEQ, tryStart, &beq, &breakTarget)) // ITER RESULT - return false; + { + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_IFEQ, tryStart, &beq, &breakTarget)) // ITER RESULT + return false; + } // result.value if (!emit1(JSOP_SWAP)) // RESULT ITER @@ -7744,7 +8613,7 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // VALUE return false; - MOZ_ASSERT(this->stackDepth == depth - 1); + MOZ_ASSERT(this->stackDepth == startDepth - 1); return true; } @@ -8066,10 +8935,10 @@ BytecodeEmitter::emitSelfHostedForceInterpreter(ParseNode* pn) } bool -BytecodeEmitter::emitSelfHostedAllowContentSpread(ParseNode* pn) +BytecodeEmitter::emitSelfHostedAllowContentIter(ParseNode* pn) { if (pn->pn_count != 2) { - reportError(pn, JSMSG_MORE_ARGS_NEEDED, "allowContentSpread", "1", ""); + reportError(pn, JSMSG_MORE_ARGS_NEEDED, "allowContentIter", "1", ""); return false; } @@ -8095,7 +8964,7 @@ BytecodeEmitter::isRestParameter(ParseNode* pn, bool* result) if (!pn->isKind(PNK_NAME)) { if (emitterMode == BytecodeEmitter::SelfHosting && pn->isKind(PNK_CALL)) { ParseNode* pn2 = pn->pn_head; - if (pn2->getKind() == PNK_NAME && pn2->name() == cx->names().allowContentSpread) + if (pn2->getKind() == PNK_NAME && pn2->name() == cx->names().allowContentIter) return isRestParameter(pn2->pn_next, result); } *result = false; @@ -8203,8 +9072,8 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn) return emitSelfHostedResumeGenerator(pn); if (pn2->name() == cx->names().forceInterpreter) return emitSelfHostedForceInterpreter(pn); - if (pn2->name() == cx->names().allowContentSpread) - return emitSelfHostedAllowContentSpread(pn); + if (pn2->name() == cx->names().allowContentIter) + return emitSelfHostedAllowContentIter(pn); // Fall through. } if (!emitGetName(pn2, callop)) @@ -8859,7 +9728,7 @@ BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) if (!updateSourceCoordNotes(pn2->pn_pos.begin)) return false; - bool allowSelfHostedSpread = false; + bool allowSelfHostedIter = false; if (pn2->isKind(PNK_ELISION)) { if (!emit1(JSOP_HOLE)) return false; @@ -8870,9 +9739,9 @@ BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) if (emitterMode == BytecodeEmitter::SelfHosting && expr->isKind(PNK_CALL) && - expr->pn_head->name() == cx->names().allowContentSpread) + expr->pn_head->name() == cx->names().allowContentIter) { - allowSelfHostedSpread = true; + allowSelfHostedIter = true; } } else { expr = pn2; @@ -8887,7 +9756,7 @@ BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) return false; if (!emit2(JSOP_PICK, 2)) // ITER ARRAY INDEX return false; - if (!emitSpread(allowSelfHostedSpread)) // ARRAY INDEX + if (!emitSpread(allowSelfHostedIter)) // ARRAY INDEX return false; } else if (afterSpread) { if (!emit1(JSOP_INITELEM_INC)) @@ -10047,15 +10916,6 @@ CGConstList::finish(ConstArray* array) array->vector[i] = list[i]; } -bool -CGObjectList::isAdded(ObjectBox* objbox) -{ - // An objbox added to CGObjectList as non-first element has non-null - // emitLink member. The first element has null emitLink. - // Check for firstbox to cover the first element. - return objbox->emitLink || objbox == firstbox; -} - /* * Find the index of the given object for code generator. * @@ -10067,15 +10927,9 @@ CGObjectList::isAdded(ObjectBox* objbox) unsigned CGObjectList::add(ObjectBox* objbox) { - if (isAdded(objbox)) - return indexOf(objbox->object); - + MOZ_ASSERT(!objbox->emitLink); objbox->emitLink = lastbox; lastbox = objbox; - - // See the comment in CGObjectList::isAdded. - if (!firstbox) - firstbox = objbox; return length++; } @@ -10102,12 +10956,7 @@ CGObjectList::finish(ObjectArray* array) MOZ_ASSERT(!*cursor); MOZ_ASSERT(objbox->object->isTenured()); *cursor = objbox->object; - - ObjectBox* tmp = objbox->emitLink; - // Clear emitLink for CGObjectList::isAdded. - objbox->emitLink = nullptr; - objbox = tmp; - } while (objbox != nullptr); + } while ((objbox = objbox->emitLink) != nullptr); MOZ_ASSERT(cursor == array->vector); } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 9a2ddb568..04307c8c1 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -43,12 +43,10 @@ class CGConstList { struct CGObjectList { uint32_t length; /* number of emitted so far objects */ - ObjectBox* firstbox; /* first emitted object */ ObjectBox* lastbox; /* last emitted object */ - CGObjectList() : length(0), firstbox(nullptr), lastbox(nullptr) {} + CGObjectList() : length(0), lastbox(nullptr) {} - bool isAdded(ObjectBox* objbox); unsigned add(ObjectBox* objbox); unsigned indexOf(JSObject* obj); void finish(ObjectArray* array); @@ -102,7 +100,9 @@ struct CGScopeNoteList { struct CGYieldOffsetList { Vector<uint32_t> list; - explicit CGYieldOffsetList(ExclusiveContext* cx) : list(cx) {} + uint32_t numYields; + uint32_t numAwaits; + explicit CGYieldOffsetList(ExclusiveContext* cx) : list(cx), numYields(0), numAwaits(0) {} MOZ_MUST_USE bool append(uint32_t offset) { return list.append(offset); } size_t length() const { return list.length(); } @@ -454,8 +454,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter JSOp strictifySetNameOp(JSOp op); - MOZ_MUST_USE bool flushPops(int* npops); - MOZ_MUST_USE bool emitCheck(ptrdiff_t delta, ptrdiff_t* offset); // Emit one bytecode. @@ -475,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); @@ -611,7 +612,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM // opcode onto the stack in the right order. In the case of SETELEM, the // value to be assigned must already be pushed. - enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign }; + enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign, Ref }; MOZ_MUST_USE bool emitElemOperands(ParseNode* pn, EmitElemOption opts); MOZ_MUST_USE bool emitElemOpBase(JSOp op); @@ -644,11 +645,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter DestructuringAssignment }; - // emitDestructuringLHS assumes the to-be-destructured value has been pushed on - // the stack and emits code to destructure a single lhs expression (either a - // name or a compound []/{} expression). - MOZ_MUST_USE bool emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav); - MOZ_MUST_USE bool emitDestructuringLHSInBranch(ParseNode* target, DestructuringFlavor flav); + // emitDestructuringLHSRef emits the lhs expression's reference. + // If the lhs expression is object property |OBJ.prop|, it emits |OBJ|. + // If it's object element |OBJ[ELEM]|, it emits |OBJ| and |ELEM|. + // If there's nothing to evaluate for the reference, it emits nothing. + // |emitted| parameter receives the number of values pushed onto the stack. + MOZ_MUST_USE bool emitDestructuringLHSRef(ParseNode* target, size_t* emitted); + + // emitSetOrInitializeDestructuring assumes the lhs expression's reference + // and the to-be-destructured value has been pushed on the stack. It emits + // code to destructure a single lhs expression (either a name or a compound + // []/{} expression). + MOZ_MUST_USE bool emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav); // emitDestructuringOps assumes the to-be-destructured value has been // pushed on the stack and emits code to destructure each part of a [] or @@ -674,6 +682,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(CompletionKind completionKind = CompletionKind::Normal, + bool allowSelfHosted = false); + + template <typename InnerEmitter> + 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|. @@ -721,7 +735,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitSelfHostedCallFunction(ParseNode* pn); MOZ_MUST_USE bool emitSelfHostedResumeGenerator(ParseNode* pn); MOZ_MUST_USE bool emitSelfHostedForceInterpreter(ParseNode* pn); - MOZ_MUST_USE bool emitSelfHostedAllowContentSpread(ParseNode* pn); + MOZ_MUST_USE bool emitSelfHostedAllowContentIter(ParseNode* pn); MOZ_MUST_USE bool emitComprehensionFor(ParseNode* compFor); MOZ_MUST_USE bool emitComprehensionForIn(ParseNode* pn); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index ff26279af..c58dab431 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -649,14 +649,12 @@ class ParseNode MOZ_ASSERT(pn_arity == PN_CODE && getKind() == PNK_FUNCTION); MOZ_ASSERT(isOp(JSOP_LAMBDA) || // lambda, genexpr isOp(JSOP_LAMBDA_ARROW) || // arrow function - isOp(JSOP_FUNWITHPROTO) || // already emitted lambda with needsProto isOp(JSOP_DEFFUN) || // non-body-level function statement isOp(JSOP_NOP) || // body-level function stmt in global code isOp(JSOP_GETLOCAL) || // body-level function stmt in function code isOp(JSOP_GETARG) || // body-level function redeclaring formal isOp(JSOP_INITLEXICAL)); // block-level function stmt - return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) && - !isOp(JSOP_FUNWITHPROTO) && !isOp(JSOP_DEFFUN); + return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) && !isOp(JSOP_DEFFUN); } /* diff --git a/js/src/jit-test/tests/auto-regress/for-of-iterator-close-debugger.js b/js/src/jit-test/tests/auto-regress/for-of-iterator-close-debugger.js new file mode 100644 index 000000000..a4d0bf654 --- /dev/null +++ b/js/src/jit-test/tests/auto-regress/for-of-iterator-close-debugger.js @@ -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; + } +} diff --git a/js/src/jit-test/tests/for-of/bug-1331444.js b/js/src/jit-test/tests/for-of/bug-1331444.js new file mode 100644 index 000000000..9770c584b --- /dev/null +++ b/js/src/jit-test/tests/for-of/bug-1331444.js @@ -0,0 +1,7 @@ +// |jit-test| error: ReferenceError + +symbols = [Symbol]; +for (comparator of[, ]) + for (a of symbols) + for (;;) + expect; diff --git a/js/src/jit-test/tests/for-of/bug-1341339.js b/js/src/jit-test/tests/for-of/bug-1341339.js new file mode 100644 index 000000000..1f88acdaf --- /dev/null +++ b/js/src/jit-test/tests/for-of/bug-1341339.js @@ -0,0 +1,9 @@ +let m = parseModule(` +function* values() {} +var iterator = values(); +for (var i=0; i < 10000; ++i) { + for (var x of iterator) {} +} +`); +m.declarationInstantiation(); +m.evaluation(); diff --git a/js/src/jit-test/tests/ion/bug1333946.js b/js/src/jit-test/tests/ion/bug1333946.js new file mode 100644 index 000000000..1fa1b9c49 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1333946.js @@ -0,0 +1,8 @@ +// |jit-test| error: 42; + +for (var x of [0]) { + for (var i = 0; ; i++) { + if (i === 20000) + throw 42; + } +} diff --git a/js/src/jit-test/tests/ion/bug1334314.js b/js/src/jit-test/tests/ion/bug1334314.js new file mode 100644 index 000000000..488fc9027 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1334314.js @@ -0,0 +1,16 @@ +// |jit-test| error: TypeError + +var g = newGlobal(); +g.parent = this; +g.eval("new Debugger(parent).onExceptionUnwind = function () { };"); + +function f() { + [[]] = []; +} +try { + f(); +} catch (e) {}; +try { + f(); +} catch (e) {}; +f(); diff --git a/js/src/jit-test/tests/parser/bug-1357075.js b/js/src/jit-test/tests/parser/bug-1357075.js new file mode 100644 index 000000000..47482e372 --- /dev/null +++ b/js/src/jit-test/tests/parser/bug-1357075.js @@ -0,0 +1,10 @@ +// |jit-test| error: TypeError + +var iterable = {}; +var iterator = { + return: 1 +}; +iterable[Symbol.iterator] = function() { + return iterator; +}; +for ([ class get {} ().iterator ] of [iterable]) {} diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index 8fc8a522d..3ab722b3d 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -487,7 +487,7 @@ GetNextNonLoopEntryPc(jsbytecode* pc) } static bool -HasLiveIteratorAtStackDepth(JSScript* script, jsbytecode* pc, uint32_t stackDepth) +HasLiveStackValueAtDepth(JSScript* script, jsbytecode* pc, uint32_t stackDepth) { if (!script->hasTrynotes()) return false; @@ -501,14 +501,31 @@ HasLiveIteratorAtStackDepth(JSScript* script, jsbytecode* pc, uint32_t stackDept if (pcOffset >= tn->start + tn->length) continue; - // For-in loops have only the iterator on stack. - if (tn->kind == JSTRY_FOR_IN && stackDepth == tn->stackDepth) - return true; + switch (tn->kind) { + case JSTRY_FOR_IN: + // For-in loops have only the iterator on stack. + if (stackDepth == tn->stackDepth) + return true; + break; + + case JSTRY_FOR_OF: + // 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; + + case JSTRY_DESTRUCTURING_ITERCLOSE: + // Destructuring code that need to call IteratorClose have both + // the iterator and the "done" value on the stack. + if (stackDepth == tn->stackDepth || stackDepth == tn->stackDepth - 1) + return true; + break; - // For-of loops have both the iterator and the result object on - // stack. The iterator is below the result object. - if (tn->kind == JSTRY_FOR_OF && stackDepth == tn->stackDepth - 1) - return true; + default: + break; + } } return false; @@ -945,7 +962,7 @@ InitFromBailout(JSContext* cx, HandleScript caller, jsbytecode* callerPC, // iterators, however, so read them out. They will be closed by // HandleExceptionBaseline. MOZ_ASSERT(cx->compartment()->isDebuggee()); - if (iter.moreFrames() || HasLiveIteratorAtStackDepth(script, pc, i + 1)) { + if (iter.moreFrames() || HasLiveStackValueAtDepth(script, pc, i + 1)) { v = iter.read(); } else { iter.skip(); diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 4dcc10b61..3fa5a80ed 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1063,6 +1063,12 @@ BaselineCompiler::emit_JSOP_NOP_DESTRUCTURING() } bool +BaselineCompiler::emit_JSOP_TRY_DESTRUCTURING_ITERCLOSE() +{ + return true; +} + +bool BaselineCompiler::emit_JSOP_LABEL() { return true; @@ -1147,7 +1153,7 @@ BaselineCompiler::emit_JSOP_PICK() // after : A B D E C // First, move value at -(amount + 1) into R0. - int depth = -(GET_INT8(pc) + 1); + int32_t depth = -(GET_INT8(pc) + 1); masm.loadValue(frame.addressOfStackValue(frame.peek(depth)), R0); // Move the other values down. @@ -1166,6 +1172,34 @@ BaselineCompiler::emit_JSOP_PICK() } bool +BaselineCompiler::emit_JSOP_UNPICK() +{ + frame.syncStack(0); + + // Pick takes the top of the stack value and moves it under the nth value. + // For instance, unpick 2: + // before: A B C D E + // after : A B E C D + + // First, move value at -1 into R0. + masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); + + // Move the other values up. + int32_t depth = -(GET_INT8(pc) + 1); + for (int32_t i = -1; i > depth; i--) { + Address source = frame.addressOfStackValue(frame.peek(i - 1)); + Address dest = frame.addressOfStackValue(frame.peek(i)); + masm.loadValue(source, R1); + masm.storeValue(R1, dest); + } + + // Store R0 under the nth value. + Address dest = frame.addressOfStackValue(frame.peek(depth)); + masm.storeValue(R0, dest); + return true; +} + +bool BaselineCompiler::emit_JSOP_GOTO() { frame.syncStack(0); @@ -1353,6 +1387,26 @@ BaselineCompiler::emit_JSOP_CHECKISOBJ() return true; } +typedef bool (*CheckIsCallableFn)(JSContext*, HandleValue, CheckIsCallableKind); +static const VMFunction CheckIsCallableInfo = + FunctionInfo<CheckIsCallableFn>(CheckIsCallable, "CheckIsCallable"); + +bool +BaselineCompiler::emit_JSOP_CHECKISCALLABLE() +{ + frame.syncStack(0); + masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); + + prepareVMCall(); + + pushArg(Imm32(GET_UINT8(pc))); + pushArg(R0); + if (!callVM(CheckIsCallableInfo)) + return false; + + return true; +} + typedef bool (*ThrowUninitializedThisFn)(JSContext*, BaselineFrame* frame); static const VMFunction ThrowUninitializedThisInfo = FunctionInfo<ThrowUninitializedThisFn>(BaselineThrowUninitializedThis, @@ -3947,7 +4001,7 @@ BaselineCompiler::emit_JSOP_MOREITER() } bool -BaselineCompiler::emit_JSOP_ISNOITER() +BaselineCompiler::emitIsMagicValue() { frame.syncStack(0); @@ -3966,6 +4020,12 @@ BaselineCompiler::emit_JSOP_ISNOITER() } bool +BaselineCompiler::emit_JSOP_ISNOITER() +{ + return emitIsMagicValue(); +} + +bool BaselineCompiler::emit_JSOP_ENDITER() { if (!emit_JSOP_JUMPTARGET()) @@ -3977,6 +4037,12 @@ BaselineCompiler::emit_JSOP_ENDITER() } bool +BaselineCompiler::emit_JSOP_ISGENCLOSING() +{ + return emitIsMagicValue(); +} + +bool BaselineCompiler::emit_JSOP_GETRVAL() { frame.syncStack(0); diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 77f4dd005..6b5bf009e 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -42,6 +42,7 @@ namespace jit { _(JSOP_DUP2) \ _(JSOP_SWAP) \ _(JSOP_PICK) \ + _(JSOP_UNPICK) \ _(JSOP_GOTO) \ _(JSOP_IFEQ) \ _(JSOP_IFNE) \ @@ -202,6 +203,7 @@ namespace jit { _(JSOP_MOREITER) \ _(JSOP_ISNOITER) \ _(JSOP_ENDITER) \ + _(JSOP_ISGENCLOSING) \ _(JSOP_GENERATOR) \ _(JSOP_INITIALYIELD) \ _(JSOP_YIELD) \ @@ -216,6 +218,7 @@ namespace jit { _(JSOP_FUNCTIONTHIS) \ _(JSOP_GLOBALTHIS) \ _(JSOP_CHECKISOBJ) \ + _(JSOP_CHECKISCALLABLE) \ _(JSOP_CHECKTHIS) \ _(JSOP_CHECKRETURN) \ _(JSOP_NEWTARGET) \ @@ -223,7 +226,7 @@ namespace jit { _(JSOP_SPREADSUPERCALL) \ _(JSOP_THROWSETCONST) \ _(JSOP_THROWSETALIASEDCONST) \ - _(JSOP_THROWSETCALLEE) \ + _(JSOP_THROWSETCALLEE) \ _(JSOP_INITHIDDENPROP_GETTER) \ _(JSOP_INITHIDDENPROP_SETTER) \ _(JSOP_INITHIDDENELEM) \ @@ -231,8 +234,9 @@ namespace jit { _(JSOP_INITHIDDENELEM_SETTER) \ _(JSOP_CHECKOBJCOERCIBLE) \ _(JSOP_DEBUGCHECKSELFHOSTED) \ - _(JSOP_JUMPTARGET) \ - _(JSOP_IS_CONSTRUCTING) + _(JSOP_JUMPTARGET) \ + _(JSOP_IS_CONSTRUCTING) \ + _(JSOP_TRY_DESTRUCTURING_ITERCLOSE) class BaselineCompiler : public BaselineCompilerSpecific { @@ -341,6 +345,8 @@ class BaselineCompiler : public BaselineCompilerSpecific MOZ_MUST_USE bool emitThrowConstAssignment(); MOZ_MUST_USE bool emitUninitializedLexicalCheck(const ValueOperand& val); + MOZ_MUST_USE bool emitIsMagicValue(); + MOZ_MUST_USE bool addPCMappingEntry(bool addIndexEntry); MOZ_MUST_USE bool addYieldOffset(); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 3b5ec6baa..ccdc5fbfa 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -11326,25 +11326,35 @@ class OutOfLineIsCallable : public OutOfLineCodeBase<CodeGenerator> } }; +template <CodeGenerator::CallableOrConstructor mode> void -CodeGenerator::visitIsCallable(LIsCallable* ins) +CodeGenerator::emitIsCallableOrConstructor(Register object, Register output, Label* failure) { - Register object = ToRegister(ins->object()); - Register output = ToRegister(ins->output()); - - OutOfLineIsCallable* ool = new(alloc()) OutOfLineIsCallable(ins); - addOutOfLineCode(ool, ins->mir()); - Label notFunction, hasCOps, done; masm.loadObjClass(object, output); - // Just skim proxies off. Their notion of isCallable() is more complicated. - masm.branchTestClassIsProxy(true, output, ool->entry()); + // Just skim proxies off. Their notion of isCallable()/isConstructor() is + // more complicated. + masm.branchTestClassIsProxy(true, output, failure); // An object is callable iff: // is<JSFunction>() || (getClass()->cOps && getClass()->cOps->call). + // An object is constructor iff: + // ((is<JSFunction>() && as<JSFunction>().isConstructor) || + // (getClass()->cOps && getClass()->cOps->construct)). masm.branchPtr(Assembler::NotEqual, output, ImmPtr(&JSFunction::class_), ¬Function); - masm.move32(Imm32(1), output); + if (mode == Callable) { + masm.move32(Imm32(1), output); + } else { + Label notConstructor; + masm.load16ZeroExtend(Address(object, JSFunction::offsetOfFlags()), output); + masm.and32(Imm32(JSFunction::CONSTRUCTOR), output); + masm.branchTest32(Assembler::Zero, output, output, ¬Constructor); + masm.move32(Imm32(1), output); + masm.jump(&done); + masm.bind(¬Constructor); + masm.move32(Imm32(0), output); + } masm.jump(&done); masm.bind(¬Function); @@ -11355,10 +11365,26 @@ CodeGenerator::visitIsCallable(LIsCallable* ins) masm.bind(&hasCOps); masm.loadPtr(Address(output, offsetof(js::Class, cOps)), output); - masm.cmpPtrSet(Assembler::NonZero, Address(output, offsetof(js::ClassOps, call)), + size_t opsOffset = mode == Callable + ? offsetof(js::ClassOps, call) + : offsetof(js::ClassOps, construct); + masm.cmpPtrSet(Assembler::NonZero, Address(output, opsOffset), ImmPtr(nullptr), output); masm.bind(&done); +} + +void +CodeGenerator::visitIsCallable(LIsCallable* ins) +{ + Register object = ToRegister(ins->object()); + Register output = ToRegister(ins->output()); + + OutOfLineIsCallable* ool = new(alloc()) OutOfLineIsCallable(ins); + addOutOfLineCode(ool, ins->mir()); + + emitIsCallableOrConstructor<Callable>(object, output, ool->entry()); + masm.bind(ool->rejoin()); } @@ -11378,6 +11404,36 @@ CodeGenerator::visitOutOfLineIsCallable(OutOfLineIsCallable* ool) masm.jump(ool->rejoin()); } +typedef bool (*CheckIsCallableFn)(JSContext*, HandleValue, CheckIsCallableKind); +static const VMFunction CheckIsCallableInfo = + FunctionInfo<CheckIsCallableFn>(CheckIsCallable, "CheckIsCallable"); + +void +CodeGenerator::visitCheckIsCallable(LCheckIsCallable* ins) +{ + ValueOperand checkValue = ToValue(ins, LCheckIsCallable::CheckValue); + Register temp = ToRegister(ins->temp()); + + // OOL code is used in the following 2 cases: + // * checkValue is not callable + // * checkValue is proxy and it's unknown whether it's callable or not + // CheckIsCallable checks if passed value is callable, regardless of the + // cases above. IsCallable operation is not observable and checking it + // again doesn't matter. + OutOfLineCode* ool = oolCallVM(CheckIsCallableInfo, ins, + ArgList(checkValue, Imm32(ins->mir()->checkKind())), + StoreNothing()); + + masm.branchTestObject(Assembler::NotEqual, checkValue, ool->entry()); + + Register object = masm.extractObject(checkValue, temp); + emitIsCallableOrConstructor<Callable>(object, temp, ool->entry()); + + masm.branchTest32(Assembler::Zero, temp, temp, ool->entry()); + + masm.bind(ool->rejoin()); +} + class OutOfLineIsConstructor : public OutOfLineCodeBase<CodeGenerator> { LIsConstructor* ins_; @@ -11404,37 +11460,8 @@ CodeGenerator::visitIsConstructor(LIsConstructor* ins) OutOfLineIsConstructor* ool = new(alloc()) OutOfLineIsConstructor(ins); addOutOfLineCode(ool, ins->mir()); - Label notFunction, notConstructor, hasCOps, done; - masm.loadObjClass(object, output); - - // Just skim proxies off. Their notion of isConstructor() is more complicated. - masm.branchTestClassIsProxy(true, output, ool->entry()); + emitIsCallableOrConstructor<Constructor>(object, output, ool->entry()); - // An object is constructor iff - // ((is<JSFunction>() && as<JSFunction>().isConstructor) || - // (getClass()->cOps && getClass()->cOps->construct)). - masm.branchPtr(Assembler::NotEqual, output, ImmPtr(&JSFunction::class_), ¬Function); - masm.load16ZeroExtend(Address(object, JSFunction::offsetOfFlags()), output); - masm.and32(Imm32(JSFunction::CONSTRUCTOR), output); - masm.branchTest32(Assembler::Zero, output, output, ¬Constructor); - masm.move32(Imm32(1), output); - masm.jump(&done); - masm.bind(¬Constructor); - masm.move32(Imm32(0), output); - masm.jump(&done); - - masm.bind(¬Function); - masm.branchPtr(Assembler::NonZero, Address(output, offsetof(js::Class, cOps)), - ImmPtr(nullptr), &hasCOps); - masm.move32(Imm32(0), output); - masm.jump(&done); - - masm.bind(&hasCOps); - masm.loadPtr(Address(output, offsetof(js::Class, cOps)), output); - masm.cmpPtrSet(Assembler::NonZero, Address(output, offsetof(js::ClassOps, construct)), - ImmPtr(nullptr), output); - - masm.bind(&done); masm.bind(ool->rejoin()); } diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index b69e919a3..d3126651b 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -364,6 +364,12 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitCallDOMNative(LCallDOMNative* lir); void visitCallGetIntrinsicValue(LCallGetIntrinsicValue* lir); void visitCallBindVar(LCallBindVar* lir); + enum CallableOrConstructor { + Callable, + Constructor + }; + template <CallableOrConstructor mode> + void emitIsCallableOrConstructor(Register object, Register output, Label* failure); void visitIsCallable(LIsCallable* lir); void visitOutOfLineIsCallable(OutOfLineIsCallable* ool); void visitIsConstructor(LIsConstructor* lir); @@ -384,6 +390,7 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitArrowNewTarget(LArrowNewTarget* ins); void visitCheckReturn(LCheckReturn* ins); void visitCheckIsObj(LCheckIsObj* ins); + void visitCheckIsCallable(LCheckIsCallable* ins); void visitCheckObjCoercible(LCheckObjCoercible* ins); void visitDebugCheckSelfHosted(LDebugCheckSelfHosted* ins); void visitNaNToZero(LNaNToZero* ins); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 2e7784ff4..54d05cac4 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -958,30 +958,35 @@ IonBuilder::build() bool IonBuilder::processIterators() { - // Find phis that must directly hold an iterator live. - Vector<MPhi*, 0, SystemAllocPolicy> worklist; + // Find and mark phis that must transitively hold an iterator live. + + Vector<MDefinition*, 8, SystemAllocPolicy> worklist; + for (size_t i = 0; i < iterators_.length(); i++) { - MInstruction* ins = iterators_[i]; - for (MUseDefIterator iter(ins); iter; iter++) { - if (iter.def()->isPhi()) { - if (!worklist.append(iter.def()->toPhi())) - return false; - } + MDefinition* iter = iterators_[i]; + if (!iter->isInWorklist()) { + if (!worklist.append(iter)) + return false; + iter->setInWorklist(); } } - // Propagate the iterator and live status of phis to all other connected - // phis. while (!worklist.empty()) { - MPhi* phi = worklist.popCopy(); - phi->setIterator(); - phi->setImplicitlyUsedUnchecked(); - - for (MUseDefIterator iter(phi); iter; iter++) { - if (iter.def()->isPhi()) { - MPhi* other = iter.def()->toPhi(); - if (!other->isIterator() && !worklist.append(other)) + MDefinition* def = worklist.popCopy(); + def->setNotInWorklist(); + + if (def->isPhi()) { + MPhi* phi = def->toPhi(); + phi->setIterator(); + phi->setImplicitlyUsedUnchecked(); + } + + for (MUseDefIterator iter(def); iter; iter++) { + MDefinition* use = iter.def(); + if (!use->isInWorklist() && (!use->isPhi() || !use->toPhi()->isIterator())) { + if (!worklist.append(use)) return false; + use->setInWorklist(); } } } @@ -1563,6 +1568,7 @@ IonBuilder::traverseBytecode() case JSOP_DUP: case JSOP_DUP2: case JSOP_PICK: + case JSOP_UNPICK: case JSOP_SWAP: case JSOP_SETARG: case JSOP_SETLOCAL: @@ -1672,6 +1678,7 @@ IonBuilder::inspectOpcode(JSOp op) switch (op) { case JSOP_NOP: case JSOP_NOP_DESTRUCTURING: + case JSOP_TRY_DESTRUCTURING_ITERCLOSE: case JSOP_LINENO: case JSOP_LOOPENTRY: case JSOP_JUMPTARGET: @@ -1935,6 +1942,10 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_CALLITER: case JSOP_NEW: case JSOP_SUPERCALL: + if (op == JSOP_CALLITER) { + if (!outermostBuilder()->iterators_.append(current->peek(-1))) + return false; + } return jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW || (JSOp)*pc == JSOP_SUPERCALL); case JSOP_EVAL: @@ -2017,6 +2028,10 @@ IonBuilder::inspectOpcode(JSOp op) current->pick(-GET_INT8(pc)); return true; + case JSOP_UNPICK: + current->unpick(-GET_INT8(pc)); + return true; + case JSOP_GETALIASEDVAR: return jsop_getaliasedvar(EnvironmentCoordinate(pc)); @@ -2169,6 +2184,9 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_CHECKISOBJ: return jsop_checkisobj(GET_UINT8(pc)); + case JSOP_CHECKISCALLABLE: + return jsop_checkiscallable(GET_UINT8(pc)); + case JSOP_CHECKOBJCOERCIBLE: return jsop_checkobjcoercible(); @@ -10886,6 +10904,15 @@ IonBuilder::jsop_checkisobj(uint8_t kind) } bool +IonBuilder::jsop_checkiscallable(uint8_t kind) +{ + MCheckIsCallable* check = MCheckIsCallable::New(alloc(), current->pop(), kind); + current->add(check); + current->push(check); + return true; +} + +bool IonBuilder::jsop_checkobjcoercible() { MDefinition* toCheck = current->peek(-1); diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 0d1bdb1e3..35ad120f7 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -783,6 +783,7 @@ class IonBuilder MOZ_MUST_USE bool jsop_debugger(); MOZ_MUST_USE bool jsop_newtarget(); MOZ_MUST_USE bool jsop_checkisobj(uint8_t kind); + MOZ_MUST_USE bool jsop_checkiscallable(uint8_t kind); MOZ_MUST_USE bool jsop_checkobjcoercible(); MOZ_MUST_USE bool jsop_pushcallobj(); @@ -1242,7 +1243,7 @@ class IonBuilder Vector<ControlFlowInfo, 4, JitAllocPolicy> loops_; Vector<ControlFlowInfo, 0, JitAllocPolicy> switches_; Vector<ControlFlowInfo, 2, JitAllocPolicy> labels_; - Vector<MInstruction*, 2, JitAllocPolicy> iterators_; + Vector<MDefinition*, 2, JitAllocPolicy> iterators_; Vector<LoopHeader, 0, JitAllocPolicy> loopHeaders_; BaselineInspector* inspector; diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index 646442b4c..966d952d3 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -328,23 +328,46 @@ NumArgAndLocalSlots(const InlineFrameIterator& frame) } static void -CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, uint32_t stackSlot) +CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, JSTryNote* tn) { + MOZ_ASSERT(tn->kind == JSTRY_FOR_IN || + 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. - uint32_t skipSlots = NumArgAndLocalSlots(frame) + stackSlot - 1; + // 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 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) { + RootedValue doneValue(cx, si.read()); + bool done = ToBoolean(doneValue); + // Do not call IteratorClose if the destructuring iterator is already + // done. + if (done) + return; + } - if (cx->isExceptionPending()) - UnwindIteratorForException(cx, obj); - else - UnwindIteratorForUncatchableException(cx, obj); + if (cx->isExceptionPending()) { + if (tn->kind == JSTRY_FOR_IN) + UnwindIteratorForException(cx, iterObject); + else + IteratorCloseForException(cx, iterObject); + } else { + UnwindIteratorForUncatchableException(cx, iterObject); + } } class IonFrameStackDepthOp @@ -413,25 +436,36 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx if (!script->hasTrynotes()) return; + bool inForOfIterClose = false; + for (TryNoteIterIon tni(cx, frame); !tni.done(); ++tni) { JSTryNote* tn = *tni; switch (tn->kind) { - case JSTRY_FOR_IN: { - MOZ_ASSERT(JSOp(*(script->main() + tn->start + tn->length)) == JSOP_ENDITER); - MOZ_ASSERT(tn->stackDepth > 0); + case JSTRY_FOR_IN: + case JSTRY_DESTRUCTURING_ITERCLOSE: + MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, + JSOp(*(script->main() + tn->start + tn->length)) == JSOP_ENDITER); + CloseLiveIteratorIon(cx, frame, tn); + break; - uint32_t localSlot = tn->stackDepth; - CloseLiveIteratorIon(cx, frame, localSlot); + case JSTRY_FOR_OF_ITERCLOSE: + inForOfIterClose = true; break; - } case JSTRY_FOR_OF: + inForOfIterClose = false; + break; + case JSTRY_LOOP: break; case JSTRY_CATCH: if (cx->isExceptionPending()) { + // See corresponding comment in ProcessTryNotes. + if (inForOfIterClose) + break; + // Ion can compile try-catch, but bailing out to catch // exceptions is slow. Reset the warm-up counter so that if we // catch many exceptions we won't Ion-compile the script. @@ -562,6 +596,7 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen ResumeFromException* rfe, jsbytecode** pc) { RootedScript script(cx, frame.baselineFrame()->script()); + bool inForOfIterClose = false; for (TryNoteIterBaseline tni(cx, frame.baselineFrame(), *pc); !tni.done(); ++tni) { JSTryNote* tn = *tni; @@ -572,7 +607,11 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen // If we're closing a legacy generator, we have to skip catch // blocks. if (cx->isClosingGenerator()) - continue; + break; + + // See corresponding comment in ProcessTryNotes. + if (inForOfIterClose) + break; SettleOnTryNote(cx, tn, frame, ei, rfe, pc); @@ -588,6 +627,10 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen } case JSTRY_FINALLY: { + // See corresponding comment in ProcessTryNotes. + if (inForOfIterClose) + break; + SettleOnTryNote(cx, tn, frame, ei, rfe, pc); rfe->kind = ResumeFromException::RESUME_FINALLY; rfe->target = script->baselineScript()->nativeCodeForPC(script, *pc); @@ -602,7 +645,7 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen uint8_t* framePointer; uint8_t* stackPointer; BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer); - Value iterValue(*(Value*) 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 @@ -614,7 +657,31 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen break; } + case JSTRY_DESTRUCTURING_ITERCLOSE: { + uint8_t* framePointer; + uint8_t* stackPointer; + BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer); + RootedValue doneValue(cx, *(reinterpret_cast<Value*>(stackPointer))); + bool done = ToBoolean(doneValue); + if (!done) { + 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_ITERCLOSE: + inForOfIterClose = true; + break; + case JSTRY_FOR_OF: + inForOfIterClose = false; + break; + case JSTRY_LOOP: break; diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index a21a529be..7f28a9020 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -4689,6 +4689,19 @@ LIRGenerator::visitCheckIsObj(MCheckIsObj* ins) } void +LIRGenerator::visitCheckIsCallable(MCheckIsCallable* ins) +{ + MDefinition* checkVal = ins->checkValue(); + MOZ_ASSERT(checkVal->type() == MIRType::Value); + + LCheckIsCallable* lir = new(alloc()) LCheckIsCallable(useBox(checkVal), + temp()); + redefine(ins, checkVal); + add(lir, ins); + assignSafepoint(lir, ins); +} + +void LIRGenerator::visitCheckObjCoercible(MCheckObjCoercible* ins) { MDefinition* checkVal = ins->checkValue(); diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 4062c0960..b2805cb7a 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -329,6 +329,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitGuardSharedTypedArray(MGuardSharedTypedArray* ins); void visitCheckReturn(MCheckReturn* ins); void visitCheckIsObj(MCheckIsObj* ins); + void visitCheckIsCallable(MCheckIsCallable* ins); void visitCheckObjCoercible(MCheckObjCoercible* ins); void visitDebugCheckSelfHosted(MDebugCheckSelfHosted* ins); }; diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 3caa7e357..2de91e2df 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -13455,8 +13455,9 @@ class MCheckIsObj { uint8_t checkKind_; - explicit MCheckIsObj(MDefinition* toCheck, uint8_t checkKind) - : MUnaryInstruction(toCheck), checkKind_(checkKind) + MCheckIsObj(MDefinition* toCheck, uint8_t checkKind) + : MUnaryInstruction(toCheck), + checkKind_(checkKind) { setResultType(MIRType::Value); setResultTypeSet(toCheck->resultTypeSet()); @@ -13475,6 +13476,33 @@ class MCheckIsObj } }; +class MCheckIsCallable + : public MUnaryInstruction, + public BoxInputsPolicy::Data +{ + uint8_t checkKind_; + + MCheckIsCallable(MDefinition* toCheck, uint8_t checkKind) + : MUnaryInstruction(toCheck), + checkKind_(checkKind) + { + setResultType(MIRType::Value); + setResultTypeSet(toCheck->resultTypeSet()); + setGuard(); + } + + public: + INSTRUCTION_HEADER(CheckIsCallable) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, checkValue)) + + uint8_t checkKind() const { return checkKind_; } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } +}; + class MCheckObjCoercible : public MUnaryInstruction, public BoxInputsPolicy::Data diff --git a/js/src/jit/MIRGraph.cpp b/js/src/jit/MIRGraph.cpp index 3a363a5bf..d6e0fa8ff 100644 --- a/js/src/jit/MIRGraph.cpp +++ b/js/src/jit/MIRGraph.cpp @@ -790,6 +790,19 @@ MBasicBlock::pick(int32_t depth) } void +MBasicBlock::unpick(int32_t depth) +{ + // unpick take the top of the stack element and move it under the depth-th + // element; + // unpick(-2): + // A B C D E + // A B C E D [ swapAt(-1) ] + // A B E C D [ swapAt(-2) ] + for (int32_t n = -1; n >= depth; n--) + swapAt(n); +} + +void MBasicBlock::swapAt(int32_t depth) { uint32_t lhsDepth = stackPosition_ + depth - 1; diff --git a/js/src/jit/MIRGraph.h b/js/src/jit/MIRGraph.h index b986218f4..705d70fa1 100644 --- a/js/src/jit/MIRGraph.h +++ b/js/src/jit/MIRGraph.h @@ -142,6 +142,9 @@ class MBasicBlock : public TempObject, public InlineListNode<MBasicBlock> // Move the definition to the top of the stack. void pick(int32_t depth); + // Move the top of the stack definition under the depth-th stack value. + void unpick(int32_t depth); + // Exchange 2 stack slots at the defined depth void swapAt(int32_t depth); diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 1a6911247..bb2ab8190 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -285,6 +285,7 @@ namespace jit { _(ArrowNewTarget) \ _(CheckReturn) \ _(CheckIsObj) \ + _(CheckIsCallable) \ _(CheckObjCoercible) \ _(DebugCheckSelfHosted) \ _(AsmJSNeg) \ diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 4edbc3c83..77b9e3647 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -1349,5 +1349,14 @@ BaselineGetFunctionThis(JSContext* cx, BaselineFrame* frame, MutableHandleValue return GetFunctionThis(cx, frame, res); } +bool +CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind) +{ + if (!IsCallable(v)) + return ThrowCheckIsCallable(cx, kind); + + return true; +} + } // namespace jit } // namespace js diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index f754d58c7..572f05373 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -13,6 +13,7 @@ #include "jit/CompileInfo.h" #include "jit/JitFrames.h" +#include "vm/Interpreter.h" namespace js { @@ -802,6 +803,9 @@ ThrowObjectCoercible(JSContext* cx, HandleValue v); MOZ_MUST_USE bool BaselineGetFunctionThis(JSContext* cx, BaselineFrame* frame, MutableHandleValue res); +MOZ_MUST_USE bool +CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind); + } // namespace jit } // namespace js diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index f8e0ce9cc..9dcb527c5 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -8893,6 +8893,27 @@ class LCheckIsObj : public LInstructionHelper<BOX_PIECES, BOX_PIECES, 0> } }; +class LCheckIsCallable : public LInstructionHelper<BOX_PIECES, BOX_PIECES, 1> +{ + public: + LIR_HEADER(CheckIsCallable) + + static const size_t CheckValue = 0; + + LCheckIsCallable(const LBoxAllocation& value, const LDefinition& temp) { + setBoxOperand(CheckValue, value); + setTemp(0, temp); + } + + const LDefinition* temp() { + return getTemp(0); + } + + MCheckIsCallable* mir() const { + return mir_->toCheckIsCallable(); + } +}; + class LCheckObjCoercible : public LCallInstructionHelper<BOX_PIECES, BOX_PIECES, 0> { public: diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index e57751437..3eea1b449 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -402,6 +402,7 @@ _(ArrowNewTarget) \ _(CheckReturn) \ _(CheckIsObj) \ + _(CheckIsCallable) \ _(CheckObjCoercible) \ _(DebugCheckSelfHosted) \ _(AsmJSLoadHeap) \ diff --git a/js/src/js.msg b/js/src/js.msg index 8766bfbf5..8d492f523 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -96,7 +96,7 @@ MSG_DEF(JSMSG_NOT_ITERABLE, 1, JSEXN_TYPEERR, "{0} is not iterable") MSG_DEF(JSMSG_NOT_ITERATOR, 1, JSEXN_TYPEERR, "{0} is not iterator") MSG_DEF(JSMSG_ALREADY_HAS_PRAGMA, 2, JSEXN_WARN, "{0} is being assigned a {1}, but already has one") MSG_DEF(JSMSG_GET_ITER_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "[Symbol.iterator]() returned a non-object value") -MSG_DEF(JSMSG_NEXT_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "iterator.next() returned a non-object value") +MSG_DEF(JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, 1, JSEXN_TYPEERR, "iterator.{0}() returned a non-object value") MSG_DEF(JSMSG_CANT_SET_PROTO, 0, JSEXN_TYPEERR, "can't set prototype of this object") MSG_DEF(JSMSG_CANT_SET_PROTO_OF, 1, JSEXN_TYPEERR, "can't set prototype of {0}") MSG_DEF(JSMSG_CANT_SET_PROTO_CYCLE, 0, JSEXN_TYPEERR, "can't set prototype: it would cause a prototype chain cycle") @@ -583,3 +583,4 @@ MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "P // Iterator MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of iterator is not callable") +MSG_DEF(JSMSG_ITERATOR_NO_THROW, 0, JSEXN_TYPEERR, "iterator does not have a 'throw' method") diff --git a/js/src/jsapi.h b/js/src/jsapi.h index cbef0f8fb..5523dfa1d 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -6622,5 +6622,14 @@ SetGetPerformanceGroupsCallback(JSContext*, GetGroupsCallback, void*); } /* namespace js */ +namespace js { + +enum class CompletionKind { + Normal, + Return, + Throw +}; + +} /* namespace js */ #endif /* jsapi_h */ diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index 63c81e6af..c1ae5dc15 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -12,6 +12,7 @@ #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/PodOperations.h" +#include "mozilla/Unused.h" #include "jsarray.h" #include "jsatom.h" @@ -1229,6 +1230,7 @@ js::CloseIterator(JSContext* cx, HandleObject obj) } return LegacyGeneratorObject::close(cx, obj); } + return true; } @@ -1246,6 +1248,56 @@ js::UnwindIteratorForException(JSContext* cx, HandleObject obj) return true; } +bool +js::IteratorCloseForException(JSContext* cx, HandleObject obj) +{ + MOZ_ASSERT(cx->isExceptionPending()); + + bool isClosingGenerator = cx->isClosingGenerator(); + JS::AutoSaveExceptionState savedExc(cx); + + // Implements IteratorClose (ES 7.4.6) for exception unwinding. See + // also the bytecode generated by BytecodeEmitter::emitIteratorClose. + + // Step 3. + // + // Get the "return" method. + RootedValue returnMethod(cx); + if (!GetProperty(cx, obj, obj, cx->names().return_, &returnMethod)) + return false; + + // Step 4. + // + // Do nothing if "return" is null or undefined. Throw a TypeError if the + // method is not IsCallable. + if (returnMethod.isNullOrUndefined()) + return true; + if (!IsCallable(returnMethod)) + return ReportIsNotFunction(cx, returnMethod); + + // Step 5, 6, 8. + // + // Call "return" if it is not null or undefined. + RootedValue rval(cx); + bool ok = Call(cx, returnMethod, obj, &rval); + if (isClosingGenerator) { + // Closing an iterator is implemented as an exception, but in spec + // terms it is a Completion value with [[Type]] return. In this case + // we *do* care if the call threw and if it returned an object. + if (!ok) + return false; + if (!rval.isObject()) + return ThrowCheckIsObject(cx, CheckIsObjectKind::IteratorReturn); + } else { + // We don't care if the call threw or that it returned an Object, as + // Step 6 says if IteratorClose is being called during a throw, the + // original throw has primacy. + savedExc.restore(); + } + + return true; +} + void js::UnwindIteratorForUncatchableException(JSContext* cx, JSObject* obj) { diff --git a/js/src/jsiter.h b/js/src/jsiter.h index 35d7ef118..f11f09b55 100644 --- a/js/src/jsiter.h +++ b/js/src/jsiter.h @@ -189,6 +189,9 @@ CloseIterator(JSContext* cx, HandleObject iterObj); bool UnwindIteratorForException(JSContext* cx, HandleObject obj); +bool +IteratorCloseForException(JSContext* cx, HandleObject obj); + void UnwindIteratorForUncatchableException(JSContext* cx, JSObject* obj); diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 31bbfb471..6adb5401e 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -92,7 +92,8 @@ const char * const js::CodeName[] = { /************************************************************************/ -#define COUNTS_LEN 16 +static bool +DecompileArgumentFromStack(JSContext* cx, int formalIndex, char** res); size_t js::GetVariableBytecodeLength(jsbytecode* pc) @@ -460,6 +461,34 @@ BytecodeParser::simulateOp(JSOp op, uint32_t offset, uint32_t* offsetStack, uint offsetStack[stackDepth] = tmp; } break; + + case JSOP_PICK: { + jsbytecode* pc = script_->offsetToPC(offset); + unsigned n = GET_UINT8(pc); + MOZ_ASSERT(ndefs == n + 1); + if (offsetStack) { + uint32_t top = stackDepth + n; + uint32_t tmp = offsetStack[stackDepth]; + for (uint32_t i = stackDepth; i < top; i++) + offsetStack[i] = offsetStack[i + 1]; + offsetStack[top] = tmp; + } + break; + } + + case JSOP_UNPICK: { + jsbytecode* pc = script_->offsetToPC(offset); + unsigned n = GET_UINT8(pc); + MOZ_ASSERT(ndefs == n + 1); + if (offsetStack) { + uint32_t top = stackDepth + n; + uint32_t tmp = offsetStack[top]; + for (uint32_t i = top; i > stackDepth; i--) + offsetStack[i] = offsetStack[i - 1]; + offsetStack[stackDepth] = tmp; + } + break; + } } stackDepth += ndefs; return stackDepth; @@ -1230,6 +1259,24 @@ ExpressionDecompiler::decompilePC(jsbytecode* pc) return write(loadAtom(pc)); case JSOP_GETARG: { unsigned slot = GET_ARGNO(pc); + + // For self-hosted scripts that are called from non-self-hosted code, + // decompiling the parameter name in the self-hosted script is + // unhelpful. Decompile the argument name instead. + if (script->selfHosted()) { + char* result; + if (!DecompileArgumentFromStack(cx, slot, &result)) + return false; + + // Note that decompiling the argument in the parent frame might + // not succeed. + if (result) { + bool ok = write(result); + js_free(result); + return ok; + } + } + JSAtom* atom = getArg(slot); if (!atom) return false; @@ -1593,12 +1640,17 @@ DecompileArgumentFromStack(JSContext* cx, int formalIndex, char** res) MOZ_ASSERT(frameIter.script()->selfHosted()); /* - * Get the second-to-top frame, the caller of the builtin that called the - * intrinsic. + * Get the second-to-top frame, the non-self-hosted caller of the builtin + * that called the intrinsic. */ ++frameIter; - if (frameIter.done() || !frameIter.hasScript() || frameIter.compartment() != cx->compartment()) + if (frameIter.done() || + !frameIter.hasScript() || + frameIter.script()->selfHosted() || + frameIter.compartment() != cx->compartment()) + { return true; + } RootedScript script(cx, frameIter.script()); jsbytecode* current = frameIter.pc(); diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index 4f7859665..e56eebb2d 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -423,6 +423,7 @@ BytecodeFallsThrough(JSOp op) case JSOP_RETRVAL: case JSOP_FINALYIELDRVAL: case JSOP_THROW: + case JSOP_THROWMSG: case JSOP_TABLESWITCH: return false; case JSOP_GOSUB: diff --git a/js/src/jsopcodeinlines.h b/js/src/jsopcodeinlines.h index cf9c8357a..5b0ce0cf9 100644 --- a/js/src/jsopcodeinlines.h +++ b/js/src/jsopcodeinlines.h @@ -27,6 +27,7 @@ GetDefCount(JSScript* script, unsigned offset) case JSOP_AND: return 1; case JSOP_PICK: + case JSOP_UNPICK: /* * Pick pops and pushes how deep it looks in the stack + 1 * items. i.e. if the stack were |a b[2] c[1] d[0]|, pick 2 @@ -44,7 +45,7 @@ GetUseCount(JSScript* script, unsigned offset) { jsbytecode* pc = script->offsetToPC(offset); - if (JSOp(*pc) == JSOP_PICK) + if (JSOp(*pc) == JSOP_PICK || JSOp(*pc) == JSOP_UNPICK) return pc[1] + 1; if (CodeSpec[*pc].nuses == -1) return StackUses(script, pc); diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 929251d8b..e86ceab3d 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -2804,9 +2804,10 @@ JSScript::assertValidJumpTargets() const for (; tn < tnlimit; tn++) { jsbytecode* tryStart = mainEntry + tn->start; jsbytecode* tryPc = tryStart - 1; - if (JSOp(*tryPc) != JSOP_TRY) + if (tn->kind != JSTRY_CATCH && tn->kind != JSTRY_FINALLY) continue; + MOZ_ASSERT(JSOp(*tryPc) == JSOP_TRY); jsbytecode* tryTarget = tryStart + tn->length; MOZ_ASSERT(mainEntry <= tryTarget && tryTarget < end); MOZ_ASSERT(BytecodeIsJumpTarget(JSOp(*tryTarget))); diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 690bc225d..87da79901 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -84,7 +84,9 @@ enum JSTryNoteKind { JSTRY_FINALLY, JSTRY_FOR_IN, JSTRY_FOR_OF, - JSTRY_LOOP + JSTRY_LOOP, + JSTRY_FOR_OF_ITERCLOSE, + JSTRY_DESTRUCTURING_ITERCLOSE }; /* diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 3f56981cd..b53914942 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2585,11 +2585,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_FOR_OF_ITERCLOSE: + return "for-of-iterclose"; + case JSTRY_DESTRUCTURING_ITERCLOSE: + return "dstr-iterclose"; + } -static const char* const TryNoteNames[] = { "catch", "finally", "for-in", "for-of", "loop" }; + MOZ_CRASH("Bad JSTryNoteKind"); +} static MOZ_MUST_USE bool TryNotes(JSContext* cx, HandleScript script, Sprinter* sp) @@ -2597,17 +2614,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)); - uint8_t startOff = script->pcToOffset(script->main()) + tn->start; - if (!sp->jsprintf(" %-7s %6u %8u %8u\n", - TryNoteNames[tn->kind], tn->stackDepth, - startOff, startOff + tn->length)) + uint32_t startOff = script->pcToOffset(script->main()) + tn->start; + if (!sp->jsprintf(" %-16s %6u %8u %8u\n", + TryNoteName(static_cast<JSTryNoteKind>(tn->kind)), + tn->stackDepth, startOff, startOff + tn->length)) { return false; } diff --git a/js/src/tests/ecma_6/Destructuring/array-default-class.js b/js/src/tests/ecma_6/Destructuring/array-default-class.js new file mode 100644 index 000000000..5aa9c579b --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/array-default-class.js @@ -0,0 +1,25 @@ +var BUGNUMBER = 1322314; +var summary = "Function in computed property in class expression in array destructuring default"; + +print(BUGNUMBER + ": " + summary); + +function* g([ + a = class E { + [ (function() { return "foo"; })() ]() { + return 10; + } + } +]) { + yield a; +} + +let C = [...g([])][0]; +let x = new C(); +assertEq(x.foo(), 10); + +C = [...g([undefined])][0]; +x = new C(); +assertEq(x.foo(), 10); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Destructuring/array-iterator-close.js b/js/src/tests/ecma_6/Destructuring/array-iterator-close.js new file mode 100644 index 000000000..ed35135db --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/array-iterator-close.js @@ -0,0 +1,93 @@ +// 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 lhs ref calls IteratorClose with falsy "done". + iterable[Symbol.iterator] = makeIterator({ + next: function() { + // "done" is undefined. + return {}; + }, + ret: function() { + returnCalled++; + return {}; + } + }); + 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); diff --git a/js/src/tests/ecma_6/Destructuring/order-super.js b/js/src/tests/ecma_6/Destructuring/order-super.js new file mode 100644 index 000000000..afe11e2d9 --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/order-super.js @@ -0,0 +1,727 @@ +var BUGNUMBER = 1204028; +var summary = "Destructuring should evaluate lhs reference before rhs in super property"; + +if (typeof assertEq === "undefined") { + assertEq = function(a, b) { + if (a !== b) + throw new Error(`expected ${b}, got ${a}\n${new Error().stack}`); + }; +} + +print(BUGNUMBER + ": " + summary); + +let logs = []; +function log(x) { + logs.push(x); +} + +let unwrapMap = new Map(); +function unwrap(maybeWrapped) { + if (unwrapMap.has(maybeWrapped)) + return unwrapMap.get(maybeWrapped); + return maybeWrapped; +} +function ToString(name) { + if (name == Symbol.iterator) + return "@@iterator"; + return String(name); +} +function logger(obj, prefix=[]) { + let wrapped = new Proxy(obj, { + get(that, name) { + if (name == "return") { + // FIXME: Bug 1147371. + // We ignore IteratorClose for now. + return obj[name]; + } + + let names = prefix.concat(ToString(name)); + log("rhs get " + names.join("::")); + let v = obj[name]; + if (typeof v === "object" || typeof v === "function") + return logger(v, names); + return v; + }, + apply(that, thisArg, args) { + let names = prefix.slice(); + log("rhs call " + names.join("::")); + let v = obj.apply(unwrap(thisArg), args); + if (typeof v === "object" || typeof v === "function") { + names[names.length - 1] += "()"; + return logger(v, names); + } + return v; + } + }); + unwrapMap.set(wrapped, obj); + return wrapped; +} + +class C1 { + constructor() { + this.clear(); + } + clear() { + this.values = {}; + } +} +for (let name of [ + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "0", "1", "length" +]) { + Object.defineProperty(C1.prototype, name, { + set: function(value) { + log("lhs set " + name); + this.values[name] = value; + } + }); +} +class C2 extends C1 { + constructor() { + super(); + + let clear = () => { + logs = []; + this.clear(); + }; + + // Array. + + clear(); + [ + super.a + ] = logger(["A"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + clear(); + [ + super[ (log("lhs before name a"), "a") ] + ] = logger(["A"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + // Array rest. + + clear(); + [ + ...super.a + ] = logger(["A", "B", "C"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set a", + ].join(",")); + assertEq(this.values.a.join(","), "A,B,C"); + + clear(); + [ + ...super[ (log("lhs before name a"), "a") ] + ] = logger(["A", "B", "C"]);; + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set a", + ].join(",")); + assertEq(this.values.a.join(","), "A,B,C"); + + // Array combined. + + clear(); + [ + super.a, + super[ (log("lhs before name b"), "b") ], + ...super[ (log("lhs before name c"), "c") ] + ] = logger(["A", "B", "C"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + + "lhs before name b", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set b", + + "lhs before name c", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set c", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b, "B"); + assertEq(this.values.c.join(","), "C"); + + // Object. + + clear(); + ({ + a: super.a + } = logger({a: "A"})); + assertEq(logs.join(","), + [ + "rhs get a", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + clear(); + ({ + a: super[ (log("lhs before name a"), "a") ] + } = logger({a: "A"})); + assertEq(logs.join(","), + [ + "lhs before name a", + "rhs get a", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + // Object combined. + + clear(); + ({ + a: super.a, + b: super[ (log("lhs before name b"), "b") ] + } = logger({a: "A", b: "B"})); + assertEq(logs.join(","), + [ + "rhs get a", + "lhs set a", + + "lhs before name b", + "rhs get b", + "lhs set b", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b, "B"); + + // == Nested == + + // Array -> Array + + clear(); + [ + [ + super[ (log("lhs before name a"), "a") ], + ...super[ (log("lhs before name b"), "b") ] + ] + ] = logger([["A", "B"]]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before name a", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set a", + + "lhs before name b", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "lhs set b", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b.length, 1); + assertEq(this.values.b[0], "B"); + + // Array rest -> Array + + clear(); + [ + ...[ + super[ (log("lhs before name a"), "a") ], + ...super[ (log("lhs before name b"), "b") ] + ] + ] = logger(["A", "B"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before name a", + "lhs set a", + + "lhs before name b", + "lhs set b", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b.join(","), "B"); + + // Array -> Object + clear(); + [ + { + a: super[ (log("lhs before name a"), "a") ] + } + ] = logger([{a: "A"}]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + + "lhs before name a", + "rhs get @@iterator()::next()::value::a", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + // Array rest -> Object + clear(); + [ + ...{ + 0: super[ (log("lhs before name 0"), "0") ], + 1: super[ (log("lhs before name 1"), "1") ], + length: super[ (log("lhs before name length"), "length") ], + } + ] = logger(["A", "B"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before name 0", + "lhs set 0", + + "lhs before name 1", + "lhs set 1", + + "lhs before name length", + "lhs set length", + ].join(",")); + assertEq(this.values["0"], "A"); + assertEq(this.values["1"], "B"); + assertEq(this.values.length, 2); + + // Object -> Array + clear(); + ({ + a: [ + super[ (log("lhs before name b"), "b") ] + ] + } = logger({a: ["B"]})); + assertEq(logs.join(","), + [ + "rhs get a", + "rhs get a::@@iterator", + "rhs call a::@@iterator", + + "lhs before name b", + "rhs get a::@@iterator()::next", + "rhs call a::@@iterator()::next", + "rhs get a::@@iterator()::next()::done", + "rhs get a::@@iterator()::next()::value", + "lhs set b", + ].join(",")); + assertEq(this.values.b, "B"); + + // Object -> Object + clear(); + ({ + a: { + b: super[ (log("lhs before name b"), "b") ] + } + } = logger({a: {b: "B"}})); + assertEq(logs.join(","), + [ + "rhs get a", + "lhs before name b", + "rhs get a::b", + "lhs set b", + ].join(",")); + assertEq(this.values.b, "B"); + + // All combined + + clear(); + [ + super[ (log("lhs before name a"), "a") ], + [ + super[ (log("lhs before name b"), "b") ], + { + c: super[ (log("lhs before name c"), "c") ], + d: { + e: super[ (log("lhs before name e"), "e") ], + f: [ + super[ (log("lhs before name g"), "g") ] + ] + } + } + ], + { + h: super[ (log("lhs before name h"), "h") ], + i: [ + super[ (log("lhs before name j"), "j") ], + { + k: [ + super[ (log("lhs before name l"), "l") ] + ] + } + ] + }, + ...[ + super[ (log("lhs before name m"), "m") ], + [ + super[ (log("lhs before name n"), "n") ], + { + o: super[ (log("lhs before name o"), "o") ], + p: { + q: super[ (log("lhs before name q"), "q") ], + r: [ + super[ (log("lhs before name s"), "s") ] + ] + } + } + ], + ...{ + 0: super[ (log("lhs before name t"), "t") ], + 1: [ + super[ (log("lhs before name u"), "u") ], + { + v: super[ (log("lhs before name v"), "v") ], + w: { + x: super[ (log("lhs before name x"), "x") ], + y: [ + super[ (log("lhs before name z"), "z") ] + ] + } + } + ], + length: super[ (log("lhs before name length"), "length") ], + } + ] + ] = logger(["A", + ["B", {c: "C", d: {e: "E", f: ["G"]}}], + {h: "H", i: ["J", {k: ["L"]}]}, + "M", + ["N", {o: "O", p: {q: "Q", r: ["S"]}}], + "T", ["U", {v: "V", w: {x: "X", y: ["Z"]}}]]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before name b", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set b", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before name c", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::c", + "lhs set c", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d", + + "lhs before name e", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::e", + "lhs set e", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator", + + "lhs before name g", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next()::value", + "lhs set g", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + + "lhs before name h", + "rhs get @@iterator()::next()::value::h", + "lhs set h", + + "rhs get @@iterator()::next()::value::i", + "rhs get @@iterator()::next()::value::i::@@iterator", + "rhs call @@iterator()::next()::value::i::@@iterator", + + "lhs before name j", + "rhs get @@iterator()::next()::value::i::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value", + "lhs set j", + + "rhs get @@iterator()::next()::value::i::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value", + + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator", + "rhs call @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator", + + "lhs before name l", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next()::value", + "lhs set l", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before name m", + "lhs set m", + + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before name n", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set n", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before name o", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::o", + "lhs set o", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p", + + "lhs before name q", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::q", + "lhs set q", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator", + + "lhs before name s", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next()::value", + "lhs set s", + + "lhs before name t", + "lhs set t", + + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before name u", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set u", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before name v", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::v", + "lhs set v", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w", + + "lhs before name x", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::x", + "lhs set x", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator", + + "lhs before name z", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next()::value", + "lhs set z", + + "lhs before name length", + "lhs set length", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b, "B"); + assertEq(this.values.c, "C"); + assertEq(this.values.e, "E"); + assertEq(this.values.g, "G"); + assertEq(this.values.h, "H"); + assertEq(this.values.j, "J"); + assertEq(this.values.l, "L"); + assertEq(this.values.m, "M"); + assertEq(this.values.n, "N"); + assertEq(this.values.o, "O"); + assertEq(this.values.q, "Q"); + assertEq(this.values.s, "S"); + assertEq(this.values.t, "T"); + assertEq(this.values.u, "U"); + assertEq(this.values.v, "V"); + assertEq(this.values.x, "X"); + assertEq(this.values.z, "Z"); + assertEq(this.values.length, 2); + } +} + +new C2(); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_6/Destructuring/order.js b/js/src/tests/ecma_6/Destructuring/order.js new file mode 100644 index 000000000..4eb48cd2b --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/order.js @@ -0,0 +1,745 @@ +var BUGNUMBER = 1204028; +var summary = "Destructuring should evaluate lhs reference before rhs"; + +print(BUGNUMBER + ": " + summary); + +let storage = { + clear() { + this.values = {}; + } +}; +storage.clear(); +let obj = new Proxy(storage, { + set(that, name, value) { + log("lhs set " + name); + storage.values[name] = value; + } +}); + +let logs = []; +function log(x) { + logs.push(x); +} + +function clear() { + logs = []; + storage.clear(); +} + +let unwrapMap = new Map(); +function unwrap(maybeWrapped) { + if (unwrapMap.has(maybeWrapped)) + return unwrapMap.get(maybeWrapped); + return maybeWrapped; +} +function ToString(name) { + if (name == Symbol.iterator) + return "@@iterator"; + return String(name); +} +function logger(obj, prefix=[]) { + let wrapped = new Proxy(obj, { + get(that, name) { + if (name == "return") { + // FIXME: Bug 1147371. + // We ignore IteratorClose for now. + return obj[name]; + } + + let names = prefix.concat(ToString(name)); + log("rhs get " + names.join("::")); + let v = obj[name]; + if (typeof v === "object" || typeof v === "function") + return logger(v, names); + return v; + }, + apply(that, thisArg, args) { + let names = prefix.slice(); + log("rhs call " + names.join("::")); + let v = obj.apply(unwrap(thisArg), args); + if (typeof v === "object" || typeof v === "function") { + names[names.length - 1] += "()"; + return logger(v, names); + } + return v; + } + }); + unwrapMap.set(wrapped, obj); + return wrapped; +} + +// Array. + +clear(); +[ + ( log("lhs before obj a"), obj ).a +] = logger(["A"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +clear(); +[ + ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ] +] = logger(["A"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +// Array rest. + +clear(); +[ + ...( log("lhs before obj a"), obj ).a +] = logger(["A", "B", "C"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set a", + ].join(",")); +assertEq(storage.values.a.join(","), "A,B,C"); + +clear(); +[ + ...( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ] +] = logger(["A", "B", "C"]);; +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set a", + ].join(",")); +assertEq(storage.values.a.join(","), "A,B,C"); + +// Array combined. + +clear(); +[ + ( log("lhs before obj a"), obj ).a, + ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ], + ...( log("lhs before obj c"), obj )[ (log("lhs before name c"), "c") ] +] = logger(["A", "B", "C"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + + "lhs before obj b", + "lhs before name b", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set b", + + "lhs before obj c", + "lhs before name c", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set c", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b, "B"); +assertEq(storage.values.c.join(","), "C"); + +// Object. + +clear(); +({ + a: ( log("lhs before obj a"), obj ).a +} = logger({a: "A"})); +assertEq(logs.join(","), + [ + "lhs before obj a", + "rhs get a", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +clear(); +({ + a: ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ] +} = logger({a: "A"})); +assertEq(logs.join(","), + [ + "lhs before obj a", + "lhs before name a", + "rhs get a", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +// Object combined. + +clear(); +({ + a: ( log("lhs before obj a"), obj ).a, + b: ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] +} = logger({a: "A", b: "B"})); +assertEq(logs.join(","), + [ + "lhs before obj a", + "rhs get a", + "lhs set a", + + "lhs before obj b", + "lhs before name b", + "rhs get b", + "lhs set b", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b, "B"); + +// == Nested == + +// Array -> Array + +clear(); +[ + [ + ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ], + ...( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] + ] +] = logger([["A", "B"]]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set a", + + "lhs before obj b", + "lhs before name b", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "lhs set b", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b.length, 1); +assertEq(storage.values.b[0], "B"); + +// Array rest -> Array + +clear(); +[ + ...[ + ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ], + ...( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] + ] +] = logger(["A", "B"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before obj a", + "lhs before name a", + "lhs set a", + + "lhs before obj b", + "lhs before name b", + "lhs set b", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b.join(","), "B"); + +// Array -> Object +clear(); +[ + { + a: ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ] + } +] = logger([{a: "A"}]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next()::value::a", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +// Array rest -> Object +clear(); +[ + ...{ + 0: ( log("lhs before obj 0"), obj )[ (log("lhs before name 0"), "0") ], + 1: ( log("lhs before obj 1"), obj )[ (log("lhs before name 1"), "1") ], + length: ( log("lhs before obj length"), obj )[ (log("lhs before name length"), "length") ], + } +] = logger(["A", "B"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before obj 0", + "lhs before name 0", + "lhs set 0", + + "lhs before obj 1", + "lhs before name 1", + "lhs set 1", + + "lhs before obj length", + "lhs before name length", + "lhs set length", + ].join(",")); +assertEq(storage.values["0"], "A"); +assertEq(storage.values["1"], "B"); +assertEq(storage.values.length, 2); + +// Object -> Array +clear(); +({ + a: [ + ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] + ] +} = logger({a: ["B"]})); +assertEq(logs.join(","), + [ + "rhs get a", + "rhs get a::@@iterator", + "rhs call a::@@iterator", + + "lhs before obj b", + "lhs before name b", + "rhs get a::@@iterator()::next", + "rhs call a::@@iterator()::next", + "rhs get a::@@iterator()::next()::done", + "rhs get a::@@iterator()::next()::value", + "lhs set b", + ].join(",")); +assertEq(storage.values.b, "B"); + +// Object -> Object +clear(); +({ + a: { + b: ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] + } +} = logger({a: {b: "B"}})); +assertEq(logs.join(","), + [ + "rhs get a", + "lhs before obj b", + "lhs before name b", + "rhs get a::b", + "lhs set b", + ].join(",")); +assertEq(storage.values.b, "B"); + +// All combined + +clear(); +[ + ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ], + [ + ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ], + { + c: ( log("lhs before obj c"), obj )[ (log("lhs before name c"), "c") ], + d: { + e: ( log("lhs before obj e"), obj )[ (log("lhs before name e"), "e") ], + f: [ + ( log("lhs before obj g"), obj )[ (log("lhs before name g"), "g") ] + ] + } + } + ], + { + h: ( log("lhs before obj h"), obj )[ (log("lhs before name h"), "h") ], + i: [ + ( log("lhs before obj j"), obj )[ (log("lhs before name j"), "j") ], + { + k: [ + ( log("lhs before obj l"), obj )[ (log("lhs before name l"), "l") ] + ] + } + ] + }, + ...[ + ( log("lhs before obj m"), obj )[ (log("lhs before name m"), "m") ], + [ + ( log("lhs before obj n"), obj )[ (log("lhs before name n"), "n") ], + { + o: ( log("lhs before obj o"), obj )[ (log("lhs before name o"), "o") ], + p: { + q: ( log("lhs before obj q"), obj )[ (log("lhs before name q"), "q") ], + r: [ + ( log("lhs before obj s"), obj )[ (log("lhs before name s"), "s") ] + ] + } + } + ], + ...{ + 0: ( log("lhs before obj t"), obj )[ (log("lhs before name t"), "t") ], + 1: [ + ( log("lhs before obj u"), obj )[ (log("lhs before name u"), "u") ], + { + v: ( log("lhs before obj v"), obj )[ (log("lhs before name v"), "v") ], + w: { + x: ( log("lhs before obj x"), obj )[ (log("lhs before name x"), "x") ], + y: [ + ( log("lhs before obj z"), obj )[ (log("lhs before name z"), "z") ] + ] + } + } + ], + length: ( log("lhs before obj length"), obj )[ (log("lhs before name length"), "length") ], + } + ] +] = logger(["A", + ["B", {c: "C", d: {e: "E", f: ["G"]}}], + {h: "H", i: ["J", {k: ["L"]}]}, + "M", + ["N", {o: "O", p: {q: "Q", r: ["S"]}}], + "T", ["U", {v: "V", w: {x: "X", y: ["Z"]}}]]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before obj b", + "lhs before name b", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set b", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before obj c", + "lhs before name c", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::c", + "lhs set c", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d", + + "lhs before obj e", + "lhs before name e", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::e", + "lhs set e", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator", + + "lhs before obj g", + "lhs before name g", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next()::value", + "lhs set g", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + + "lhs before obj h", + "lhs before name h", + "rhs get @@iterator()::next()::value::h", + "lhs set h", + + "rhs get @@iterator()::next()::value::i", + "rhs get @@iterator()::next()::value::i::@@iterator", + "rhs call @@iterator()::next()::value::i::@@iterator", + + "lhs before obj j", + "lhs before name j", + "rhs get @@iterator()::next()::value::i::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value", + "lhs set j", + + "rhs get @@iterator()::next()::value::i::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value", + + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator", + "rhs call @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator", + + "lhs before obj l", + "lhs before name l", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next()::value", + "lhs set l", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before obj m", + "lhs before name m", + "lhs set m", + + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before obj n", + "lhs before name n", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set n", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before obj o", + "lhs before name o", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::o", + "lhs set o", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p", + + "lhs before obj q", + "lhs before name q", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::q", + "lhs set q", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator", + + "lhs before obj s", + "lhs before name s", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next()::value", + "lhs set s", + + "lhs before obj t", + "lhs before name t", + "lhs set t", + + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before obj u", + "lhs before name u", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set u", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before obj v", + "lhs before name v", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::v", + "lhs set v", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w", + + "lhs before obj x", + "lhs before name x", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::x", + "lhs set x", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator", + + "lhs before obj z", + "lhs before name z", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next()::value", + "lhs set z", + + "lhs before obj length", + "lhs before name length", + "lhs set length", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b, "B"); +assertEq(storage.values.c, "C"); +assertEq(storage.values.e, "E"); +assertEq(storage.values.g, "G"); +assertEq(storage.values.h, "H"); +assertEq(storage.values.j, "J"); +assertEq(storage.values.l, "L"); +assertEq(storage.values.m, "M"); +assertEq(storage.values.n, "N"); +assertEq(storage.values.o, "O"); +assertEq(storage.values.q, "Q"); +assertEq(storage.values.s, "S"); +assertEq(storage.values.t, "T"); +assertEq(storage.values.u, "U"); +assertEq(storage.values.v, "V"); +assertEq(storage.values.x, "X"); +assertEq(storage.values.z, "Z"); +assertEq(storage.values.length, 2); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_6/Generators/delegating-yield-2.js b/js/src/tests/ecma_6/Generators/delegating-yield-2.js index 918fb33e1..34cb3f4a9 100644 --- a/js/src/tests/ecma_6/Generators/delegating-yield-2.js +++ b/js/src/tests/ecma_6/Generators/delegating-yield-2.js @@ -25,8 +25,8 @@ assertThrowsValue(function () { outer.throw(42) }, 42); inner = g1(); outer = delegate(inner); assertIteratorNext(outer, 1); -inner.throw = function(e) { return e*2; }; -assertEq(84, outer.throw(42)); +inner.throw = function(e) { return { value: e*2 }; }; +assertEq(84, outer.throw(42).value); assertIteratorDone(outer, undefined); // Monkeypatching inner.next. @@ -41,7 +41,9 @@ outer = delegate(inner); assertIteratorNext(outer, 1); delete GeneratorObjectPrototype.throw; var outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42); -assertThrowsValue(outer_throw_42, 42); +// yield* protocol violation: no 'throw' method +assertThrowsInstanceOf(outer_throw_42, TypeError); +// Now done, so just throws. assertThrowsValue(outer_throw_42, 42); // Monkeypunch a different throw handler. @@ -49,11 +51,11 @@ inner = g2(); outer = delegate(inner); outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42); assertIteratorNext(outer, 1); -GeneratorObjectPrototype.throw = function(e) { return e*2; } -assertEq(84, outer_throw_42()); -assertEq(84, outer_throw_42()); +GeneratorObjectPrototype.throw = function(e) { return { value: e*2 }; } +assertEq(84, outer_throw_42().value); +assertEq(84, outer_throw_42().value); // This continues indefinitely. -assertEq(84, outer_throw_42()); +assertEq(84, outer_throw_42().value); assertIteratorDone(outer, undefined); // The same, but restoring the original pre-monkey throw. @@ -61,8 +63,8 @@ inner = g2(); outer = delegate(inner); outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42); assertIteratorNext(outer, 1); -assertEq(84, outer_throw_42()); -assertEq(84, outer_throw_42()); +assertEq(84, outer_throw_42().value); +assertEq(84, outer_throw_42().value); GeneratorObjectPrototype.throw = GeneratorObjectPrototype_throw; assertIteratorResult(outer_throw_42(), 42, false); assertIteratorDone(outer, undefined); diff --git a/js/src/tests/ecma_6/Generators/yield-iterator-close.js b/js/src/tests/ecma_6/Generators/yield-iterator-close.js new file mode 100644 index 000000000..970ad494d --- /dev/null +++ b/js/src/tests/ecma_6/Generators/yield-iterator-close.js @@ -0,0 +1,58 @@ +// Test that IteratorClose is called when a Generator is abruptly completed by +// Generator.return. + +var returnCalled = 0; +function* wrapNoThrow() { + let iter = { + [Symbol.iterator]() { + return this; + }, + next() { + return { value: 10, done: false }; + }, + return() { + returnCalled++; + return {}; + } + }; + for (const i of iter) { + yield i; + } +} + +// Breaking calls Generator.return, which causes the yield above to resume with +// an abrupt completion of kind "return", which then calls +// iter.return. +for (const i of wrapNoThrow()) { + break; +} +assertEq(returnCalled, 1); + +function* wrapThrow() { + let iter = { + [Symbol.iterator]() { + return this; + }, + next() { + return { value: 10, done: false }; + }, + return() { + throw 42; + } + }; + for (const i of iter) { + yield i; + } +} + +// Breaking calls Generator.return, which, like above, calls iter.return. If +// iter.return throws, since the yield is resuming with an abrupt completion of +// kind "return", the newly thrown value takes precedence over returning. +assertThrowsValue(() => { + for (const i of wrapThrow()) { + break; + } +}, 42); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Generators/yield-star-iterator-close.js b/js/src/tests/ecma_6/Generators/yield-star-iterator-close.js new file mode 100644 index 000000000..91ad31cb6 --- /dev/null +++ b/js/src/tests/ecma_6/Generators/yield-star-iterator-close.js @@ -0,0 +1,153 @@ +// Tests that the "return" method on iterators is called in yield* +// expressions. + +function test() { + var returnCalled = 0; + var returnCalledExpected = 0; + var nextCalled = 0; + var nextCalledExpected = 0; + var throwCalled = 0; + var throwCalledExpected = 0; + var iterable = {}; + iterable[Symbol.iterator] = makeIterator({ + next: function() { + nextCalled++; + return { done: false }; + }, + ret: function() { + returnCalled++; + return { done: true, value: "iter.return" }; + } + }); + + function* y() { + yield* iterable; + } + + // G.p.throw on an iterator without "throw" calls IteratorClose. + var g1 = y(); + g1.next(); + assertThrowsInstanceOf(function() { + g1.throw("foo"); + }, TypeError); + assertEq(returnCalled, ++returnCalledExpected); + assertEq(nextCalled, ++nextCalledExpected); + g1.next(); + assertEq(nextCalled, nextCalledExpected); + + // G.p.return calls "return", and if the result.done is true, return the + // result. + var g2 = y(); + g2.next(); + var v2 = g2.return("test return"); + assertEq(v2.done, true); + assertEq(v2.value, "iter.return"); + assertEq(returnCalled, ++returnCalledExpected); + assertEq(nextCalled, ++nextCalledExpected); + g2.next(); + assertEq(nextCalled, nextCalledExpected); + + // G.p.return calls "return", and if the result.done is false, continue + // yielding. + iterable[Symbol.iterator] = makeIterator({ + next: function() { + nextCalled++; + return { done: false }; + }, + ret: function() { + returnCalled++; + return { done: false, value: "iter.return" }; + } + }); + var g3 = y(); + g3.next(); + var v3 = g3.return("test return"); + assertEq(v3.done, false); + assertEq(v3.value, "iter.return"); + assertEq(returnCalled, ++returnCalledExpected); + assertEq(nextCalled, ++nextCalledExpected); + g3.next(); + assertEq(nextCalled, ++nextCalledExpected); + + // G.p.return throwing does not re-call iter.return. + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + throw "in iter.return"; + } + }); + var g4 = y(); + g4.next(); + assertThrowsValue(function() { + g4.return("in test"); + }, "in iter.return"); + assertEq(returnCalled, ++returnCalledExpected); + + // G.p.return expects iter.return to return an Object. + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + return 42; + } + }); + var g5 = y(); + g5.next(); + assertThrowsInstanceOf(function() { + g5.return("foo"); + }, TypeError); + assertEq(returnCalled, ++returnCalledExpected); + + // IteratorClose expects iter.return to return an Object. + var g6 = y(); + g6.next(); + var exc; + try { + g6.throw("foo"); + } catch (e) { + exc = e; + } finally { + assertEq(exc instanceof TypeError, true); + // The message test is here because instanceof TypeError doesn't + // distinguish the non-Object return TypeError and the + // throw-method-is-not-defined iterator protocol error. + assertEq(exc.toString().indexOf("non-object") > 0, true); + } + assertEq(returnCalled, ++returnCalledExpected); + + // G.p.return passes its argument to "return". + iterable[Symbol.iterator] = makeIterator({ + ret: function(x) { + assertEq(x, "in test"); + returnCalled++; + return { done: true }; + } + }); + var g7 = y(); + g7.next(); + g7.return("in test"); + assertEq(returnCalled, ++returnCalledExpected); + + // If a throw method is present, do not call "return". + iterable[Symbol.iterator] = makeIterator({ + throw: function(e) { + throwCalled++; + throw e; + }, + ret: function(x) { + returnCalled++; + return { done: true }; + } + }); + var g8 = y(); + g8.next(); + assertThrowsValue(function() { + g8.throw("foo"); + }, "foo"); + assertEq(throwCalled, ++throwCalledExpected); + assertEq(returnCalled, returnCalledExpected); +} + +test(); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Statements/for-inof-finally.js b/js/src/tests/ecma_6/Statements/for-inof-finally.js new file mode 100644 index 000000000..e1f0c77e1 --- /dev/null +++ b/js/src/tests/ecma_6/Statements/for-inof-finally.js @@ -0,0 +1,78 @@ +var BUGNUMBER = 1332881; +var summary = + "Leaving for-in and try should handle stack value in correct order"; + +print(BUGNUMBER + ": " + summary); + +var called = 0; +function reset() { + called = 0; +} +var obj = { + [Symbol.iterator]() { + return { + next() { + return { value: 10, done: false }; + }, + return() { + called++; + return {}; + } + }; + } +}; + +var a = (function () { + for (var x in [0]) { + try {} finally { + return 11; + } + } +})(); +assertEq(a, 11); + +reset(); +var b = (function () { + for (var x of obj) { + try {} finally { + return 12; + } + } +})(); +assertEq(called, 1); +assertEq(b, 12); + +reset(); +var c = (function () { + for (var x in [0]) { + for (var y of obj) { + try {} finally { + return 13; + } + } + } +})(); +assertEq(called, 1); +assertEq(c, 13); + +reset(); +var d = (function () { + for (var x in [0]) { + for (var y of obj) { + try {} finally { + for (var z in [0]) { + for (var w of obj) { + try {} finally { + return 14; + } + } + } + } + } + } +})(); +assertEq(called, 2); +assertEq(d, 14); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_6/Statements/for-of-iterator-close-throw.js b/js/src/tests/ecma_6/Statements/for-of-iterator-close-throw.js new file mode 100644 index 000000000..1974e416b --- /dev/null +++ b/js/src/tests/ecma_6/Statements/for-of-iterator-close-throw.js @@ -0,0 +1,35 @@ +function test() { + var returnCalled = 0; + var returnCalledExpected = 0; + var catchEntered = 0; + var finallyEntered = 0; + var finallyEnteredExpected = 0; + var iterable = {}; + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + throw 42; + } + }); + + // inner try cannot catch IteratorClose throwing + assertThrowsValue(function() { + for (var x of iterable) { + try { + return; + } catch (e) { + catchEntered++; + } finally { + finallyEntered++; + } + } + }, 42); + assertEq(returnCalled, ++returnCalledExpected); + assertEq(catchEntered, 0); + assertEq(finallyEntered, ++finallyEnteredExpected); +} + +test(); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Statements/for-of-iterator-close.js b/js/src/tests/ecma_6/Statements/for-of-iterator-close.js new file mode 100644 index 000000000..b28895d8f --- /dev/null +++ b/js/src/tests/ecma_6/Statements/for-of-iterator-close.js @@ -0,0 +1,102 @@ +// Tests that IteratorReturn is called when a for-of loop has an abrupt +// completion value during non-iterator code. + +function test() { + var returnCalled = 0; + var returnCalledExpected = 0; + var iterable = {}; + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + return {}; + } + }); + + // break calls iter.return + for (var x of iterable) + break; + assertEq(returnCalled, ++returnCalledExpected); + + // throw in body calls iter.return + assertThrowsValue(function() { + for (var x of iterable) + throw "in body"; + }, "in body"); + assertEq(returnCalled, ++returnCalledExpected); + + // throw in lhs ref calls iter.return + function throwlhs() { + throw "in lhs"; + } + assertThrowsValue(function() { + for ((throwlhs()) of iterable) + continue; + }, "in lhs"); + assertEq(returnCalled, ++returnCalledExpected); + + // throw in iter.return doesn't re-call iter.return + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + throw "in iter.return"; + } + }); + assertThrowsValue(function() { + for (var x of iterable) + break; + }, "in iter.return"); + assertEq(returnCalled, ++returnCalledExpected); + + // throw in iter.next doesn't call IteratorClose + iterable[Symbol.iterator] = makeIterator({ + next: function() { + throw "in next"; + } + }); + assertThrowsValue(function() { + for (var x of iterable) + break; + }, "in next"); + assertEq(returnCalled, returnCalledExpected); + + // "return" must return an Object. + iterable[Symbol.iterator] = makeIterator({ + ret: function() { + returnCalled++; + return 42; + } + }); + assertThrowsInstanceOf(function() { + for (var x of iterable) + break; + }, TypeError); + assertEq(returnCalled, ++returnCalledExpected); + + // continue doesn't call iter.return for the loop it's continuing + var i = 0; + iterable[Symbol.iterator] = makeIterator({ + next: function() { + return { done: i++ > 5 }; + }, + ret: function() { + returnCalled++; + return {}; + } + }); + for (var x of iterable) + continue; + assertEq(returnCalled, returnCalledExpected); + + // continue does call iter.return for loops it skips + i = 0; + L: do { + for (var x of iterable) + continue L; + } while (false); + assertEq(returnCalled, ++returnCalledExpected); +} + +test(); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/shell.js b/js/src/tests/ecma_6/shell.js index 06be6f2db..756da9f36 100644 --- a/js/src/tests/ecma_6/shell.js +++ b/js/src/tests/ecma_6/shell.js @@ -3,21 +3,43 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ (function(global) { - /** Yield every permutation of the elements in some array. */ - global.Permutations = function* Permutations(items) { - if (items.length == 0) { - yield []; - } else { - items = items.slice(0); - for (let i = 0; i < items.length; i++) { - let swap = items[0]; - items[0] = items[i]; - items[i] = swap; - for (let e of Permutations(items.slice(1, items.length))) - yield [items[0]].concat(e); - } - } - }; + /** Yield every permutation of the elements in some array. */ + global.Permutations = function* Permutations(items) { + if (items.length == 0) { + yield []; + } else { + items = items.slice(0); + for (let i = 0; i < items.length; i++) { + let swap = items[0]; + items[0] = items[i]; + items[i] = swap; + for (let e of Permutations(items.slice(1, items.length))) + yield [items[0]].concat(e); + } + } + }; + + /** Make an iterator with a return method. */ + global.makeIterator = function makeIterator(overrides) { + var throwMethod; + if (overrides && overrides.throw) + throwMethod = overrides.throw; + var iterator = { + throw: throwMethod, + next: function(x) { + if (overrides && overrides.next) + return overrides.next(x); + return { done: false }; + }, + return: function(x) { + if (overrides && overrides.ret) + return overrides.ret(x); + return { done: true }; + } + }; + + return function() { return iterator; }; + }; })(this); if (typeof assertThrowsInstanceOf === 'undefined') { diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index bd0705446..e971dc844 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -13,7 +13,7 @@ #define FOR_EACH_COMMON_PROPERTYNAME(macro) \ macro(add, add, "add") \ - macro(allowContentSpread, allowContentSpread, "allowContentSpread") \ + macro(allowContentIter, allowContentIter, "allowContentIter") \ macro(anonymous, anonymous, "anonymous") \ macro(Any, Any, "Any") \ macro(apply, apply, "apply") \ diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 9cba1f4dc..b747e4d7a 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1080,6 +1080,9 @@ js::UnwindEnvironmentToTryPc(JSScript* script, JSTryNote* tn) if (tn->kind == JSTRY_CATCH || tn->kind == JSTRY_FINALLY) { pc -= JSOP_TRY_LENGTH; MOZ_ASSERT(*pc == JSOP_TRY); + } else if (tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE) { + pc -= JSOP_TRY_DESTRUCTURING_ITERCLOSE_LENGTH; + MOZ_ASSERT(*pc == JSOP_TRY_DESTRUCTURING_ITERCLOSE); } return pc; } @@ -1156,6 +1159,7 @@ enum HandleErrorContinuation static HandleErrorContinuation ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs) { + bool inForOfIterClose = false; for (TryNoteIterInterpreter tni(cx, regs); !tni.done(); ++tni) { JSTryNote* tn = *tni; @@ -1164,10 +1168,38 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs) /* Catch cannot intercept the closing of a generator. */ if (cx->isClosingGenerator()) break; + + // If IteratorClose due to abnormal completion threw inside a + // for-of loop, it is not catchable by try statements inside of + // the for-of loop. + // + // This is handled by this weirdness in the exception handler + // instead of in bytecode because it is hard to do so in bytecode: + // + // 1. IteratorClose emitted due to abnormal completion (break, + // throw, return) are emitted inline, at the source location of + // the break, throw, or return statement. For example: + // + // for (x of iter) { + // try { return; } catch (e) { } + // } + // + // From the try-note nesting's perspective, the IteratorClose + // resulting from |return| is covered by the inner try, when it + // should not be. + // + // 2. Try-catch notes cannot be disjoint. That is, we can't have + // multiple notes with disjoint pc ranges jumping to the same + // catch block. + if (inForOfIterClose) + break; SettleOnTryNote(cx, tn, ei, regs); return CatchContinuation; case JSTRY_FINALLY: + // See note above. + if (inForOfIterClose) + break; SettleOnTryNote(cx, tn, ei, regs); return FinallyContinuation; @@ -1189,7 +1221,31 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs) 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); + RootedValue doneValue(cx, sp[-1]); + bool done = ToBoolean(doneValue); + if (!done) { + RootedObject iterObject(cx, &sp[-2].toObject()); + if (!IteratorCloseForException(cx, iterObject)) { + SettleOnTryNote(cx, tn, ei, regs); + return ErrorReturnContinuation; + } + } + break; + } + + case JSTRY_FOR_OF_ITERCLOSE: + inForOfIterClose = true; + break; + case JSTRY_FOR_OF: + inForOfIterClose = false; + break; + case JSTRY_LOOP: break; @@ -1860,14 +1916,11 @@ CASE(EnableInterruptsPseudoOpcode) /* Various 1-byte no-ops. */ CASE(JSOP_NOP) CASE(JSOP_NOP_DESTRUCTURING) -CASE(JSOP_UNUSED183) -CASE(JSOP_UNUSED187) CASE(JSOP_UNUSED192) CASE(JSOP_UNUSED209) CASE(JSOP_UNUSED210) CASE(JSOP_UNUSED211) -CASE(JSOP_UNUSED219) -CASE(JSOP_UNUSED220) +CASE(JSOP_TRY_DESTRUCTURING_ITERCLOSE) CASE(JSOP_UNUSED221) CASE(JSOP_UNUSED222) CASE(JSOP_UNUSED223) @@ -2156,6 +2209,13 @@ CASE(JSOP_ENDITER) } END_CASE(JSOP_ENDITER) +CASE(JSOP_ISGENCLOSING) +{ + bool b = REGS.sp[-1].isMagic(JS_GENERATOR_CLOSING); + PUSH_BOOLEAN(b); +} +END_CASE(JSOP_ISGENCLOSING) + CASE(JSOP_DUP) { MOZ_ASSERT(REGS.stackDepth() >= 1); @@ -2193,6 +2253,16 @@ CASE(JSOP_PICK) } END_CASE(JSOP_PICK) +CASE(JSOP_UNPICK) +{ + int i = GET_UINT8(REGS.pc); + MOZ_ASSERT(REGS.stackDepth() >= unsigned(i) + 1); + Value lval = REGS.sp[-1]; + memmove(REGS.sp - i, REGS.sp - (i + 1), sizeof(Value) * i); + REGS.sp[-(i + 1)] = lval; +} +END_CASE(JSOP_UNPICK) + CASE(JSOP_BINDGNAME) CASE(JSOP_BINDNAME) { @@ -2594,6 +2664,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)) { @@ -5031,7 +5110,16 @@ js::ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind) { switch (kind) { case CheckIsObjectKind::IteratorNext: - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NEXT_RETURNED_PRIMITIVE); + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "next"); + break; + case CheckIsObjectKind::IteratorReturn: + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "return"); + break; + case CheckIsObjectKind::IteratorThrow: + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "throw"); break; case CheckIsObjectKind::GetIterator: JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_GET_ITER_RETURNED_PRIMITIVE); @@ -5043,6 +5131,19 @@ js::ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind) } 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) { RootedFunction fun(cx); diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index 1ffe1fdca..330dbef5f 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -562,12 +562,21 @@ ReportRuntimeRedeclaration(JSContext* cx, HandlePropertyName name, const char* r enum class CheckIsObjectKind : uint8_t { IteratorNext, + IteratorReturn, + IteratorThrow, GetIterator }; bool ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind); +enum class CheckIsCallableKind : uint8_t { + IteratorReturn +}; + +bool +ThrowCheckIsCallable(JSContext* cx, CheckIsCallableKind kind); + bool ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame); diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index f6636004d..4b044c8d8 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1880,8 +1880,14 @@ * Stack: fun, name => fun */ \ macro(JSOP_SETFUNNAME, 182,"setfunname", NULL, 2, 2, 1, JOF_UINT8) \ - macro(JSOP_UNUSED183, 183,"unused183", NULL, 1, 0, 0, JOF_BYTE) \ - \ + /* + * Moves the top of the stack value under the nth element of the stack. + * Category: Operators + * Type: Stack Operations + * Operands: uint8_t n + * Stack: v[n], v[n-1], ..., v[1], v[0] => v[0], v[n], v[n-1], ..., v[1] + */ \ + macro(JSOP_UNPICK, 183,"unpick", NULL, 2, 0, 0, JOF_UINT8) \ /* * Pops the top of stack value, pushes property of it onto the stack. * @@ -1910,8 +1916,16 @@ * Stack: => this */ \ macro(JSOP_GLOBALTHIS, 186,"globalthis", NULL, 1, 0, 1, JOF_BYTE) \ - macro(JSOP_UNUSED187, 187,"unused187", NULL, 1, 0, 0, JOF_BYTE) \ - \ + /* + * Pushes a boolean indicating whether the top of the stack is + * MagicValue(JS_GENERATOR_CLOSING). + * + * Category: Statements + * Type: For-In Statement + * Operands: + * Stack: val => val, res + */ \ + macro(JSOP_ISGENCLOSING, 187, "isgenclosing", NULL, 1, 1, 2, JOF_BYTE) \ /* * Pushes unsigned 24-bit int immediate integer operand onto the stack. * Category: Literals @@ -2187,8 +2201,26 @@ */ \ macro(JSOP_HOLE, 218, "hole", NULL, 1, 0, 1, JOF_BYTE) \ \ - macro(JSOP_UNUSED219, 219,"unused219", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED220, 220,"unused220", 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) \ + \ + /* + * No-op used by the exception unwinder to determine the correct + * environment to unwind to when performing IteratorClose due to + * destructuring. + * Category: Other + * Operands: + * Stack: => + */ \ + macro(JSOP_TRY_DESTRUCTURING_ITERCLOSE, 220, "try-destructuring-iterclose", 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) \ macro(JSOP_UNUSED223, 223,"unused223", NULL, 1, 0, 0, JOF_BYTE) \ |