1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sw=4 et tw=78:
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
17 * The Original Code is Mozilla Communicator client code, released
20 * The Initial Developer of the Original Code is
21 * Netscape Communications Corporation.
22 * Portions created by the Initial Developer are Copyright (C) 1998
23 * the Initial Developer. All Rights Reserved.
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
42 * JavaScript iterators.
44 #include <string.h> /* for memcpy */
53 #include "jsbuiltins.h"
55 #include "jsversion.h"
59 #include "jshashtable.h"
70 #include "jsstaticcheck.h"
74 #if JS_HAS_XML_SUPPORT
78 #include "jscntxtinlines.h"
79 #include "jsinterpinlines.h"
80 #include "jsobjinlines.h"
81 #include "jsstrinlines.h"
84 using namespace js::gc
;
86 static void iterator_finalize(JSContext
*cx
, JSObject
*obj
);
87 static void iterator_trace(JSTracer
*trc
, JSObject
*obj
);
88 static JSObject
*iterator_iterator(JSContext
*cx
, JSObject
*obj
, JSBool keysonly
);
90 Class js_IteratorClass
= {
92 JSCLASS_HAS_PRIVATE
| JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator
) |
93 JSCLASS_MARK_IS_TRACE
,
94 PropertyStub
, /* addProperty */
95 PropertyStub
, /* delProperty */
96 PropertyStub
, /* getProperty */
97 PropertyStub
, /* setProperty */
103 NULL
, /* checkAccess */
105 NULL
, /* construct */
106 NULL
, /* xdrObject */
107 NULL
, /* hasInstance */
108 JS_CLASS_TRACE(iterator_trace
),
111 NULL
, /* outerObject */
112 NULL
, /* innerObject */
119 NativeIterator::mark(JSTracer
*trc
)
121 MarkIdRange(trc
, begin(), end(), "props");
123 MarkObject(trc
, *obj
, "obj");
127 iterator_finalize(JSContext
*cx
, JSObject
*obj
)
129 JS_ASSERT(obj
->getClass() == &js_IteratorClass
);
131 NativeIterator
*ni
= obj
->getNativeIterator();
134 obj
->setNativeIterator(NULL
);
139 iterator_trace(JSTracer
*trc
, JSObject
*obj
)
141 NativeIterator
*ni
= obj
->getNativeIterator();
147 struct IdHashPolicy
{
149 static HashNumber
hash(jsid id
) {
150 return JSID_BITS(id
);
152 static bool match(jsid id1
, jsid id2
) {
157 typedef HashSet
<jsid
, IdHashPolicy
, ContextAllocPolicy
> IdSet
;
160 NewKeyValuePair(JSContext
*cx
, jsid id
, const Value
&val
, Value
*rval
)
162 Value vec
[2] = { IdToValue(id
), val
};
163 AutoArrayRooter
tvr(cx
, JS_ARRAY_LENGTH(vec
), vec
);
165 JSObject
*aobj
= NewDenseCopiedArray(cx
, 2, vec
);
168 rval
->setObject(*aobj
);
173 Enumerate(JSContext
*cx
, JSObject
*obj
, JSObject
*pobj
, jsid id
,
174 bool enumerable
, bool sharedPermanent
, uintN flags
, IdSet
& ht
,
177 IdSet::AddPtr p
= ht
.lookupForAdd(id
);
178 JS_ASSERT_IF(obj
== pobj
&& !obj
->isProxy(), !p
);
180 /* If we've already seen this, we definitely won't add it. */
181 if (JS_UNLIKELY(!!p
))
185 * It's not necessary to add properties to the hash table at the end of the
186 * prototype chain -- but a proxy might return duplicated properties, so
187 * always add for them.
189 if ((pobj
->getProto() || pobj
->isProxy()) && !ht
.add(p
, id
))
192 if (JS_UNLIKELY(flags
& JSITER_OWNONLY
)) {
194 * Shared-permanent hack: If this property is shared permanent
195 * and pobj and obj have the same class, then treat it as an own
196 * property of obj, even if pobj != obj. (But see bug 575997.)
198 * Omit the magic __proto__ property so that JS code can use
199 * Object.getOwnPropertyNames without worrying about it.
201 if (!pobj
->getProto() && id
== ATOM_TO_JSID(cx
->runtime
->atomState
.protoAtom
))
203 if (pobj
!= obj
&& !(sharedPermanent
&& pobj
->getClass() == obj
->getClass()))
207 if (enumerable
|| (flags
& JSITER_HIDDEN
))
208 return props
->append(id
);
214 EnumerateNativeProperties(JSContext
*cx
, JSObject
*obj
, JSObject
*pobj
, uintN flags
, IdSet
&ht
,
217 size_t initialLength
= props
->length();
219 /* Collect all unique properties from this object's scope. */
220 for (Shape::Range r
= pobj
->lastProperty()->all(); !r
.empty(); r
.popFront()) {
221 const Shape
&shape
= r
.front();
223 if (!JSID_IS_DEFAULT_XML_NAMESPACE(shape
.id
) &&
225 !Enumerate(cx
, obj
, pobj
, shape
.id
, shape
.enumerable(),
226 shape
.isSharedPermanent(), flags
, ht
, props
))
232 Reverse(props
->begin() + initialLength
, props
->end());
237 EnumerateDenseArrayProperties(JSContext
*cx
, JSObject
*obj
, JSObject
*pobj
, uintN flags
,
238 IdSet
&ht
, AutoIdVector
*props
)
240 if (!Enumerate(cx
, obj
, pobj
, ATOM_TO_JSID(cx
->runtime
->atomState
.lengthAtom
), false, true,
245 if (pobj
->getArrayLength() > 0) {
246 size_t capacity
= pobj
->getDenseArrayCapacity();
247 Value
*vp
= pobj
->getDenseArrayElements();
248 for (size_t i
= 0; i
< capacity
; ++i
, ++vp
) {
249 if (!vp
->isMagic(JS_ARRAY_HOLE
)) {
250 /* Dense arrays never get so large that i would not fit into an integer id. */
251 if (!Enumerate(cx
, obj
, pobj
, INT_TO_JSID(i
), true, false, flags
, ht
, props
))
261 Snapshot(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
*props
)
264 * FIXME: Bug 575997 - We won't need to initialize this hash table if
265 * (flags & JSITER_OWNONLY) when we eliminate inheritance of
266 * shared-permanent properties as own properties.
272 JSObject
*pobj
= obj
;
274 Class
*clasp
= pobj
->getClass();
275 if (pobj
->isNative() &&
276 !pobj
->getOps()->enumerate
&&
277 !(clasp
->flags
& JSCLASS_NEW_ENUMERATE
)) {
278 if (!clasp
->enumerate(cx
, pobj
))
280 if (!EnumerateNativeProperties(cx
, obj
, pobj
, flags
, ht
, props
))
282 } else if (pobj
->isDenseArray()) {
283 if (!EnumerateDenseArrayProperties(cx
, obj
, pobj
, flags
, ht
, props
))
286 if (pobj
->isProxy()) {
287 AutoIdVector
proxyProps(cx
);
288 if (flags
& JSITER_OWNONLY
) {
289 if (flags
& JSITER_HIDDEN
) {
290 if (!JSProxy::getOwnPropertyNames(cx
, pobj
, proxyProps
))
293 if (!JSProxy::keys(cx
, pobj
, proxyProps
))
297 if (!JSProxy::enumerate(cx
, pobj
, proxyProps
))
300 for (size_t n
= 0, len
= proxyProps
.length(); n
< len
; n
++) {
301 if (!Enumerate(cx
, obj
, pobj
, proxyProps
[n
], true, false, flags
, ht
, props
))
304 /* Proxy objects enumerate the prototype on their own, so we are done here. */
308 JSIterateOp op
= (flags
& JSITER_HIDDEN
) ? JSENUMERATE_INIT_ALL
: JSENUMERATE_INIT
;
309 if (!pobj
->enumerate(cx
, op
, &state
, NULL
))
311 if (state
.isMagic(JS_NATIVE_ENUMERATE
)) {
312 if (!EnumerateNativeProperties(cx
, obj
, pobj
, flags
, ht
, props
))
317 if (!pobj
->enumerate(cx
, JSENUMERATE_NEXT
, &state
, &id
))
321 if (!Enumerate(cx
, obj
, pobj
, id
, true, false, flags
, ht
, props
))
327 if (JS_UNLIKELY(pobj
->isXML()))
329 } while ((pobj
= pobj
->getProto()) != NULL
);
337 VectorToIdArray(JSContext
*cx
, AutoIdVector
&props
, JSIdArray
**idap
)
339 JS_STATIC_ASSERT(sizeof(JSIdArray
) > sizeof(jsid
));
340 size_t len
= props
.length();
341 size_t idsz
= len
* sizeof(jsid
);
342 size_t sz
= (sizeof(JSIdArray
) - sizeof(jsid
)) + idsz
;
343 JSIdArray
*ida
= static_cast<JSIdArray
*>(cx
->malloc(sz
));
347 ida
->length
= static_cast<jsint
>(len
);
348 memcpy(ida
->vector
, props
.begin(), idsz
);
354 GetPropertyNames(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
*props
)
356 return Snapshot(cx
, obj
, flags
& (JSITER_OWNONLY
| JSITER_HIDDEN
), props
);
362 GetCustomIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, Value
*vp
)
364 /* Check whether we have a valid __iterator__ method. */
365 JSAtom
*atom
= cx
->runtime
->atomState
.iteratorAtom
;
366 if (!js_GetMethod(cx
, obj
, ATOM_TO_JSID(atom
), JSGET_NO_METHOD_BARRIER
, vp
))
369 /* If there is no custom __iterator__ method, we are done here. */
370 if (!vp
->isObject()) {
375 /* Otherwise call it and return that object. */
377 Value arg
= BooleanValue((flags
& JSITER_FOREACH
) == 0);
378 if (!ExternalInvoke(cx
, ObjectValue(*obj
), *vp
, 1, &arg
, vp
))
380 if (vp
->isPrimitive()) {
382 * We are always coming from js_ValueToIterator, and we are no longer on
383 * trace, so the object we are iterating over is on top of the stack (-1).
385 JSAutoByteString bytes
;
386 if (!js_AtomToPrintableString(cx
, atom
, &bytes
))
388 js_ReportValueError2(cx
, JSMSG_BAD_TRAP_RETURN_VALUE
,
389 -1, ObjectValue(*obj
), NULL
, bytes
.ptr());
395 template <typename T
>
397 Compare(T
*a
, T
*b
, size_t c
)
399 size_t n
= (c
+ size_t(7)) / size_t(8);
401 case 0: do { if (*a
++ != *b
++) return false;
402 case 7: if (*a
++ != *b
++) return false;
403 case 6: if (*a
++ != *b
++) return false;
404 case 5: if (*a
++ != *b
++) return false;
405 case 4: if (*a
++ != *b
++) return false;
406 case 3: if (*a
++ != *b
++) return false;
407 case 2: if (*a
++ != *b
++) return false;
408 case 1: if (*a
++ != *b
++) return false;
414 static inline JSObject
*
415 NewIteratorObject(JSContext
*cx
, uintN flags
)
417 if (flags
& JSITER_ENUMERATE
) {
419 * Non-escaping native enumerator objects do not need map, proto, or
420 * parent. However, code in jstracer.cpp and elsewhere may find such a
421 * native enumerator object via the stack and (as for all objects that
422 * are not stillborn, with the exception of "NoSuchMethod" internal
423 * helper objects) expect it to have a non-null map pointer, so we
424 * share an empty Enumerator scope in the runtime.
426 JSObject
*obj
= js_NewGCObject(cx
, FINALIZE_OBJECT0
);
429 obj
->init(cx
, &js_IteratorClass
, NULL
, NULL
, NULL
, false);
430 obj
->setMap(cx
->runtime
->emptyEnumeratorShape
);
434 return NewBuiltinClassInstance(cx
, &js_IteratorClass
);
438 NativeIterator::allocateIterator(JSContext
*cx
, uint32 slength
, const AutoIdVector
&props
)
440 size_t plength
= props
.length();
441 NativeIterator
*ni
= (NativeIterator
*)
442 cx
->malloc(sizeof(NativeIterator
) + plength
* sizeof(jsid
) + slength
* sizeof(uint32
));
445 ni
->props_array
= ni
->props_cursor
= (jsid
*) (ni
+ 1);
446 ni
->props_end
= (jsid
*)ni
->props_array
+ plength
;
448 memcpy(ni
->props_array
, props
.begin(), plength
* sizeof(jsid
));
453 NativeIterator::init(JSObject
*obj
, uintN flags
, uint32 slength
, uint32 key
)
457 this->shapes_array
= (uint32
*) this->props_end
;
458 this->shapes_length
= slength
;
459 this->shapes_key
= key
;
463 RegisterEnumerator(JSContext
*cx
, JSObject
*iterobj
, NativeIterator
*ni
)
465 /* Register non-escaping native enumerators (for-in) with the current context. */
466 if (ni
->flags
& JSITER_ENUMERATE
) {
467 ni
->next
= cx
->enumerators
;
468 cx
->enumerators
= iterobj
;
470 JS_ASSERT(!(ni
->flags
& JSITER_ACTIVE
));
471 ni
->flags
|= JSITER_ACTIVE
;
476 VectorToKeyIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
&keys
,
477 uint32 slength
, uint32 key
, Value
*vp
)
479 JS_ASSERT(!(flags
& JSITER_FOREACH
));
481 JSObject
*iterobj
= NewIteratorObject(cx
, flags
);
485 NativeIterator
*ni
= NativeIterator::allocateIterator(cx
, slength
, keys
);
488 ni
->init(obj
, flags
, slength
, key
);
492 * Fill in the shape array from scratch. We can't use the array that was
493 * computed for the cache lookup earlier, as constructing iterobj could
494 * have triggered a shape-regenerating GC. Don't bother with regenerating
495 * the shape key; if such a GC *does* occur, we can only get hits through
496 * the one-slot lastNativeIterator cache.
498 JSObject
*pobj
= obj
;
501 ni
->shapes_array
[ind
++] = pobj
->shape();
502 pobj
= pobj
->getProto();
504 JS_ASSERT(ind
== slength
);
507 iterobj
->setNativeIterator(ni
);
508 vp
->setObject(*iterobj
);
510 RegisterEnumerator(cx
, iterobj
, ni
);
517 VectorToKeyIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
&props
, Value
*vp
)
519 return VectorToKeyIterator(cx
, obj
, flags
, props
, 0, 0, vp
);
523 VectorToValueIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
&keys
,
526 JS_ASSERT(flags
& JSITER_FOREACH
);
528 JSObject
*iterobj
= NewIteratorObject(cx
, flags
);
532 NativeIterator
*ni
= NativeIterator::allocateIterator(cx
, 0, keys
);
535 ni
->init(obj
, flags
, 0, 0);
537 iterobj
->setNativeIterator(ni
);
538 vp
->setObject(*iterobj
);
540 RegisterEnumerator(cx
, iterobj
, ni
);
545 EnumeratedIdVectorToIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
&props
, Value
*vp
)
547 if (!(flags
& JSITER_FOREACH
))
548 return VectorToKeyIterator(cx
, obj
, flags
, props
, vp
);
550 return VectorToValueIterator(cx
, obj
, flags
, props
, vp
);
553 typedef Vector
<uint32
, 8> ShapeVector
;
556 UpdateNativeIterator(NativeIterator
*ni
, JSObject
*obj
)
558 // Update the object for which the native iterator is associated, so
559 // SuppressDeletedPropertyHelper will recognize the iterator as a match.
564 GetIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, Value
*vp
)
566 Vector
<uint32
, 8> shapes(cx
);
569 bool keysOnly
= (flags
== JSITER_ENUMERATE
);
572 /* Enumerate Iterator.prototype directly. */
573 JSIteratorOp op
= obj
->getClass()->ext
.iteratorObject
;
574 if (op
&& (obj
->getClass() != &js_IteratorClass
|| obj
->getNativeIterator())) {
575 JSObject
*iterobj
= op(cx
, obj
, !(flags
& JSITER_FOREACH
));
578 vp
->setObject(*iterobj
);
584 * Check to see if this is the same as the most recent object which
585 * was iterated over. We don't explicitly check for shapeless
586 * objects here, as they are not inserted into the cache and
587 * will result in a miss.
589 JSObject
*last
= cx
->compartment
->nativeIterCache
.last
;
590 JSObject
*proto
= obj
->getProto();
592 NativeIterator
*lastni
= last
->getNativeIterator();
593 if (!(lastni
->flags
& JSITER_ACTIVE
) &&
595 obj
->shape() == lastni
->shapes_array
[0] &&
596 proto
&& proto
->isNative() &&
597 proto
->shape() == lastni
->shapes_array
[1] &&
598 !proto
->getProto()) {
599 vp
->setObject(*last
);
600 UpdateNativeIterator(lastni
, obj
);
601 RegisterEnumerator(cx
, last
, lastni
);
607 * The iterator object for JSITER_ENUMERATE never escapes, so we
608 * don't care for the proper parent/proto to be set. This also
609 * allows us to re-use a previous iterator object that is not
612 JSObject
*pobj
= obj
;
614 if (!pobj
->isNative() ||
615 obj
->getOps()->enumerate
||
616 pobj
->getClass()->enumerate
!= JS_EnumerateStub
) {
620 uint32 shape
= pobj
->shape();
621 key
= (key
+ (key
<< 16)) ^ shape
;
622 if (!shapes
.append(shape
))
624 pobj
= pobj
->getProto();
627 JSObject
*iterobj
= cx
->compartment
->nativeIterCache
.get(key
);
629 NativeIterator
*ni
= iterobj
->getNativeIterator();
630 if (!(ni
->flags
& JSITER_ACTIVE
) &&
631 ni
->shapes_key
== key
&&
632 ni
->shapes_length
== shapes
.length() &&
633 Compare(ni
->shapes_array
, shapes
.begin(), ni
->shapes_length
)) {
634 vp
->setObject(*iterobj
);
636 UpdateNativeIterator(ni
, obj
);
637 RegisterEnumerator(cx
, iterobj
, ni
);
638 if (shapes
.length() == 2)
639 cx
->compartment
->nativeIterCache
.last
= iterobj
;
647 return JSProxy::iterate(cx
, obj
, flags
, vp
);
648 if (!GetCustomIterator(cx
, obj
, flags
, vp
))
650 if (!vp
->isUndefined())
654 /* NB: for (var p in null) succeeds by iterating over no properties. */
656 AutoIdVector
keys(cx
);
657 if (flags
& JSITER_FOREACH
) {
658 if (JS_LIKELY(obj
!= NULL
) && !Snapshot(cx
, obj
, flags
, &keys
))
660 JS_ASSERT(shapes
.empty());
661 if (!VectorToValueIterator(cx
, obj
, flags
, keys
, vp
))
664 if (JS_LIKELY(obj
!= NULL
) && !Snapshot(cx
, obj
, flags
, &keys
))
666 if (!VectorToKeyIterator(cx
, obj
, flags
, keys
, shapes
.length(), key
, vp
))
670 JSObject
*iterobj
= &vp
->toObject();
672 /* Cache the iterator object if possible. */
674 cx
->compartment
->nativeIterCache
.set(key
, iterobj
);
676 if (shapes
.length() == 2)
677 cx
->compartment
->nativeIterCache
.last
= iterobj
;
684 iterator_iterator(JSContext
*cx
, JSObject
*obj
, JSBool keysonly
)
690 Iterator(JSContext
*cx
, uintN argc
, Value
*vp
)
692 Value
*argv
= JS_ARGV(cx
, vp
);
693 bool keyonly
= argc
>= 2 ? js_ValueToBoolean(argv
[1]) : false;
694 uintN flags
= JSITER_OWNONLY
| (keyonly
? 0 : (JSITER_FOREACH
| JSITER_KEYVALUE
));
695 *vp
= argc
>= 1 ? argv
[0] : UndefinedValue();
696 return js_ValueToIterator(cx
, flags
, vp
);
700 js_ThrowStopIteration(JSContext
*cx
)
704 JS_ASSERT(!JS_IsExceptionPending(cx
));
705 if (js_FindClassObject(cx
, NULL
, JSProto_StopIteration
, &v
))
706 cx
->setPendingException(v
);
711 iterator_next(JSContext
*cx
, uintN argc
, Value
*vp
)
713 JSObject
*obj
= ToObject(cx
, &vp
[1]);
714 if (!obj
|| !InstanceOf(cx
, obj
, &js_IteratorClass
, vp
+ 2))
717 if (!js_IteratorMore(cx
, obj
, vp
))
719 if (!vp
->toBoolean()) {
720 js_ThrowStopIteration(cx
);
723 return js_IteratorNext(cx
, obj
, vp
);
726 #define JSPROP_ROPERM (JSPROP_READONLY | JSPROP_PERMANENT)
728 static JSFunctionSpec iterator_methods
[] = {
729 JS_FN(js_next_str
, iterator_next
, 0,JSPROP_ROPERM
),
734 * Call ToObject(v).__iterator__(keyonly) if ToObject(v).__iterator__ exists.
735 * Otherwise construct the default iterator.
737 JS_FRIEND_API(JSBool
)
738 js_ValueToIterator(JSContext
*cx
, uintN flags
, Value
*vp
)
740 /* JSITER_KEYVALUE must always come with JSITER_FOREACH */
741 JS_ASSERT_IF(flags
& JSITER_KEYVALUE
, flags
& JSITER_FOREACH
);
744 * Make sure the more/next state machine doesn't get stuck. A value might be
745 * left in iterValue when a trace is left due to an operation time-out after
746 * JSOP_MOREITER but before the value is picked up by FOR*.
748 cx
->iterValue
.setMagic(JS_NO_ITER_VALUE
);
751 if (vp
->isObject()) {
753 obj
= &vp
->toObject();
756 * Enumerating over null and undefined gives an empty enumerator.
757 * This is contrary to ECMA-262 9.9 ToObject, invoked from step 3 of
758 * the first production in 12.6.4 and step 4 of the second production,
759 * but it's "web JS" compatible. ES5 fixed for-in to match this de-facto
762 if ((flags
& JSITER_ENUMERATE
)) {
763 if (!js_ValueToObjectOrNull(cx
, *vp
, &obj
))
767 obj
= js_ValueToNonNullObject(cx
, *vp
);
773 return GetIterator(cx
, obj
, flags
, vp
);
776 #if JS_HAS_GENERATORS
777 static JS_REQUIRES_STACK JSBool
778 CloseGenerator(JSContext
*cx
, JSObject
*genobj
);
781 JS_FRIEND_API(JSBool
)
782 js_CloseIterator(JSContext
*cx
, JSObject
*obj
)
784 cx
->iterValue
.setMagic(JS_NO_ITER_VALUE
);
786 Class
*clasp
= obj
->getClass();
787 if (clasp
== &js_IteratorClass
) {
788 /* Remove enumerators from the active list, which is a stack. */
789 NativeIterator
*ni
= obj
->getNativeIterator();
791 if (ni
->flags
& JSITER_ENUMERATE
) {
792 JS_ASSERT(cx
->enumerators
== obj
);
793 cx
->enumerators
= ni
->next
;
795 JS_ASSERT(ni
->flags
& JSITER_ACTIVE
);
796 ni
->flags
&= ~JSITER_ACTIVE
;
799 * Reset the enumerator; it may still be in the cached iterators
800 * for this thread, and can be reused.
802 ni
->props_cursor
= ni
->props_array
;
805 #if JS_HAS_GENERATORS
806 else if (clasp
== &js_GeneratorClass
) {
807 return CloseGenerator(cx
, obj
);
814 * Suppress enumeration of deleted properties. This function must be called
815 * when a property is deleted and there might be active enumerators.
817 * We maintain a list of active non-escaping for-in enumerators. To suppress
818 * a property, we check whether each active enumerator contains the (obj, id)
819 * pair and has not yet enumerated |id|. If so, and |id| is the next property,
820 * we simply advance the cursor. Otherwise, we delete |id| from the list.
822 * We do not suppress enumeration of a property deleted along an object's
823 * prototype chain. Only direct deletions on the object are handled.
825 * This function can suppress multiple properties at once. The |predicate|
826 * argument is an object which can be called on an id and returns true or
827 * false. It also must have a method |matchesAtMostOne| which allows us to
828 * stop searching after the first deletion if true.
830 template<typename IdPredicate
>
832 SuppressDeletedPropertyHelper(JSContext
*cx
, JSObject
*obj
, IdPredicate predicate
)
834 JSObject
*iterobj
= cx
->enumerators
;
837 NativeIterator
*ni
= iterobj
->getNativeIterator();
838 /* This only works for identified surpressed keys, not values. */
839 if (ni
->isKeyIter() && ni
->obj
== obj
&& ni
->props_cursor
< ni
->props_end
) {
840 /* Check whether id is still to come. */
841 jsid
*props_cursor
= ni
->current();
842 jsid
*props_end
= ni
->end();
843 for (jsid
*idp
= props_cursor
; idp
< props_end
; ++idp
) {
844 if (predicate(*idp
)) {
846 * Check whether another property along the prototype chain
847 * became visible as a result of this deletion.
849 if (obj
->getProto()) {
850 AutoObjectRooter
proto(cx
, obj
->getProto());
851 AutoObjectRooter
obj2(cx
);
853 if (!proto
.object()->lookupProperty(cx
, *idp
, obj2
.addr(), &prop
))
857 if (obj2
.object()->isNative())
858 attrs
= ((Shape
*) prop
)->attributes();
859 else if (!obj2
.object()->getAttributes(cx
, *idp
, &attrs
))
862 if (attrs
& JSPROP_ENUMERATE
)
868 * If lookupProperty or getAttributes above removed a property from
871 if (props_end
!= ni
->props_end
|| props_cursor
!= ni
->props_cursor
)
875 * No property along the prototype chain stepped in to take the
876 * property's place, so go ahead and delete id from the list.
877 * If it is the next property to be enumerated, just skip it.
879 if (idp
== props_cursor
) {
882 memmove(idp
, idp
+ 1, (props_end
- (idp
+ 1)) * sizeof(jsid
));
883 ni
->props_end
= ni
->end() - 1;
885 if (predicate
.matchesAtMostOne())
895 class SingleIdPredicate
{
898 SingleIdPredicate(jsid id
) : id(id
) {}
900 bool operator()(jsid id
) { return id
== this->id
; }
901 bool matchesAtMostOne() { return true; }
905 js_SuppressDeletedProperty(JSContext
*cx
, JSObject
*obj
, jsid id
)
907 id
= js_CheckForStringIndex(id
);
908 return SuppressDeletedPropertyHelper(cx
, obj
, SingleIdPredicate(id
));
911 class IndexRangePredicate
{
914 IndexRangePredicate(jsint begin
, jsint end
) : begin(begin
), end(end
) {}
916 bool operator()(jsid id
) {
917 return JSID_IS_INT(id
) && begin
<= JSID_TO_INT(id
) && JSID_TO_INT(id
) < end
;
919 bool matchesAtMostOne() { return false; }
923 js_SuppressDeletedIndexProperties(JSContext
*cx
, JSObject
*obj
, jsint begin
, jsint end
)
925 return SuppressDeletedPropertyHelper(cx
, obj
, IndexRangePredicate(begin
, end
));
929 js_IteratorMore(JSContext
*cx
, JSObject
*iterobj
, Value
*rval
)
931 /* Fast path for native iterators */
932 NativeIterator
*ni
= NULL
;
933 if (iterobj
->getClass() == &js_IteratorClass
) {
934 /* Key iterators are handled by fast-paths. */
935 ni
= iterobj
->getNativeIterator();
936 bool more
= ni
->props_cursor
< ni
->props_end
;
937 if (ni
->isKeyIter() || !more
) {
938 rval
->setBoolean(more
);
943 /* We might still have a pending value. */
944 if (!cx
->iterValue
.isMagic(JS_NO_ITER_VALUE
)) {
945 rval
->setBoolean(true);
949 /* Fetch and cache the next value from the iterator. */
951 jsid id
= ATOM_TO_JSID(cx
->runtime
->atomState
.nextAtom
);
952 if (!js_GetMethod(cx
, iterobj
, id
, JSGET_METHOD_BARRIER
, rval
))
954 if (!ExternalInvoke(cx
, ObjectValue(*iterobj
), *rval
, 0, NULL
, rval
)) {
955 /* Check for StopIteration. */
956 if (!cx
->isExceptionPending() || !js_ValueIsStopIteration(cx
->getPendingException()))
959 cx
->clearPendingException();
960 cx
->iterValue
.setMagic(JS_NO_ITER_VALUE
);
961 rval
->setBoolean(false);
965 JS_ASSERT(!ni
->isKeyIter());
966 jsid id
= *ni
->current();
968 if (!ni
->obj
->getProperty(cx
, id
, rval
))
970 if ((ni
->flags
& JSITER_KEYVALUE
) && !NewKeyValuePair(cx
, id
, *rval
, rval
))
974 /* Cache the value returned by iterobj.next() so js_IteratorNext() can find it. */
975 JS_ASSERT(!rval
->isMagic(JS_NO_ITER_VALUE
));
976 cx
->iterValue
= *rval
;
977 rval
->setBoolean(true);
982 js_IteratorNext(JSContext
*cx
, JSObject
*iterobj
, Value
*rval
)
984 /* Fast path for native iterators */
985 if (iterobj
->getClass() == &js_IteratorClass
) {
987 * Implement next directly as all the methods of the native iterator are
988 * read-only and permanent.
990 NativeIterator
*ni
= iterobj
->getNativeIterator();
991 if (ni
->isKeyIter()) {
992 JS_ASSERT(ni
->props_cursor
< ni
->props_end
);
993 *rval
= IdToValue(*ni
->current());
996 if (rval
->isString())
1001 if (rval
->isInt32() && (jsuint(i
= rval
->toInt32()) < INT_STRING_LIMIT
)) {
1002 str
= JSString::intString(i
);
1004 str
= js_ValueToString(cx
, *rval
);
1009 rval
->setString(str
);
1014 JS_ASSERT(!cx
->iterValue
.isMagic(JS_NO_ITER_VALUE
));
1015 *rval
= cx
->iterValue
;
1016 cx
->iterValue
.setMagic(JS_NO_ITER_VALUE
);
1022 stopiter_hasInstance(JSContext
*cx
, JSObject
*obj
, const Value
*v
, JSBool
*bp
)
1024 *bp
= js_ValueIsStopIteration(*v
);
1028 Class js_StopIterationClass
= {
1029 js_StopIteration_str
,
1030 JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration
) |
1031 JSCLASS_FREEZE_PROTO
,
1032 PropertyStub
, /* addProperty */
1033 PropertyStub
, /* delProperty */
1034 PropertyStub
, /* getProperty */
1035 PropertyStub
, /* setProperty */
1039 NULL
, /* finalize */
1040 NULL
, /* reserved0 */
1041 NULL
, /* checkAccess */
1043 NULL
, /* construct */
1044 NULL
, /* xdrObject */
1045 stopiter_hasInstance
1048 #if JS_HAS_GENERATORS
1051 generator_finalize(JSContext
*cx
, JSObject
*obj
)
1053 JSGenerator
*gen
= (JSGenerator
*) obj
->getPrivate();
1058 * gen is open when a script has not called its close method while
1059 * explicitly manipulating it.
1061 JS_ASSERT(gen
->state
== JSGEN_NEWBORN
||
1062 gen
->state
== JSGEN_CLOSED
||
1063 gen
->state
== JSGEN_OPEN
);
1068 generator_trace(JSTracer
*trc
, JSObject
*obj
)
1070 JSGenerator
*gen
= (JSGenerator
*) obj
->getPrivate();
1075 * Do not mark if the generator is running; the contents may be trash and
1076 * will be replaced when the generator stops.
1078 if (gen
->state
== JSGEN_RUNNING
|| gen
->state
== JSGEN_CLOSING
)
1081 JSStackFrame
*fp
= gen
->floatingFrame();
1082 JS_ASSERT(gen
->liveFrame() == fp
);
1083 MarkValueRange(trc
, gen
->floatingStack
, fp
->formalArgsEnd(), "generator slots");
1084 js_TraceStackFrame(trc
, fp
);
1085 MarkValueRange(trc
, fp
->slots(), gen
->regs
.sp
, "generator slots");
1088 Class js_GeneratorClass
= {
1090 JSCLASS_HAS_PRIVATE
| JSCLASS_HAS_CACHED_PROTO(JSProto_Generator
) |
1091 JSCLASS_IS_ANONYMOUS
| JSCLASS_MARK_IS_TRACE
,
1092 PropertyStub
, /* addProperty */
1093 PropertyStub
, /* delProperty */
1094 PropertyStub
, /* getProperty */
1095 PropertyStub
, /* setProperty */
1100 NULL
, /* reserved */
1101 NULL
, /* checkAccess */
1103 NULL
, /* construct */
1104 NULL
, /* xdrObject */
1105 NULL
, /* hasInstance */
1106 JS_CLASS_TRACE(generator_trace
),
1108 NULL
, /* equality */
1109 NULL
, /* outerObject */
1110 NULL
, /* innerObject */
1117 RebaseRegsFromTo(JSFrameRegs
*regs
, JSStackFrame
*from
, JSStackFrame
*to
)
1120 regs
->sp
= to
->slots() + (regs
->sp
- from
->slots());
1124 * Called from the JSOP_GENERATOR case in the interpreter, with fp referring
1125 * to the frame by which the generator function was activated. Create a new
1126 * JSGenerator object, which contains its own JSStackFrame that we populate
1127 * from *fp. We know that upon return, the JSOP_GENERATOR opcode will return
1128 * from the activation in fp, so we can steal away fp->callobj and fp->argsobj
1129 * if they are non-null.
1131 JS_REQUIRES_STACK JSObject
*
1132 js_NewGenerator(JSContext
*cx
)
1134 JSObject
*obj
= NewBuiltinClassInstance(cx
, &js_GeneratorClass
);
1138 JSStackFrame
*stackfp
= cx
->fp();
1139 JS_ASSERT(stackfp
->base() == cx
->regs
->sp
);
1140 JS_ASSERT(stackfp
->actualArgs() <= stackfp
->formalArgs());
1142 /* Load and compute stack slot counts. */
1143 Value
*stackvp
= stackfp
->actualArgs() - 2;
1144 uintN vplen
= stackfp
->formalArgsEnd() - stackvp
;
1146 /* Compute JSGenerator size. */
1147 uintN nbytes
= sizeof(JSGenerator
) +
1148 (-1 + /* one Value included in JSGenerator */
1150 VALUES_PER_STACK_FRAME
+
1151 stackfp
->numSlots()) * sizeof(Value
);
1153 JSGenerator
*gen
= (JSGenerator
*) cx
->malloc(nbytes
);
1157 /* Cut up floatingStack space. */
1158 Value
*genvp
= gen
->floatingStack
;
1159 JSStackFrame
*genfp
= reinterpret_cast<JSStackFrame
*>(genvp
+ vplen
);
1161 /* Initialize JSGenerator. */
1163 gen
->state
= JSGEN_NEWBORN
;
1164 gen
->enumerators
= NULL
;
1165 gen
->floating
= genfp
;
1167 /* Initialize regs stored in generator. */
1168 gen
->regs
= *cx
->regs
;
1169 RebaseRegsFromTo(&gen
->regs
, stackfp
, genfp
);
1171 /* Copy frame off the stack. */
1172 genfp
->stealFrameAndSlots(genvp
, stackfp
, stackvp
, cx
->regs
->sp
);
1173 genfp
->initFloatingGenerator();
1175 obj
->setPrivate(gen
);
1180 js_FloatingFrameToGenerator(JSStackFrame
*fp
)
1182 JS_ASSERT(fp
->isGeneratorFrame() && fp
->isFloatingGenerator());
1183 char *floatingStackp
= (char *)(fp
->actualArgs() - 2);
1184 char *p
= floatingStackp
- offsetof(JSGenerator
, floatingStack
);
1185 return reinterpret_cast<JSGenerator
*>(p
);
1188 typedef enum JSGeneratorOp
{
1196 * Start newborn or restart yielding generator and perform the requested
1197 * operation inside its frame.
1199 static JS_REQUIRES_STACK JSBool
1200 SendToGenerator(JSContext
*cx
, JSGeneratorOp op
, JSObject
*obj
,
1201 JSGenerator
*gen
, const Value
&arg
)
1203 if (gen
->state
== JSGEN_RUNNING
|| gen
->state
== JSGEN_CLOSING
) {
1204 js_ReportValueError(cx
, JSMSG_NESTING_GENERATOR
,
1205 JSDVG_SEARCH_STACK
, ObjectOrNullValue(obj
),
1206 JS_GetFunctionId(gen
->floatingFrame()->fun()));
1210 /* Check for OOM errors here, where we can fail easily. */
1211 if (!cx
->ensureGeneratorStackSpace())
1214 JS_ASSERT(gen
->state
== JSGEN_NEWBORN
|| gen
->state
== JSGEN_OPEN
);
1218 if (gen
->state
== JSGEN_OPEN
) {
1220 * Store the argument to send as the result of the yield
1223 gen
->regs
.sp
[-1] = arg
;
1225 gen
->state
= JSGEN_RUNNING
;
1229 cx
->setPendingException(arg
);
1230 gen
->state
= JSGEN_RUNNING
;
1234 JS_ASSERT(op
== JSGENOP_CLOSE
);
1235 cx
->setPendingException(MagicValue(JS_GENERATOR_CLOSING
));
1236 gen
->state
= JSGEN_CLOSING
;
1240 JSStackFrame
*genfp
= gen
->floatingFrame();
1241 Value
*genvp
= gen
->floatingStack
;
1242 uintN vplen
= genfp
->formalArgsEnd() - genvp
;
1244 JSStackFrame
*stackfp
;
1249 * Get a pointer to new frame/slots. This memory is not "claimed", so
1250 * the code before pushExecuteFrame must not reenter the interpreter.
1252 GeneratorFrameGuard frame
;
1253 if (!cx
->stack().getGeneratorFrame(cx
, vplen
, genfp
->numSlots(), &frame
)) {
1254 gen
->state
= JSGEN_CLOSED
;
1257 stackfp
= frame
.fp();
1258 stackvp
= frame
.vp();
1260 /* Copy frame onto the stack. */
1261 stackfp
->stealFrameAndSlots(stackvp
, genfp
, genvp
, gen
->regs
.sp
);
1262 stackfp
->resetGeneratorPrev(cx
);
1263 stackfp
->unsetFloatingGenerator();
1264 RebaseRegsFromTo(&gen
->regs
, genfp
, stackfp
);
1265 MUST_FLOW_THROUGH("restore");
1267 /* Officially push frame. frame's destructor pops. */
1268 cx
->stack().pushGeneratorFrame(cx
, &gen
->regs
, &frame
);
1270 cx
->enterGenerator(gen
); /* OOM check above. */
1271 JSObject
*enumerators
= cx
->enumerators
;
1272 cx
->enumerators
= gen
->enumerators
;
1274 ok
= RunScript(cx
, stackfp
->script(), stackfp
);
1276 gen
->enumerators
= cx
->enumerators
;
1277 cx
->enumerators
= enumerators
;
1278 cx
->leaveGenerator(gen
);
1281 * Copy the stack frame and rebase the regs, but not before popping
1282 * the stack, since cx->regs == &gen->regs.
1284 genfp
->stealFrameAndSlots(genvp
, stackfp
, stackvp
, gen
->regs
.sp
);
1285 genfp
->setFloatingGenerator();
1287 MUST_FLOW_LABEL(restore
)
1288 RebaseRegsFromTo(&gen
->regs
, stackfp
, genfp
);
1290 if (gen
->floatingFrame()->isYielding()) {
1291 /* Yield cannot fail, throw or be called on closing. */
1293 JS_ASSERT(!cx
->isExceptionPending());
1294 JS_ASSERT(gen
->state
== JSGEN_RUNNING
);
1295 JS_ASSERT(op
!= JSGENOP_CLOSE
);
1296 genfp
->clearYielding();
1297 gen
->state
= JSGEN_OPEN
;
1301 genfp
->clearReturnValue();
1302 gen
->state
= JSGEN_CLOSED
;
1304 /* Returned, explicitly or by falling off the end. */
1305 if (op
== JSGENOP_CLOSE
)
1307 return js_ThrowStopIteration(cx
);
1311 * An error, silent termination by operation callback or an exception.
1312 * Propagate the condition to the caller.
1317 static JS_REQUIRES_STACK JSBool
1318 CloseGenerator(JSContext
*cx
, JSObject
*obj
)
1320 JS_ASSERT(obj
->getClass() == &js_GeneratorClass
);
1322 JSGenerator
*gen
= (JSGenerator
*) obj
->getPrivate();
1324 /* Generator prototype object. */
1328 if (gen
->state
== JSGEN_CLOSED
)
1331 return SendToGenerator(cx
, JSGENOP_CLOSE
, obj
, gen
, UndefinedValue());
1335 * Common subroutine of generator_(next|send|throw|close) methods.
1338 generator_op(JSContext
*cx
, JSGeneratorOp op
, Value
*vp
, uintN argc
)
1342 JSObject
*obj
= ToObject(cx
, &vp
[1]);
1343 if (!obj
|| !InstanceOf(cx
, obj
, &js_GeneratorClass
, vp
+ 2))
1346 JSGenerator
*gen
= (JSGenerator
*) obj
->getPrivate();
1348 /* This happens when obj is the generator prototype. See bug 352885. */
1349 goto closed_generator
;
1352 if (gen
->state
== JSGEN_NEWBORN
) {
1359 if (argc
>= 1 && !vp
[2].isUndefined()) {
1360 js_ReportValueError(cx
, JSMSG_BAD_GENERATOR_SEND
,
1361 JSDVG_SEARCH_STACK
, vp
[2], NULL
);
1367 JS_ASSERT(op
== JSGENOP_CLOSE
);
1368 gen
->state
= JSGEN_CLOSED
;
1371 } else if (gen
->state
== JSGEN_CLOSED
) {
1376 return js_ThrowStopIteration(cx
);
1378 cx
->setPendingException(argc
>= 1 ? vp
[2] : UndefinedValue());
1381 JS_ASSERT(op
== JSGENOP_CLOSE
);
1386 bool undef
= ((op
== JSGENOP_SEND
|| op
== JSGENOP_THROW
) && argc
!= 0);
1387 if (!SendToGenerator(cx
, op
, obj
, gen
, undef
? vp
[2] : UndefinedValue()))
1389 *vp
= gen
->floatingFrame()->returnValue();
1394 generator_send(JSContext
*cx
, uintN argc
, Value
*vp
)
1396 return generator_op(cx
, JSGENOP_SEND
, vp
, argc
);
1400 generator_next(JSContext
*cx
, uintN argc
, Value
*vp
)
1402 return generator_op(cx
, JSGENOP_NEXT
, vp
, argc
);
1406 generator_throw(JSContext
*cx
, uintN argc
, Value
*vp
)
1408 return generator_op(cx
, JSGENOP_THROW
, vp
, argc
);
1412 generator_close(JSContext
*cx
, uintN argc
, Value
*vp
)
1414 return generator_op(cx
, JSGENOP_CLOSE
, vp
, argc
);
1417 static JSFunctionSpec generator_methods
[] = {
1418 JS_FN(js_next_str
, generator_next
, 0,JSPROP_ROPERM
),
1419 JS_FN(js_send_str
, generator_send
, 1,JSPROP_ROPERM
),
1420 JS_FN(js_throw_str
, generator_throw
, 1,JSPROP_ROPERM
),
1421 JS_FN(js_close_str
, generator_close
, 0,JSPROP_ROPERM
),
1425 #endif /* JS_HAS_GENERATORS */
1428 js_InitIteratorClasses(JSContext
*cx
, JSObject
*obj
)
1430 JSObject
*proto
, *stop
;
1432 /* Idempotency required: we initialize several things, possibly lazily. */
1433 if (!js_GetClassObject(cx
, obj
, JSProto_StopIteration
, &stop
))
1438 proto
= js_InitClass(cx
, obj
, NULL
, &js_IteratorClass
, Iterator
, 2,
1439 NULL
, iterator_methods
, NULL
, NULL
);
1443 #if JS_HAS_GENERATORS
1444 /* Initialize the generator internals if configured. */
1445 if (!js_InitClass(cx
, obj
, NULL
, &js_GeneratorClass
, NULL
, 0,
1446 NULL
, generator_methods
, NULL
, NULL
)) {
1451 return js_InitClass(cx
, obj
, NULL
, &js_StopIterationClass
, NULL
, 0,
1452 NULL
, NULL
, NULL
, NULL
);