mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 725907 - for-of improvements, part 4: Add .next() method to iterator objects and make for-of call it. r=bhackett.
This commit is contained in:
parent
7e92dcab82
commit
23d6c5d5a9
11
js/src/jit-test/tests/for-of/array-iterator-changing.js
Normal file
11
js/src/jit-test/tests/for-of/array-iterator-changing.js
Normal file
@ -0,0 +1,11 @@
|
||||
// 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);
|
27
js/src/jit-test/tests/for-of/array-iterator-generic.js
Normal file
27
js/src/jit-test/tests/for-of/array-iterator-generic.js
Normal file
@ -0,0 +1,27 @@
|
||||
// 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});
|
12
js/src/jit-test/tests/for-of/array-iterator-growing-1.js
Normal file
12
js/src/jit-test/tests/for-of/array-iterator-growing-1.js
Normal file
@ -0,0 +1,12 @@
|
||||
// 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);
|
9
js/src/jit-test/tests/for-of/array-iterator-null.js
Normal file
9
js/src/jit-test/tests/for-of/array-iterator-null.js
Normal file
@ -0,0 +1,9 @@
|
||||
// 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);
|
||||
}
|
23
js/src/jit-test/tests/for-of/array-iterator-proxy.js
Normal file
23
js/src/jit-test/tests/for-of/array-iterator-proxy.js
Normal file
@ -0,0 +1,23 @@
|
||||
// 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");
|
9
js/src/jit-test/tests/for-of/array-iterator-shrinking.js
Normal file
9
js/src/jit-test/tests/for-of/array-iterator-shrinking.js
Normal file
@ -0,0 +1,9 @@
|
||||
// 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);
|
22
js/src/jit-test/tests/for-of/array-iterator-surfaces-2.js
Normal file
22
js/src/jit-test/tests/for-of/array-iterator-surfaces-2.js
Normal file
@ -0,0 +1,22 @@
|
||||
// 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));
|
5
js/src/jit-test/tests/for-of/next-1.js
Normal file
5
js/src/jit-test/tests/for-of/next-1.js
Normal file
@ -0,0 +1,5 @@
|
||||
// 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);
|
6
js/src/jit-test/tests/for-of/next-2.js
Normal file
6
js/src/jit-test/tests/for-of/next-2.js
Normal file
@ -0,0 +1,6 @@
|
||||
// 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);
|
8
js/src/jit-test/tests/for-of/next-3.js
Normal file
8
js/src/jit-test/tests/for-of/next-3.js
Normal file
@ -0,0 +1,8 @@
|
||||
// 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);
|
7
js/src/jit-test/tests/for-of/next-surfaces.js
Normal file
7
js/src/jit-test/tests/for-of/next-surfaces.js
Normal file
@ -0,0 +1,7 @@
|
||||
// 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);
|
6
js/src/jit-test/tests/for-of/semantics-06.js
Normal file
6
js/src/jit-test/tests/for-of/semantics-06.js
Normal file
@ -0,0 +1,6 @@
|
||||
// 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);
|
15
js/src/jit-test/tests/for-of/semantics-07.js
Normal file
15
js/src/jit-test/tests/for-of/semantics-07.js
Normal file
@ -0,0 +1,15 @@
|
||||
// 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.');
|
7
js/src/jit-test/tests/for-of/semantics-08.js
Normal file
7
js/src/jit-test/tests/for-of/semantics-08.js
Normal file
@ -0,0 +1,7 @@
|
||||
// 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';
|
25
js/src/jit-test/tests/for-of/semantics-09.js
Normal file
25
js/src/jit-test/tests/for-of/semantics-09.js
Normal file
@ -0,0 +1,25 @@
|
||||
// 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');
|
31
js/src/jit-test/tests/for-of/semantics-10.js
Normal file
31
js/src/jit-test/tests/for-of/semantics-10.js
Normal file
@ -0,0 +1,31 @@
|
||||
// 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);
|
41
js/src/jit-test/tests/for-of/semantics-11.js
Normal file
41
js/src/jit-test/tests/for-of/semantics-11.js
Normal file
@ -0,0 +1,41 @@
|
||||
// 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');
|
14
js/src/jit-test/tests/for-of/string-iterator-generic.js
Normal file
14
js/src/jit-test/tests/for-of/string-iterator-generic.js
Normal file
@ -0,0 +1,14 @@
|
||||
// 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'}));
|
26
js/src/jit-test/tests/for-of/strings.js
Normal file
26
js/src/jit-test/tests/for-of/strings.js
Normal file
@ -0,0 +1,26 @@
|
||||
// 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'));
|
@ -1865,9 +1865,7 @@ 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},
|
||||
@ -4517,11 +4515,8 @@ JS_PUBLIC_API(JSBool)
|
||||
JS_ArrayIterator(JSContext *cx, unsigned argc, jsval *vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
JSObject *target = NonNullObject(cx, args.thisv());
|
||||
if (!target)
|
||||
return false;
|
||||
Rooted<JSObject*> iterobj(cx, target);
|
||||
iterobj = ElementIteratorObject::create(cx, iterobj);
|
||||
Rooted<Value> target(cx, args.thisv());
|
||||
JSObject *iterobj = ElementIteratorObject::create(cx, target);
|
||||
if (!iterobj)
|
||||
return false;
|
||||
vp->setObject(*iterobj);
|
||||
|
@ -4036,7 +4036,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 + 20)
|
||||
#define JSCLASS_GLOBAL_SLOT_COUNT (JSProto_LIMIT * 3 + 21)
|
||||
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
|
||||
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
|
||||
#define JSCLASS_GLOBAL_FLAGS \
|
||||
|
@ -51,31 +51,6 @@ 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
|
||||
@ -737,6 +712,18 @@ GetIterator(JSContext *cx, HandleObject obj, unsigned flags, Value *vp)
|
||||
|
||||
}
|
||||
|
||||
JSBool
|
||||
js_ThrowStopIteration(JSContext *cx)
|
||||
{
|
||||
JS_ASSERT(!JS_IsExceptionPending(cx));
|
||||
RootedValue v(cx);
|
||||
if (js_FindClassObject(cx, NullPtr(), JSProto_StopIteration, &v))
|
||||
cx->setPendingException(v);
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
/*** Iterator objects ****************************************************************************/
|
||||
|
||||
static JSBool
|
||||
Iterator(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
@ -757,16 +744,6 @@ Iterator(JSContext *cx, unsigned argc, Value *vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
JSBool
|
||||
js_ThrowStopIteration(JSContext *cx)
|
||||
{
|
||||
JS_ASSERT(!JS_IsExceptionPending(cx));
|
||||
RootedValue v(cx);
|
||||
if (js_FindClassObject(cx, NullPtr(), JSProto_StopIteration, &v))
|
||||
cx->setPendingException(v);
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
static bool
|
||||
IsIterator(const Value &v)
|
||||
{
|
||||
@ -812,7 +789,7 @@ static JSFunctionSpec iterator_methods[] = {
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
JSObject *
|
||||
static JSObject *
|
||||
iterator_iteratorObject(JSContext *cx, HandleObject obj, JSBool keysonly)
|
||||
{
|
||||
return obj;
|
||||
@ -861,6 +838,105 @@ Class PropertyIteratorObject::class_ = {
|
||||
}
|
||||
};
|
||||
|
||||
const uint32_t CLOSED_INDEX = UINT32_MAX;
|
||||
|
||||
JSObject *
|
||||
ElementIteratorObject::create(JSContext *cx, Handle<Value> target)
|
||||
{
|
||||
GlobalObject *global = GetCurrentGlobal(cx);
|
||||
Rooted<JSObject*> proto(cx, global->getOrCreateElementIteratorPrototype(cx));
|
||||
if (!proto)
|
||||
return NULL;
|
||||
JSObject *iterobj = NewObjectWithGivenProto(cx, &ElementIteratorClass, proto, global);
|
||||
if (iterobj) {
|
||||
iterobj->setReservedSlot(TargetSlot, target);
|
||||
iterobj->setReservedSlot(IndexSlot, Int32Value(0));
|
||||
}
|
||||
return iterobj;
|
||||
}
|
||||
|
||||
static bool
|
||||
IsElementIterator(const Value &v)
|
||||
{
|
||||
return v.isObject() && v.toObject().isElementIterator();
|
||||
}
|
||||
|
||||
JSBool
|
||||
ElementIteratorObject::next(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod(cx, IsElementIterator, next_impl, args);
|
||||
}
|
||||
|
||||
bool
|
||||
ElementIteratorObject::next_impl(JSContext *cx, CallArgs args)
|
||||
{
|
||||
JSObject *iterobj = &args.thisv().toObject();
|
||||
uint32_t i, length;
|
||||
Value target = iterobj->getReservedSlot(TargetSlot);
|
||||
Rooted<JSObject*> 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;
|
||||
args.rval().setString(c);
|
||||
} else {
|
||||
if (!obj->getElement(cx, obj, i, &args.rval()))
|
||||
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 js::ElementIteratorClass = {
|
||||
"Iterator",
|
||||
JSCLASS_IMPLEMENTS_BARRIERS |
|
||||
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 */
|
||||
};
|
||||
|
||||
JSFunctionSpec ElementIteratorObject::methods[] = {
|
||||
JS_FN("next", next, 0, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
#if JS_HAS_GENERATORS
|
||||
static JSBool
|
||||
CloseGenerator(JSContext *cx, JSObject *genobj);
|
||||
@ -1103,75 +1179,6 @@ 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<ElementIteratorObject*> 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<js::ElementIteratorObject *>(this);
|
||||
}
|
||||
|
||||
JSBool
|
||||
js_IteratorMore(JSContext *cx, HandleObject iterobj, Value *rval)
|
||||
{
|
||||
@ -1207,25 +1214,6 @@ 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))
|
||||
@ -1314,6 +1302,8 @@ Class js::StopIterationClass = {
|
||||
NULL /* construct */
|
||||
};
|
||||
|
||||
/*** Generators **********************************************************************************/
|
||||
|
||||
#if JS_HAS_GENERATORS
|
||||
|
||||
static void
|
||||
@ -1756,83 +1746,75 @@ static JSFunctionSpec generator_methods[] = {
|
||||
|
||||
#endif /* JS_HAS_GENERATORS */
|
||||
|
||||
static bool
|
||||
InitIteratorClass(JSContext *cx, Handle<GlobalObject*> global)
|
||||
{
|
||||
Rooted<JSObject*> 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);
|
||||
|
||||
iteratorProto->asPropertyIterator().setNativeIterator(ni);
|
||||
|
||||
RootedFunction ctor(cx, 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;
|
||||
|
||||
return DefineConstructorAndPrototype(cx, global, JSProto_Iterator, ctor, iteratorProto);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
GlobalObject::initGeneratorClass(JSContext *cx, Handle<GlobalObject*> global)
|
||||
GlobalObject::initIteratorClasses(JSContext *cx, Handle<GlobalObject *> global)
|
||||
{
|
||||
Rooted<JSObject*> 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;
|
||||
|
||||
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);
|
||||
|
||||
Rooted<JSFunction*> ctor(cx, 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;
|
||||
}
|
||||
|
||||
Rooted<JSObject*> proto(cx);
|
||||
if (global->getSlot(ELEMENT_ITERATOR_PROTO).isUndefined()) {
|
||||
Class *cls = &ElementIteratorClass;
|
||||
proto = global->createBlankPrototypeInheriting(cx, cls, *iteratorProto);
|
||||
if (!proto || !DefinePropertiesAndBrand(cx, proto, NULL, ElementIteratorObject::methods))
|
||||
return false;
|
||||
global->setReservedSlot(ELEMENT_ITERATOR_PROTO, ObjectValue(*proto));
|
||||
}
|
||||
|
||||
#if JS_HAS_GENERATORS
|
||||
RootedObject proto(cx, global->createBlankPrototype(cx, &GeneratorClass));
|
||||
if (!proto || !DefinePropertiesAndBrand(cx, proto, NULL, generator_methods))
|
||||
return false;
|
||||
global->setReservedSlot(GENERATOR_PROTO, ObjectValue(*proto));
|
||||
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));
|
||||
}
|
||||
#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<GlobalObject*> 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<GlobalObject*> global(cx, &obj->asGlobal());
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
RootedObject iter(cx);
|
||||
if (!js_GetClassObject(cx, global, JSProto_Iterator, &iter))
|
||||
if (!GlobalObject::initIteratorClasses(cx, global))
|
||||
return NULL;
|
||||
if (iter)
|
||||
return iter;
|
||||
|
||||
if (!InitIteratorClass(cx, global) || !GlobalObject::initGeneratorClass(cx, global))
|
||||
return NULL;
|
||||
return InitStopIterationClass(cx, global);
|
||||
return global->getIteratorPrototype();
|
||||
}
|
||||
|
@ -83,66 +83,31 @@ 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<Value> target);
|
||||
static JSFunctionSpec methods[];
|
||||
|
||||
enum {
|
||||
TargetSlot,
|
||||
IndexSlot,
|
||||
NumSlots
|
||||
};
|
||||
|
||||
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);
|
||||
static JSBool next(JSContext *cx, unsigned argc, Value *vp);
|
||||
static bool next_impl(JSContext *cx, JS::CallArgs args);
|
||||
};
|
||||
|
||||
bool
|
||||
|
@ -91,7 +91,8 @@ class GlobalObject : public JSObject
|
||||
static const unsigned FROM_BUFFER_UINT8CLAMPED = FROM_BUFFER_FLOAT64 + 1;
|
||||
|
||||
/* One-off properties stored after slots for built-ins. */
|
||||
static const unsigned GENERATOR_PROTO = FROM_BUFFER_UINT8CLAMPED + 1;
|
||||
static const unsigned ELEMENT_ITERATOR_PROTO = FROM_BUFFER_UINT8CLAMPED + 1;
|
||||
static const unsigned GENERATOR_PROTO = ELEMENT_ITERATOR_PROTO + 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;
|
||||
@ -310,14 +311,28 @@ class GlobalObject : public JSObject
|
||||
return &self->getPrototype(key).toObject();
|
||||
}
|
||||
|
||||
JSObject *getOrCreateGeneratorPrototype(JSContext *cx) {
|
||||
Value v = getSlotRef(GENERATOR_PROTO);
|
||||
JSObject *getIteratorPrototype() {
|
||||
return &getPrototype(JSProto_Iterator).toObject();
|
||||
}
|
||||
|
||||
private:
|
||||
JSObject *getOrCreateIteratorSubclassPrototype(JSContext *cx, unsigned slot) {
|
||||
Value v = getSlotRef(slot);
|
||||
if (v.isObject())
|
||||
return &v.toObject();
|
||||
Rooted<GlobalObject*> self(cx, this);
|
||||
if (!js_InitIteratorClasses(cx, this))
|
||||
if (!initIteratorClasses(cx, self))
|
||||
return NULL;
|
||||
return &self->getSlot(GENERATOR_PROTO).toObject();
|
||||
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);
|
||||
}
|
||||
|
||||
JSObject *getOrCreateDataViewPrototype(JSContext *cx) {
|
||||
@ -369,7 +384,9 @@ class GlobalObject : public JSObject
|
||||
|
||||
bool getFunctionNamespace(JSContext *cx, Value *vp);
|
||||
|
||||
static bool initGeneratorClass(JSContext *cx, Handle<GlobalObject*> global);
|
||||
// Implemented in jsiter.cpp.
|
||||
static bool initIteratorClasses(JSContext *cx, Handle<GlobalObject*> global);
|
||||
|
||||
static bool initStandardClasses(JSContext *cx, Handle<GlobalObject*> global);
|
||||
|
||||
typedef js::Vector<js::Debugger *, 0, js::SystemAllocPolicy> DebuggerVector;
|
||||
|
Loading…
Reference in New Issue
Block a user