summaryrefslogtreecommitdiffstats
path: root/js
diff options
context:
space:
mode:
authorMoonchild <mcwerewolf@gmail.com>2018-03-16 08:36:30 +0100
committerGitHub <noreply@github.com>2018-03-16 08:36:30 +0100
commit11bdaa144d8a38ecd897dde278cb1db9b8313961 (patch)
tree1ca375db23843dcd4c593bcb28d7173856bddc10 /js
parent0d3ee51ad2d61bffba14232b2df913d3a773d771 (diff)
parent114794557687aebca601c38ba0f0a52a43b44d4a (diff)
downloadUXP-11bdaa144d8a38ecd897dde278cb1db9b8313961.tar
UXP-11bdaa144d8a38ecd897dde278cb1db9b8313961.tar.gz
UXP-11bdaa144d8a38ecd897dde278cb1db9b8313961.tar.lz
UXP-11bdaa144d8a38ecd897dde278cb1db9b8313961.tar.xz
UXP-11bdaa144d8a38ecd897dde278cb1db9b8313961.zip
Merge pull request #66 from janekptacijarabaci/js_iterable_closures_1
Close iterator after error in: Map, Set, WeakMap, WeakSet, Array.from, Promise.{all,race}
Diffstat (limited to 'js')
-rw-r--r--js/src/builtin/Array.js46
-rw-r--r--js/src/builtin/Map.js11
-rw-r--r--js/src/builtin/Promise.cpp53
-rw-r--r--js/src/builtin/Set.js7
-rw-r--r--js/src/builtin/Utilities.js23
-rw-r--r--js/src/builtin/WeakMap.js11
-rw-r--r--js/src/builtin/WeakSet.js7
-rw-r--r--js/src/js.msg3
-rw-r--r--js/src/jsapi.h6
-rw-r--r--js/src/tests/ecma_6/Array/from-iterator-close.js183
-rw-r--r--js/src/tests/ecma_6/Map/constructor-iterator-close.js253
-rw-r--r--js/src/tests/ecma_6/Promise/iterator-close.js234
-rw-r--r--js/src/vm/ForOfIterator.cpp51
13 files changed, 851 insertions, 37 deletions
diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js
index 54b47b72f..5ab0b71be 100644
--- a/js/src/builtin/Array.js
+++ b/js/src/builtin/Array.js
@@ -784,7 +784,7 @@ function ArrayKeys() {
return CreateArrayIterator(this, ITEM_KIND_KEY);
}
-// ES6 draft rev31 (2015/01/15) 22.1.2.1 Array.from(source[, mapfn[, thisArg]]).
+// ES 2017 draft 0f10dba4ad18de92d47d421f378233a2eae8f077 22.1.2.1
function ArrayFrom(items, mapfn=undefined, thisArg=undefined) {
// Step 1.
var C = this;
@@ -795,43 +795,61 @@ function ArrayFrom(items, mapfn=undefined, thisArg=undefined) {
ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, mapfn));
var T = thisArg;
- // Steps 4-5.
+ // Step 4.
var usingIterator = GetMethod(items, std_iterator);
- // Step 6.
+ // Step 5.
if (usingIterator !== undefined) {
- // Steps 6.a-c.
+ // Steps 5.a-c.
var A = IsConstructor(C) ? new C() : [];
- // Steps 6.d-e.
+ // Step 5.c.
var iterator = GetIterator(items, usingIterator);
- // Step 6.f.
+ // Step 5.d.
var k = 0;
- // Step 6.g.
+ // 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) {
- // Steps 6.g.i-iii.
+ // 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);
+ ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
+ }
+ */
+
+ // Step 5.e.iii.
var next = callContentFunction(iterator.next, iterator);
if (!IsObject(next))
ThrowTypeError(JSMSG_NEXT_RETURNED_PRIMITIVE);
- // Step 6.g.iv.
+ // Step 5.e.iv.
if (next.done) {
A.length = k;
return A;
}
- // Steps 6.g.v-vi.
+ // Steps 5.e.v.
var nextValue = next.value;
- // Steps 6.g.vii-viii.
- var mappedValue = mapping ? callContentFunction(mapfn, thisArg, nextValue, k) : nextValue;
+ // Steps 5.e.vi-vii.
+ try {
+ var mappedValue = mapping ? callContentFunction(mapfn, thisArg, nextValue, k) : nextValue;
- // Steps 6.g.ix-xi.
- _DefineDataProperty(A, k++, mappedValue);
+ // 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;
+ }
}
}
diff --git a/js/src/builtin/Map.js b/js/src/builtin/Map.js
index 432364614..27a12bfff 100644
--- a/js/src/builtin/Map.js
+++ b/js/src/builtin/Map.js
@@ -40,11 +40,18 @@ function MapConstructorInit(iterable) {
var nextItem = next.value;
// Step 8.d.
- if (!IsObject(nextItem))
+ if (!IsObject(nextItem)) {
+ IteratorCloseThrow(iter);
ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "Map");
+ }
// Steps 8.e-j.
- callContentFunction(adder, map, nextItem[0], nextItem[1]);
+ try {
+ callContentFunction(adder, map, nextItem[0], nextItem[1]);
+ } catch (e) {
+ IteratorCloseThrow(iter);
+ throw e;
+ }
}
}
diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp
index 59c97e529..c781a336d 100644
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -1369,7 +1369,8 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /
static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator,
HandleObject C, HandleObject promiseObj,
- HandleObject resolve, HandleObject reject);
+ HandleObject resolve, HandleObject reject,
+ bool* done);
// ES2016, 25.4.4.1.
static bool
@@ -1410,12 +1411,14 @@ Promise_static_all(JSContext* cx, unsigned argc, Value* vp)
// Step 6 (implicit).
// Step 7.
- bool result = PerformPromiseAll(cx, iter, C, resultPromise, resolve, reject);
+ bool done;
+ bool result = PerformPromiseAll(cx, iter, C, resultPromise, resolve, reject, &done);
// Step 8.
if (!result) {
// Step 8.a.
- // TODO: implement iterator closing.
+ if (!done)
+ iter.closeThrow();
// Step 8.b.
return AbruptRejectPromise(cx, args, resultPromise, reject);
@@ -1598,8 +1601,11 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res
// ES2016, 25.4.4.1.1.
static MOZ_MUST_USE bool
PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
- HandleObject promiseObj, HandleObject resolve, HandleObject reject)
+ HandleObject promiseObj, HandleObject resolve, HandleObject reject,
+ bool* done)
{
+ *done = false;
+
RootedObject unwrappedPromiseObj(cx);
if (IsWrapper(promiseObj)) {
unwrappedPromiseObj = CheckedUnwrap(promiseObj);
@@ -1666,14 +1672,19 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
RootedValue rejectFunVal(cx, ObjectOrNullValue(reject));
while (true) {
- bool done;
- // Steps a, b, c, e, f, g.
- if (!iterator.next(&nextValue, &done))
+ // Steps a-c, e-g.
+ if (!iterator.next(&nextValue, done)) {
+ // Steps b, f.
+ *done = true;
+
+ // Steps c, g.
return false;
+ }
// Step d.
- if (done) {
+ if (*done) {
// Step d.i (implicit).
+
// Step d.ii.
int32_t remainingCount = dataHolder->decreaseRemainingCount();
@@ -1822,7 +1833,8 @@ PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp)
static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator,
HandleObject C, HandleObject promiseObj,
- HandleObject resolve, HandleObject reject);
+ HandleObject resolve, HandleObject reject,
+ bool* done);
// ES2016, 25.4.4.3.
static bool
@@ -1863,12 +1875,14 @@ Promise_static_race(JSContext* cx, unsigned argc, Value* vp)
// Step 6 (implicit).
// Step 7.
- bool result = PerformPromiseRace(cx, iter, C, resultPromise, resolve, reject);
+ bool done;
+ bool result = PerformPromiseRace(cx, iter, C, resultPromise, resolve, reject, &done);
// Step 8.
if (!result) {
// Step 8.a.
- // TODO: implement iterator closing.
+ if (!done)
+ iter.closeThrow();
// Step 8.b.
return AbruptRejectPromise(cx, args, resultPromise, reject);
@@ -1882,25 +1896,30 @@ Promise_static_race(JSContext* cx, unsigned argc, Value* vp)
// ES2016, 25.4.4.3.1.
static MOZ_MUST_USE bool
PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C,
- HandleObject promiseObj, HandleObject resolve, HandleObject reject)
+ HandleObject promiseObj, HandleObject resolve, HandleObject reject,
+ bool* done)
{
+ *done = false;
MOZ_ASSERT(C->isConstructor());
RootedValue CVal(cx, ObjectValue(*C));
RootedValue nextValue(cx);
RootedValue resolveFunVal(cx, ObjectOrNullValue(resolve));
RootedValue rejectFunVal(cx, ObjectOrNullValue(reject));
- bool done;
while (true) {
// Steps a-c, e-g.
- if (!iterator.next(&nextValue, &done))
+ if (!iterator.next(&nextValue, done)) {
+ // Steps b, f.
+ *done = true;
+
+ // Steps c, g.
return false;
+ }
// Step d.
- if (done) {
- // Step d.i.
- // TODO: implement iterator closing.
+ if (*done) {
+ // Step d.i (implicit).
// Step d.ii.
return true;
diff --git a/js/src/builtin/Set.js b/js/src/builtin/Set.js
index accc70120..c61a49ef8 100644
--- a/js/src/builtin/Set.js
+++ b/js/src/builtin/Set.js
@@ -40,7 +40,12 @@ function SetConstructorInit(iterable) {
var nextValue = next.value;
// Steps 8.d-e.
- callContentFunction(adder, set, nextValue);
+ try {
+ callContentFunction(adder, set, nextValue);
+ } catch (e) {
+ IteratorCloseThrow(iter);
+ throw e;
+ }
}
}
diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js
index bfb1fe7f4..2dece3801 100644
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -154,6 +154,29 @@ 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 066a72bfe..708be8424 100644
--- a/js/src/builtin/WeakMap.js
+++ b/js/src/builtin/WeakMap.js
@@ -40,10 +40,17 @@ function WeakMapConstructorInit(iterable) {
var nextItem = next.value;
// Step 8.d.
- if (!IsObject(nextItem))
+ if (!IsObject(nextItem)) {
+ IteratorCloseThrow(iter);
ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "WeakMap");
+ }
// Steps 8.e-j.
- callContentFunction(adder, map, nextItem[0], nextItem[1]);
+ try {
+ callContentFunction(adder, map, nextItem[0], nextItem[1]);
+ } catch (e) {
+ IteratorCloseThrow(iter);
+ throw e;
+ }
}
}
diff --git a/js/src/builtin/WeakSet.js b/js/src/builtin/WeakSet.js
index eb7c2378f..8589f9dc6 100644
--- a/js/src/builtin/WeakSet.js
+++ b/js/src/builtin/WeakSet.js
@@ -40,7 +40,12 @@ function WeakSetConstructorInit(iterable) {
var nextValue = next.value;
// Steps 8.d-e.
- callContentFunction(adder, set, nextValue);
+ try {
+ callContentFunction(adder, set, nextValue);
+ } catch (e) {
+ IteratorCloseThrow(iter);
+ throw e;
+ }
}
}
diff --git a/js/src/js.msg b/js/src/js.msg
index cb5fc383b..2a818056f 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -579,3 +579,6 @@ MSG_DEF(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCa
MSG_DEF(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.")
MSG_DEF(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.")
MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "Promise rejection value is a non-unwrappable cross-compartment wrapper.")
+
+// Iterator
+MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of iterator is not callable")
diff --git a/js/src/jsapi.h b/js/src/jsapi.h
index 2d6ff462c..a93852fa5 100644
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -6167,6 +6167,12 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(ForOfIterator) {
bool next(JS::MutableHandleValue val, bool* done);
/**
+ * Close the iterator.
+ * For the case that completion type is throw.
+ */
+ void closeThrow();
+
+ /**
* If initialized with throwOnNonCallable = false, check whether
* the value is iterable.
*/
diff --git a/js/src/tests/ecma_6/Array/from-iterator-close.js b/js/src/tests/ecma_6/Array/from-iterator-close.js
new file mode 100644
index 000000000..fa97ea282
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/from-iterator-close.js
@@ -0,0 +1,183 @@
+var BUGNUMBER = 1180306;
+var summary = 'Array.from should close iterator on error';
+
+print(BUGNUMBER + ": " + summary);
+
+function test(ctor, { mapVal=undefined,
+ nextVal=undefined,
+ nextThrowVal=undefined,
+ modifier=undefined,
+ exceptionVal=undefined,
+ exceptionType=undefined,
+ closed=true }) {
+ let iterable = {
+ closed: false,
+ [Symbol.iterator]() {
+ let iterator = {
+ first: true,
+ next() {
+ if (this.first) {
+ this.first = false;
+ if (nextThrowVal)
+ throw nextThrowVal;
+ return nextVal;
+ }
+ return { value: undefined, done: true };
+ },
+ return() {
+ iterable.closed = true;
+ return {};
+ }
+ };
+ if (modifier)
+ modifier(iterator, iterable);
+
+ return iterator;
+ }
+ };
+ if (exceptionVal) {
+ let caught = false;
+ try {
+ ctor.from(iterable, mapVal);
+ } catch (e) {
+ assertEq(e, exceptionVal);
+ caught = true;
+ }
+ assertEq(caught, true);
+ } else if (exceptionType) {
+ assertThrowsInstanceOf(() => ctor.from(iterable, mapVal), exceptionType);
+ } else {
+ ctor.from(iterable, mapVal);
+ }
+ assertEq(iterable.closed, closed);
+}
+
+// == Error cases with close ==
+
+// ES 2017 draft 22.1.2.1 step 5.e.i.1.
+// Cannot test.
+
+// ES 2017 draft 22.1.2.1 step 5.e.vi.2.
+test(Array, {
+ mapVal: () => { throw "map throws"; },
+ nextVal: { value: 1, done: false },
+ exceptionVal: "map throws",
+ closed: true,
+});
+
+// ES 2017 draft 22.1.2.1 step 5.e.ix.
+class MyArray extends Array {
+ constructor() {
+ return new Proxy({}, {
+ defineProperty() {
+ throw "defineProperty throws";
+ }
+ });
+ }
+}
+test(MyArray, {
+ nextVal: { value: 1, done: false },
+ exceptionVal: "defineProperty throws",
+ closed: true,
+});
+
+// ES 2017 draft 7.4.6 step 3.
+// if GetMethod fails, the thrown value should be used.
+test(MyArray, {
+ nextVal: { value: 1, done: false },
+ modifier: (iterator, iterable) => {
+ Object.defineProperty(iterator, "return", {
+ get: function() {
+ iterable.closed = true;
+ throw "return getter throws";
+ }
+ });
+ },
+ exceptionVal: "return getter throws",
+ closed: true,
+});
+test(MyArray, {
+ nextVal: { value: 1, done: false },
+ modifier: (iterator, iterable) => {
+ Object.defineProperty(iterator, "return", {
+ get: function() {
+ iterable.closed = true;
+ return "non object";
+ }
+ });
+ },
+ exceptionType: TypeError,
+ closed: true,
+});
+test(MyArray, {
+ nextVal: { value: 1, done: false },
+ modifier: (iterator, iterable) => {
+ Object.defineProperty(iterator, "return", {
+ get: function() {
+ iterable.closed = true;
+ // Non callable.
+ return {};
+ }
+ });
+ },
+ exceptionType: TypeError,
+ closed: true,
+});
+
+// ES 2017 draft 7.4.6 steps 6.
+// if return method throws, the thrown value should be ignored.
+test(MyArray, {
+ nextVal: { value: 1, done: false },
+ modifier: (iterator, iterable) => {
+ iterator.return = function() {
+ iterable.closed = true;
+ throw "return throws";
+ };
+ },
+ exceptionVal: "defineProperty throws",
+ closed: true,
+});
+
+test(MyArray, {
+ nextVal: { value: 1, done: false },
+ modifier: (iterator, iterable) => {
+ iterator.return = function() {
+ iterable.closed = true;
+ return "non object";
+ };
+ },
+ exceptionVal: "defineProperty throws",
+ closed: true,
+});
+
+// == Error cases without close ==
+
+// ES 2017 draft 22.1.2.1 step 5.e.iii.
+test(Array, {
+ nextThrowVal: "next throws",
+ exceptionVal: "next throws",
+ closed: false,
+});
+
+test(Array, {
+ nextVal: { value: {}, get done() { throw "done getter throws"; } },
+ exceptionVal: "done getter throws",
+ closed: false,
+});
+
+// ES 2017 draft 22.1.2.1 step 5.e.v.
+test(Array, {
+ nextVal: { get value() { throw "value getter throws"; }, done: false },
+ exceptionVal: "value getter throws",
+ closed: false,
+});
+
+// == Successful cases ==
+
+test(Array, {
+ nextVal: { value: 1, done: false },
+ closed: false,
+});
+
+if (typeof reportCompare === 'function')
+ reportCompare(true, true);
diff --git a/js/src/tests/ecma_6/Map/constructor-iterator-close.js b/js/src/tests/ecma_6/Map/constructor-iterator-close.js
new file mode 100644
index 000000000..ba4bee4a7
--- /dev/null
+++ b/js/src/tests/ecma_6/Map/constructor-iterator-close.js
@@ -0,0 +1,253 @@
+var BUGNUMBER = 1180306;
+var summary = 'Map/Set/WeakMap/WeakSet constructor should close iterator on error';
+
+print(BUGNUMBER + ": " + summary);
+
+function test(ctors, { nextVal=undefined,
+ nextThrowVal=undefined,
+ modifier=undefined,
+ exceptionVal=undefined,
+ exceptionType=undefined,
+ closed=true }) {
+ function getIterable() {
+ let iterable = {
+ closed: false,
+ [Symbol.iterator]() {
+ let iterator = {
+ first: true,
+ next() {
+ if (this.first) {
+ this.first = false;
+ if (nextThrowVal)
+ throw nextThrowVal;
+ return nextVal;
+ }
+ return { value: undefined, done: true };
+ },
+ return() {
+ iterable.closed = true;
+ return {};
+ }
+ };
+ if (modifier)
+ modifier(iterator, iterable);
+
+ return iterator;
+ }
+ };
+ return iterable;
+ }
+
+ for (let ctor of ctors) {
+ let iterable = getIterable();
+ if (exceptionVal) {
+ let caught = false;
+ try {
+ new ctor(iterable);
+ } catch (e) {
+ assertEq(e, exceptionVal);
+ caught = true;
+ }
+ assertEq(caught, true);
+ } else if (exceptionType) {
+ assertThrowsInstanceOf(() => new ctor(iterable), exceptionType);
+ } else {
+ new ctor(iterable);
+ }
+ assertEq(iterable.closed, closed);
+ }
+}
+
+// == Error cases with close ==
+
+// ES 2017 draft 23.1.1.1 Map step 8.d.ii.
+// ES 2017 draft 23.3.1.1 WeakMap step 8.d.ii.
+test([Map, WeakMap], {
+ nextVal: { value: "non object", done: false },
+ exceptionType: TypeError,
+ closed: true,
+});
+
+// ES 2017 draft 23.1.1.1 Map step 8.f.
+// ES 2017 draft 23.3.1.1 WeakMap step 8.f.
+test([Map, WeakMap], {
+ nextVal: { value: { get 0() { throw "0 getter throws"; } }, done: false },
+ exceptionVal: "0 getter throws",
+ closed: true,
+});
+
+// ES 2017 draft 23.1.1.1 Map step 8.h.
+// ES 2017 draft 23.3.1.1 WeakMap step 8.h.
+test([Map, WeakMap], {
+ nextVal: { value: { 0: {}, get 1() { throw "1 getter throws"; } }, done: false },
+ exceptionVal: "1 getter throws",
+ closed: true,
+});
+
+// ES 2017 draft 23.1.1.1 Map step 8.j.
+// ES 2017 draft 23.3.1.1 WeakMap step 8.j.
+class MyMap extends Map {
+ set(k, v) {
+ throw "setter throws";
+ }
+}
+class MyWeakMap extends WeakMap {
+ set(k, v) {
+ throw "setter throws";
+ }
+}
+test([MyMap, MyWeakMap], {
+ nextVal: { value: [{}, {}], done: false },
+ exceptionVal: "setter throws",
+ closed: true,
+});
+
+// ES 2017 draft 23.2.1.1 Set step 8.e.
+// ES 2017 draft 23.4.1.1 WeakSet step 8.e.
+class MySet extends Set {
+ add(v) {
+ throw "adder throws";
+ }
+}
+class MyWeakSet extends WeakSet {
+ add(v) {
+ throw "adder throws";
+ }
+}
+test([MySet, MyWeakSet], {
+ nextVal: { value: {}, done: false },
+ exceptionVal: "adder throws",
+ closed: true,
+});
+
+// ES 2017 draft 7.4.6 step 3.
+// if GetMethod fails, the thrown value should be used.
+test([MyMap, MySet, MyWeakMap, MyWeakSet], {
+ nextVal: { value: [{}, {}], done: false },
+ modifier: (iterator, iterable) => {
+ Object.defineProperty(iterator, "return", {
+ get: function() {
+ iterable.closed = true;
+ throw "return getter throws";
+ }
+ });
+ },
+ exceptionVal: "return getter throws",
+ closed: true,
+});
+test([MyMap, MySet, MyWeakMap, MyWeakSet], {
+ nextVal: { value: [{}, {}], done: false },
+ modifier: (iterator, iterable) => {
+ Object.defineProperty(iterator, "return", {
+ get: function() {
+ iterable.closed = true;
+ return "non object";
+ }
+ });
+ },
+ exceptionType: TypeError,
+ closed: true,
+});
+test([MyMap, MySet, MyWeakMap, MyWeakSet], {
+ nextVal: { value: [{}, {}], done: false },
+ modifier: (iterator, iterable) => {
+ Object.defineProperty(iterator, "return", {
+ get: function() {
+ iterable.closed = true;
+ // Non callable.
+ return {};
+ }
+ });
+ },
+ exceptionType: TypeError,
+ closed: true,
+});
+
+// ES 2017 draft 7.4.6 steps 6.
+// if return method throws, the thrown value should be ignored.
+test([MyMap, MyWeakMap], {
+ nextVal: { value: [{}, {}], done: false },
+ modifier: (iterator, iterable) => {
+ iterator.return = function() {
+ iterable.closed = true;
+ throw "return throws";
+ };
+ },
+ exceptionVal: "setter throws",
+ closed: true,
+});
+test([MySet, MyWeakSet], {
+ nextVal: { value: [{}, {}], done: false },
+ modifier: (iterator, iterable) => {
+ iterator.return = function() {
+ iterable.closed = true;
+ throw "return throws";
+ };
+ },
+ exceptionVal: "adder throws",
+ closed: true,
+});
+
+test([MyMap, MyWeakMap], {
+ nextVal: { value: [{}, {}], done: false },
+ modifier: (iterator, iterable) => {
+ iterator.return = function() {
+ iterable.closed = true;
+ return "non object";
+ };
+ },
+ exceptionVal: "setter throws",
+ closed: true,
+});
+test([MySet, MyWeakSet], {
+ nextVal: { value: [{}, {}], done: false },
+ modifier: (iterator, iterable) => {
+ iterator.return = function() {
+ iterable.closed = true;
+ return "non object";
+ };
+ },
+ exceptionVal: "adder throws",
+ closed: true,
+});
+
+// == Error cases without close ==
+
+// ES 2017 draft 23.1.1.1 Map step 8.a.
+// ES 2017 draft 23.3.1.1 WeakMap step 8.a.
+// ES 2017 draft 23.2.1.1 Set step 8.a.
+// ES 2017 draft 23.4.1.1 WeakSet step 8.a.
+test([Map, WeakMap, Set, WeakSet], {
+ nextThrowVal: "next throws",
+ exceptionVal: "next throws",
+ closed: false,
+});
+test([Map, WeakMap, Set, WeakSet], {
+ nextVal: { value: {}, get done() { throw "done getter throws"; } },
+ exceptionVal: "done getter throws",
+ closed: false,
+});
+
+// ES 2017 draft 23.1.1.1 Map step 8.c.
+// ES 2017 draft 23.3.1.1 WeakMap step 8.c.
+// ES 2017 draft 23.2.1.1 Set step 8.c.
+// ES 2017 draft 23.4.1.1 WeakSet step 8.c.
+test([Map, WeakMap, Set, WeakSet], {
+ nextVal: { get value() { throw "value getter throws"; }, done: false },
+ exceptionVal: "value getter throws",
+ closed: false,
+});
+
+// == Successful cases ==
+
+test([Map, WeakMap], {
+ nextVal: { value: [{}, {}], done: false },
+ closed: false,
+});
+test([Set, WeakSet], {
+ nextVal: { value: {}, done: false },
+ closed: false,
+});
+
+if (typeof reportCompare === 'function')
+ reportCompare(true, true);
diff --git a/js/src/tests/ecma_6/Promise/iterator-close.js b/js/src/tests/ecma_6/Promise/iterator-close.js
new file mode 100644
index 000000000..f17260d05
--- /dev/null
+++ b/js/src/tests/ecma_6/Promise/iterator-close.js
@@ -0,0 +1,234 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+
+var BUGNUMBER = 1180306;
+var summary = 'Promise.{all,race} should close iterator on error';
+
+print(BUGNUMBER + ": " + summary);
+
+function test(ctor, props, { nextVal=undefined,
+ nextThrowVal=undefined,
+ modifier=undefined,
+ rejectReason=undefined,
+ rejectType=undefined,
+ closed=true }) {
+ function getIterable() {
+ let iterable = {
+ closed: false,
+ [Symbol.iterator]() {
+ let iterator = {
+ first: true,
+ next() {
+ if (this.first) {
+ this.first = false;
+ if (nextThrowVal)
+ throw nextThrowVal;
+ return nextVal;
+ }
+ return { value: undefined, done: true };
+ },
+ return() {
+ iterable.closed = true;
+ return {};
+ }
+ };
+ if (modifier)
+ modifier(iterator, iterable);
+
+ return iterator;
+ }
+ };
+ return iterable;
+ }
+ for (let prop of props) {
+ let iterable = getIterable();
+ let e;
+ ctor[prop](iterable).catch(e_ => { e = e_; });
+ drainJobQueue();
+ if(rejectType)
+ assertEq(e instanceof rejectType, true);
+ else
+ assertEq(e, rejectReason);
+ assertEq(iterable.closed, closed);
+ }
+}
+
+// == Error cases with close ==
+
+// ES 2017 draft 25.4.4.1.1 step 6.i.
+// ES 2017 draft 25.4.4.3.1 step 3.h.
+class MyPromiseStaticResolveGetterThrows extends Promise {
+ static get resolve() {
+ throw "static resolve getter throws";
+ }
+};
+test(MyPromiseStaticResolveGetterThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ rejectReason: "static resolve getter throws",
+ closed: true,
+});
+
+class MyPromiseStaticResolveThrows extends Promise {
+ static resolve() {
+ throw "static resolve throws";
+ }
+};
+test(MyPromiseStaticResolveThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ rejectReason: "static resolve throws",
+ closed: true,
+});
+
+// ES 2017 draft 25.4.4.1.1 step 6.q.
+// ES 2017 draft 25.4.4.3.1 step 3.i.
+class MyPromiseThenGetterThrows extends Promise {
+ static resolve() {
+ return {
+ get then() {
+ throw "then getter throws";
+ }
+ };
+ }
+};
+test(MyPromiseThenGetterThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ rejectReason: "then getter throws",
+ closed: true,
+});
+
+class MyPromiseThenThrows extends Promise {
+ static resolve() {
+ return {
+ then() {
+ throw "then throws";
+ }
+ };
+ }
+};
+test(MyPromiseThenThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ rejectReason: "then throws",
+ closed: true,
+});
+
+// ES 2017 draft 7.4.6 step 3.
+// if GetMethod fails, the thrown value should be used.
+test(MyPromiseThenThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ modifier: (iterator, iterable) => {
+ Object.defineProperty(iterator, "return", {
+ get: function() {
+ iterable.closed = true;
+ throw "return getter throws";
+ }
+ });
+ },
+ rejectReason: "return getter throws",
+ closed: true,
+});
+test(MyPromiseThenThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ modifier: (iterator, iterable) => {
+ Object.defineProperty(iterator, "return", {
+ get: function() {
+ iterable.closed = true;
+ return "non object";
+ }
+ });
+ },
+ rejectType: TypeError,
+ closed: true,
+});
+test(MyPromiseThenThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ modifier: (iterator, iterable) => {
+ Object.defineProperty(iterator, "return", {
+ get: function() {
+ iterable.closed = true;
+ // Non callable.
+ return {};
+ }
+ });
+ },
+ rejectType: TypeError,
+ closed: true,
+});
+
+// ES 2017 draft 7.4.6 steps 6.
+// if return method throws, the thrown value should be ignored.
+test(MyPromiseThenThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ modifier: (iterator, iterable) => {
+ iterator.return = function() {
+ iterable.closed = true;
+ throw "return throws";
+ };
+ },
+ rejectReason: "then throws",
+ closed: true,
+});
+
+test(MyPromiseThenThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ modifier: (iterator, iterable) => {
+ iterator.return = function() {
+ iterable.closed = true;
+ return "non object";
+ };
+ },
+ rejectReason: "then throws",
+ closed: true,
+});
+
+// == Error cases without close ==
+
+// ES 2017 draft 25.4.4.1.1 step 6.a.
+test(Promise, ["all", "race"], {
+ nextThrowVal: "next throws",
+ rejectReason: "next throws",
+ closed: false,
+});
+
+test(Promise, ["all", "race"], {
+ nextVal: { value: {}, get done() { throw "done getter throws"; } },
+ rejectReason: "done getter throws",
+ closed: false,
+});
+
+// ES 2017 draft 25.4.4.1.1 step 6.e.
+test(Promise, ["all", "race"], {
+ nextVal: { get value() { throw "value getter throws"; }, done: false },
+ rejectReason: "value getter throws",
+ closed: false,
+});
+
+// ES 2017 draft 25.4.4.1.1 step 6.d.iii.2.
+let first = true;
+class MyPromiseResolveThrows extends Promise {
+ constructor(executer) {
+ if (first) {
+ first = false;
+ super((resolve, reject) => {
+ executer(() => {
+ throw "resolve throws";
+ }, reject);
+ });
+ return;
+ }
+ super(executer);
+ }
+};
+test(MyPromiseResolveThrows, ["all"], {
+ nextVal: { value: undefined, done: true },
+ rejectReason: "resolve throws",
+ closed: false,
+});
+
+// == Successful cases ==
+
+test(Promise, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ closed: false,
+});
+
+if (typeof reportCompare === 'function')
+ reportCompare(true, true);
diff --git a/js/src/vm/ForOfIterator.cpp b/js/src/vm/ForOfIterator.cpp
index 7bd521a6a..a67b36774 100644
--- a/js/src/vm/ForOfIterator.cpp
+++ b/js/src/vm/ForOfIterator.cpp
@@ -151,6 +151,57 @@ ForOfIterator::next(MutableHandleValue vp, bool* done)
return GetProperty(cx_, resultObj, resultObj, cx_->names().value, vp);
}
+// ES 2017 draft 0f10dba4ad18de92d47d421f378233a2eae8f077 7.4.6.
+// When completion.[[Type]] is throw.
+void
+ForOfIterator::closeThrow()
+{
+ MOZ_ASSERT(iterator);
+
+ RootedValue completionException(cx_);
+ if (cx_->isExceptionPending()) {
+ if (!GetAndClearException(cx_, &completionException))
+ completionException.setUndefined();
+ }
+
+ // Steps 1-2 (implicit)
+
+ // Step 3 (partial).
+ RootedValue returnVal(cx_);
+ if (!GetProperty(cx_, iterator, iterator, cx_->names().return_, &returnVal))
+ return;
+
+ // Step 4.
+ if (returnVal.isUndefined()) {
+ cx_->setPendingException(completionException);
+ return;
+ }
+
+ // Step 3 (remaining part)
+ if (!returnVal.isObject()) {
+ JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, JSMSG_RETURN_NOT_CALLABLE);
+ return;
+ }
+ RootedObject returnObj(cx_, &returnVal.toObject());
+ if (!returnObj->isCallable()) {
+ JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, JSMSG_RETURN_NOT_CALLABLE);
+ return;
+ }
+
+ // Step 5.
+ RootedValue innerResultValue(cx_);
+ if (!js::Call(cx_, returnVal, iterator, &innerResultValue)) {
+ if (cx_->isExceptionPending())
+ cx_->clearPendingException();
+ }
+
+ // Step 6.
+ cx_->setPendingException(completionException);
+
+ // Steps 7-9 (skipped).
+ return;
+}
+
bool
ForOfIterator::materializeArrayIterator()
{