diff --git a/js/src/jit-test/tests/for-of/array-iterator-changing.js b/js/src/jit-test/tests/for-of/array-iterator-changing.js deleted file mode 100644 index 0769ebfab77..00000000000 --- a/js/src/jit-test/tests/for-of/array-iterator-changing.js +++ /dev/null @@ -1,11 +0,0 @@ -// Array iterators reflect changes to elements of the underlying array. - -load(libdir + "asserts.js"); -var arr = [0, 1, 2]; -var it = arr.iterator(); -arr[0] = 1000; -arr[2] = 2000; -assertEq(it.next(), 1000); -assertEq(it.next(), 1); -assertEq(it.next(), 2000); -assertThrowsValue(function () { it.next(); }, StopIteration); diff --git a/js/src/jit-test/tests/for-of/array-iterator-generic.js b/js/src/jit-test/tests/for-of/array-iterator-generic.js deleted file mode 100644 index 5b76863cd51..00000000000 --- a/js/src/jit-test/tests/for-of/array-iterator-generic.js +++ /dev/null @@ -1,27 +0,0 @@ -// Array.prototype.iterator is generic. -// That is, it can be applied to arraylike objects and strings, not just arrays. - -load(libdir + "asserts.js"); - -function test(obj) { - var it = Array.prototype.iterator.call(obj); - for (var i = 0; i < (obj.length >>> 0); i++) - assertEq(it.next(), obj[i]); - assertThrowsValue(function () { it.next(); }, StopIteration); -} - -test({length: 0}); -test({length: 0, 0: 'x', 1: 'y'}); -test({length: 2, 0: 'x', 1: 'y'}); -test(Object.create(['x', 'y', 'z'])); -test(Object.create({length: 2, 0: 'x', 1: 'y'})); -test(""); -test("ponies"); - -// Perverse length values. -test({length: 0x1f00000000}); -test({length: -0xfffffffe, 0: 'a', 1: 'b'}); -test({length: "011", 9: 9, 10: 10, 11: 11}); -test({length: -0}); -test({length: 2.7, 0: 0, 1: 1, 2: 2}); -test({length: {valueOf: function () { return 3; }}, 0: 0, 1: 1, 2: 2}); diff --git a/js/src/jit-test/tests/for-of/array-iterator-growing-1.js b/js/src/jit-test/tests/for-of/array-iterator-growing-1.js deleted file mode 100644 index edb31078c76..00000000000 --- a/js/src/jit-test/tests/for-of/array-iterator-growing-1.js +++ /dev/null @@ -1,12 +0,0 @@ -// If an array with an active iterator is lengthened, the iterator visits the new elements. - -load(libdir + "asserts.js"); -var arr = [0, 1]; -var it = arr.iterator(); -it.next(); -it.next(); -arr[2] = 2; -arr.length = 4; -assertEq(it.next(), 2); -assertEq(it.next(), undefined); -assertThrowsValue(function () { it.next(); }, StopIteration); diff --git a/js/src/jit-test/tests/for-of/array-iterator-null.js b/js/src/jit-test/tests/for-of/array-iterator-null.js deleted file mode 100644 index df492e514c6..00000000000 --- a/js/src/jit-test/tests/for-of/array-iterator-null.js +++ /dev/null @@ -1,9 +0,0 @@ -// Array.prototype.iterator applied to undefined or null does not throw (until .next is called). - -load(libdir + "asserts.js"); -for (var v of [undefined, null]) { - var it = Array.prototype.iterator.call(v); - - // This will throw because the iterator is trying to get v.length. - assertThrowsInstanceOf(function () { it.next(); }, TypeError); -} diff --git a/js/src/jit-test/tests/for-of/array-iterator-proxy.js b/js/src/jit-test/tests/for-of/array-iterator-proxy.js deleted file mode 100644 index 13fd2aa2bde..00000000000 --- a/js/src/jit-test/tests/for-of/array-iterator-proxy.js +++ /dev/null @@ -1,23 +0,0 @@ -// An array iterator for a proxy calls the traps in a predictable order. - -load(libdir + "asserts.js"); - -var s = ''; -var it = Array.prototype.iterator.call(Proxy.create({ - get: function (recipient, name) { - if (name == 'length') { - s += 'L'; - return 2; - } else { - s += name; - return name; - } - } -})); - -assertEq(it.next(), "0"); -s += ' '; -assertEq(it.next(), "1"); -s += ' '; -assertThrowsValue(function () { it.next(); }, StopIteration); -assertEq(s, "L0 L1 L"); diff --git a/js/src/jit-test/tests/for-of/array-iterator-shrinking.js b/js/src/jit-test/tests/for-of/array-iterator-shrinking.js deleted file mode 100644 index 338658944ce..00000000000 --- a/js/src/jit-test/tests/for-of/array-iterator-shrinking.js +++ /dev/null @@ -1,9 +0,0 @@ -// If an array is truncated to the left of an iterator it, it.next() throws StopIteration. - -load(libdir + "asserts.js"); -var arr = [0, 1, 2]; -var it = arr.iterator(); -it.next(); -it.next(); -arr.length = 1; -assertThrowsValue(function () { it.next(); }, StopIteration); diff --git a/js/src/jit-test/tests/for-of/array-iterator-surfaces-2.js b/js/src/jit-test/tests/for-of/array-iterator-surfaces-2.js deleted file mode 100644 index 00be20a6123..00000000000 --- a/js/src/jit-test/tests/for-of/array-iterator-surfaces-2.js +++ /dev/null @@ -1,22 +0,0 @@ -// Superficial tests for iterators created by Array.prototype.iterator - -var proto = Object.getPrototypeOf([].iterator()); -assertEq(Object.getPrototypeOf(proto), Iterator.prototype); - -function check(it) { - assertEq(typeof it, 'object'); - assertEq(Object.getPrototypeOf(it), proto); - assertEq(Object.getOwnPropertyNames(it).length, 0); - assertEq(it.iterator(), it); - - // for-in enumerates the iterator's properties. - it.x = 0; - var s = ''; - for (var p in it) - s += p + '.'; - assertEq(s, 'x.'); -} - -check([].iterator()); -check(Array.prototype.iterator.call({})); -check(Array.prototype.iterator.call(undefined)); diff --git a/js/src/jit-test/tests/for-of/next-1.js b/js/src/jit-test/tests/for-of/next-1.js deleted file mode 100644 index 6d246a22aa8..00000000000 --- a/js/src/jit-test/tests/for-of/next-1.js +++ /dev/null @@ -1,5 +0,0 @@ -// Iterator.prototype.next throws if applied to a value that isn't an iterator. - -load(libdir + "asserts.js"); -for (var v of [null, undefined, false, 0, "ponies", {}, [], this]) - assertThrowsInstanceOf(function () { Iterator.prototype.next.call(v); }, TypeError); diff --git a/js/src/jit-test/tests/for-of/next-2.js b/js/src/jit-test/tests/for-of/next-2.js deleted file mode 100644 index c784d2445d7..00000000000 --- a/js/src/jit-test/tests/for-of/next-2.js +++ /dev/null @@ -1,6 +0,0 @@ -// Iterator.prototype.next throws if applied to a non-iterator that inherits from an iterator. - -load(libdir + "asserts.js"); -var it = [1, 2].iterator(); -var v = Object.create(it); -assertThrowsInstanceOf(function () { Iterator.prototype.next.call(v); }, TypeError); diff --git a/js/src/jit-test/tests/for-of/next-3.js b/js/src/jit-test/tests/for-of/next-3.js deleted file mode 100644 index 3234ffae411..00000000000 --- a/js/src/jit-test/tests/for-of/next-3.js +++ /dev/null @@ -1,8 +0,0 @@ -// The .next method of array iterators works across compartment boundaries. - -load(libdir + "asserts.js"); -var g = newGlobal('new-compartment'); -g.eval("var it = [1, 2].iterator();"); -assertEq(g.it.next(), 1); -assertEq([].iterator().next.call(g.it), 2); -assertThrowsValue([].iterator().next.bind(g.it), StopIteration); diff --git a/js/src/jit-test/tests/for-of/next-surfaces.js b/js/src/jit-test/tests/for-of/next-surfaces.js deleted file mode 100644 index e86acabdc3d..00000000000 --- a/js/src/jit-test/tests/for-of/next-surfaces.js +++ /dev/null @@ -1,7 +0,0 @@ -// Test superficial features of the Iterator.prototype.next builtin function. - -assertEq(Iterator.prototype.next.length, 0); -var desc = Object.getOwnPropertyDescriptor(Iterator.prototype, "next"); -assertEq(desc.configurable, true); -assertEq(desc.enumerable, false); -assertEq(desc.writable, true); diff --git a/js/src/jit-test/tests/for-of/semantics-06.js b/js/src/jit-test/tests/for-of/semantics-06.js deleted file mode 100644 index 0c52690663f..00000000000 --- a/js/src/jit-test/tests/for-of/semantics-06.js +++ /dev/null @@ -1,6 +0,0 @@ -// Deleting the .next method makes for-of stop working on arrays. - -load(libdir + "asserts.js"); -var iterProto = Object.getPrototypeOf([].iterator()); -delete iterProto.next; -assertThrowsInstanceOf(function () { for (var v of []) ; }, TypeError); diff --git a/js/src/jit-test/tests/for-of/semantics-07.js b/js/src/jit-test/tests/for-of/semantics-07.js deleted file mode 100644 index 03ea49efa40..00000000000 --- a/js/src/jit-test/tests/for-of/semantics-07.js +++ /dev/null @@ -1,15 +0,0 @@ -// Deleting the .next method of an iterator in the middle of a for-of loop -// causes a TypeError at the next iteration. - -load(libdir + "asserts.js"); -var iterProto = Object.getPrototypeOf([].iterator()); -var s = ''; -assertThrowsInstanceOf(function () { - for (var v of ['duck', 'duck', 'duck', 'goose', 'FAIL']) { - s += v; - if (v === 'goose') - delete iterProto.next; - s += '.'; - } -}, TypeError); -assertEq(s, 'duck.duck.duck.goose.'); diff --git a/js/src/jit-test/tests/for-of/semantics-08.js b/js/src/jit-test/tests/for-of/semantics-08.js deleted file mode 100644 index d43ed00b71e..00000000000 --- a/js/src/jit-test/tests/for-of/semantics-08.js +++ /dev/null @@ -1,7 +0,0 @@ -// A for-of loop exits if the iterator's .next method throws another compartment's StopIteration. - -var g = newGlobal('new-compartment'); -var it = g.eval("({ iterator: function () { return this; }, " + - "next: function () { throw StopIteration; } });"); -for (x of it) - throw 'FAIL'; diff --git a/js/src/jit-test/tests/for-of/semantics-09.js b/js/src/jit-test/tests/for-of/semantics-09.js deleted file mode 100644 index 787219cb6eb..00000000000 --- a/js/src/jit-test/tests/for-of/semantics-09.js +++ /dev/null @@ -1,25 +0,0 @@ -// The LHS of a for-of loop is not evaluated until after the .next() method returns. - -var s; -function f() { - s += 'f'; - return {}; -} - -// Test 1: .next() throws StopIteration right away. f is never called. -s = ''; -for (f().x of []) - s += '.'; -assertEq(s, ''); - -// Test 2: check proper interleaving of f calls, iterator.next() calls, and the loop body. -function g() { - s += 'g'; - yield 0; - s += 'g'; - yield 1; - s += 'g'; -} -for (f().x of g()) - s += '.'; -assertEq(s, 'gf.gf.g'); diff --git a/js/src/jit-test/tests/for-of/semantics-10.js b/js/src/jit-test/tests/for-of/semantics-10.js deleted file mode 100644 index 480cb5b3369..00000000000 --- a/js/src/jit-test/tests/for-of/semantics-10.js +++ /dev/null @@ -1,31 +0,0 @@ -// The LHS of a for-loop is not bound to a particular scope until after the .next() method returns. - -var obj = {}; - -// Test 1 -function g() { - obj.x = 0; - yield 1; -} -var x = 2, n = 0; -with (obj) { - for (x of g()) // g().next() inserts a binding for x on obj - n++; -} -assertEq(x, 2); -assertEq(obj.x, 1); -assertEq(n, 1); - -// Test 2 -function h() { - delete obj.x; - yield 3; -} -n = 0; -with (obj) { - for (x of h()) // h().next() deletes the binding for x on obj - n++; -} -assertEq(x, 3); -assertEq("x" in obj, false); -assertEq(n, 1); diff --git a/js/src/jit-test/tests/for-of/semantics-11.js b/js/src/jit-test/tests/for-of/semantics-11.js deleted file mode 100644 index 6e6c4988d39..00000000000 --- a/js/src/jit-test/tests/for-of/semantics-11.js +++ /dev/null @@ -1,41 +0,0 @@ -// for-of on a proxy causes a predictable sequence of trap calls. - -var s = ''; - -var i = 0; -var next_fn = Proxy.createFunction({}, function () { - s += "n"; - if (i == 3) - throw StopIteration; - return i++; -}); - -var it = Proxy.create({ - get: function (receiver, name) { - if (name == 'toSource') { - s += '?'; - return function () 'it'; - } - assertEq(name, "next"); - s += "N"; - return next_fn; - } -}); - -var iterator_fn = Proxy.createFunction({}, function () { - s += 'i'; - return it; -}); - -var obj = Proxy.create({ - get: function (receiver, name) { - assertEq(name, "iterator"); - s += "I"; - return iterator_fn; - } -}); - -for (var v of obj) - s += v; - -assertEq(s, 'IiNn0Nn1Nn2Nn'); diff --git a/js/src/jit-test/tests/for-of/string-iterator-generic.js b/js/src/jit-test/tests/for-of/string-iterator-generic.js deleted file mode 100644 index b6afb8105fe..00000000000 --- a/js/src/jit-test/tests/for-of/string-iterator-generic.js +++ /dev/null @@ -1,14 +0,0 @@ -// String.prototype.iterator is generic. - -load(libdir + "asserts.js"); - -function test(obj) { - var it = Array.prototype.iterator.call(obj); - for (var i = 0; i < (obj.length >>> 0); i++) - assertEq(it.next(), obj[i]); - assertThrowsValue(function () { it.next(); }, StopIteration); -} - -test({length: 0}); -test(Object.create(['x', 'y', 'z'])); -test(Object.create({length: 2, 0: 'x', 1: 'y'})); diff --git a/js/src/jit-test/tests/for-of/strings.js b/js/src/jit-test/tests/for-of/strings.js deleted file mode 100644 index 34553a44843..00000000000 --- a/js/src/jit-test/tests/for-of/strings.js +++ /dev/null @@ -1,26 +0,0 @@ -// for-of works on strings and String objects. - -function test(s) { - var copy = ''; - for (var v of s) { - assertEq(typeof v, 'string'); - assertEq(v.length, 1); - copy += v; - } - assertEq(copy, String(s)); -} - -test(''); -test('abc'); -test('a \0 \ufffe \ufeff'); - -// Non-BMP characters are generally passed to JS in UTF-16, as surrogate pairs. -// ES requires that such pairs be treated as two 16-bit "characters" in pretty -// much every circumstance, including string indexing. We anticipate the same -// requirement will be imposed here, though it's not a sure thing. -test('\ud808\udf45'); - -test(new String('')); -test(new String('abc')); -test(new String('a \0 \ufffe \ufeff')); -test(new String('\ud808\udf45')); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index c4a39d3bf96..09e1e620c91 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -1811,7 +1811,9 @@ static JSStdName standard_class_names[] = { {js_InitXMLClass, EAGER_ATOM(isXMLName), CLASP(XML)}, #endif +#if JS_HAS_GENERATORS {js_InitIteratorClasses, EAGER_CLASS_ATOM(Iterator), &PropertyIteratorObject::class_}, +#endif /* Typed Arrays */ {js_InitTypedArrayClasses, EAGER_CLASS_ATOM(ArrayBuffer), &ArrayBufferClass}, @@ -4410,8 +4412,11 @@ JS_PUBLIC_API(JSBool) JS_ArrayIterator(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); - Rooted target(cx, args.thisv()); - JSObject *iterobj = ElementIteratorObject::create(cx, target); + JSObject *target = NonNullObject(cx, args.thisv()); + if (!target) + return false; + Rooted iterobj(cx, target); + iterobj = ElementIteratorObject::create(cx, iterobj); if (!iterobj) return false; vp->setObject(*iterobj); diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 62c2f340941..97c2a6f8490 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -3786,7 +3786,7 @@ struct JSClass { * with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was * prevously allowed, but is now an ES5 violation and thus unsupported. */ -#define JSCLASS_GLOBAL_SLOT_COUNT (JSProto_LIMIT * 3 + 9) +#define JSCLASS_GLOBAL_SLOT_COUNT (JSProto_LIMIT * 3 + 8) #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \ (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n))) #define JSCLASS_GLOBAL_FLAGS \ diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index befda54ed68..73344176fa5 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -52,6 +52,31 @@ using namespace mozilla; using namespace js; using namespace js::gc; +Class js::ElementIteratorClass = { + "ElementIterator", + JSCLASS_HAS_RESERVED_SLOTS(ElementIteratorObject::NumSlots), + JS_PropertyStub, /* addProperty */ + JS_PropertyStub, /* delProperty */ + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + NULL, /* finalize */ + NULL, /* checkAccess */ + NULL, /* call */ + NULL, /* construct */ + NULL, /* hasInstance */ + NULL, /* trace */ + { + NULL, /* equality */ + NULL, /* outerObject */ + NULL, /* innerObject */ + NULL, /* iteratorObject */ + NULL /* unused */ + } +}; + static const gc::AllocKind ITERATOR_FINALIZE_KIND = gc::FINALIZE_OBJECT2; void @@ -713,18 +738,6 @@ GetIterator(JSContext *cx, HandleObject obj, unsigned flags, Value *vp) } -JSBool -js_ThrowStopIteration(JSContext *cx) -{ - JS_ASSERT(!cx->isExceptionPending()); - Value v; - if (js_FindClassObject(cx, NULL, JSProto_StopIteration, &v)) - cx->setPendingException(v); - return false; -} - -/*** Iterator objects ****************************************************************************/ - static JSBool Iterator(JSContext *cx, unsigned argc, Value *vp) { @@ -745,6 +758,17 @@ Iterator(JSContext *cx, unsigned argc, Value *vp) return true; } +JSBool +js_ThrowStopIteration(JSContext *cx) +{ + Value v; + + JS_ASSERT(!JS_IsExceptionPending(cx)); + if (js_FindClassObject(cx, NULL, JSProto_StopIteration, &v)) + cx->setPendingException(v); + return JS_FALSE; +} + static JSBool iterator_iterator(JSContext *cx, unsigned argc, Value *vp) { @@ -784,7 +808,7 @@ static JSFunctionSpec iterator_methods[] = { JS_FS_END }; -static JSObject * +JSObject * iterator_iteratorObject(JSContext *cx, HandleObject obj, JSBool keysonly) { return obj; @@ -833,99 +857,6 @@ Class PropertyIteratorObject::class_ = { } }; -const uint32_t CLOSED_INDEX = UINT32_MAX; - -JSObject * -ElementIteratorObject::create(JSContext *cx, Handle target) -{ - GlobalObject *global = GetCurrentGlobal(cx); - Rooted proto(cx, global->getOrCreateElementIteratorPrototype(cx)); - if (!proto) - return NULL; - JSObject *iterobj = NewObjectWithGivenProto(cx, &class_, proto, global); - if (iterobj) { - iterobj->setReservedSlot(TargetSlot, target); - iterobj->setReservedSlot(IndexSlot, Int32Value(0)); - } - return iterobj; -} - -JSBool -ElementIteratorObject::next(JSContext *cx, unsigned argc, Value *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - JSObject *iterobj; - if (!NonGenericMethodGuard(cx, args, next, &class_, &iterobj)) - return false; - if (!iterobj) - return true; - - uint32_t i, length; - Value target = iterobj->getReservedSlot(TargetSlot); - Rooted obj(cx); - - // Get target.length. - if (target.isString()) { - length = uint32_t(target.toString()->length()); - } else { - obj = ValueToObject(cx, target); - if (!obj) - goto close; - if (!js_GetLengthProperty(cx, obj, &length)) - goto close; - } - - // Check target.length. - i = uint32_t(iterobj->getReservedSlot(IndexSlot).toInt32()); - if (i >= length) { - js_ThrowStopIteration(cx); - goto close; - } - - // Get target[i]. - JS_ASSERT(i + 1 > i); - if (target.isString()) { - JSString *c = cx->runtime->staticStrings.getUnitStringForElement(cx, target.toString(), i); - if (!c) - goto close; - vp->setString(c); - } else { - if (!obj->getElement(cx, obj, i, vp)) - goto close; - } - - // On success, bump the index. - iterobj->setReservedSlot(IndexSlot, Int32Value(int32_t(i + 1))); - return true; - - close: - // Close the iterator. The TargetSlot will never be used again, so don't keep a - // reference to it. - iterobj->setReservedSlot(TargetSlot, UndefinedValue()); - iterobj->setReservedSlot(IndexSlot, Int32Value(int32_t(CLOSED_INDEX))); - return false; -} - -Class ElementIteratorObject::class_ = { - "Iterator", - JSCLASS_IMPLEMENTS_BARRIERS | - JSCLASS_HAS_RESERVED_SLOTS(NumSlots), - JS_PropertyStub, /* addProperty */ - JS_PropertyStub, /* delProperty */ - JS_PropertyStub, /* getProperty */ - JS_StrictPropertyStub, /* setProperty */ - JS_EnumerateStub, - JS_ResolveStub, - JS_ConvertStub, - NULL /* finalize */ -}; - -JSFunctionSpec ElementIteratorObject::methods[] = { - JS_FN("next", next, 0, 0), - JS_FS_END -}; - #if JS_HAS_GENERATORS static JSBool CloseGenerator(JSContext *cx, JSObject *genobj); @@ -1168,6 +1099,75 @@ js_SuppressDeletedElements(JSContext *cx, HandleObject obj, uint32_t begin, uint return SuppressDeletedPropertyHelper(cx, obj, IndexRangePredicate(begin, end)); } +const uint32_t CLOSED_INDEX = UINT32_MAX; + +JSObject * +ElementIteratorObject::create(JSContext *cx, HandleObject obj) +{ + JS_ASSERT(obj); + JSObject *iterobj = NewObjectWithGivenProto(cx, &ElementIteratorClass, NULL, obj); + if (iterobj) { + iterobj->setReservedSlot(TargetSlot, ObjectValue(*obj)); + iterobj->setReservedSlot(IndexSlot, Int32Value(0)); + } + return iterobj; +} + +inline uint32_t +ElementIteratorObject::getIndex() const +{ + return uint32_t(getReservedSlot(IndexSlot).toInt32()); +} + +inline JSObject * +ElementIteratorObject::getTargetObject() const +{ + return &getReservedSlot(TargetSlot).toObject(); +} + +inline void +ElementIteratorObject::setIndex(uint32_t index) +{ + setReservedSlot(IndexSlot, Int32Value(int32_t(index))); +} + +bool +ElementIteratorObject::iteratorNext(JSContext *cx, Value *vp) +{ + Rooted self(cx, this); + + uint32_t i, length; + RootedObject obj(cx, getTargetObject()); + if (!js_GetLengthProperty(cx, obj, &length)) + goto error; + + i = self->getIndex(); + if (i >= length) { + self->setIndex(CLOSED_INDEX); + vp->setMagic(JS_NO_ITER_VALUE); + return true; + } + + JS_ASSERT(i + 1 > i); + if (!obj->getElement(cx, obj, i, vp)) + goto error; + + /* On success, bump the index. */ + self->setIndex(i + 1); + return true; + + error: + self->setIndex(CLOSED_INDEX); + return false; +} + +inline js::ElementIteratorObject * +JSObject::asElementIterator() +{ + JS_ASSERT(isElementIterator()); + return static_cast(this); +} + JSBool js_IteratorMore(JSContext *cx, HandleObject iterobj, Value *rval) { @@ -1203,6 +1203,25 @@ js_IteratorMore(JSContext *cx, HandleObject iterobj, Value *rval) return false; if ((ni->flags & JSITER_KEYVALUE) && !NewKeyValuePair(cx, id, *rval, rval)) return false; + } else if (iterobj->isElementIterator()) { + /* + * Like native iterators, element iterators do not have a .next + * method, so this fast path is necessary for correctness. + */ + if (!iterobj->asElementIterator()->iteratorNext(cx, rval)) + return false; + if (rval->isMagic(JS_NO_ITER_VALUE)) { + cx->iterValue.setMagic(JS_NO_ITER_VALUE); + rval->setBoolean(false); + return true; + } + } else if (iterobj->isProxy()) { + if (!Proxy::iteratorNext(cx, iterobj, rval)) + return false; + if (rval->isMagic(JS_NO_ITER_VALUE)) { + rval->setBoolean(false); + return true; + } } else { /* Call the iterator object's .next method. */ if (!GetMethod(cx, iterobj, cx->runtime->atomState.nextAtom, 0, rval)) @@ -1291,8 +1310,6 @@ Class js::StopIterationClass = { NULL /* construct */ }; -/*** Generators **********************************************************************************/ - #if JS_HAS_GENERATORS static void @@ -1687,76 +1704,84 @@ static JSFunctionSpec generator_methods[] = { #endif /* JS_HAS_GENERATORS */ -/* static */ bool -GlobalObject::initIteratorClasses(JSContext *cx, Handle global) +static bool +InitIteratorClass(JSContext *cx, Handle global) { - Rooted iteratorProto(cx); - Value iteratorProtoVal = global->getPrototype(JSProto_Iterator); - if (iteratorProtoVal.isObject()) { - iteratorProto = &iteratorProtoVal.toObject(); - } else { - iteratorProto = global->createBlankPrototype(cx, &PropertyIteratorObject::class_); - if (!iteratorProto) - return false; + Rooted iteratorProto(cx, + global->createBlankPrototype(cx, &PropertyIteratorObject::class_)); + if (!iteratorProto) + return false; - AutoIdVector blank(cx); - NativeIterator *ni = NativeIterator::allocateIterator(cx, 0, blank); - if (!ni) - return false; - ni->init(NULL, 0 /* flags */, 0, 0); + AutoIdVector blank(cx); + NativeIterator *ni = NativeIterator::allocateIterator(cx, 0, blank); + if (!ni) + return false; + ni->init(NULL, 0 /* flags */, 0, 0); - iteratorProto->asPropertyIterator().setNativeIterator(ni); + iteratorProto->asPropertyIterator().setNativeIterator(ni); - Rooted ctor(cx); - ctor = global->createConstructor(cx, Iterator, CLASS_NAME(cx, Iterator), 2); - if (!ctor) - return false; - if (!LinkConstructorAndPrototype(cx, ctor, iteratorProto)) - return false; - if (!DefinePropertiesAndBrand(cx, iteratorProto, NULL, iterator_methods)) - return false; - if (!DefineConstructorAndPrototype(cx, global, JSProto_Iterator, ctor, iteratorProto)) - return false; - } + RootedFunction ctor(cx); + ctor = global->createConstructor(cx, Iterator, CLASS_NAME(cx, Iterator), 2); + if (!ctor) + return false; - Rooted proto(cx); - if (global->getSlot(ELEMENT_ITERATOR_PROTO).isUndefined()) { - Class *cls = &ElementIteratorObject::class_; - proto = global->createBlankPrototypeInheriting(cx, cls, *iteratorProto); - if (!proto || !DefinePropertiesAndBrand(cx, proto, NULL, ElementIteratorObject::methods)) - return false; - global->setReservedSlot(ELEMENT_ITERATOR_PROTO, ObjectValue(*proto)); - } + if (!LinkConstructorAndPrototype(cx, ctor, iteratorProto)) + return false; + if (!DefinePropertiesAndBrand(cx, iteratorProto, NULL, iterator_methods)) + return false; + + return DefineConstructorAndPrototype(cx, global, JSProto_Iterator, ctor, iteratorProto); +} + +/* static */ bool +GlobalObject::initGeneratorClass(JSContext *cx, Handle global) +{ #if JS_HAS_GENERATORS - if (global->getSlot(GENERATOR_PROTO).isUndefined()) { - proto = global->createBlankPrototype(cx, &GeneratorClass); - if (!proto || !DefinePropertiesAndBrand(cx, proto, NULL, generator_methods)) - return false; - global->setReservedSlot(GENERATOR_PROTO, ObjectValue(*proto)); - } + RootedObject proto(cx, global->createBlankPrototype(cx, &GeneratorClass)); + if (!proto || !DefinePropertiesAndBrand(cx, proto, NULL, generator_methods)) + return false; + global->setReservedSlot(GENERATOR_PROTO, ObjectValue(*proto)); #endif - - if (global->getPrototype(JSProto_StopIteration).isUndefined()) { - proto = global->createBlankPrototype(cx, &StopIterationClass); - if (!proto || !proto->freeze(cx)) - return false; - - /* This should use a non-JSProtoKey'd slot, but this is easier for now. */ - if (!DefineConstructorAndPrototype(cx, global, JSProto_StopIteration, proto, proto)) - return false; - - MarkStandardClassInitializedNoProto(global, &StopIterationClass); - } - return true; } +static JSObject * +InitStopIterationClass(JSContext *cx, Handle global) +{ + RootedObject proto(cx, global->createBlankPrototype(cx, &StopIterationClass)); + if (!proto || !proto->freeze(cx)) + return NULL; + + /* This should use a non-JSProtoKey'd slot, but this is easier for now. */ + if (!DefineConstructorAndPrototype(cx, global, JSProto_StopIteration, proto, proto)) + return NULL; + + MarkStandardClassInitializedNoProto(global, &StopIterationClass); + + return proto; +} + JSObject * js_InitIteratorClasses(JSContext *cx, JSObject *obj) { + JS_ASSERT(obj->isNative()); + Rooted global(cx, &obj->asGlobal()); - if (!GlobalObject::initIteratorClasses(cx, global)) + + /* + * Bail if Iterator has already been initialized. We test for Iterator + * rather than for StopIteration because if js_InitIteratorClasses recurs, + * as happens when the StopIteration object is frozen, initializing the + * Iterator class a second time will assert. + */ + JSObject *iter; + if (!js_GetClassObject(cx, global, JSProto_Iterator, &iter)) return NULL; - return global->getIteratorPrototype(); + if (iter) + return iter; + + if (!InitIteratorClass(cx, global) || !GlobalObject::initGeneratorClass(cx, global)) + return NULL; + return InitStopIterationClass(cx, global); } diff --git a/js/src/jsiter.h b/js/src/jsiter.h index 1ddda36d151..6d5c0deee3c 100644 --- a/js/src/jsiter.h +++ b/js/src/jsiter.h @@ -83,32 +83,66 @@ class PropertyIteratorObject : public JSObject static void finalize(FreeOp *fop, JSObject *obj); }; -/* - * Array iterators are roughly like this: - * - * Array.prototype.iterator = function iterator() { - * for (var i = 0; i < (this.length >>> 0); i++) - * yield this[i]; - * } - * - * However they are not generators. They are a different class. The semantics - * of Array iterators will be given in the eventual ES6 spec in full detail. - */ class ElementIteratorObject : public JSObject { public: - static JSObject *create(JSContext *cx, Handle target); - static Class class_; - static JSFunctionSpec methods[]; - - private: enum { TargetSlot, IndexSlot, NumSlots }; - static JSBool next(JSContext *cx, unsigned argc, Value *vp); + static JSObject *create(JSContext *cx, HandleObject target); + + inline uint32_t getIndex() const; + inline void setIndex(uint32_t index); + inline JSObject *getTargetObject() const; + + /* + Array iterators are like this: + + Array.prototype[iterate] = function () { + for (var i = 0; i < (this.length >>> 0); i++) { + var desc = Object.getOwnPropertyDescriptor(this, i); + yield desc === undefined ? undefined : this[i]; + } + } + + This has the following implications: + + - Array iterators are generic; Array.prototype[iterate] can be transferred to + any other object to create iterators over it. + + - The next() method of an Array iterator is non-reentrant. Trying to reenter, + e.g. by using it on an object with a length getter that calls it.next() on + the same iterator, causes a TypeError. + + - The iterator fetches obj.length every time its next() method is called. + + - The iterator converts obj.length to a whole number using ToUint32. As a + consequence the iterator can't go on forever; it can yield at most 2^32-1 + values. Then i will be 0xffffffff, and no possible length value will be + greater than that. + + - The iterator does not skip "array holes". When it encounters a hole, it + yields undefined. + + - The iterator never consults the prototype chain. + + - If an element has a getter which throws, the exception is propagated, and + the iterator is closed (that is, all future calls to next() will simply + throw StopIteration). + + Note that if next() were reentrant, even more details of its inner + workings would be observable. + */ + + /* + * If there are any more elements to visit, store the value of the next + * element in *vp, increment the index, and return true. If not, call + * vp->setMagic(JS_NO_ITER_VALUE) and return true. Return false on error. + */ + bool iteratorNext(JSContext *cx, Value *vp); }; bool diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index 5909308dda6..04ceff5dd62 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -71,8 +71,7 @@ class GlobalObject : public JSObject /* One-off properties stored after slots for built-ins. */ static const unsigned THROWTYPEERROR = STANDARD_CLASS_SLOTS; - static const unsigned ELEMENT_ITERATOR_PROTO = THROWTYPEERROR + 1; - static const unsigned GENERATOR_PROTO = ELEMENT_ITERATOR_PROTO + 1; + static const unsigned GENERATOR_PROTO = THROWTYPEERROR + 1; static const unsigned REGEXP_STATICS = GENERATOR_PROTO + 1; static const unsigned FUNCTION_NS = REGEXP_STATICS + 1; static const unsigned RUNTIME_CODEGEN_ENABLED = FUNCTION_NS + 1; @@ -268,28 +267,14 @@ class GlobalObject : public JSObject return &self->getPrototype(key).toObject(); } - JSObject *getIteratorPrototype() { - return &getPrototype(JSProto_Iterator).toObject(); - } - - private: - JSObject *getOrCreateIteratorSubclassPrototype(JSContext *cx, unsigned slot) { - Value v = getSlotRef(slot); + JSObject *getOrCreateGeneratorPrototype(JSContext *cx) { + Value v = getSlotRef(GENERATOR_PROTO); if (v.isObject()) return &v.toObject(); Rooted self(cx, this); - if (!initIteratorClasses(cx, self)) + if (!js_InitIteratorClasses(cx, this)) return NULL; - return &self->getSlot(slot).toObject(); - } - - public: - JSObject *getOrCreateElementIteratorPrototype(JSContext *cx) { - return getOrCreateIteratorSubclassPrototype(cx, ELEMENT_ITERATOR_PROTO); - } - - JSObject *getOrCreateGeneratorPrototype(JSContext *cx) { - return getOrCreateIteratorSubclassPrototype(cx, GENERATOR_PROTO); + return &self->getSlot(GENERATOR_PROTO).toObject(); } inline RegExpStatics *getRegExpStatics() const; @@ -314,9 +299,7 @@ class GlobalObject : public JSObject bool getFunctionNamespace(JSContext *cx, Value *vp); - // Implemented in jsiter.cpp. - static bool initIteratorClasses(JSContext *cx, Handle global); - + static bool initGeneratorClass(JSContext *cx, Handle global); static bool initStandardClasses(JSContext *cx, Handle global); typedef js::Vector DebuggerVector;