summaryrefslogtreecommitdiffstats
path: root/js/src
diff options
context:
space:
mode:
authorwolfbeast <mcwerewolf@gmail.com>2018-03-27 13:21:40 +0200
committerwolfbeast <mcwerewolf@gmail.com>2018-03-27 13:21:40 +0200
commite19749682050ff716fc9ff3bbc05ee3911570670 (patch)
tree4213a4e8efdaadea0409642702ec6c499f7b9ccc /js/src
parent72721d1d032db2099593076bf96f274623af3c26 (diff)
parent70c8cf8db71880c1ab1f8fee4787a19316960dac (diff)
downloadUXP-e19749682050ff716fc9ff3bbc05ee3911570670.tar
UXP-e19749682050ff716fc9ff3bbc05ee3911570670.tar.gz
UXP-e19749682050ff716fc9ff3bbc05ee3911570670.tar.lz
UXP-e19749682050ff716fc9ff3bbc05ee3911570670.tar.xz
UXP-e19749682050ff716fc9ff3bbc05ee3911570670.zip
Merge remote-tracking branch 'janek/js_IteratorClose_1'
Diffstat (limited to 'js/src')
-rw-r--r--js/src/builtin/Array.js46
-rw-r--r--js/src/builtin/Classes.js2
-rw-r--r--js/src/builtin/Map.js38
-rw-r--r--js/src/builtin/Set.js36
-rw-r--r--js/src/builtin/TypedArray.js4
-rw-r--r--js/src/builtin/Utilities.js23
-rw-r--r--js/src/builtin/WeakMap.js38
-rw-r--r--js/src/builtin/WeakSet.js36
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp1780
-rw-r--r--js/src/frontend/BytecodeEmitter.h17
-rw-r--r--js/src/jit-test/tests/auto-regress/for-of-iterator-close-debugger.js15
-rw-r--r--js/src/jit-test/tests/for-of/bug-1331444.js7
-rw-r--r--js/src/jit-test/tests/for-of/bug-1341339.js9
-rw-r--r--js/src/jit-test/tests/ion/bug1333946.js8
-rw-r--r--js/src/jit-test/tests/ion/bug1334314.js16
-rw-r--r--js/src/jit-test/tests/parser/bug-1357075.js10
-rw-r--r--js/src/jit/BaselineBailouts.cpp35
-rw-r--r--js/src/jit/BaselineCompiler.cpp40
-rw-r--r--js/src/jit/BaselineCompiler.h11
-rw-r--r--js/src/jit/CodeGenerator.cpp109
-rw-r--r--js/src/jit/CodeGenerator.h7
-rw-r--r--js/src/jit/IonBuilder.cpp58
-rw-r--r--js/src/jit/IonBuilder.h3
-rw-r--r--js/src/jit/JitFrames.cpp99
-rw-r--r--js/src/jit/Lowering.cpp13
-rw-r--r--js/src/jit/Lowering.h1
-rw-r--r--js/src/jit/MIR.h32
-rw-r--r--js/src/jit/MOpcodes.h1
-rw-r--r--js/src/jit/VMFunctions.cpp9
-rw-r--r--js/src/jit/VMFunctions.h4
-rw-r--r--js/src/jit/shared/LIR-shared.h21
-rw-r--r--js/src/jit/shared/LOpcodes-shared.h1
-rw-r--r--js/src/js.msg3
-rw-r--r--js/src/jsapi.h9
-rw-r--r--js/src/jsiter.cpp52
-rw-r--r--js/src/jsiter.h3
-rw-r--r--js/src/jsopcode.cpp60
-rw-r--r--js/src/jsopcode.h1
-rw-r--r--js/src/jsopcodeinlines.h3
-rw-r--r--js/src/jsscript.cpp3
-rw-r--r--js/src/jsscript.h4
-rw-r--r--js/src/shell/js.cpp36
-rw-r--r--js/src/tests/ecma_6/Destructuring/array-iterator-close.js93
-rw-r--r--js/src/tests/ecma_6/Generators/delegating-yield-2.js20
-rw-r--r--js/src/tests/ecma_6/Generators/yield-iterator-close.js58
-rw-r--r--js/src/tests/ecma_6/Generators/yield-star-iterator-close.js153
-rw-r--r--js/src/tests/ecma_6/Statements/for-inof-finally.js78
-rw-r--r--js/src/tests/ecma_6/Statements/for-of-iterator-close-throw.js35
-rw-r--r--js/src/tests/ecma_6/Statements/for-of-iterator-close.js102
-rw-r--r--js/src/tests/ecma_6/shell.js52
-rw-r--r--js/src/vm/CommonPropertyNames.h2
-rw-r--r--js/src/vm/Interpreter.cpp100
-rw-r--r--js/src/vm/Interpreter.h9
-rw-r--r--js/src/vm/Opcodes.h34
54 files changed, 2603 insertions, 836 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 2dece3801..bfb1fe7f4 100644
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -154,29 +154,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 a4cfeb753..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, &noteIndex_))
+ 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, &noteIndex_))
+ 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()))
@@ -4509,7 +5220,7 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri
}
bool
-BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted)
+BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted /* = false */)
{
MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting,
".next() iteration is prohibited in self-hosted code because it "
@@ -4530,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
@@ -4617,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, &noteIndex_))
- 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)
{
@@ -4776,6 +5486,9 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
// Here's pseudo code for |let [a, b, , c=y, ...d] = x;|
//
+ // Lines that are annotated "covered by trynote" mean that upon throwing
+ // an exception, IteratorClose is called on iter only if done is false.
+ //
// let x, y;
// let a, b, c, d;
// let iter, lref, result, done, value; // stack values
@@ -4783,7 +5496,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
// iter = x[Symbol.iterator]();
//
// // ==== emitted by loop for a ====
- // lref = GetReference(a);
+ // lref = GetReference(a); // covered by trynote
//
// result = iter.next();
// done = result.done;
@@ -4793,10 +5506,10 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
// else
// value = result.value;
//
- // SetOrInitialize(lref, value);
+ // SetOrInitialize(lref, value); // covered by trynote
//
// // ==== emitted by loop for b ====
- // lref = GetReference(b);
+ // lref = GetReference(b); // covered by trynote
//
// if (done) {
// value = undefined;
@@ -4809,7 +5522,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
// value = result.value;
// }
//
- // SetOrInitialize(lref, value);
+ // SetOrInitialize(lref, value); // covered by trynote
//
// // ==== emitted by loop for elision ====
// if (done) {
@@ -4824,7 +5537,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
// }
//
// // ==== emitted by loop for c ====
- // lref = GetReference(c);
+ // lref = GetReference(c); // covered by trynote
//
// if (done) {
// value = undefined;
@@ -4838,19 +5551,23 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
// }
//
// if (value === undefined)
- // value = y;
+ // value = y; // covered by trynote
//
- // SetOrInitialize(lref, value);
+ // SetOrInitialize(lref, value); // covered by trynote
//
// // ==== emitted by loop for d ====
- // lref = GetReference(d);
+ // lref = GetReference(d); // covered by trynote
//
// if (done)
// value = [];
// else
// value = [...iter];
//
- // SetOrInitialize(lref, value);
+ // SetOrInitialize(lref, value); // covered by trynote
+ //
+ // // === emitted after loop ===
+ // if (!done)
+ // IteratorClose(iter);
// Use an iterator to destructure the RHS, instead of index lookup. We
// must leave the *original* value on the stack.
@@ -4859,25 +5576,61 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
if (!emitIterator()) // ... OBJ ITER
return false;
+ // For an empty pattern [], call IteratorClose unconditionally. Nothing
+ // else needs to be done.
+ if (!pattern->pn_head)
+ return emitIteratorClose(); // ... OBJ
+
+ // Push an initial FALSE value for DONE.
+ if (!emit1(JSOP_FALSE)) // ... OBJ ITER FALSE
+ return false;
+
+ // JSTRY_DESTRUCTURING_ITERCLOSE expects the iterator and the done value
+ // to be the second to top and the top of the stack, respectively.
+ // IteratorClose is called upon exception only if done is false.
+ int32_t tryNoteDepth = stackDepth;
+
for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
- bool isHead = member == pattern->pn_head;
- bool hasNext = !!member->pn_next;
+ bool isFirst = member == pattern->pn_head;
+ DebugOnly<bool> hasNext = !!member->pn_next;
- if (member->isKind(PNK_SPREAD)) {
- size_t emitted = 0;
- if (!emitDestructuringLHSRef(member, &emitted)) // ... OBJ ITER ?DONE *LREF
+ 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.
- // ... OBJ ITER DONE *LREF
- if (emitted) {
- if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE
- return false;
- }
-
+ // ... OBJ ITER *LREF DONE
if (!ifThenElse.emitIfElse()) // ... OBJ ITER *LREF
return false;
@@ -4900,13 +5653,22 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF ARRAY
return false;
- if (!isHead) {
+ if (!isFirst) {
if (!ifThenElse.emitEnd())
return false;
MOZ_ASSERT(ifThenElse.pushed() == 1);
}
- if (!emitSetOrInitializeDestructuring(member, flav)) // ... OBJ ITER
+ // At this point the iterator is done. Unpick a TRUE value for DONE above ITER.
+ if (!emit1(JSOP_TRUE)) // ... OBJ ITER *LREF ARRAY TRUE
+ return false;
+ if (!emit2(JSOP_UNPICK, emitted + 1)) // ... OBJ ITER TRUE *LREF ARRAY
+ return false;
+
+ auto emitAssignment = [member, flav](BytecodeEmitter* bce) {
+ return bce->emitSetOrInitializeDestructuring(member, flav); // ... OBJ ITER TRUE
+ };
+ if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment))
return false;
MOZ_ASSERT(!hasNext);
@@ -4914,63 +5676,30 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
}
ParseNode* pndefault = nullptr;
- ParseNode* subpattern = member;
- if (subpattern->isKind(PNK_ASSIGN)) {
- pndefault = subpattern->pn_right;
- subpattern = subpattern->pn_left;
- }
-
- bool isElision = subpattern->isKind(PNK_ELISION);
-
- MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD));
+ if (member->isKind(PNK_ASSIGN))
+ pndefault = member->pn_right;
- size_t emitted = 0;
- if (!isElision) {
- if (!emitDestructuringLHSRef(subpattern, &emitted)) // ... OBJ ITER ?DONE *LREF
- return false;
- }
+ MOZ_ASSERT(!member->isKind(PNK_SPREAD));
IfThenElseEmitter ifAlreadyDone(this);
- if (!isHead) {
- // If this element is not the first element of the pattern,
- // iterator can already be completed.
- // ... OBJ ITER DONE *LREF
- if (emitted) {
- if (hasNext) {
- if (!emitDupAt(emitted)) // ... OBJ ITER DONE *LREF DONE
- return false;
- } else {
- if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE
- return false;
- }
- } else {
- if (hasNext) {
- // The position of LREF in the following stack comment
- // isn't accurate for the operation, but it's equivalent
- // since LREF is nothing
- if (!emit1(JSOP_DUP)) // ... OBJ ITER DONE *LREF DONE
- return false;
- }
- }
- if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER ?DONE *LREF
+ if (!isFirst) {
+ // ... OBJ ITER *LREF DONE
+ if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER *LREF
return false;
- if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF
+ if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER *LREF UNDEF
return false;
- if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF
+ if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER *LREF UNDEF
return false;
- if (!ifAlreadyDone.emitElse()) // ... OBJ ITER ?DONE *LREF
+ // The iterator is done. Unpick a TRUE value for DONE above ITER.
+ if (!emit1(JSOP_TRUE)) // ... OBJ ITER *LREF UNDEF TRUE
+ return false;
+ if (!emit2(JSOP_UNPICK, emitted + 1)) // ... OBJ ITER TRUE *LREF UNDEF
return false;
- if (hasNext) {
- if (emitted) {
- if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE
- return false;
- }
- if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF
- return false;
- }
+ if (!ifAlreadyDone.emitElse()) // ... OBJ ITER *LREF
+ return false;
}
if (emitted) {
@@ -4987,59 +5716,74 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER *LREF RESULT DONE
return false;
- if (hasNext) {
- if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT DONE DONE
- return false;
- if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE
- return false;
- }
+ if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT DONE DONE
+ return false;
+ if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE
+ return false;
IfThenElseEmitter ifDone(this);
- if (!ifDone.emitIfElse()) // ... OBJ ITER ?DONE *LREF RESULT
+ if (!ifDone.emitIfElse()) // ... OBJ ITER DONE *LREF RESULT
return false;
- if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE *LREF
+ if (!emit1(JSOP_POP)) // ... OBJ ITER DONE *LREF
return false;
- if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF
+ if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER DONE *LREF UNDEF
return false;
- if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF
+ if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER DONE *LREF UNDEF
return false;
- if (!ifDone.emitElse()) // ... OBJ ITER ?DONE *LREF RESULT
+ if (!ifDone.emitElse()) // ... OBJ ITER DONE *LREF RESULT
return false;
- if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER ?DONE *LREF VALUE
+ if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER DONE *LREF VALUE
return false;
if (!ifDone.emitEnd())
return false;
MOZ_ASSERT(ifDone.pushed() == 0);
- if (!isHead) {
+ if (!isFirst) {
if (!ifAlreadyDone.emitEnd())
return false;
- MOZ_ASSERT(ifAlreadyDone.pushed() == 1);
+ MOZ_ASSERT(ifAlreadyDone.pushed() == 2);
}
if (pndefault) {
- if (!emitDefault(pndefault, subpattern)) // ... OBJ ITER ?DONE *LREF VALUE
+ auto emitDefault = [pndefault, lhsPattern](BytecodeEmitter* bce) {
+ return bce->emitDefault(pndefault, lhsPattern); // ... OBJ ITER DONE *LREF VALUE
+ };
+
+ if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitDefault))
return false;
}
if (!isElision) {
- if (!emitSetOrInitializeDestructuring(subpattern,
- flav)) // ... OBJ ITER ?DONE
- {
+ auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) {
+ return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); // ... OBJ ITER DONE
+ };
+
+ if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment))
return false;
- }
} else {
- if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE
+ if (!emit1(JSOP_POP)) // ... OBJ ITER DONE
return false;
}
}
+ // The last DONE value is on top of the stack. If not DONE, call
+ // IteratorClose.
+ // ... OBJ ITER DONE
+ IfThenElseEmitter ifDone(this);
+ if (!ifDone.emitIfElse()) // ... OBJ ITER
+ return false;
if (!emit1(JSOP_POP)) // ... OBJ
return false;
+ if (!ifDone.emitElse()) // ... OBJ ITER
+ return false;
+ if (!emitIteratorClose()) // ... OBJ
+ return false;
+ if (!ifDone.emitEnd())
+ return false;
return true;
}
@@ -5710,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.
@@ -5744,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, &noteIndex))
- 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))
+ if (!tryCatch.emitTry())
return false;
- // Emit jump over catch and/or finally.
- JumpList catchJump;
- if (!emitJump(JSOP_GOTO, &catchJump))
- 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));
@@ -5824,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))
- 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))
+ if (!emitTree(finallyNode))
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;
@@ -6296,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;
@@ -6316,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
@@ -6337,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;
}
@@ -6354,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_POP)) // ITER UNDEF
return false;
- if (!emit1(JSOP_DUP)) // ITER ITER
+ 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);
}
@@ -6408,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
@@ -6818,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
@@ -6855,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 (!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;
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);
@@ -6912,7 +7600,7 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn)
}
// Pop the result and the iter.
- return emitUint16Operand(JSOP_POPN, 2); //
+ return emitUint16Operand(JSOP_POPN, 3); //
}
bool
@@ -7609,7 +8297,7 @@ BytecodeEmitter::emitReturn(ParseNode* pn)
return false;
}
- NonLocalExitControl nle(this);
+ NonLocalExitControl nle(this, NonLocalExitControl::Return);
if (!nle.prepareForNonLocalJumpToOutermost())
return false;
@@ -7679,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, &noteIndex))
- 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;
+ // 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;
- // THROW? = 'throw' in ITER
- if (!emit1(JSOP_EXCEPTION)) // ITER EXCEPTION
+ stackDepth = savedDepthTemp;
+ if (!ifThrowMethodIsNotDefined.emitEnd()) // ITER OLDRESULT EXCEPTION ITER THROW
return false;
- if (!emit1(JSOP_SWAP)) // EXCEPTION 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 (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER
+ if (!emit2(JSOP_PICK, 2)) // ITER OLDRESULT THROW ITER EXCEPTION
return false;
- if (!emitAtomOp(cx->names().throw_, JSOP_STRING)) // EXCEPTION ITER ITER "throw"
+ if (!emitCall(JSOP_CALL, 1, iter)) // ITER OLDRESULT RESULT
+ return false;
+ checkTypeSet(JSOP_CALL);
+ if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) // ITER OLDRESULT RESULT
+ return false;
+ if (!emit1(JSOP_SWAP)) // ITER RESULT OLDRESULT
+ return false;
+ if (!emit1(JSOP_POP)) // ITER RESULT
+ return false;
+ 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 (!emit1(JSOP_SWAP)) // EXCEPTION ITER "throw" ITER
+
+ 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;
- if (!emit1(JSOP_IN)) // EXCEPTION ITER THROW?
+ if (!ifGeneratorClosing.emitIf()) // ITER RESULT FTYPE FVALUE
return false;
- // if (THROW?) goto delegate
- JumpList checkThrow;
- if (!emitJump(JSOP_IFNE, &checkThrow)) // EXCEPTION ITER
+
+ // Step ii.
+ //
+ // Get the "return" method.
+ if (!emitDupAt(3)) // ITER RESULT FTYPE FVALUE ITER
return false;
- if (!emit1(JSOP_POP)) // EXCEPTION
+ if (!emit1(JSOP_DUP)) // ITER RESULT FTYPE FVALUE ITER ITER
return false;
- if (!emit1(JSOP_THROW)) // throw EXCEPTION
+ if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) // ITER RESULT FTYPE FVALUE ITER RET
return false;
- if (!emitJumpTargetAndPatch(checkThrow)) // delegate:
+ // Step iii.
+ //
+ // Do nothing if "return" is undefined.
+ IfThenElseEmitter ifReturnMethodIsDefined(this);
+ if (!emit1(JSOP_DUP)) // ITER RESULT FTYPE FVALUE ITER RET RET
return false;
- // RESULT = ITER.throw(EXCEPTION) // EXCEPTION ITER
- stackDepth = uint32_t(depth);
- if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER
+ if (!emit1(JSOP_UNDEFINED)) // ITER RESULT FTYPE FVALUE ITER RET RET UNDEFINED
return false;
- if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER ITER
+ if (!emit1(JSOP_NE)) // ITER RESULT FTYPE FVALUE ITER RET ?NEQL
return false;
- if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) // EXCEPTION ITER ITER THROW
+
+ // 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)) // EXCEPTION ITER THROW ITER
+ if (!emit1(JSOP_SWAP)) // ITER OLDRESULT FTYPE FVALUE RET ITER
return false;
- if (!emit2(JSOP_PICK, 3)) // ITER THROW ITER EXCEPTION
+ if (!emit1(JSOP_GETRVAL)) // ITER OLDRESULT FTYPE FVALUE RET ITER RVAL
return false;
- if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT
+ 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 (!tryNoteList.append(JSTRY_CATCH, depth, tryStart.offset + JSOP_TRY_LENGTH, tryEnd.offset))
+ if (!emit1(JSOP_POP)) // ITER RESULT FTYPE FVALUE
+ return false;
+ if (!ifReturnMethodIsDefined.emitEnd())
+ return false;
+
+ if (!ifGeneratorClosing.emitEnd())
return false;
- // After the try/catch block: send the received value to the iterator.
- if (!emitJumpTargetAndPatch(send)) // send:
+ if (!tryCatch.emitEnd())
return false;
- // Send location.
+ // 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;
@@ -7801,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;
@@ -7812,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
@@ -7825,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;
}
@@ -8147,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;
}
@@ -8176,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;
@@ -8284,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))
@@ -8940,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;
@@ -8951,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;
@@ -8968,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))
diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h
index 066c06672..04307c8c1 100644
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -100,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(); }
@@ -452,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.
@@ -473,6 +473,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter
// Helper to emit JSOP_CHECKISOBJ.
MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind);
+ // Helper to emit JSOP_CHECKISCALLABLE.
+ MOZ_MUST_USE bool emitCheckIsCallable(CheckIsCallableKind kind);
+
// Emit a bytecode followed by an uint16 immediate operand stored in
// big-endian order.
MOZ_MUST_USE bool emitUint16Operand(JSOp op, uint32_t operand);
@@ -679,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|.
@@ -726,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/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 2f0d41707..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;
@@ -1381,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,
@@ -3975,7 +4001,7 @@ BaselineCompiler::emit_JSOP_MOREITER()
}
bool
-BaselineCompiler::emit_JSOP_ISNOITER()
+BaselineCompiler::emitIsMagicValue()
{
frame.syncStack(0);
@@ -3994,6 +4020,12 @@ BaselineCompiler::emit_JSOP_ISNOITER()
}
bool
+BaselineCompiler::emit_JSOP_ISNOITER()
+{
+ return emitIsMagicValue();
+}
+
+bool
BaselineCompiler::emit_JSOP_ENDITER()
{
if (!emit_JSOP_JUMPTARGET())
@@ -4005,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 60dac0966..6b5bf009e 100644
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -203,6 +203,7 @@ namespace jit {
_(JSOP_MOREITER) \
_(JSOP_ISNOITER) \
_(JSOP_ENDITER) \
+ _(JSOP_ISGENCLOSING) \
_(JSOP_GENERATOR) \
_(JSOP_INITIALYIELD) \
_(JSOP_YIELD) \
@@ -217,6 +218,7 @@ namespace jit {
_(JSOP_FUNCTIONTHIS) \
_(JSOP_GLOBALTHIS) \
_(JSOP_CHECKISOBJ) \
+ _(JSOP_CHECKISCALLABLE) \
_(JSOP_CHECKTHIS) \
_(JSOP_CHECKRETURN) \
_(JSOP_NEWTARGET) \
@@ -224,7 +226,7 @@ namespace jit {
_(JSOP_SPREADSUPERCALL) \
_(JSOP_THROWSETCONST) \
_(JSOP_THROWSETALIASEDCONST) \
- _(JSOP_THROWSETCALLEE) \
+ _(JSOP_THROWSETCALLEE) \
_(JSOP_INITHIDDENPROP_GETTER) \
_(JSOP_INITHIDDENPROP_SETTER) \
_(JSOP_INITHIDDENELEM) \
@@ -232,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
{
@@ -342,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_), &notFunction);
- 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, &notConstructor);
+ masm.move32(Imm32(1), output);
+ masm.jump(&done);
+ masm.bind(&notConstructor);
+ masm.move32(Imm32(0), output);
+ }
masm.jump(&done);
masm.bind(&notFunction);
@@ -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_), &notFunction);
- masm.load16ZeroExtend(Address(object, JSFunction::offsetOfFlags()), output);
- masm.and32(Imm32(JSFunction::CONSTRUCTOR), output);
- masm.branchTest32(Assembler::Zero, output, output, &notConstructor);
- masm.move32(Imm32(1), output);
- masm.jump(&done);
- masm.bind(&notConstructor);
- masm.move32(Imm32(0), output);
- masm.jump(&done);
-
- masm.bind(&notFunction);
- 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 c4df415a4..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();
}
}
}
@@ -1673,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:
@@ -1936,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:
@@ -2174,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();
@@ -10891,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/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-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/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 8ae9c43b0..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,13 +1916,11 @@ CASE(EnableInterruptsPseudoOpcode)
/* Various 1-byte no-ops. */
CASE(JSOP_NOP)
CASE(JSOP_NOP_DESTRUCTURING)
-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)
@@ -2155,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);
@@ -2603,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)) {
@@ -5040,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);
@@ -5052,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 b59b9388c..4b044c8d8 100644
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1916,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
@@ -2193,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) \