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 */
114 NULL
/* wrappedObject */
119 NativeIterator::mark(JSTracer
*trc
)
122 MarkIdRange(trc
, beginKey(), endKey(), "props");
124 MarkValueRange(trc
, beginValue(), endValue(), "props");
126 MarkObject(trc
, *obj
, "obj");
130 iterator_finalize(JSContext
*cx
, JSObject
*obj
)
132 JS_ASSERT(obj
->getClass() == &js_IteratorClass
);
134 NativeIterator
*ni
= obj
->getNativeIterator();
137 obj
->setNativeIterator(NULL
);
142 iterator_trace(JSTracer
*trc
, JSObject
*obj
)
144 NativeIterator
*ni
= obj
->getNativeIterator();
150 struct IdHashPolicy
{
152 static HashNumber
hash(jsid id
) {
153 return JSID_BITS(id
);
155 static bool match(jsid id1
, jsid id2
) {
160 typedef HashSet
<jsid
, IdHashPolicy
, ContextAllocPolicy
> IdSet
;
163 NewKeyValuePair(JSContext
*cx
, jsid id
, const Value
&val
, Value
*rval
)
165 Value vec
[2] = { IdToValue(id
), val
};
166 AutoArrayRooter
tvr(cx
, JS_ARRAY_LENGTH(vec
), vec
);
168 JSObject
*aobj
= js_NewArrayObject(cx
, 2, vec
);
171 rval
->setObject(*aobj
);
175 struct KeyEnumeration
177 typedef AutoIdVector ResultVector
;
179 static JS_ALWAYS_INLINE
bool
180 append(JSContext
*, AutoIdVector
&keys
, JSObject
*, jsid id
, uintN flags
)
182 JS_ASSERT((flags
& JSITER_FOREACH
) == 0);
183 return keys
.append(id
);
187 struct ValueEnumeration
189 typedef AutoValueVector ResultVector
;
191 static JS_ALWAYS_INLINE
bool
192 append(JSContext
*cx
, AutoValueVector
&vals
, JSObject
*obj
, jsid id
, uintN flags
)
194 JS_ASSERT(flags
& JSITER_FOREACH
);
199 /* Do the lookup on the original object instead of the prototype. */
200 Value
*vp
= vals
.end() - 1;
201 if (!obj
->getProperty(cx
, id
, vp
))
203 if ((flags
& JSITER_KEYVALUE
) && !NewKeyValuePair(cx
, id
, *vp
, vp
))
210 template <class EnumPolicy
>
212 Enumerate(JSContext
*cx
, JSObject
*obj
, JSObject
*pobj
, jsid id
,
213 bool enumerable
, bool sharedPermanent
, uintN flags
, IdSet
& ht
,
214 typename
EnumPolicy::ResultVector
*props
)
216 IdSet::AddPtr p
= ht
.lookupForAdd(id
);
217 JS_ASSERT_IF(obj
== pobj
&& !obj
->isProxy(), !p
);
219 /* If we've already seen this, we definitely won't add it. */
220 if (JS_UNLIKELY(!!p
))
224 * It's not necessary to add properties to the hash table at the end of the
225 * prototype chain -- but a proxy might return duplicated properties, so
226 * always add for them.
228 if ((pobj
->getProto() || pobj
->isProxy()) && !ht
.add(p
, id
))
231 if (JS_UNLIKELY(flags
& JSITER_OWNONLY
)) {
233 * Shared-permanent hack: If this property is shared permanent
234 * and pobj and obj have the same class, then treat it as an own
235 * property of obj, even if pobj != obj. (But see bug 575997.)
237 * Omit the magic __proto__ property so that JS code can use
238 * Object.getOwnPropertyNames without worrying about it.
240 if (!pobj
->getProto() && id
== ATOM_TO_JSID(cx
->runtime
->atomState
.protoAtom
))
242 if (pobj
!= obj
&& !(sharedPermanent
&& pobj
->getClass() == obj
->getClass()))
246 if (enumerable
|| (flags
& JSITER_HIDDEN
))
247 return EnumPolicy::append(cx
, *props
, obj
, id
, flags
);
252 template <class EnumPolicy
>
254 EnumerateNativeProperties(JSContext
*cx
, JSObject
*obj
, JSObject
*pobj
, uintN flags
, IdSet
&ht
,
255 typename
EnumPolicy::ResultVector
*props
)
257 JS_LOCK_OBJ(cx
, pobj
);
259 size_t initialLength
= props
->length();
261 /* Collect all unique properties from this object's scope. */
262 for (Shape::Range r
= pobj
->lastProperty()->all(); !r
.empty(); r
.popFront()) {
263 const Shape
&shape
= r
.front();
265 if (!JSID_IS_DEFAULT_XML_NAMESPACE(shape
.id
) &&
267 !Enumerate
<EnumPolicy
>(cx
, obj
, pobj
, shape
.id
, shape
.enumerable(),
268 shape
.isSharedPermanent(), flags
, ht
, props
))
274 Reverse(props
->begin() + initialLength
, props
->end());
276 JS_UNLOCK_OBJ(cx
, pobj
);
280 template <class EnumPolicy
>
282 EnumerateDenseArrayProperties(JSContext
*cx
, JSObject
*obj
, JSObject
*pobj
, uintN flags
,
283 IdSet
&ht
, typename
EnumPolicy::ResultVector
*props
)
285 if (!Enumerate
<EnumPolicy
>(cx
, obj
, pobj
, ATOM_TO_JSID(cx
->runtime
->atomState
.lengthAtom
), false, true,
290 if (pobj
->getArrayLength() > 0) {
291 size_t capacity
= pobj
->getDenseArrayCapacity();
292 Value
*vp
= pobj
->dslots
;
293 for (size_t i
= 0; i
< capacity
; ++i
, ++vp
) {
294 if (!vp
->isMagic(JS_ARRAY_HOLE
)) {
295 /* Dense arrays never get so large that i would not fit into an integer id. */
296 if (!Enumerate
<EnumPolicy
>(cx
, obj
, pobj
, INT_TO_JSID(i
), true, false, flags
, ht
, props
))
305 template <class EnumPolicy
>
307 Snapshot(JSContext
*cx
, JSObject
*obj
, uintN flags
, typename
EnumPolicy::ResultVector
*props
)
310 * FIXME: Bug 575997 - We won't need to initialize this hash table if
311 * (flags & JSITER_OWNONLY) when we eliminate inheritance of
312 * shared-permanent properties as own properties.
318 JSObject
*pobj
= obj
;
320 Class
*clasp
= pobj
->getClass();
321 if (pobj
->isNative() &&
322 !pobj
->getOps()->enumerate
&&
323 !(clasp
->flags
& JSCLASS_NEW_ENUMERATE
)) {
324 if (!clasp
->enumerate(cx
, pobj
))
326 if (!EnumerateNativeProperties
<EnumPolicy
>(cx
, obj
, pobj
, flags
, ht
, props
))
328 } else if (pobj
->isDenseArray()) {
329 if (!EnumerateDenseArrayProperties
<EnumPolicy
>(cx
, obj
, pobj
, flags
, ht
, props
))
332 if (pobj
->isProxy()) {
333 AutoIdVector
proxyProps(cx
);
334 if (flags
& JSITER_OWNONLY
) {
335 if (!JSProxy::enumerateOwn(cx
, pobj
, proxyProps
))
338 if (!JSProxy::enumerate(cx
, pobj
, proxyProps
))
341 for (size_t n
= 0, len
= proxyProps
.length(); n
< len
; n
++) {
342 if (!Enumerate
<EnumPolicy
>(cx
, obj
, pobj
, proxyProps
[n
], true, false, flags
, ht
, props
))
345 /* Proxy objects enumerate the prototype on their own, so we are done here. */
349 JSIterateOp op
= (flags
& JSITER_HIDDEN
) ? JSENUMERATE_INIT_ALL
: JSENUMERATE_INIT
;
350 if (!pobj
->enumerate(cx
, op
, &state
, NULL
))
352 if (state
.isMagic(JS_NATIVE_ENUMERATE
)) {
353 if (!EnumerateNativeProperties
<EnumPolicy
>(cx
, obj
, pobj
, flags
, ht
, props
))
358 if (!pobj
->enumerate(cx
, JSENUMERATE_NEXT
, &state
, &id
))
362 if (!Enumerate
<EnumPolicy
>(cx
, obj
, pobj
, id
, true, false, flags
, ht
, props
))
368 if (JS_UNLIKELY(pobj
->isXML()))
370 } while ((pobj
= pobj
->getProto()) != NULL
);
376 VectorToIdArray(JSContext
*cx
, AutoIdVector
&props
, JSIdArray
**idap
)
378 JS_STATIC_ASSERT(sizeof(JSIdArray
) > sizeof(jsid
));
379 size_t len
= props
.length();
380 size_t idsz
= len
* sizeof(jsid
);
381 size_t sz
= (sizeof(JSIdArray
) - sizeof(jsid
)) + idsz
;
382 JSIdArray
*ida
= static_cast<JSIdArray
*>(cx
->malloc(sz
));
386 ida
->length
= static_cast<jsint
>(len
);
387 memcpy(ida
->vector
, props
.begin(), idsz
);
393 GetPropertyNames(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
*props
)
395 return Snapshot
<KeyEnumeration
>(cx
, obj
, flags
& (JSITER_OWNONLY
| JSITER_HIDDEN
), props
);
399 GetCustomIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, Value
*vp
)
401 /* Check whether we have a valid __iterator__ method. */
402 JSAtom
*atom
= cx
->runtime
->atomState
.iteratorAtom
;
403 if (!js_GetMethod(cx
, obj
, ATOM_TO_JSID(atom
), JSGET_NO_METHOD_BARRIER
, vp
))
406 /* If there is no custom __iterator__ method, we are done here. */
407 if (vp
->isUndefined())
410 /* Otherwise call it and return that object. */
412 Value arg
= BooleanValue((flags
& JSITER_FOREACH
) == 0);
413 if (!ExternalInvoke(cx
, obj
, *vp
, 1, &arg
, vp
))
415 if (vp
->isPrimitive()) {
417 * We are always coming from js_ValueToIterator, and we are no longer on
418 * trace, so the object we are iterating over is on top of the stack (-1).
420 js_ReportValueError2(cx
, JSMSG_BAD_TRAP_RETURN_VALUE
,
421 -1, ObjectValue(*obj
), NULL
,
422 js_AtomToPrintableString(cx
, atom
));
428 template <typename T
>
430 Compare(T
*a
, T
*b
, size_t c
)
432 size_t n
= (c
+ size_t(7)) / size_t(8);
434 case 0: do { if (*a
++ != *b
++) return false;
435 case 7: if (*a
++ != *b
++) return false;
436 case 6: if (*a
++ != *b
++) return false;
437 case 5: if (*a
++ != *b
++) return false;
438 case 4: if (*a
++ != *b
++) return false;
439 case 3: if (*a
++ != *b
++) return false;
440 case 2: if (*a
++ != *b
++) return false;
441 case 1: if (*a
++ != *b
++) return false;
447 static inline JSObject
*
448 NewIteratorObject(JSContext
*cx
, uintN flags
)
450 if (flags
& JSITER_ENUMERATE
) {
452 * Non-escaping native enumerator objects do not need map, proto, or
453 * parent. However, code in jstracer.cpp and elsewhere may find such a
454 * native enumerator object via the stack and (as for all objects that
455 * are not stillborn, with the exception of "NoSuchMethod" internal
456 * helper objects) expect it to have a non-null map pointer, so we
457 * share an empty Enumerator scope in the runtime.
459 JSObject
*obj
= js_NewGCObject(cx
);
462 obj
->init(&js_IteratorClass
, NULL
, NULL
, NullValue(), cx
);
463 obj
->setMap(cx
->runtime
->emptyEnumeratorShape
);
467 return NewBuiltinClassInstance(cx
, &js_IteratorClass
);
471 NativeIterator::allocateKeyIterator(JSContext
*cx
, uint32 slength
, const AutoIdVector
&props
)
473 size_t plength
= props
.length();
474 NativeIterator
*ni
= (NativeIterator
*)
475 cx
->malloc(sizeof(NativeIterator
) + plength
* sizeof(jsid
) + slength
* sizeof(uint32
));
478 ni
->props_array
= ni
->props_cursor
= (jsid
*) (ni
+ 1);
479 ni
->props_end
= (jsid
*)ni
->props_array
+ plength
;
481 memcpy(ni
->props_array
, props
.begin(), plength
* sizeof(jsid
));
486 NativeIterator::allocateValueIterator(JSContext
*cx
, const AutoValueVector
&props
)
488 size_t plength
= props
.length();
489 NativeIterator
*ni
= (NativeIterator
*)
490 cx
->malloc(sizeof(NativeIterator
) + plength
* sizeof(Value
));
493 ni
->props_array
= ni
->props_cursor
= (Value
*) (ni
+ 1);
494 ni
->props_end
= (Value
*)ni
->props_array
+ plength
;
496 memcpy(ni
->props_array
, props
.begin(), plength
* sizeof(Value
));
501 NativeIterator::init(JSObject
*obj
, uintN flags
, uint32 slength
, uint32 key
)
505 this->shapes_array
= (uint32
*) this->props_end
;
506 this->shapes_length
= slength
;
507 this->shapes_key
= key
;
511 RegisterEnumerator(JSContext
*cx
, JSObject
*iterobj
, NativeIterator
*ni
)
513 /* Register non-escaping native enumerators (for-in) with the current context. */
514 if (ni
->flags
& JSITER_ENUMERATE
) {
515 ni
->next
= cx
->enumerators
;
516 cx
->enumerators
= iterobj
;
518 JS_ASSERT(!(ni
->flags
& JSITER_ACTIVE
));
519 ni
->flags
|= JSITER_ACTIVE
;
524 VectorToKeyIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
&keys
,
525 uint32 slength
, uint32 key
, Value
*vp
)
527 JS_ASSERT(!(flags
& JSITER_FOREACH
));
529 JSObject
*iterobj
= NewIteratorObject(cx
, flags
);
533 NativeIterator
*ni
= NativeIterator::allocateKeyIterator(cx
, slength
, keys
);
536 ni
->init(obj
, flags
, slength
, key
);
540 * Fill in the shape array from scratch. We can't use the array that was
541 * computed for the cache lookup earlier, as constructing iterobj could
542 * have triggered a shape-regenerating GC. Don't bother with regenerating
543 * the shape key; if such a GC *does* occur, we can only get hits through
544 * the one-slot lastNativeIterator cache.
546 JSObject
*pobj
= obj
;
549 ni
->shapes_array
[ind
++] = pobj
->shape();
550 pobj
= pobj
->getProto();
552 JS_ASSERT(ind
== slength
);
555 iterobj
->setNativeIterator(ni
);
556 vp
->setObject(*iterobj
);
558 RegisterEnumerator(cx
, iterobj
, ni
);
563 VectorToKeyIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
&props
, Value
*vp
)
565 return VectorToKeyIterator(cx
, obj
, flags
, props
, 0, 0, vp
);
569 VectorToValueIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoValueVector
&vals
,
572 JS_ASSERT(flags
& JSITER_FOREACH
);
574 JSObject
*iterobj
= NewIteratorObject(cx
, flags
);
578 NativeIterator
*ni
= NativeIterator::allocateValueIterator(cx
, vals
);
581 ni
->init(obj
, flags
, 0, 0);
583 iterobj
->setNativeIterator(ni
);
584 vp
->setObject(*iterobj
);
586 RegisterEnumerator(cx
, iterobj
, ni
);
591 EnumeratedIdVectorToIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
&props
, Value
*vp
)
593 if (!(flags
& JSITER_FOREACH
))
594 return VectorToKeyIterator(cx
, obj
, flags
, props
, vp
);
596 /* For for-each iteration, we need to look up the value of each id. */
598 size_t plength
= props
.length();
600 AutoValueVector
vals(cx
);
601 if (!vals
.reserve(plength
))
604 for (size_t i
= 0; i
< plength
; ++i
) {
605 if (!ValueEnumeration::append(cx
, vals
, obj
, props
[i
], flags
))
609 return VectorToValueIterator(cx
, obj
, flags
, vals
, vp
);
612 typedef Vector
<uint32
, 8> ShapeVector
;
615 GetIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, Value
*vp
)
619 Vector
<uint32
, 8> shapes(cx
);
622 bool keysOnly
= (flags
== JSITER_ENUMERATE
);
627 * Check to see if this is the same as the most recent object which
628 * was iterated over. We don't explicitly check for shapeless
629 * objects here, as they are not inserted into the cache and
630 * will result in a miss.
632 JSObject
*last
= JS_THREAD_DATA(cx
)->lastNativeIterator
;
633 JSObject
*proto
= obj
->getProto();
635 NativeIterator
*lastni
= last
->getNativeIterator();
636 if (!(lastni
->flags
& JSITER_ACTIVE
) &&
638 obj
->shape() == lastni
->shapes_array
[0] &&
639 proto
&& proto
->isNative() &&
640 proto
->shape() == lastni
->shapes_array
[1] &&
641 !proto
->getProto()) {
642 vp
->setObject(*last
);
643 RegisterEnumerator(cx
, last
, lastni
);
649 * The iterator object for JSITER_ENUMERATE never escapes, so we
650 * don't care for the proper parent/proto to be set. This also
651 * allows us to re-use a previous iterator object that is not
654 JSObject
*pobj
= obj
;
656 if (!pobj
->isNative() ||
657 obj
->getOps()->enumerate
||
658 pobj
->getClass()->enumerate
!= JS_EnumerateStub
) {
662 uint32 shape
= pobj
->shape();
663 key
= (key
+ (key
<< 16)) ^ shape
;
664 if (!shapes
.append(shape
))
666 pobj
= pobj
->getProto();
669 hash
= key
% JS_ARRAY_LENGTH(JS_THREAD_DATA(cx
)->cachedNativeIterators
);
670 hp
= &JS_THREAD_DATA(cx
)->cachedNativeIterators
[hash
];
671 JSObject
*iterobj
= *hp
;
673 NativeIterator
*ni
= iterobj
->getNativeIterator();
674 if (!(ni
->flags
& JSITER_ACTIVE
) &&
675 ni
->shapes_key
== key
&&
676 ni
->shapes_length
== shapes
.length() &&
677 Compare(ni
->shapes_array
, shapes
.begin(), ni
->shapes_length
)) {
678 vp
->setObject(*iterobj
);
680 RegisterEnumerator(cx
, iterobj
, ni
);
681 if (shapes
.length() == 2)
682 JS_THREAD_DATA(cx
)->lastNativeIterator
= iterobj
;
690 return JSProxy::iterate(cx
, obj
, flags
, vp
);
691 if (!GetCustomIterator(cx
, obj
, flags
, vp
))
693 if (!vp
->isUndefined())
697 /* NB: for (var p in null) succeeds by iterating over no properties. */
699 if (flags
& JSITER_FOREACH
) {
700 AutoValueVector
vals(cx
);
701 if (JS_LIKELY(obj
!= NULL
) && !Snapshot
<ValueEnumeration
>(cx
, obj
, flags
, &vals
))
703 JS_ASSERT(shapes
.empty());
704 if (!VectorToValueIterator(cx
, obj
, flags
, vals
, vp
))
707 AutoIdVector
keys(cx
);
708 if (JS_LIKELY(obj
!= NULL
) && !Snapshot
<KeyEnumeration
>(cx
, obj
, flags
, &keys
))
710 if (!VectorToKeyIterator(cx
, obj
, flags
, keys
, shapes
.length(), key
, vp
))
714 JSObject
*iterobj
= &vp
->toObject();
716 /* Cache the iterator object if possible. */
717 if (shapes
.length()) {
718 uint32 hash
= key
% NATIVE_ITER_CACHE_SIZE
;
719 JSObject
**hp
= &JS_THREAD_DATA(cx
)->cachedNativeIterators
[hash
];
723 if (shapes
.length() == 2)
724 JS_THREAD_DATA(cx
)->lastNativeIterator
= iterobj
;
729 iterator_iterator(JSContext
*cx
, JSObject
*obj
, JSBool keysonly
)
735 Iterator(JSContext
*cx
, uintN argc
, Value
*vp
)
737 Value
*argv
= JS_ARGV(cx
, vp
);
738 bool keyonly
= argc
>= 2 ? js_ValueToBoolean(argv
[1]) : false;
739 uintN flags
= JSITER_OWNONLY
| (keyonly
? 0 : (JSITER_FOREACH
| JSITER_KEYVALUE
));
740 *vp
= argc
>= 1 ? argv
[0] : UndefinedValue();
741 return js_ValueToIterator(cx
, flags
, vp
);
745 js_ThrowStopIteration(JSContext
*cx
)
749 JS_ASSERT(!JS_IsExceptionPending(cx
));
750 if (js_FindClassObject(cx
, NULL
, JSProto_StopIteration
, &v
))
751 SetPendingException(cx
, v
);
756 iterator_next(JSContext
*cx
, uintN argc
, Value
*vp
)
760 obj
= ComputeThisFromVp(cx
, vp
);
761 if (!InstanceOf(cx
, obj
, &js_IteratorClass
, vp
+ 2))
764 if (!js_IteratorMore(cx
, obj
, vp
))
766 if (!vp
->toBoolean()) {
767 js_ThrowStopIteration(cx
);
770 return js_IteratorNext(cx
, obj
, vp
);
773 #define JSPROP_ROPERM (JSPROP_READONLY | JSPROP_PERMANENT)
775 static JSFunctionSpec iterator_methods
[] = {
776 JS_FN(js_next_str
, iterator_next
, 0,JSPROP_ROPERM
),
781 * Call ToObject(v).__iterator__(keyonly) if ToObject(v).__iterator__ exists.
782 * Otherwise construct the default iterator.
784 JS_FRIEND_API(JSBool
)
785 js_ValueToIterator(JSContext
*cx
, uintN flags
, Value
*vp
)
787 /* JSITER_KEYVALUE must always come with JSITER_FOREACH */
788 JS_ASSERT_IF(flags
& JSITER_KEYVALUE
, flags
& JSITER_FOREACH
);
791 * Make sure the more/next state machine doesn't get stuck. A value might be
792 * left in iterValue when a trace is left due to an operation time-out after
793 * JSOP_MOREITER but before the value is picked up by FOR*.
795 cx
->iterValue
.setMagic(JS_NO_ITER_VALUE
);
798 if (vp
->isObject()) {
800 obj
= &vp
->toObject();
803 * Enumerating over null and undefined gives an empty enumerator.
804 * This is contrary to ECMA-262 9.9 ToObject, invoked from step 3 of
805 * the first production in 12.6.4 and step 4 of the second production,
806 * but it's "web JS" compatible. ES5 fixed for-in to match this de-facto
809 if ((flags
& JSITER_ENUMERATE
)) {
810 if (!js_ValueToObjectOrNull(cx
, *vp
, &obj
))
813 return GetIterator(cx
, NULL
, flags
, vp
);
815 obj
= js_ValueToNonNullObject(cx
, *vp
);
821 AutoObjectRooter
tvr(cx
, obj
);
823 /* Enumerate Iterator.prototype directly. */
824 JSIteratorOp op
= obj
->getClass()->ext
.iteratorObject
;
825 if (op
&& (obj
->getClass() != &js_IteratorClass
|| obj
->getNativeIterator())) {
826 JSObject
*iterobj
= op(cx
, obj
, !(flags
& JSITER_FOREACH
));
829 vp
->setObject(*iterobj
);
833 return GetIterator(cx
, obj
, flags
, vp
);
836 #if JS_HAS_GENERATORS
837 static JS_REQUIRES_STACK JSBool
838 CloseGenerator(JSContext
*cx
, JSObject
*genobj
);
841 JS_FRIEND_API(JSBool
)
842 js_CloseIterator(JSContext
*cx
, JSObject
*obj
)
844 cx
->iterValue
.setMagic(JS_NO_ITER_VALUE
);
846 Class
*clasp
= obj
->getClass();
847 if (clasp
== &js_IteratorClass
) {
848 /* Remove enumerators from the active list, which is a stack. */
849 NativeIterator
*ni
= obj
->getNativeIterator();
851 if (ni
->flags
& JSITER_ENUMERATE
) {
852 JS_ASSERT(cx
->enumerators
== obj
);
853 cx
->enumerators
= ni
->next
;
855 JS_ASSERT(ni
->flags
& JSITER_ACTIVE
);
856 ni
->flags
&= ~JSITER_ACTIVE
;
859 * Reset the enumerator; it may still be in the cached iterators
860 * for this thread, and can be reused.
862 ni
->props_cursor
= ni
->props_array
;
865 #if JS_HAS_GENERATORS
866 else if (clasp
== &js_GeneratorClass
) {
867 return CloseGenerator(cx
, obj
);
874 * Suppress enumeration of deleted properties. We maintain a list of all active
875 * non-escaping for-in enumerators. Whenever a property is deleted, we check
876 * whether any active enumerator contains the (obj, id) pair and has not
877 * enumerated id yet. If so, we delete the id from the list (or advance the
878 * cursor if it is the next id to be enumerated).
880 * We do not suppress enumeration of a property deleted along an object's
881 * prototype chain. Only direct deletions on the object are handled.
884 js_SuppressDeletedProperty(JSContext
*cx
, JSObject
*obj
, jsid id
)
886 JSObject
*iterobj
= cx
->enumerators
;
889 NativeIterator
*ni
= iterobj
->getNativeIterator();
890 /* This only works for identified surpressed keys, not values. */
891 if (ni
->isKeyIter() && ni
->obj
== obj
&& ni
->props_cursor
< ni
->props_end
) {
892 /* Check whether id is still to come. */
893 jsid
*props_cursor
= ni
->currentKey();
894 jsid
*props_end
= ni
->endKey();
895 for (jsid
*idp
= props_cursor
; idp
< props_end
; ++idp
) {
898 * Check whether another property along the prototype chain
899 * became visible as a result of this deletion.
901 if (obj
->getProto()) {
902 AutoObjectRooter
proto(cx
, obj
->getProto());
903 AutoObjectRooter
obj2(cx
);
905 if (!proto
.object()->lookupProperty(cx
, id
, obj2
.addr(), &prop
))
909 if (obj2
.object()->isNative()) {
910 attrs
= ((Shape
*) prop
)->attributes();
911 JS_UNLOCK_OBJ(cx
, obj2
.object());
912 } else if (!obj2
.object()->getAttributes(cx
, id
, &attrs
)) {
915 if (attrs
& JSPROP_ENUMERATE
)
921 * If lookupProperty or getAttributes above removed a property from
924 if (props_end
!= ni
->props_end
|| props_cursor
!= ni
->props_cursor
)
928 * No property along the prototype chain steppeded in to take the
929 * property's place, so go ahead and delete id from the list.
930 * If it is the next property to be enumerated, just skip it.
932 if (idp
== props_cursor
) {
935 memmove(idp
, idp
+ 1, (props_end
- (idp
+ 1)) * sizeof(jsid
));
936 ni
->props_end
= ni
->endKey() - 1;
948 js_IteratorMore(JSContext
*cx
, JSObject
*iterobj
, Value
*rval
)
950 /* Fast path for native iterators */
951 if (iterobj
->getClass() == &js_IteratorClass
) {
953 * Implement next directly as all the methods of native iterator are
954 * read-only and permanent.
956 NativeIterator
*ni
= iterobj
->getNativeIterator();
957 rval
->setBoolean(ni
->props_cursor
< ni
->props_end
);
961 /* We might still have a pending value. */
962 if (!cx
->iterValue
.isMagic(JS_NO_ITER_VALUE
)) {
963 rval
->setBoolean(true);
967 /* Fetch and cache the next value from the iterator. */
968 jsid id
= ATOM_TO_JSID(cx
->runtime
->atomState
.nextAtom
);
969 if (!js_GetMethod(cx
, iterobj
, id
, JSGET_METHOD_BARRIER
, rval
))
971 if (!ExternalInvoke(cx
, iterobj
, *rval
, 0, NULL
, rval
)) {
972 /* Check for StopIteration. */
973 if (!cx
->throwing
|| !js_ValueIsStopIteration(cx
->exception
))
976 /* Inline JS_ClearPendingException(cx). */
977 cx
->throwing
= JS_FALSE
;
978 cx
->exception
.setUndefined();
979 cx
->iterValue
.setMagic(JS_NO_ITER_VALUE
);
980 rval
->setBoolean(false);
984 /* Cache the value returned by iterobj.next() so js_IteratorNext() can find it. */
985 JS_ASSERT(!rval
->isMagic(JS_NO_ITER_VALUE
));
986 cx
->iterValue
= *rval
;
987 rval
->setBoolean(true);
992 js_IteratorNext(JSContext
*cx
, JSObject
*iterobj
, Value
*rval
)
994 /* Fast path for native iterators */
995 if (iterobj
->getClass() == &js_IteratorClass
) {
997 * Implement next directly as all the methods of the native iterator are
998 * read-only and permanent.
1000 NativeIterator
*ni
= iterobj
->getNativeIterator();
1001 JS_ASSERT(ni
->props_cursor
< ni
->props_end
);
1002 if (ni
->isKeyIter()) {
1003 *rval
= IdToValue(*ni
->currentKey());
1006 *rval
= *ni
->currentValue();
1007 ni
->incValueCursor();
1010 if (rval
->isString() || !ni
->isKeyIter())
1015 if (rval
->isInt32() && (jsuint(i
= rval
->toInt32()) < INT_STRING_LIMIT
)) {
1016 str
= JSString::intString(i
);
1018 str
= js_ValueToString(cx
, *rval
);
1023 rval
->setString(str
);
1027 JS_ASSERT(!cx
->iterValue
.isMagic(JS_NO_ITER_VALUE
));
1028 *rval
= cx
->iterValue
;
1029 cx
->iterValue
.setMagic(JS_NO_ITER_VALUE
);
1035 stopiter_hasInstance(JSContext
*cx
, JSObject
*obj
, const Value
*v
, JSBool
*bp
)
1037 *bp
= js_ValueIsStopIteration(*v
);
1041 Class js_StopIterationClass
= {
1042 js_StopIteration_str
,
1043 JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration
),
1044 PropertyStub
, /* addProperty */
1045 PropertyStub
, /* delProperty */
1046 PropertyStub
, /* getProperty */
1047 PropertyStub
, /* setProperty */
1051 NULL
, /* finalize */
1052 NULL
, /* reserved0 */
1053 NULL
, /* checkAccess */
1055 NULL
, /* construct */
1056 NULL
, /* xdrObject */
1057 stopiter_hasInstance
1060 #if JS_HAS_GENERATORS
1063 generator_finalize(JSContext
*cx
, JSObject
*obj
)
1065 JSGenerator
*gen
= (JSGenerator
*) obj
->getPrivate();
1070 * gen is open when a script has not called its close method while
1071 * explicitly manipulating it.
1073 JS_ASSERT(gen
->state
== JSGEN_NEWBORN
||
1074 gen
->state
== JSGEN_CLOSED
||
1075 gen
->state
== JSGEN_OPEN
);
1080 generator_trace(JSTracer
*trc
, JSObject
*obj
)
1082 JSGenerator
*gen
= (JSGenerator
*) obj
->getPrivate();
1087 * Do not mark if the generator is running; the contents may be trash and
1088 * will be replaced when the generator stops.
1090 if (gen
->state
== JSGEN_RUNNING
|| gen
->state
== JSGEN_CLOSING
)
1093 JSStackFrame
*fp
= gen
->floatingFrame();
1094 JS_ASSERT(gen
->liveFrame() == fp
);
1095 MarkValueRange(trc
, gen
->floatingStack
, fp
->formalArgsEnd(), "generator slots");
1096 js_TraceStackFrame(trc
, fp
);
1097 MarkValueRange(trc
, fp
->slots(), gen
->regs
.sp
, "generator slots");
1100 Class js_GeneratorClass
= {
1102 JSCLASS_HAS_PRIVATE
| JSCLASS_HAS_CACHED_PROTO(JSProto_Generator
) |
1103 JSCLASS_IS_ANONYMOUS
| JSCLASS_MARK_IS_TRACE
,
1104 PropertyStub
, /* addProperty */
1105 PropertyStub
, /* delProperty */
1106 PropertyStub
, /* getProperty */
1107 PropertyStub
, /* setProperty */
1112 NULL
, /* reserved */
1113 NULL
, /* checkAccess */
1115 NULL
, /* construct */
1116 NULL
, /* xdrObject */
1117 NULL
, /* hasInstance */
1118 JS_CLASS_TRACE(generator_trace
),
1120 NULL
, /* equality */
1121 NULL
, /* outerObject */
1122 NULL
, /* innerObject */
1124 NULL
, /* wrappedObject */
1129 RebaseRegsFromTo(JSFrameRegs
*regs
, JSStackFrame
*from
, JSStackFrame
*to
)
1132 regs
->sp
= to
->slots() + (regs
->sp
- from
->slots());
1136 * Called from the JSOP_GENERATOR case in the interpreter, with fp referring
1137 * to the frame by which the generator function was activated. Create a new
1138 * JSGenerator object, which contains its own JSStackFrame that we populate
1139 * from *fp. We know that upon return, the JSOP_GENERATOR opcode will return
1140 * from the activation in fp, so we can steal away fp->callobj and fp->argsobj
1141 * if they are non-null.
1143 JS_REQUIRES_STACK JSObject
*
1144 js_NewGenerator(JSContext
*cx
)
1146 JSObject
*obj
= NewBuiltinClassInstance(cx
, &js_GeneratorClass
);
1150 JSStackFrame
*stackfp
= cx
->fp();
1151 JS_ASSERT(stackfp
->base() == cx
->regs
->sp
);
1152 JS_ASSERT(stackfp
->actualArgs() <= stackfp
->formalArgs());
1154 /* Load and compute stack slot counts. */
1155 Value
*stackvp
= stackfp
->actualArgs() - 2;
1156 uintN vplen
= stackfp
->formalArgsEnd() - stackvp
;
1158 /* Compute JSGenerator size. */
1159 uintN nbytes
= sizeof(JSGenerator
) +
1160 (-1 + /* one Value included in JSGenerator */
1162 VALUES_PER_STACK_FRAME
+
1163 stackfp
->numSlots()) * sizeof(Value
);
1165 JSGenerator
*gen
= (JSGenerator
*) cx
->malloc(nbytes
);
1169 /* Cut up floatingStack space. */
1170 Value
*genvp
= gen
->floatingStack
;
1171 JSStackFrame
*genfp
= reinterpret_cast<JSStackFrame
*>(genvp
+ vplen
);
1173 /* Initialize JSGenerator. */
1175 gen
->state
= JSGEN_NEWBORN
;
1176 gen
->enumerators
= NULL
;
1177 gen
->floating
= genfp
;
1179 /* Initialize regs stored in generator. */
1180 gen
->regs
= *cx
->regs
;
1181 RebaseRegsFromTo(&gen
->regs
, stackfp
, genfp
);
1183 /* Copy frame off the stack. */
1184 genfp
->stealFrameAndSlots(genvp
, stackfp
, stackvp
, cx
->regs
->sp
);
1185 genfp
->initFloatingGenerator();
1187 obj
->setPrivate(gen
);
1192 js_FloatingFrameToGenerator(JSStackFrame
*fp
)
1194 JS_ASSERT(fp
->isGeneratorFrame() && fp
->isFloatingGenerator());
1195 char *floatingStackp
= (char *)(fp
->actualArgs() - 2);
1196 char *p
= floatingStackp
- offsetof(JSGenerator
, floatingStack
);
1197 return reinterpret_cast<JSGenerator
*>(p
);
1200 typedef enum JSGeneratorOp
{
1208 * Start newborn or restart yielding generator and perform the requested
1209 * operation inside its frame.
1211 static JS_REQUIRES_STACK JSBool
1212 SendToGenerator(JSContext
*cx
, JSGeneratorOp op
, JSObject
*obj
,
1213 JSGenerator
*gen
, const Value
&arg
)
1215 if (gen
->state
== JSGEN_RUNNING
|| gen
->state
== JSGEN_CLOSING
) {
1216 js_ReportValueError(cx
, JSMSG_NESTING_GENERATOR
,
1217 JSDVG_SEARCH_STACK
, ObjectOrNullValue(obj
),
1218 JS_GetFunctionId(gen
->floatingFrame()->fun()));
1222 /* Check for OOM errors here, where we can fail easily. */
1223 if (!cx
->ensureGeneratorStackSpace())
1226 JS_ASSERT(gen
->state
== JSGEN_NEWBORN
|| gen
->state
== JSGEN_OPEN
);
1230 if (gen
->state
== JSGEN_OPEN
) {
1232 * Store the argument to send as the result of the yield
1235 gen
->regs
.sp
[-1] = arg
;
1237 gen
->state
= JSGEN_RUNNING
;
1241 SetPendingException(cx
, arg
);
1242 gen
->state
= JSGEN_RUNNING
;
1246 JS_ASSERT(op
== JSGENOP_CLOSE
);
1247 SetPendingException(cx
, MagicValue(JS_GENERATOR_CLOSING
));
1248 gen
->state
= JSGEN_CLOSING
;
1252 JSStackFrame
*genfp
= gen
->floatingFrame();
1253 Value
*genvp
= gen
->floatingStack
;
1254 uintN vplen
= genfp
->formalArgsEnd() - genvp
;
1256 JSStackFrame
*stackfp
;
1261 * Get a pointer to new frame/slots. This memory is not "claimed", so
1262 * the code before pushExecuteFrame must not reenter the interpreter.
1264 GeneratorFrameGuard frame
;
1265 if (!cx
->stack().getGeneratorFrame(cx
, vplen
, genfp
->numSlots(), &frame
)) {
1266 gen
->state
= JSGEN_CLOSED
;
1269 stackfp
= frame
.fp();
1270 stackvp
= frame
.vp();
1272 /* Copy frame onto the stack. */
1273 stackfp
->stealFrameAndSlots(stackvp
, genfp
, genvp
, gen
->regs
.sp
);
1274 stackfp
->setPrev(cx
->regs
);
1275 stackfp
->unsetFloatingGenerator();
1276 RebaseRegsFromTo(&gen
->regs
, genfp
, stackfp
);
1277 MUST_FLOW_THROUGH("restore");
1279 /* Officially push frame. frame's destructor pops. */
1280 cx
->stack().pushGeneratorFrame(cx
, &gen
->regs
, &frame
);
1282 cx
->enterGenerator(gen
); /* OOM check above. */
1283 JSObject
*enumerators
= cx
->enumerators
;
1284 cx
->enumerators
= gen
->enumerators
;
1286 ok
= RunScript(cx
, stackfp
->script(), stackfp
->fun(), stackfp
->scopeChain());
1288 gen
->enumerators
= cx
->enumerators
;
1289 cx
->enumerators
= enumerators
;
1290 cx
->leaveGenerator(gen
);
1293 * Copy the stack frame and rebase the regs, but not before popping
1294 * the stack, since cx->regs == &gen->regs.
1296 genfp
->stealFrameAndSlots(genvp
, stackfp
, stackvp
, gen
->regs
.sp
);
1297 genfp
->setFloatingGenerator();
1299 MUST_FLOW_LABEL(restore
)
1300 RebaseRegsFromTo(&gen
->regs
, stackfp
, genfp
);
1302 if (gen
->floatingFrame()->isYielding()) {
1303 /* Yield cannot fail, throw or be called on closing. */
1305 JS_ASSERT(!cx
->throwing
);
1306 JS_ASSERT(gen
->state
== JSGEN_RUNNING
);
1307 JS_ASSERT(op
!= JSGENOP_CLOSE
);
1308 genfp
->clearYielding();
1309 gen
->state
= JSGEN_OPEN
;
1313 genfp
->clearReturnValue();
1314 gen
->state
= JSGEN_CLOSED
;
1316 /* Returned, explicitly or by falling off the end. */
1317 if (op
== JSGENOP_CLOSE
)
1319 return js_ThrowStopIteration(cx
);
1323 * An error, silent termination by operation callback or an exception.
1324 * Propagate the condition to the caller.
1329 static JS_REQUIRES_STACK JSBool
1330 CloseGenerator(JSContext
*cx
, JSObject
*obj
)
1332 JS_ASSERT(obj
->getClass() == &js_GeneratorClass
);
1334 JSGenerator
*gen
= (JSGenerator
*) obj
->getPrivate();
1336 /* Generator prototype object. */
1340 if (gen
->state
== JSGEN_CLOSED
)
1343 return SendToGenerator(cx
, JSGENOP_CLOSE
, obj
, gen
, UndefinedValue());
1347 * Common subroutine of generator_(next|send|throw|close) methods.
1350 generator_op(JSContext
*cx
, JSGeneratorOp op
, Value
*vp
, uintN argc
)
1355 obj
= ComputeThisFromVp(cx
, vp
);
1356 if (!InstanceOf(cx
, obj
, &js_GeneratorClass
, vp
+ 2))
1359 JSGenerator
*gen
= (JSGenerator
*) obj
->getPrivate();
1361 /* This happens when obj is the generator prototype. See bug 352885. */
1362 goto closed_generator
;
1365 if (gen
->state
== JSGEN_NEWBORN
) {
1372 if (argc
>= 1 && !vp
[2].isUndefined()) {
1373 js_ReportValueError(cx
, JSMSG_BAD_GENERATOR_SEND
,
1374 JSDVG_SEARCH_STACK
, vp
[2], NULL
);
1380 JS_ASSERT(op
== JSGENOP_CLOSE
);
1381 gen
->state
= JSGEN_CLOSED
;
1384 } else if (gen
->state
== JSGEN_CLOSED
) {
1389 return js_ThrowStopIteration(cx
);
1391 SetPendingException(cx
, argc
>= 1 ? vp
[2] : UndefinedValue());
1394 JS_ASSERT(op
== JSGENOP_CLOSE
);
1399 bool undef
= ((op
== JSGENOP_SEND
|| op
== JSGENOP_THROW
) && argc
!= 0);
1400 if (!SendToGenerator(cx
, op
, obj
, gen
, undef
? vp
[2] : UndefinedValue()))
1402 *vp
= gen
->floatingFrame()->returnValue();
1407 generator_send(JSContext
*cx
, uintN argc
, Value
*vp
)
1409 return generator_op(cx
, JSGENOP_SEND
, vp
, argc
);
1413 generator_next(JSContext
*cx
, uintN argc
, Value
*vp
)
1415 return generator_op(cx
, JSGENOP_NEXT
, vp
, argc
);
1419 generator_throw(JSContext
*cx
, uintN argc
, Value
*vp
)
1421 return generator_op(cx
, JSGENOP_THROW
, vp
, argc
);
1425 generator_close(JSContext
*cx
, uintN argc
, Value
*vp
)
1427 return generator_op(cx
, JSGENOP_CLOSE
, vp
, argc
);
1430 static JSFunctionSpec generator_methods
[] = {
1431 JS_FN(js_next_str
, generator_next
, 0,JSPROP_ROPERM
),
1432 JS_FN(js_send_str
, generator_send
, 1,JSPROP_ROPERM
),
1433 JS_FN(js_throw_str
, generator_throw
, 1,JSPROP_ROPERM
),
1434 JS_FN(js_close_str
, generator_close
, 0,JSPROP_ROPERM
),
1438 #endif /* JS_HAS_GENERATORS */
1441 js_InitIteratorClasses(JSContext
*cx
, JSObject
*obj
)
1443 JSObject
*proto
, *stop
;
1445 /* Idempotency required: we initialize several things, possibly lazily. */
1446 if (!js_GetClassObject(cx
, obj
, JSProto_StopIteration
, &stop
))
1451 proto
= js_InitClass(cx
, obj
, NULL
, &js_IteratorClass
, Iterator
, 2,
1452 NULL
, iterator_methods
, NULL
, NULL
);
1456 #if JS_HAS_GENERATORS
1457 /* Initialize the generator internals if configured. */
1458 if (!js_InitClass(cx
, obj
, NULL
, &js_GeneratorClass
, NULL
, 0,
1459 NULL
, generator_methods
, NULL
, NULL
)) {
1464 return js_InitClass(cx
, obj
, NULL
, &js_StopIterationClass
, NULL
, 0,
1465 NULL
, NULL
, NULL
, NULL
);