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 "jsobjinlines.h"
80 #include "jsstrinlines.h"
84 static void iterator_finalize(JSContext
*cx
, JSObject
*obj
);
85 static void iterator_trace(JSTracer
*trc
, JSObject
*obj
);
86 static JSObject
*iterator_iterator(JSContext
*cx
, JSObject
*obj
, JSBool keysonly
);
88 Class js_IteratorClass
= {
90 JSCLASS_HAS_PRIVATE
| JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator
) | JSCLASS_MARK_IS_TRACE
,
91 PropertyStub
, /* addProperty */
92 PropertyStub
, /* delProperty */
93 PropertyStub
, /* getProperty */
94 PropertyStub
, /* setProperty */
100 NULL
, /* checkAccess */
102 NULL
, /* construct */
103 NULL
, /* xdrObject */
104 NULL
, /* hasInstance */
105 JS_CLASS_TRACE(iterator_trace
),
108 NULL
, /* outerObject */
109 NULL
, /* innerObject */
111 NULL
/* wrappedObject */
116 NativeIterator::mark(JSTracer
*trc
)
119 MarkIdRange(trc
, beginKey(), endKey(), "props");
121 MarkValueRange(trc
, beginValue(), endValue(), "props");
123 MarkObject(trc
, obj
, "obj");
127 * Shared code to close iterator's state either through an explicit call or
128 * when GC detects that the iterator is no longer reachable.
131 iterator_finalize(JSContext
*cx
, JSObject
*obj
)
133 JS_ASSERT(obj
->getClass() == &js_IteratorClass
);
135 /* Avoid double work if the iterator was closed by JSOP_ENDITER. */
136 NativeIterator
*ni
= obj
->getNativeIterator();
139 obj
->setNativeIterator(NULL
);
144 iterator_trace(JSTracer
*trc
, JSObject
*obj
)
146 NativeIterator
*ni
= obj
->getNativeIterator();
152 struct IdHashPolicy
{
154 static HashNumber
hash(jsid id
) {
155 return JSID_BITS(id
);
157 static bool match(jsid id1
, jsid id2
) {
162 typedef HashSet
<jsid
, IdHashPolicy
, ContextAllocPolicy
> IdSet
;
165 NewKeyValuePair(JSContext
*cx
, jsid id
, const Value
&val
, Value
*rval
)
167 Value vec
[2] = { IdToValue(id
), val
};
168 AutoArrayRooter
tvr(cx
, JS_ARRAY_LENGTH(vec
), vec
);
170 JSObject
*aobj
= js_NewArrayObject(cx
, 2, vec
);
173 rval
->setObject(*aobj
);
177 struct KeyEnumeration
179 typedef AutoIdVector ResultVector
;
181 static JS_ALWAYS_INLINE
bool
182 append(JSContext
*, AutoIdVector
&keys
, JSObject
*, jsid id
, uintN flags
)
184 JS_ASSERT((flags
& JSITER_FOREACH
) == 0);
185 return keys
.append(id
);
189 struct ValueEnumeration
191 typedef AutoValueVector ResultVector
;
193 static JS_ALWAYS_INLINE
bool
194 append(JSContext
*cx
, AutoValueVector
&vals
, JSObject
*obj
, jsid id
, uintN flags
)
196 JS_ASSERT(flags
& JSITER_FOREACH
);
201 /* Do the lookup on the original object instead of the prototype. */
202 Value
*vp
= vals
.end() - 1;
203 if (!obj
->getProperty(cx
, id
, vp
))
205 if ((flags
& JSITER_KEYVALUE
) && !NewKeyValuePair(cx
, id
, *vp
, vp
))
212 template <class EnumPolicy
>
214 Enumerate(JSContext
*cx
, JSObject
*obj
, JSObject
*pobj
, jsid id
,
215 bool enumerable
, bool sharedPermanent
, uintN flags
, IdSet
& ht
,
216 typename
EnumPolicy::ResultVector
&props
)
218 IdSet::AddPtr p
= ht
.lookupForAdd(id
);
219 JS_ASSERT_IF(obj
== pobj
&& !obj
->isProxy(), !p
);
221 /* If we've already seen this, we definitely won't add it. */
222 if (JS_UNLIKELY(!!p
))
226 * It's not necessary to add properties to the hash table at the end of the
227 * prototype chain -- but a proxy might return duplicated properties, so
228 * always add for them.
230 if ((pobj
->getProto() || pobj
->isProxy()) && !ht
.add(p
, id
))
233 if (JS_UNLIKELY(flags
& JSITER_OWNONLY
)) {
235 * Shared-permanent hack: If this property is shared permanent
236 * and pobj and obj have the same class, then treat it as an own
237 * property of obj, even if pobj != obj. (But see bug 575997.)
239 * Omit the magic __proto__ property so that JS code can use
240 * Object.getOwnPropertyNames without worrying about it.
242 if (!pobj
->getProto() && id
== ATOM_TO_JSID(cx
->runtime
->atomState
.protoAtom
))
244 if (pobj
!= obj
&& !(sharedPermanent
&& pobj
->getClass() == obj
->getClass()))
248 if (enumerable
|| (flags
& JSITER_HIDDEN
))
249 return EnumPolicy::append(cx
, props
, obj
, id
, flags
);
254 template <class EnumPolicy
>
256 EnumerateNativeProperties(JSContext
*cx
, JSObject
*obj
, JSObject
*pobj
, uintN flags
, IdSet
&ht
,
257 typename
EnumPolicy::ResultVector
&props
)
259 JS_LOCK_OBJ(cx
, pobj
);
261 size_t initialLength
= props
.length();
263 /* Collect all unique properties from this object's scope. */
264 JSScope
*scope
= pobj
->scope();
265 for (JSScopeProperty
*sprop
= scope
->lastProperty(); sprop
; sprop
= sprop
->parent
) {
266 if (!JSID_IS_DEFAULT_XML_NAMESPACE(sprop
->id
) &&
268 !Enumerate
<EnumPolicy
>(cx
, obj
, pobj
, sprop
->id
, sprop
->enumerable(), sprop
->isSharedPermanent(),
275 Reverse(props
.begin() + initialLength
, props
.end());
277 JS_UNLOCK_SCOPE(cx
, scope
);
281 template <class EnumPolicy
>
283 EnumerateDenseArrayProperties(JSContext
*cx
, JSObject
*obj
, JSObject
*pobj
, uintN flags
,
284 IdSet
&ht
, typename
EnumPolicy::ResultVector
&props
)
286 if (!Enumerate
<EnumPolicy
>(cx
, obj
, pobj
, ATOM_TO_JSID(cx
->runtime
->atomState
.lengthAtom
), false, true,
291 if (pobj
->getArrayLength() > 0) {
292 size_t capacity
= pobj
->getDenseArrayCapacity();
293 Value
*vp
= pobj
->dslots
;
294 for (size_t i
= 0; i
< capacity
; ++i
, ++vp
) {
295 if (!vp
->isMagic(JS_ARRAY_HOLE
)) {
296 /* Dense arrays never get so large that i would not fit into an integer id. */
297 if (!Enumerate
<EnumPolicy
>(cx
, obj
, pobj
, INT_TO_JSID(i
), true, false, flags
, ht
, props
))
306 template <class EnumPolicy
>
308 Snapshot(JSContext
*cx
, JSObject
*obj
, uintN flags
, typename
EnumPolicy::ResultVector
&props
)
311 * FIXME: Bug 575997 - We won't need to initialize this hash table if
312 * (flags & JSITER_OWNONLY) when we eliminate inheritance of
313 * shared-permanent properties as own properties.
319 JSObject
*pobj
= obj
;
321 Class
*clasp
= pobj
->getClass();
322 if (pobj
->isNative() &&
323 !pobj
->getOps()->enumerate
&&
324 !(clasp
->flags
& JSCLASS_NEW_ENUMERATE
)) {
325 if (!clasp
->enumerate(cx
, pobj
))
327 if (!EnumerateNativeProperties
<EnumPolicy
>(cx
, obj
, pobj
, flags
, ht
, props
))
329 } else if (pobj
->isDenseArray()) {
330 if (!EnumerateDenseArrayProperties
<EnumPolicy
>(cx
, obj
, pobj
, flags
, ht
, props
))
333 if (pobj
->isProxy()) {
334 AutoIdVector
proxyProps(cx
);
335 if (flags
& JSITER_OWNONLY
) {
336 if (!JSProxy::enumerateOwn(cx
, pobj
, proxyProps
))
339 if (!JSProxy::enumerate(cx
, pobj
, proxyProps
))
342 for (size_t n
= 0, len
= proxyProps
.length(); n
< len
; n
++) {
343 if (!Enumerate
<EnumPolicy
>(cx
, obj
, pobj
, proxyProps
[n
], true, false, flags
, ht
, props
))
346 /* Proxy objects enumerate the prototype on their own, so we are done here. */
350 JSIterateOp op
= (flags
& JSITER_HIDDEN
) ? JSENUMERATE_INIT_ALL
: JSENUMERATE_INIT
;
351 if (!pobj
->enumerate(cx
, op
, &state
, NULL
))
353 if (state
.isMagic(JS_NATIVE_ENUMERATE
)) {
354 if (!EnumerateNativeProperties
<EnumPolicy
>(cx
, obj
, pobj
, flags
, ht
, props
))
359 if (!pobj
->enumerate(cx
, JSENUMERATE_NEXT
, &state
, &id
))
363 if (!Enumerate
<EnumPolicy
>(cx
, obj
, pobj
, id
, true, false, flags
, ht
, props
))
369 if (JS_UNLIKELY(pobj
->isXML()))
371 } while ((pobj
= pobj
->getProto()) != NULL
);
377 VectorToIdArray(JSContext
*cx
, AutoIdVector
&props
, JSIdArray
**idap
)
379 JS_STATIC_ASSERT(sizeof(JSIdArray
) > sizeof(jsid
));
380 size_t len
= props
.length();
381 size_t idsz
= len
* sizeof(jsid
);
382 size_t sz
= (sizeof(JSIdArray
) - sizeof(jsid
)) + idsz
;
383 JSIdArray
*ida
= static_cast<JSIdArray
*>(cx
->malloc(sz
));
387 ida
->length
= static_cast<jsint
>(len
);
388 memcpy(ida
->vector
, props
.begin(), idsz
);
394 GetPropertyNames(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
&props
)
396 return Snapshot
<KeyEnumeration
>(cx
, obj
, flags
& (JSITER_OWNONLY
| JSITER_HIDDEN
), props
);
400 GetCustomIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, Value
*vp
)
402 /* Check whether we have a valid __iterator__ method. */
403 JSAtom
*atom
= cx
->runtime
->atomState
.iteratorAtom
;
404 if (!js_GetMethod(cx
, obj
, ATOM_TO_JSID(atom
), JSGET_NO_METHOD_BARRIER
, vp
))
407 /* If there is no custom __iterator__ method, we are done here. */
408 if (vp
->isUndefined())
411 /* Otherwise call it and return that object. */
413 Value arg
= BooleanValue((flags
& JSITER_FOREACH
) == 0);
414 if (!InternalCall(cx
, obj
, *vp
, 1, &arg
, vp
))
416 if (vp
->isPrimitive()) {
418 * We are always coming from js_ValueToIterator, and we are no longer on
419 * trace, so the object we are iterating over is on top of the stack (-1).
421 js_ReportValueError2(cx
, JSMSG_BAD_TRAP_RETURN_VALUE
,
422 -1, ObjectValue(*obj
), NULL
,
423 js_AtomToPrintableString(cx
, atom
));
429 template <typename T
>
431 Compare(T
*a
, T
*b
, size_t c
)
433 size_t n
= (c
+ size_t(7)) / size_t(8);
435 case 0: do { if (*a
++ != *b
++) return false;
436 case 7: if (*a
++ != *b
++) return false;
437 case 6: if (*a
++ != *b
++) return false;
438 case 5: if (*a
++ != *b
++) return false;
439 case 4: if (*a
++ != *b
++) return false;
440 case 3: if (*a
++ != *b
++) return false;
441 case 2: if (*a
++ != *b
++) return false;
442 case 1: if (*a
++ != *b
++) return false;
448 static inline JSObject
*
449 NewIteratorObject(JSContext
*cx
, uintN flags
)
451 if (flags
& JSITER_ENUMERATE
) {
453 * Non-escaping native enumerator objects do not need map, proto, or
454 * parent. However, code in jstracer.cpp and elsewhere may find such a
455 * native enumerator object via the stack and (as for all objects that
456 * are not stillborn, with the exception of "NoSuchMethod" internal
457 * helper objects) expect it to have a non-null map pointer, so we
458 * share an empty Enumerator scope in the runtime.
460 JSObject
*obj
= js_NewGCObject(cx
);
463 obj
->map
= cx
->runtime
->emptyEnumeratorScope
->hold();
464 obj
->init(&js_IteratorClass
, NULL
, NULL
, NullValue());
468 return NewBuiltinClassInstance(cx
, &js_IteratorClass
);
472 NativeIterator::allocateKeyIterator(JSContext
*cx
, uint32 slength
, const AutoIdVector
&props
)
474 size_t plength
= props
.length();
475 NativeIterator
*ni
= (NativeIterator
*)
476 cx
->malloc(sizeof(NativeIterator
) + plength
* sizeof(jsid
) + slength
* sizeof(uint32
));
479 ni
->props_array
= ni
->props_cursor
= (jsid
*) (ni
+ 1);
480 ni
->props_end
= (jsid
*)ni
->props_array
+ plength
;
482 memcpy(ni
->props_array
, props
.begin(), plength
* sizeof(jsid
));
487 NativeIterator::allocateValueIterator(JSContext
*cx
, uint32 slength
, const AutoValueVector
&props
)
489 size_t plength
= props
.length();
490 NativeIterator
*ni
= (NativeIterator
*)
491 cx
->malloc(sizeof(NativeIterator
) + plength
* sizeof(Value
) + slength
* sizeof(uint32
));
494 ni
->props_array
= ni
->props_cursor
= (Value
*) (ni
+ 1);
495 ni
->props_end
= (Value
*)ni
->props_array
+ plength
;
497 memcpy(ni
->props_array
, props
.begin(), plength
* sizeof(Value
));
502 NativeIterator::init(JSObject
*obj
, uintN flags
, const uint32
*sarray
, uint32 slength
, uint32 key
)
506 this->shapes_array
= (uint32
*) this->props_end
;
507 this->shapes_length
= slength
;
508 this->shapes_key
= key
;
510 memcpy(this->shapes_array
, sarray
, slength
* sizeof(uint32
));
514 RegisterEnumerator(JSContext
*cx
, JSObject
*iterobj
, NativeIterator
*ni
)
516 /* Register non-escaping native enumerators (for-in) with the current context. */
517 if (ni
->flags
& JSITER_ENUMERATE
) {
518 ni
->next
= cx
->enumerators
;
519 cx
->enumerators
= iterobj
;
524 VectorToKeyIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
&keys
,
525 const uint32
*sarray
, uint32 slength
, uint32 key
, Value
*vp
)
527 JS_ASSERT(!(flags
& JSITER_FOREACH
));
529 JSObject
*iterobj
= NewIteratorObject(cx
, flags
);
534 NativeIterator
*ni
= NativeIterator::allocateKeyIterator(cx
, slength
, keys
);
537 ni
->init(obj
, flags
, sarray
, slength
, key
);
539 iterobj
->setNativeIterator(ni
);
540 vp
->setObject(*iterobj
);
542 RegisterEnumerator(cx
, iterobj
, ni
);
547 VectorToKeyIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
&props
, Value
*vp
)
549 return VectorToKeyIterator(cx
, obj
, flags
, props
, NULL
, 0, 0, vp
);
553 VectorToValueIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoValueVector
&vals
,
554 const uint32
*sarray
, uint32 slength
, uint32 key
, Value
*vp
)
556 JS_ASSERT(flags
& JSITER_FOREACH
);
558 JSObject
*iterobj
= NewIteratorObject(cx
, flags
);
562 NativeIterator
*ni
= NativeIterator::allocateValueIterator(cx
, slength
, vals
);
565 ni
->init(obj
, flags
, sarray
, slength
, key
);
567 iterobj
->setNativeIterator(ni
);
568 vp
->setObject(*iterobj
);
570 RegisterEnumerator(cx
, iterobj
, ni
);
575 VectorToValueIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoValueVector
&props
, Value
*vp
)
577 return VectorToValueIterator(cx
, obj
, flags
, props
, NULL
, 0, 0, vp
);
581 EnumeratedIdVectorToIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, AutoIdVector
&props
, Value
*vp
)
583 if (!(flags
& JSITER_FOREACH
))
584 return VectorToKeyIterator(cx
, obj
, flags
, props
, vp
);
586 /* For for-each iteration, we need to look up the value of each id. */
588 size_t plength
= props
.length();
590 AutoValueVector
vals(cx
);
591 if (!vals
.reserve(plength
))
594 for (size_t i
= 0; i
< plength
; ++i
) {
595 if (!ValueEnumeration::append(cx
, vals
, obj
, props
[i
], flags
))
599 return VectorToValueIterator(cx
, obj
, flags
, vals
, vp
);
602 typedef Vector
<uint32
, 8> ShapeVector
;
605 GetIterator(JSContext
*cx
, JSObject
*obj
, uintN flags
, Value
*vp
)
609 Vector
<uint32
, 8> shapes(cx
);
612 bool keysOnly
= (flags
== JSITER_ENUMERATE
);
617 * The iterator object for JSITER_ENUMERATE never escapes, so we
618 * don't care for the proper parent/proto to be set. This also
619 * allows us to re-use a previous iterator object that was freed
622 JSObject
*pobj
= obj
;
624 if (!pobj
->isNative() ||
625 obj
->getOps()->enumerate
||
626 pobj
->getClass()->enumerate
!= JS_EnumerateStub
) {
630 uint32 shape
= pobj
->shape();
631 key
= (key
+ (key
<< 16)) ^ shape
;
632 if (!shapes
.append(shape
))
634 pobj
= pobj
->getProto();
637 hash
= key
% JS_ARRAY_LENGTH(JS_THREAD_DATA(cx
)->cachedNativeIterators
);
638 hp
= &JS_THREAD_DATA(cx
)->cachedNativeIterators
[hash
];
639 JSObject
*iterobj
= *hp
;
641 NativeIterator
*ni
= iterobj
->getNativeIterator();
642 if (ni
->shapes_key
== key
&&
643 ni
->shapes_length
== shapes
.length() &&
644 Compare(ni
->shapes_array
, shapes
.begin(), ni
->shapes_length
)) {
645 vp
->setObject(*iterobj
);
648 RegisterEnumerator(cx
, iterobj
, ni
);
656 return JSProxy::iterate(cx
, obj
, flags
, vp
);
657 if (!GetCustomIterator(cx
, obj
, flags
, vp
))
659 if (!vp
->isUndefined())
663 /* NB: for (var p in null) succeeds by iterating over no properties. */
665 if (flags
& JSITER_FOREACH
) {
666 AutoValueVector
vals(cx
);
667 if (JS_LIKELY(obj
!= NULL
) && !Snapshot
<ValueEnumeration
>(cx
, obj
, flags
, vals
))
669 return VectorToValueIterator(cx
, obj
, flags
, vals
, shapes
.begin(), shapes
.length(), key
, vp
);
672 AutoIdVector
keys(cx
);
673 if (JS_LIKELY(obj
!= NULL
) && !Snapshot
<KeyEnumeration
>(cx
, obj
, flags
, keys
))
675 return VectorToKeyIterator(cx
, obj
, flags
, keys
, shapes
.begin(), shapes
.length(), key
, vp
);
679 iterator_iterator(JSContext
*cx
, JSObject
*obj
, JSBool keysonly
)
685 Iterator(JSContext
*cx
, JSObject
*iterobj
, uintN argc
, Value
*argv
, Value
*rval
)
690 keyonly
= js_ValueToBoolean(argv
[1]);
691 flags
= JSITER_OWNONLY
| (keyonly
? 0 : (JSITER_FOREACH
| JSITER_KEYVALUE
));
693 return js_ValueToIterator(cx
, flags
, rval
);
697 js_ThrowStopIteration(JSContext
*cx
)
701 JS_ASSERT(!JS_IsExceptionPending(cx
));
702 if (js_FindClassObject(cx
, NULL
, JSProto_StopIteration
, &v
))
703 SetPendingException(cx
, v
);
708 iterator_next(JSContext
*cx
, uintN argc
, Value
*vp
)
712 obj
= ComputeThisFromVp(cx
, vp
);
713 if (!InstanceOf(cx
, obj
, &js_IteratorClass
, vp
+ 2))
716 if (!js_IteratorMore(cx
, obj
, vp
))
718 if (!vp
->toBoolean()) {
719 js_ThrowStopIteration(cx
);
722 return js_IteratorNext(cx
, obj
, vp
);
725 #define JSPROP_ROPERM (JSPROP_READONLY | JSPROP_PERMANENT)
727 static JSFunctionSpec iterator_methods
[] = {
728 JS_FN(js_next_str
, iterator_next
, 0,JSPROP_ROPERM
),
733 * Call ToObject(v).__iterator__(keyonly) if ToObject(v).__iterator__ exists.
734 * Otherwise construct the default iterator.
736 JS_FRIEND_API(JSBool
)
737 js_ValueToIterator(JSContext
*cx
, uintN flags
, Value
*vp
)
739 /* JSITER_KEYVALUE must always come with JSITER_FOREACH */
740 JS_ASSERT_IF(flags
& JSITER_KEYVALUE
, flags
& JSITER_FOREACH
);
743 * Make sure the more/next state machine doesn't get stuck. A value might be
744 * left in iterValue when a trace is left due to an operation time-out after
745 * JSOP_MOREITER but before the value is picked up by FOR*.
747 cx
->iterValue
.setMagic(JS_NO_ITER_VALUE
);
750 if (vp
->isObject()) {
752 obj
= &vp
->toObject();
755 * Enumerating over null and undefined gives an empty enumerator.
756 * This is contrary to ECMA-262 9.9 ToObject, invoked from step 3 of
757 * the first production in 12.6.4 and step 4 of the second production,
758 * but it's "web JS" compatible. ES5 fixed for-in to match this de-facto
761 if ((flags
& JSITER_ENUMERATE
)) {
762 if (!js_ValueToObjectOrNull(cx
, *vp
, &obj
))
765 return GetIterator(cx
, NULL
, flags
, vp
);
767 obj
= js_ValueToNonNullObject(cx
, *vp
);
773 AutoObjectRooter
tvr(cx
, obj
);
775 /* Enumerate Iterator.prototype directly. */
776 JSIteratorOp op
= obj
->getClass()->ext
.iteratorObject
;
777 if (op
&& (obj
->getClass() != &js_IteratorClass
|| obj
->getNativeIterator())) {
778 JSObject
*iterobj
= op(cx
, obj
, !(flags
& JSITER_FOREACH
));
781 vp
->setObject(*iterobj
);
785 return GetIterator(cx
, obj
, flags
, vp
);
788 #if JS_HAS_GENERATORS
789 static JS_REQUIRES_STACK JSBool
790 CloseGenerator(JSContext
*cx
, JSObject
*genobj
);
793 JS_FRIEND_API(JSBool
)
794 js_CloseIterator(JSContext
*cx
, JSObject
*obj
)
796 cx
->iterValue
.setMagic(JS_NO_ITER_VALUE
);
798 Class
*clasp
= obj
->getClass();
799 if (clasp
== &js_IteratorClass
) {
800 /* Remove enumerators from the active list, which is a stack. */
801 NativeIterator
*ni
= obj
->getNativeIterator();
802 if (ni
->flags
& JSITER_ENUMERATE
) {
803 JS_ASSERT(cx
->enumerators
== obj
);
804 cx
->enumerators
= ni
->next
;
807 /* Cache the iterator object if possible. */
808 if (ni
->shapes_length
) {
809 uint32 hash
= ni
->shapes_key
% NATIVE_ITER_CACHE_SIZE
;
810 JSObject
**hp
= &JS_THREAD_DATA(cx
)->cachedNativeIterators
[hash
];
811 ni
->props_cursor
= ni
->props_array
;
815 iterator_finalize(cx
, obj
);
818 #if JS_HAS_GENERATORS
819 else if (clasp
== &js_GeneratorClass
) {
820 return CloseGenerator(cx
, obj
);
827 * Suppress enumeration of deleted properties. We maintain a list of all active
828 * non-escaping for-in enumerators. Whenever a property is deleted, we check
829 * whether any active enumerator contains the (obj, id) pair and has not
830 * enumerated id yet. If so, we delete the id from the list (or advance the
831 * cursor if it is the next id to be enumerated).
833 * We do not suppress enumeration of a property deleted along an object's
834 * prototype chain. Only direct deletions on the object are handled.
837 js_SuppressDeletedProperty(JSContext
*cx
, JSObject
*obj
, jsid id
)
839 JSObject
*iterobj
= cx
->enumerators
;
842 NativeIterator
*ni
= iterobj
->getNativeIterator();
843 /* This only works for identified surpressed keys, not values. */
844 if (ni
->isKeyIter() && ni
->obj
== obj
&& ni
->props_cursor
< ni
->props_end
) {
845 /* Check whether id is still to come. */
846 jsid
*props_cursor
= ni
->currentKey();
847 jsid
*props_end
= ni
->endKey();
848 for (jsid
*idp
= props_cursor
; idp
< props_end
; ++idp
) {
851 * Check whether another property along the prototype chain
852 * became visible as a result of this deletion.
854 if (obj
->getProto()) {
855 AutoObjectRooter
proto(cx
, obj
->getProto());
856 AutoObjectRooter
obj2(cx
);
858 if (!proto
.object()->lookupProperty(cx
, id
, obj2
.addr(), &prop
))
862 if (obj2
.object()->isNative()) {
863 attrs
= ((JSScopeProperty
*) prop
)->attributes();
864 JS_UNLOCK_OBJ(cx
, obj2
.object());
865 } else if (!obj2
.object()->getAttributes(cx
, id
, &attrs
)) {
868 if (attrs
& JSPROP_ENUMERATE
)
874 * If lookupProperty or getAttributes above removed a property from
877 if (props_end
!= ni
->props_end
|| props_cursor
!= ni
->props_cursor
)
881 * No property along the prototype chain steppeded in to take the
882 * property's place, so go ahead and delete id from the list.
883 * If it is the next property to be enumerated, just skip it.
885 if (idp
== props_cursor
) {
888 memmove(idp
, idp
+ 1, (props_end
- (idp
+ 1)) * sizeof(jsid
));
889 ni
->props_end
= ni
->endKey() - 1;
901 js_IteratorMore(JSContext
*cx
, JSObject
*iterobj
, Value
*rval
)
903 /* Fast path for native iterators */
904 if (iterobj
->getClass() == &js_IteratorClass
) {
906 * Implement next directly as all the methods of native iterator are
907 * read-only and permanent.
909 NativeIterator
*ni
= iterobj
->getNativeIterator();
910 rval
->setBoolean(ni
->props_cursor
< ni
->props_end
);
914 /* We might still have a pending value. */
915 if (!cx
->iterValue
.isMagic(JS_NO_ITER_VALUE
)) {
916 rval
->setBoolean(true);
920 /* Fetch and cache the next value from the iterator. */
921 jsid id
= ATOM_TO_JSID(cx
->runtime
->atomState
.nextAtom
);
922 if (!js_GetMethod(cx
, iterobj
, id
, JSGET_METHOD_BARRIER
, rval
))
924 if (!InternalCall(cx
, iterobj
, *rval
, 0, NULL
, rval
)) {
925 /* Check for StopIteration. */
926 if (!cx
->throwing
|| !js_ValueIsStopIteration(cx
->exception
))
929 /* Inline JS_ClearPendingException(cx). */
930 cx
->throwing
= JS_FALSE
;
931 cx
->exception
.setUndefined();
932 cx
->iterValue
.setMagic(JS_NO_ITER_VALUE
);
933 rval
->setBoolean(false);
937 /* Cache the value returned by iterobj.next() so js_IteratorNext() can find it. */
938 JS_ASSERT(!rval
->isMagic(JS_NO_ITER_VALUE
));
939 cx
->iterValue
= *rval
;
940 rval
->setBoolean(true);
945 js_IteratorNext(JSContext
*cx
, JSObject
*iterobj
, Value
*rval
)
947 /* Fast path for native iterators */
948 if (iterobj
->getClass() == &js_IteratorClass
) {
950 * Implement next directly as all the methods of the native iterator are
951 * read-only and permanent.
953 NativeIterator
*ni
= iterobj
->getNativeIterator();
954 JS_ASSERT(ni
->props_cursor
< ni
->props_end
);
955 if (ni
->isKeyIter()) {
956 *rval
= IdToValue(*ni
->currentKey());
959 *rval
= *ni
->currentValue();
960 ni
->incValueCursor();
963 if (rval
->isString() || !ni
->isKeyIter())
968 if (rval
->isInt32() && (jsuint(i
= rval
->toInt32()) < INT_STRING_LIMIT
)) {
969 str
= JSString::intString(i
);
971 str
= js_ValueToString(cx
, *rval
);
976 rval
->setString(str
);
980 JS_ASSERT(!cx
->iterValue
.isMagic(JS_NO_ITER_VALUE
));
981 *rval
= cx
->iterValue
;
982 cx
->iterValue
.setMagic(JS_NO_ITER_VALUE
);
988 stopiter_hasInstance(JSContext
*cx
, JSObject
*obj
, const Value
*v
, JSBool
*bp
)
990 *bp
= js_ValueIsStopIteration(*v
);
994 Class js_StopIterationClass
= {
995 js_StopIteration_str
,
996 JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration
),
997 PropertyStub
, /* addProperty */
998 PropertyStub
, /* delProperty */
999 PropertyStub
, /* getProperty */
1000 PropertyStub
, /* setProperty */
1004 NULL
, /* finalize */
1005 NULL
, /* reserved0 */
1006 NULL
, /* checkAccess */
1008 NULL
, /* construct */
1009 NULL
, /* xdrObject */
1010 stopiter_hasInstance
1013 #if JS_HAS_GENERATORS
1016 generator_finalize(JSContext
*cx
, JSObject
*obj
)
1018 JSGenerator
*gen
= (JSGenerator
*) obj
->getPrivate();
1023 * gen is open when a script has not called its close method while
1024 * explicitly manipulating it.
1026 JS_ASSERT(gen
->state
== JSGEN_NEWBORN
||
1027 gen
->state
== JSGEN_CLOSED
||
1028 gen
->state
== JSGEN_OPEN
);
1033 generator_trace(JSTracer
*trc
, JSObject
*obj
)
1035 JSGenerator
*gen
= (JSGenerator
*) obj
->getPrivate();
1040 * Do not mark if the generator is running; the contents may be trash and
1041 * will be replaced when the generator stops.
1043 if (gen
->state
== JSGEN_RUNNING
|| gen
->state
== JSGEN_CLOSING
)
1046 JSStackFrame
*fp
= gen
->getFloatingFrame();
1047 JS_ASSERT(gen
->getLiveFrame() == fp
);
1048 MarkValueRange(trc
, gen
->floatingStack
, fp
->argEnd(), "generator slots");
1049 js_TraceStackFrame(trc
, fp
);
1050 MarkValueRange(trc
, fp
->slots(), gen
->savedRegs
.sp
, "generator slots");
1053 Class js_GeneratorClass
= {
1055 JSCLASS_HAS_PRIVATE
| JSCLASS_HAS_CACHED_PROTO(JSProto_Generator
) |
1056 JSCLASS_IS_ANONYMOUS
| JSCLASS_MARK_IS_TRACE
,
1057 PropertyStub
, /* addProperty */
1058 PropertyStub
, /* delProperty */
1059 PropertyStub
, /* getProperty */
1060 PropertyStub
, /* setProperty */
1065 NULL
, /* reserved */
1066 NULL
, /* checkAccess */
1068 NULL
, /* construct */
1069 NULL
, /* xdrObject */
1070 NULL
, /* hasInstance */
1071 JS_CLASS_TRACE(generator_trace
),
1073 NULL
, /* equality */
1074 NULL
, /* outerObject */
1075 NULL
, /* innerObject */
1077 NULL
, /* wrappedObject */
1082 * Called from the JSOP_GENERATOR case in the interpreter, with fp referring
1083 * to the frame by which the generator function was activated. Create a new
1084 * JSGenerator object, which contains its own JSStackFrame that we populate
1085 * from *fp. We know that upon return, the JSOP_GENERATOR opcode will return
1086 * from the activation in fp, so we can steal away fp->callobj and fp->argsobj
1087 * if they are non-null.
1089 JS_REQUIRES_STACK JSObject
*
1090 js_NewGenerator(JSContext
*cx
)
1092 JSObject
*obj
= NewBuiltinClassInstance(cx
, &js_GeneratorClass
);
1096 /* Load and compute stack slot counts. */
1097 JSStackFrame
*fp
= cx
->fp
;
1098 uintN argc
= fp
->argc
;
1099 uintN nargs
= JS_MAX(argc
, fp
->fun
->nargs
);
1100 uintN vplen
= 2 + nargs
;
1102 /* Compute JSGenerator size. */
1103 uintN nbytes
= sizeof(JSGenerator
) +
1104 (-1 + /* one Value included in JSGenerator */
1106 VALUES_PER_STACK_FRAME
+
1107 fp
->script
->nslots
) * sizeof(Value
);
1109 JSGenerator
*gen
= (JSGenerator
*) cx
->malloc(nbytes
);
1113 /* Cut up floatingStack space. */
1114 Value
*vp
= gen
->floatingStack
;
1115 JSStackFrame
*newfp
= reinterpret_cast<JSStackFrame
*>(vp
+ vplen
);
1116 Value
*slots
= newfp
->slots();
1118 /* Initialize JSGenerator. */
1120 gen
->state
= JSGEN_NEWBORN
;
1121 gen
->savedRegs
.pc
= cx
->regs
->pc
;
1122 JS_ASSERT(cx
->regs
->sp
== fp
->slots() + fp
->script
->nfixed
);
1123 gen
->savedRegs
.sp
= slots
+ fp
->script
->nfixed
;
1125 gen
->enumerators
= NULL
;
1126 gen
->liveFrame
= newfp
;
1128 /* Copy generator's stack frame copy in from |cx->fp|. */
1129 newfp
->setCallObj(fp
->maybeCallObj());
1130 if (fp
->hasCallObj()) { /* Steal call object. */
1131 fp
->getCallObj()->setPrivate(newfp
);
1132 fp
->setCallObj(NULL
);
1134 newfp
->setArgsObj(fp
->maybeArgsObj());
1135 if (fp
->hasArgsObj()) { /* Steal args object. */
1136 fp
->getArgsObj()->setPrivate(newfp
);
1137 fp
->setArgsObj(NULL
);
1139 newfp
->script
= fp
->script
;
1140 newfp
->fun
= fp
->fun
;
1141 newfp
->thisv
= fp
->thisv
;
1142 newfp
->argc
= fp
->argc
;
1143 newfp
->argv
= vp
+ 2;
1144 newfp
->rval
= fp
->rval
;
1145 newfp
->setAnnotation(NULL
);
1146 newfp
->setScopeChain(fp
->maybeScopeChain());
1147 JS_ASSERT(!fp
->hasBlockChain());
1148 newfp
->setBlockChain(NULL
);
1149 newfp
->flags
= fp
->flags
| JSFRAME_GENERATOR
| JSFRAME_FLOATING_GENERATOR
;
1150 JS_ASSERT(!newfp
->hasIMacroPC());
1152 /* Copy in arguments and slots. */
1153 memcpy(vp
, fp
->argv
- 2, vplen
* sizeof(Value
));
1154 memcpy(slots
, fp
->slots(), fp
->script
->nfixed
* sizeof(Value
));
1156 obj
->setPrivate(gen
);
1161 js_FloatingFrameToGenerator(JSStackFrame
*fp
)
1163 JS_ASSERT(fp
->isGenerator() && fp
->isFloatingGenerator());
1164 char *floatingStackp
= (char *)(fp
->argv
- 2);
1165 char *p
= floatingStackp
- offsetof(JSGenerator
, floatingStack
);
1166 return reinterpret_cast<JSGenerator
*>(p
);
1169 typedef enum JSGeneratorOp
{
1177 * Start newborn or restart yielding generator and perform the requested
1178 * operation inside its frame.
1180 static JS_REQUIRES_STACK JSBool
1181 SendToGenerator(JSContext
*cx
, JSGeneratorOp op
, JSObject
*obj
,
1182 JSGenerator
*gen
, const Value
&arg
)
1184 if (gen
->state
== JSGEN_RUNNING
|| gen
->state
== JSGEN_CLOSING
) {
1185 js_ReportValueError(cx
, JSMSG_NESTING_GENERATOR
,
1186 JSDVG_SEARCH_STACK
, ObjectOrNullValue(obj
),
1187 JS_GetFunctionId(gen
->getFloatingFrame()->fun
));
1191 /* Check for OOM errors here, where we can fail easily. */
1192 if (!cx
->ensureGeneratorStackSpace())
1195 JS_ASSERT(gen
->state
== JSGEN_NEWBORN
|| gen
->state
== JSGEN_OPEN
);
1199 if (gen
->state
== JSGEN_OPEN
) {
1201 * Store the argument to send as the result of the yield
1204 gen
->savedRegs
.sp
[-1] = arg
;
1206 gen
->state
= JSGEN_RUNNING
;
1210 SetPendingException(cx
, arg
);
1211 gen
->state
= JSGEN_RUNNING
;
1215 JS_ASSERT(op
== JSGENOP_CLOSE
);
1216 SetPendingException(cx
, MagicValue(JS_GENERATOR_CLOSING
));
1217 gen
->state
= JSGEN_CLOSING
;
1221 JSStackFrame
*genfp
= gen
->getFloatingFrame();
1224 Value
*genVp
= gen
->floatingStack
;
1225 uintN vplen
= gen
->vplen
;
1226 uintN nfixed
= genfp
->script
->nslots
;
1229 * Get a pointer to new frame/slots. This memory is not "claimed", so
1230 * the code before pushExecuteFrame must not reenter the interpreter.
1232 ExecuteFrameGuard frame
;
1233 if (!cx
->stack().getExecuteFrame(cx
, cx
->fp
, vplen
, nfixed
, frame
)) {
1234 gen
->state
= JSGEN_CLOSED
;
1238 Value
*vp
= frame
.getvp();
1239 JSStackFrame
*fp
= frame
.getFrame();
1242 * Copy and rebase stack frame/args/slots. The "floating" flag must
1243 * only be set on the generator's frame. See args_or_call_trace.
1245 uintN usedBefore
= gen
->savedRegs
.sp
- genVp
;
1246 memcpy(vp
, genVp
, usedBefore
* sizeof(Value
));
1247 fp
->flags
&= ~JSFRAME_FLOATING_GENERATOR
;
1249 gen
->savedRegs
.sp
= fp
->slots() + (gen
->savedRegs
.sp
- genfp
->slots());
1250 JS_ASSERT(uintN(gen
->savedRegs
.sp
- fp
->slots()) <= fp
->script
->nslots
);
1253 JSObject
*callobjBefore
= fp
->maybeCallObj();
1254 JSObject
*argsobjBefore
= fp
->maybeArgsObj();
1258 * Repoint Call, Arguments, Block and With objects to the new live
1259 * frame. Call and Arguments are done directly because we have
1260 * pointers to them. Block and With objects are done indirectly through
1261 * 'liveFrame'. See js_LiveFrameToFloating comment in jsiter.h.
1263 if (genfp
->hasCallObj())
1264 fp
->getCallObj()->setPrivate(fp
);
1265 if (genfp
->hasArgsObj())
1266 fp
->getArgsObj()->setPrivate(fp
);
1267 gen
->liveFrame
= fp
;
1268 (void)cx
->enterGenerator(gen
); /* OOM check above. */
1270 /* Officially push |fp|. |frame|'s destructor pops. */
1271 cx
->stack().pushExecuteFrame(cx
, frame
, gen
->savedRegs
, NULL
);
1273 /* Swap the enumerators stack for the generator's stack. */
1274 JSObject
*enumerators
= cx
->enumerators
;
1275 cx
->enumerators
= gen
->enumerators
;
1279 /* Restore the original enumerators stack. */
1280 gen
->enumerators
= cx
->enumerators
;
1281 cx
->enumerators
= enumerators
;
1283 /* Restore call/args/block objects. */
1284 cx
->leaveGenerator(gen
);
1285 gen
->liveFrame
= genfp
;
1286 if (fp
->hasArgsObj())
1287 fp
->getArgsObj()->setPrivate(genfp
);
1288 if (fp
->hasCallObj())
1289 fp
->getCallObj()->setPrivate(genfp
);
1291 JS_ASSERT_IF(argsobjBefore
, argsobjBefore
== fp
->maybeArgsObj());
1292 JS_ASSERT_IF(callobjBefore
, callobjBefore
== fp
->maybeCallObj());
1294 /* Copy and rebase stack frame/args/slots. Restore "floating" flag. */
1295 JS_ASSERT(uintN(gen
->savedRegs
.sp
- fp
->slots()) <= fp
->script
->nslots
);
1296 uintN usedAfter
= gen
->savedRegs
.sp
- vp
;
1297 memcpy(genVp
, vp
, usedAfter
* sizeof(Value
));
1298 genfp
->flags
|= JSFRAME_FLOATING_GENERATOR
;
1299 genfp
->argv
= genVp
+ 2;
1300 gen
->savedRegs
.sp
= genfp
->slots() + (gen
->savedRegs
.sp
- fp
->slots());
1301 JS_ASSERT(uintN(gen
->savedRegs
.sp
- genfp
->slots()) <= genfp
->script
->nslots
);
1304 if (gen
->getFloatingFrame()->flags
& JSFRAME_YIELDING
) {
1305 /* Yield cannot fail, throw or be called on closing. */
1307 JS_ASSERT(!cx
->throwing
);
1308 JS_ASSERT(gen
->state
== JSGEN_RUNNING
);
1309 JS_ASSERT(op
!= JSGENOP_CLOSE
);
1310 genfp
->flags
&= ~JSFRAME_YIELDING
;
1311 gen
->state
= JSGEN_OPEN
;
1315 genfp
->rval
.setUndefined();
1316 gen
->state
= JSGEN_CLOSED
;
1318 /* Returned, explicitly or by falling off the end. */
1319 if (op
== JSGENOP_CLOSE
)
1321 return js_ThrowStopIteration(cx
);
1325 * An error, silent termination by operation callback or an exception.
1326 * Propagate the condition to the caller.
1331 static JS_REQUIRES_STACK JSBool
1332 CloseGenerator(JSContext
*cx
, JSObject
*obj
)
1334 JS_ASSERT(obj
->getClass() == &js_GeneratorClass
);
1336 JSGenerator
*gen
= (JSGenerator
*) obj
->getPrivate();
1338 /* Generator prototype object. */
1342 if (gen
->state
== JSGEN_CLOSED
)
1345 return SendToGenerator(cx
, JSGENOP_CLOSE
, obj
, gen
, UndefinedValue());
1349 * Common subroutine of generator_(next|send|throw|close) methods.
1352 generator_op(JSContext
*cx
, JSGeneratorOp op
, Value
*vp
, uintN argc
)
1357 obj
= ComputeThisFromVp(cx
, vp
);
1358 if (!InstanceOf(cx
, obj
, &js_GeneratorClass
, vp
+ 2))
1361 JSGenerator
*gen
= (JSGenerator
*) obj
->getPrivate();
1363 /* This happens when obj is the generator prototype. See bug 352885. */
1364 goto closed_generator
;
1367 if (gen
->state
== JSGEN_NEWBORN
) {
1374 if (argc
>= 1 && !vp
[2].isUndefined()) {
1375 js_ReportValueError(cx
, JSMSG_BAD_GENERATOR_SEND
,
1376 JSDVG_SEARCH_STACK
, vp
[2], NULL
);
1382 JS_ASSERT(op
== JSGENOP_CLOSE
);
1383 gen
->state
= JSGEN_CLOSED
;
1386 } else if (gen
->state
== JSGEN_CLOSED
) {
1391 return js_ThrowStopIteration(cx
);
1393 SetPendingException(cx
, argc
>= 1 ? vp
[2] : UndefinedValue());
1396 JS_ASSERT(op
== JSGENOP_CLOSE
);
1401 bool undef
= ((op
== JSGENOP_SEND
|| op
== JSGENOP_THROW
) && argc
!= 0);
1402 if (!SendToGenerator(cx
, op
, obj
, gen
, undef
? vp
[2] : UndefinedValue()))
1404 *vp
= gen
->getFloatingFrame()->rval
;
1409 generator_send(JSContext
*cx
, uintN argc
, Value
*vp
)
1411 return generator_op(cx
, JSGENOP_SEND
, vp
, argc
);
1415 generator_next(JSContext
*cx
, uintN argc
, Value
*vp
)
1417 return generator_op(cx
, JSGENOP_NEXT
, vp
, argc
);
1421 generator_throw(JSContext
*cx
, uintN argc
, Value
*vp
)
1423 return generator_op(cx
, JSGENOP_THROW
, vp
, argc
);
1427 generator_close(JSContext
*cx
, uintN argc
, Value
*vp
)
1429 return generator_op(cx
, JSGENOP_CLOSE
, vp
, argc
);
1432 static JSFunctionSpec generator_methods
[] = {
1433 JS_FN(js_next_str
, generator_next
, 0,JSPROP_ROPERM
),
1434 JS_FN(js_send_str
, generator_send
, 1,JSPROP_ROPERM
),
1435 JS_FN(js_throw_str
, generator_throw
, 1,JSPROP_ROPERM
),
1436 JS_FN(js_close_str
, generator_close
, 0,JSPROP_ROPERM
),
1440 #endif /* JS_HAS_GENERATORS */
1443 js_InitIteratorClasses(JSContext
*cx
, JSObject
*obj
)
1445 JSObject
*proto
, *stop
;
1447 /* Idempotency required: we initialize several things, possibly lazily. */
1448 if (!js_GetClassObject(cx
, obj
, JSProto_StopIteration
, &stop
))
1453 proto
= js_InitClass(cx
, obj
, NULL
, &js_IteratorClass
, Iterator
, 2,
1454 NULL
, iterator_methods
, NULL
, NULL
);
1458 #if JS_HAS_GENERATORS
1459 /* Initialize the generator internals if configured. */
1460 if (!js_InitClass(cx
, obj
, NULL
, &js_GeneratorClass
, NULL
, 0,
1461 NULL
, generator_methods
, NULL
, NULL
)) {
1466 return js_InitClass(cx
, obj
, NULL
, &js_StopIterationClass
, NULL
, 0,
1467 NULL
, NULL
, NULL
, NULL
);