1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sw=4 et tw=78:
3  *
4  * ***** BEGIN LICENSE BLOCK *****
5  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6  *
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/
11  *
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
15  * License.
16  *
17  * The Original Code is Mozilla Communicator client code, released
18  * March 31, 1998.
19  *
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.
24  *
25  * Contributor(s):
26  *
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.
38  *
39  * ***** END LICENSE BLOCK ***** */
40 
41 /*
42  * JavaScript iterators.
43  */
44 #include "jsstddef.h"
45 #include <string.h>     /* for memcpy */
46 #include "jstypes.h"
47 #include "jsutil.h"
48 #include "jsarena.h"
49 #include "jsapi.h"
50 #include "jsarray.h"
51 #include "jsatom.h"
52 #include "jsbool.h"
53 #include "jscntxt.h"
54 #include "jsconfig.h"
55 #include "jsexn.h"
56 #include "jsfun.h"
57 #include "jsgc.h"
58 #include "jsinterp.h"
59 #include "jsiter.h"
60 #include "jslock.h"
61 #include "jsnum.h"
62 #include "jsobj.h"
63 #include "jsopcode.h"
64 #include "jsscope.h"
65 #include "jsscript.h"
66 
67 #if JS_HAS_XML_SUPPORT
68 #include "jsxml.h"
69 #endif
70 
71 extern const char js_throw_str[]; /* from jsscan.h */
72 
73 #define JSSLOT_ITER_STATE       (JSSLOT_PRIVATE)
74 #define JSSLOT_ITER_FLAGS       (JSSLOT_PRIVATE + 1)
75 
76 #if JSSLOT_ITER_FLAGS >= JS_INITIAL_NSLOTS
77 #error JS_INITIAL_NSLOTS must be greater than JSSLOT_ITER_FLAGS.
78 #endif
79 
80 /*
81  * Shared code to close iterator's state either through an explicit call or
82  * when GC detects that the iterator is no longer reachable.
83  */
84 void
js_CloseIteratorState(JSContext * cx,JSObject * iterobj)85 js_CloseIteratorState(JSContext *cx, JSObject *iterobj)
86 {
87     jsval *slots;
88     jsval state, parent;
89     JSObject *iterable;
90 
91     JS_ASSERT(JS_InstanceOf(cx, iterobj, &js_IteratorClass, NULL));
92     slots = iterobj->slots;
93 
94     /* Avoid double work if js_CloseNativeIterator was called on obj. */
95     state = slots[JSSLOT_ITER_STATE];
96     if (JSVAL_IS_NULL(state))
97         return;
98 
99     /* Protect against failure to fully initialize obj. */
100     parent = slots[JSSLOT_PARENT];
101     if (!JSVAL_IS_PRIMITIVE(parent)) {
102         iterable = JSVAL_TO_OBJECT(parent);
103 #if JS_HAS_XML_SUPPORT
104         if ((JSVAL_TO_INT(slots[JSSLOT_ITER_FLAGS]) & JSITER_FOREACH) &&
105             OBJECT_IS_XML(cx, iterable)) {
106             ((JSXMLObjectOps *) iterable->map->ops)->
107                 enumerateValues(cx, iterable, JSENUMERATE_DESTROY, &state,
108                                 NULL, NULL);
109         } else
110 #endif
111             OBJ_ENUMERATE(cx, iterable, JSENUMERATE_DESTROY, &state, NULL);
112     }
113     slots[JSSLOT_ITER_STATE] = JSVAL_NULL;
114 }
115 
116 JSClass js_IteratorClass = {
117     "Iterator",
118     JSCLASS_HAS_RESERVED_SLOTS(2) | /* slots for state and flags */
119     JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator),
120     JS_PropertyStub,  JS_PropertyStub,  JS_PropertyStub,  JS_PropertyStub,
121     JS_EnumerateStub, JS_ResolveStub,   JS_ConvertStub,   JS_FinalizeStub,
122     JSCLASS_NO_OPTIONAL_MEMBERS
123 };
124 
125 static JSBool
InitNativeIterator(JSContext * cx,JSObject * iterobj,JSObject * obj,uintN flags)126 InitNativeIterator(JSContext *cx, JSObject *iterobj, JSObject *obj, uintN flags)
127 {
128     jsval state;
129     JSBool ok;
130 
131     JS_ASSERT(JSVAL_TO_PRIVATE(iterobj->slots[JSSLOT_CLASS]) ==
132               &js_IteratorClass);
133 
134     /* Initialize iterobj in case of enumerate hook failure. */
135     iterobj->slots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(obj);
136     iterobj->slots[JSSLOT_ITER_STATE] = JSVAL_NULL;
137     iterobj->slots[JSSLOT_ITER_FLAGS] = INT_TO_JSVAL(flags);
138     if (!js_RegisterCloseableIterator(cx, iterobj))
139         return JS_FALSE;
140     if (!obj)
141         return JS_TRUE;
142 
143     ok =
144 #if JS_HAS_XML_SUPPORT
145          ((flags & JSITER_FOREACH) && OBJECT_IS_XML(cx, obj))
146          ? ((JSXMLObjectOps *) obj->map->ops)->
147                enumerateValues(cx, obj, JSENUMERATE_INIT, &state, NULL, NULL)
148          :
149 #endif
150            OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &state, NULL);
151     if (!ok)
152         return JS_FALSE;
153 
154     iterobj->slots[JSSLOT_ITER_STATE] = state;
155     if (flags & JSITER_ENUMERATE) {
156         /*
157          * The enumerating iterator needs the original object to suppress
158          * enumeration of deleted or shadowed prototype properties. Since the
159          * enumerator never escapes to scripts, we use the prototype slot to
160          * store the original object.
161          */
162         JS_ASSERT(obj != iterobj);
163         iterobj->slots[JSSLOT_PROTO] = OBJECT_TO_JSVAL(obj);
164     }
165     return JS_TRUE;
166 }
167 
168 static JSBool
Iterator(JSContext * cx,JSObject * iterobj,uintN argc,jsval * argv,jsval * rval)169 Iterator(JSContext *cx, JSObject *iterobj, uintN argc, jsval *argv, jsval *rval)
170 {
171     JSBool keyonly;
172     uintN flags;
173     JSObject *obj;
174 
175     keyonly = JS_FALSE;
176     if (!js_ValueToBoolean(cx, argv[1], &keyonly))
177         return JS_FALSE;
178     flags = keyonly ? 0 : JSITER_FOREACH;
179 
180     if (cx->fp->flags & JSFRAME_CONSTRUCTING) {
181         /* XXX work around old valueOf call hidden beneath js_ValueToObject */
182         if (!JSVAL_IS_PRIMITIVE(argv[0])) {
183             obj = JSVAL_TO_OBJECT(argv[0]);
184         } else {
185             obj = js_ValueToNonNullObject(cx, argv[0]);
186             if (!obj)
187                 return JS_FALSE;
188             argv[0] = OBJECT_TO_JSVAL(obj);
189         }
190         return InitNativeIterator(cx, iterobj, obj, flags);
191     }
192 
193     *rval = argv[0];
194     return js_ValueToIterator(cx, flags, rval);
195 }
196 
197 static JSBool
NewKeyValuePair(JSContext * cx,jsid key,jsval val,jsval * rval)198 NewKeyValuePair(JSContext *cx, jsid key, jsval val, jsval *rval)
199 {
200     jsval vec[2];
201     JSTempValueRooter tvr;
202     JSObject *aobj;
203 
204     vec[0] = ID_TO_VALUE(key);
205     vec[1] = val;
206 
207     JS_PUSH_TEMP_ROOT(cx, 2, vec, &tvr);
208     aobj = js_NewArrayObject(cx, 2, vec);
209     *rval = OBJECT_TO_JSVAL(aobj);
210     JS_POP_TEMP_ROOT(cx, &tvr);
211 
212     return aobj != NULL;
213 }
214 
215 static JSBool
IteratorNextImpl(JSContext * cx,JSObject * obj,jsval * rval)216 IteratorNextImpl(JSContext *cx, JSObject *obj, jsval *rval)
217 {
218     JSObject *iterable;
219     jsval state;
220     uintN flags;
221     JSBool foreach, ok;
222     jsid id;
223 
224     JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_IteratorClass);
225 
226     iterable = OBJ_GET_PARENT(cx, obj);
227     JS_ASSERT(iterable);
228     state = OBJ_GET_SLOT(cx, obj, JSSLOT_ITER_STATE);
229     if (JSVAL_IS_NULL(state))
230         goto stop;
231 
232     flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, obj, JSSLOT_ITER_FLAGS));
233     JS_ASSERT(!(flags & JSITER_ENUMERATE));
234     foreach = (flags & JSITER_FOREACH) != 0;
235     ok =
236 #if JS_HAS_XML_SUPPORT
237          (foreach && OBJECT_IS_XML(cx, iterable))
238          ? ((JSXMLObjectOps *) iterable->map->ops)->
239                enumerateValues(cx, iterable, JSENUMERATE_NEXT, &state,
240                                &id, rval)
241          :
242 #endif
243            OBJ_ENUMERATE(cx, iterable, JSENUMERATE_NEXT, &state, &id);
244     if (!ok)
245         return JS_FALSE;
246 
247     OBJ_SET_SLOT(cx, obj, JSSLOT_ITER_STATE, state);
248     if (JSVAL_IS_NULL(state))
249         goto stop;
250 
251     if (foreach) {
252 #if JS_HAS_XML_SUPPORT
253         if (!OBJECT_IS_XML(cx, iterable) &&
254             !OBJ_GET_PROPERTY(cx, iterable, id, rval)) {
255             return JS_FALSE;
256         }
257 #endif
258         if (!NewKeyValuePair(cx, id, *rval, rval))
259             return JS_FALSE;
260     } else {
261         *rval = ID_TO_VALUE(id);
262     }
263     return JS_TRUE;
264 
265   stop:
266     JS_ASSERT(OBJ_GET_SLOT(cx, obj, JSSLOT_ITER_STATE) == JSVAL_NULL);
267     *rval = JSVAL_HOLE;
268     return JS_TRUE;
269 }
270 
271 static JSBool
js_ThrowStopIteration(JSContext * cx,JSObject * obj)272 js_ThrowStopIteration(JSContext *cx, JSObject *obj)
273 {
274     jsval v;
275 
276     JS_ASSERT(!JS_IsExceptionPending(cx));
277     if (js_FindClassObject(cx, NULL, INT_TO_JSID(JSProto_StopIteration), &v))
278         JS_SetPendingException(cx, v);
279     return JS_FALSE;
280 }
281 
282 static JSBool
iterator_next(JSContext * cx,JSObject * obj,uintN argc,jsval * argv,jsval * rval)283 iterator_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
284               jsval *rval)
285 {
286     if (!JS_InstanceOf(cx, obj, &js_IteratorClass, argv))
287         return JS_FALSE;
288 
289     if (!IteratorNextImpl(cx, obj, rval))
290         return JS_FALSE;
291 
292     if (*rval == JSVAL_HOLE) {
293         *rval = JSVAL_NULL;
294         js_ThrowStopIteration(cx, obj);
295         return JS_FALSE;
296     }
297     return JS_TRUE;
298 }
299 
300 static JSBool
iterator_self(JSContext * cx,JSObject * obj,uintN argc,jsval * argv,jsval * rval)301 iterator_self(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
302               jsval *rval)
303 {
304     *rval = OBJECT_TO_JSVAL(obj);
305     return JS_TRUE;
306 }
307 
308 static JSFunctionSpec iterator_methods[] = {
309     {js_iterator_str, iterator_self, 0,JSPROP_READONLY|JSPROP_PERMANENT,0},
310     {js_next_str,     iterator_next, 0,JSPROP_READONLY|JSPROP_PERMANENT,0},
311     {0,0,0,0,0}
312 };
313 
314 uintN
js_GetNativeIteratorFlags(JSContext * cx,JSObject * iterobj)315 js_GetNativeIteratorFlags(JSContext *cx, JSObject *iterobj)
316 {
317     if (OBJ_GET_CLASS(cx, iterobj) != &js_IteratorClass)
318         return 0;
319     return JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
320 }
321 
322 void
js_CloseNativeIterator(JSContext * cx,JSObject * iterobj)323 js_CloseNativeIterator(JSContext *cx, JSObject *iterobj)
324 {
325     uintN flags;
326 
327     /*
328      * If this iterator is not an instance of the native default iterator
329      * class, leave it to be GC'ed.
330      */
331     if (!JS_InstanceOf(cx, iterobj, &js_IteratorClass, NULL))
332         return;
333 
334     /*
335      * If this iterator was not created by js_ValueToIterator called from the
336      * for-in loop code in js_Interpret, leave it to be GC'ed.
337      */
338     flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
339     if (!(flags & JSITER_ENUMERATE))
340         return;
341 
342     js_CloseIteratorState(cx, iterobj);
343 }
344 
345 /*
346  * Call ToObject(v).__iterator__(keyonly) if ToObject(v).__iterator__ exists.
347  * Otherwise construct the defualt iterator.
348  */
349 JSBool
js_ValueToIterator(JSContext * cx,uintN flags,jsval * vp)350 js_ValueToIterator(JSContext *cx, uintN flags, jsval *vp)
351 {
352     JSObject *obj;
353     JSTempValueRooter tvr;
354     const JSAtom *atom;
355     JSBool ok;
356     JSObject *iterobj;
357     jsval arg;
358     JSString *str;
359 
360     JS_ASSERT(!(flags & ~(JSITER_ENUMERATE |
361                           JSITER_FOREACH |
362                           JSITER_KEYVALUE)));
363 
364     /* JSITER_KEYVALUE must always come with JSITER_FOREACH */
365     JS_ASSERT(!(flags & JSITER_KEYVALUE) || (flags & JSITER_FOREACH));
366 
367     /* XXX work around old valueOf call hidden beneath js_ValueToObject */
368     if (!JSVAL_IS_PRIMITIVE(*vp)) {
369         obj = JSVAL_TO_OBJECT(*vp);
370     } else {
371         /*
372          * Enumerating over null and undefined gives an empty enumerator.
373          * This is contrary to ECMA-262 9.9 ToObject, invoked from step 3 of
374          * the first production in 12.6.4 and step 4 of the second production,
375          * but it's "web JS" compatible.
376          */
377         if ((flags & JSITER_ENUMERATE)) {
378             if (!js_ValueToObject(cx, *vp, &obj))
379                 return JS_FALSE;
380             if (!obj)
381                 goto default_iter;
382         } else {
383             obj = js_ValueToNonNullObject(cx, *vp);
384             if (!obj)
385                 return JS_FALSE;
386         }
387     }
388 
389     JS_ASSERT(obj);
390     JS_PUSH_TEMP_ROOT_OBJECT(cx, obj, &tvr);
391 
392     atom = cx->runtime->atomState.iteratorAtom;
393 #if JS_HAS_XML_SUPPORT
394     if (OBJECT_IS_XML(cx, obj)) {
395         if (!js_GetXMLFunction(cx, obj, ATOM_TO_JSID(atom), vp))
396             goto bad;
397     } else
398 #endif
399     {
400         if (!OBJ_GET_PROPERTY(cx, obj, ATOM_TO_JSID(atom), vp))
401             goto bad;
402     }
403 
404     if (JSVAL_IS_VOID(*vp)) {
405       default_iter:
406         /*
407          * Fail over to the default enumerating native iterator.
408          *
409          * Create iterobj with a NULL parent to ensure that we use the correct
410          * scope chain to lookup the iterator's constructor. Since we use the
411          * parent slot to keep track of the iterable, we must fix it up after.
412          */
413         iterobj = js_NewObject(cx, &js_IteratorClass, NULL, NULL);
414         if (!iterobj)
415             goto bad;
416 
417         /* Store iterobj in *vp to protect it from GC (callers must root vp). */
418         *vp = OBJECT_TO_JSVAL(iterobj);
419 
420         if (!InitNativeIterator(cx, iterobj, obj, flags))
421             goto bad;
422     } else {
423         arg = BOOLEAN_TO_JSVAL((flags & JSITER_FOREACH) == 0);
424         if (!js_InternalInvoke(cx, obj, *vp, JSINVOKE_ITERATOR, 1, &arg, vp))
425             goto bad;
426         if (JSVAL_IS_PRIMITIVE(*vp)) {
427             str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, *vp, NULL);
428             if (str) {
429                 JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL,
430                                        JSMSG_BAD_ITERATOR_RETURN,
431                                        JSSTRING_CHARS(str),
432                                        JSSTRING_CHARS(ATOM_TO_STRING(atom)));
433             }
434             goto bad;
435         }
436     }
437 
438     ok = JS_TRUE;
439   out:
440     if (obj)
441         JS_POP_TEMP_ROOT(cx, &tvr);
442     return ok;
443   bad:
444     ok = JS_FALSE;
445     goto out;
446 }
447 
448 static JSBool
CallEnumeratorNext(JSContext * cx,JSObject * iterobj,uintN flags,jsval * rval)449 CallEnumeratorNext(JSContext *cx, JSObject *iterobj, uintN flags, jsval *rval)
450 {
451     JSObject *obj, *origobj;
452     jsval state;
453     JSBool foreach;
454     jsid id;
455     JSObject *obj2;
456     JSBool cond;
457     JSClass *clasp;
458     JSExtendedClass *xclasp;
459     JSProperty *prop;
460     JSString *str;
461 
462     JS_ASSERT(flags & JSITER_ENUMERATE);
463     JS_ASSERT(JSVAL_TO_PRIVATE(iterobj->slots[JSSLOT_CLASS]) ==
464               &js_IteratorClass);
465 
466     obj = JSVAL_TO_OBJECT(iterobj->slots[JSSLOT_PARENT]);
467     origobj = JSVAL_TO_OBJECT(iterobj->slots[JSSLOT_PROTO]);
468     state = iterobj->slots[JSSLOT_ITER_STATE];
469     if (JSVAL_IS_NULL(state))
470         goto stop;
471 
472     foreach = (flags & JSITER_FOREACH) != 0;
473 #if JS_HAS_XML_SUPPORT
474     /*
475      * Treat an XML object specially only when it starts the prototype chain.
476      * Otherwise we need to do the usual deleted and shadowed property checks.
477      */
478     if (obj == origobj && OBJECT_IS_XML(cx, obj)) {
479         if (foreach) {
480             JSXMLObjectOps *xmlops = (JSXMLObjectOps *) obj->map->ops;
481 
482             if (!xmlops->enumerateValues(cx, obj, JSENUMERATE_NEXT, &state,
483                                          &id, rval)) {
484                 return JS_FALSE;
485             }
486         } else {
487             if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_NEXT, &state, &id))
488                 return JS_FALSE;
489         }
490         iterobj->slots[JSSLOT_ITER_STATE] = state;
491         if (JSVAL_IS_NULL(state))
492             goto stop;
493     } else
494 #endif
495     {
496       restart:
497         if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_NEXT, &state, &id))
498             return JS_TRUE;
499 
500         iterobj->slots[JSSLOT_ITER_STATE] = state;
501         if (JSVAL_IS_NULL(state)) {
502 #if JS_HAS_XML_SUPPORT
503             if (OBJECT_IS_XML(cx, obj)) {
504                 /*
505                  * We just finished enumerating an XML obj that is present on
506                  * the prototype chain of a non-XML origobj. Stop further
507                  * prototype chain searches because XML objects don't
508                  * enumerate prototypes.
509                  */
510                 JS_ASSERT(origobj != obj);
511                 JS_ASSERT(!OBJECT_IS_XML(cx, origobj));
512             } else
513 #endif
514             {
515                 obj = OBJ_GET_PROTO(cx, obj);
516                 if (obj) {
517                     iterobj->slots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(obj);
518                     if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &state, NULL))
519                         return JS_FALSE;
520                     iterobj->slots[JSSLOT_ITER_STATE] = state;
521                     if (!JSVAL_IS_NULL(state))
522                         goto restart;
523                 }
524             }
525             goto stop;
526         }
527 
528         /* Skip properties not in obj when looking from origobj. */
529         if (!OBJ_LOOKUP_PROPERTY(cx, origobj, id, &obj2, &prop))
530             return JS_FALSE;
531         if (!prop)
532             goto restart;
533         OBJ_DROP_PROPERTY(cx, obj2, prop);
534 
535         /*
536          * If the id was found in a prototype object or an unrelated object
537          * (specifically, not in an inner object for obj), skip it. This step
538          * means that all OBJ_LOOKUP_PROPERTY implementations must return an
539          * object further along on the prototype chain, or else possibly an
540          * object returned by the JSExtendedClass.outerObject optional hook.
541          */
542         if (obj != obj2) {
543             cond = JS_FALSE;
544             clasp = OBJ_GET_CLASS(cx, obj2);
545             if (clasp->flags & JSCLASS_IS_EXTENDED) {
546                 xclasp = (JSExtendedClass *) clasp;
547                 cond = xclasp->outerObject &&
548                     xclasp->outerObject(cx, obj2) == obj;
549             }
550             if (!cond)
551                 goto restart;
552         }
553 
554         if (foreach) {
555             /* Get property querying the original object. */
556             if (!OBJ_GET_PROPERTY(cx, origobj, id, rval))
557                 return JS_FALSE;
558         }
559     }
560 
561     if (foreach) {
562         if (flags & JSITER_KEYVALUE) {
563             if (!NewKeyValuePair(cx, id, *rval, rval))
564                 return JS_FALSE;
565         }
566     } else {
567         /* Make rval a string for uniformity and compatibility. */
568         if (JSID_IS_ATOM(id)) {
569             *rval = ATOM_KEY(JSID_TO_ATOM(id));
570         }
571 #if JS_HAS_XML_SUPPORT
572         else if (JSID_IS_OBJECT(id)) {
573             str = js_ValueToString(cx, OBJECT_JSID_TO_JSVAL(id));
574             if (!str)
575                 return JS_FALSE;
576             *rval = STRING_TO_JSVAL(str);
577         }
578 #endif
579         else {
580             str = js_NumberToString(cx, (jsdouble)JSID_TO_INT(id));
581             if (!str)
582                 return JS_FALSE;
583             *rval = STRING_TO_JSVAL(str);
584         }
585     }
586     return JS_TRUE;
587 
588   stop:
589     JS_ASSERT(iterobj->slots[JSSLOT_ITER_STATE] == JSVAL_NULL);
590     *rval = JSVAL_HOLE;
591     return JS_TRUE;
592 }
593 
594 JSBool
js_CallIteratorNext(JSContext * cx,JSObject * iterobj,jsval * rval)595 js_CallIteratorNext(JSContext *cx, JSObject *iterobj, jsval *rval)
596 {
597     uintN flags;
598 
599     /* Fast path for native iterators */
600     if (OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass) {
601         flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
602         if (flags & JSITER_ENUMERATE)
603             return CallEnumeratorNext(cx, iterobj, flags, rval);
604 
605         /*
606          * Call next directly as all the methods of the native iterator are
607          * read-only and permanent.
608          */
609         if (!IteratorNextImpl(cx, iterobj, rval))
610             return JS_FALSE;
611     } else {
612         jsid id = ATOM_TO_JSID(cx->runtime->atomState.nextAtom);
613 
614         if (!JS_GetMethodById(cx, iterobj, id, &iterobj, rval))
615             return JS_FALSE;
616         if (!js_InternalCall(cx, iterobj, *rval, 0, NULL, rval)) {
617             /* Check for StopIteration. */
618             if (!cx->throwing ||
619                 JSVAL_IS_PRIMITIVE(cx->exception) ||
620                 OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(cx->exception))
621                     != &js_StopIterationClass) {
622                 return JS_FALSE;
623             }
624 
625             /* Inline JS_ClearPendingException(cx). */
626             cx->throwing = JS_FALSE;
627             cx->exception = JSVAL_VOID;
628             *rval = JSVAL_HOLE;
629             return JS_TRUE;
630         }
631     }
632 
633     return JS_TRUE;
634 }
635 
636 static JSBool
stopiter_hasInstance(JSContext * cx,JSObject * obj,jsval v,JSBool * bp)637 stopiter_hasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
638 {
639     *bp = !JSVAL_IS_PRIMITIVE(v) &&
640           OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == &js_StopIterationClass;
641     return JS_TRUE;
642 }
643 
644 JSClass js_StopIterationClass = {
645     js_StopIteration_str,
646     JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration),
647     JS_PropertyStub,  JS_PropertyStub,
648     JS_PropertyStub,  JS_PropertyStub,
649     JS_EnumerateStub, JS_ResolveStub,
650     JS_ConvertStub,   JS_FinalizeStub,
651     NULL,             NULL,
652     NULL,             NULL,
653     NULL,             stopiter_hasInstance,
654     NULL,             NULL
655 };
656 
657 #if JS_HAS_GENERATORS
658 
659 static void
generator_finalize(JSContext * cx,JSObject * obj)660 generator_finalize(JSContext *cx, JSObject *obj)
661 {
662     JSGenerator *gen;
663 
664     gen = (JSGenerator *) JS_GetPrivate(cx, obj);
665     if (gen) {
666         /*
667          * gen can be open on shutdown when close hooks are ignored or when
668          * the embedding cancels scheduled close hooks.
669          */
670         JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_CLOSED ||
671                   gen->state == JSGEN_OPEN);
672         JS_free(cx, gen);
673     }
674 }
675 
676 static uint32
generator_mark(JSContext * cx,JSObject * obj,void * arg)677 generator_mark(JSContext *cx, JSObject *obj, void *arg)
678 {
679     JSGenerator *gen;
680 
681     gen = (JSGenerator *) JS_GetPrivate(cx, obj);
682     if (gen) {
683         /*
684          * We must mark argv[-2], as js_MarkStackFrame will not.  Note that
685          * js_MarkStackFrame will mark thisp (argv[-1]) and actual arguments,
686          * plus any missing formals and local GC roots.
687          */
688         JS_ASSERT(!JSVAL_IS_PRIMITIVE(gen->frame.argv[-2]));
689         GC_MARK(cx, JSVAL_TO_GCTHING(gen->frame.argv[-2]), "generator");
690         js_MarkStackFrame(cx, &gen->frame);
691     }
692     return 0;
693 }
694 
695 JSClass js_GeneratorClass = {
696     js_Generator_str,
697     JSCLASS_HAS_PRIVATE | JSCLASS_IS_ANONYMOUS |
698     JSCLASS_HAS_CACHED_PROTO(JSProto_Generator),
699     JS_PropertyStub,  JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
700     JS_EnumerateStub, JS_ResolveStub,  JS_ConvertStub,  generator_finalize,
701     NULL,             NULL,            NULL,            NULL,
702     NULL,             NULL,            generator_mark,  NULL
703 };
704 
705 /*
706  * Called from the JSOP_GENERATOR case in the interpreter, with fp referring
707  * to the frame by which the generator function was activated.  Create a new
708  * JSGenerator object, which contains its own JSStackFrame that we populate
709  * from *fp.  We know that upon return, the JSOP_GENERATOR opcode will return
710  * from the activation in fp, so we can steal away fp->callobj and fp->argsobj
711  * if they are non-null.
712  */
713 JSObject *
js_NewGenerator(JSContext * cx,JSStackFrame * fp)714 js_NewGenerator(JSContext *cx, JSStackFrame *fp)
715 {
716     JSObject *obj;
717     uintN argc, nargs, nvars, depth, nslots;
718     JSGenerator *gen;
719     jsval *newsp;
720 
721     /* After the following return, failing control flow must goto bad. */
722     obj = js_NewObject(cx, &js_GeneratorClass, NULL, NULL);
723     if (!obj)
724         return NULL;
725 
726     /* Load and compute stack slot counts. */
727     argc = fp->argc;
728     nargs = JS_MAX(argc, fp->fun->nargs);
729     nvars = fp->nvars;
730     depth = fp->script->depth;
731     nslots = 2 + nargs + nvars + 2 * depth;
732 
733     /* Allocate obj's private data struct. */
734     gen = (JSGenerator *)
735           JS_malloc(cx, sizeof(JSGenerator) + (nslots - 1) * sizeof(jsval));
736     if (!gen)
737         goto bad;
738 
739     gen->obj = obj;
740 
741     /* Steal away objects reflecting fp and point them at gen->frame. */
742     gen->frame.callobj = fp->callobj;
743     if (fp->callobj) {
744         JS_SetPrivate(cx, fp->callobj, &gen->frame);
745         fp->callobj = NULL;
746     }
747     gen->frame.argsobj = fp->argsobj;
748     if (fp->argsobj) {
749         JS_SetPrivate(cx, fp->argsobj, &gen->frame);
750         fp->argsobj = NULL;
751     }
752 
753     /* These two references can be shared with fp until it goes away. */
754     gen->frame.varobj = fp->varobj;
755     gen->frame.thisp = fp->thisp;
756 
757     /* Copy call-invariant script and function references. */
758     gen->frame.script = fp->script;
759     gen->frame.fun = fp->fun;
760 
761     /* Use newsp to carve space out of gen->stack. */
762     newsp = gen->stack;
763     gen->arena.next = NULL;
764     gen->arena.base = (jsuword) newsp;
765     gen->arena.limit = gen->arena.avail = (jsuword) (newsp + nslots);
766 
767 #define COPY_STACK_ARRAY(vec,cnt,num)                                         \
768     JS_BEGIN_MACRO                                                            \
769         gen->frame.cnt = cnt;                                                 \
770         gen->frame.vec = newsp;                                               \
771         newsp += (num);                                                       \
772         memcpy(gen->frame.vec, fp->vec, (num) * sizeof(jsval));               \
773     JS_END_MACRO
774 
775     /* Copy argv, rval, and vars. */
776     *newsp++ = fp->argv[-2];
777     *newsp++ = fp->argv[-1];
778     COPY_STACK_ARRAY(argv, argc, nargs);
779     gen->frame.rval = fp->rval;
780     COPY_STACK_ARRAY(vars, nvars, nvars);
781 
782 #undef COPY_STACK_ARRAY
783 
784     /* Initialize or copy virtual machine state. */
785     gen->frame.down = NULL;
786     gen->frame.annotation = NULL;
787     gen->frame.scopeChain = fp->scopeChain;
788     gen->frame.pc = fp->pc;
789 
790     /* Allocate generating pc and operand stack space. */
791     gen->frame.spbase = gen->frame.sp = newsp + depth;
792 
793     /* Copy remaining state (XXX sharp* and xml* should be local vars). */
794     gen->frame.sharpDepth = 0;
795     gen->frame.sharpArray = NULL;
796     gen->frame.flags = fp->flags | JSFRAME_GENERATOR;
797     gen->frame.dormantNext = NULL;
798     gen->frame.xmlNamespace = NULL;
799     gen->frame.blockChain = NULL;
800 
801     /* Note that gen is newborn. */
802     gen->state = JSGEN_NEWBORN;
803 
804     if (!JS_SetPrivate(cx, obj, gen)) {
805         JS_free(cx, gen);
806         goto bad;
807     }
808 
809     /*
810      * Register with GC to ensure that suspended finally blocks will be
811      * executed.
812      */
813     js_RegisterGenerator(cx, gen);
814     return obj;
815 
816   bad:
817     cx->weakRoots.newborn[GCX_OBJECT] = NULL;
818     return NULL;
819 }
820 
821 typedef enum JSGeneratorOp {
822     JSGENOP_NEXT,
823     JSGENOP_SEND,
824     JSGENOP_THROW,
825     JSGENOP_CLOSE
826 } JSGeneratorOp;
827 
828 /*
829  * Start newborn or restart yielding generator and perform the requested
830  * operation inside its frame.
831  */
832 static JSBool
SendToGenerator(JSContext * cx,JSGeneratorOp op,JSObject * obj,JSGenerator * gen,jsval arg,jsval * rval)833 SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj,
834                 JSGenerator *gen, jsval arg, jsval *rval)
835 {
836     JSStackFrame *fp;
837     jsval junk;
838     JSArena *arena;
839     JSBool ok;
840 
841     JS_ASSERT(gen->state ==  JSGEN_NEWBORN || gen->state == JSGEN_OPEN);
842     switch (op) {
843       case JSGENOP_NEXT:
844       case JSGENOP_SEND:
845         if (gen->state == JSGEN_OPEN) {
846             /*
847              * Store the argument to send as the result of the yield
848              * expression.
849              */
850             gen->frame.sp[-1] = arg;
851         }
852         gen->state = JSGEN_RUNNING;
853         break;
854 
855       case JSGENOP_THROW:
856         JS_SetPendingException(cx, arg);
857         gen->state = JSGEN_RUNNING;
858         break;
859 
860       default:
861         JS_ASSERT(op == JSGENOP_CLOSE);
862         JS_SetPendingException(cx, JSVAL_ARETURN);
863         gen->state = JSGEN_CLOSING;
864         break;
865     }
866 
867     /* Extend the current stack pool with gen->arena. */
868     arena = cx->stackPool.current;
869     JS_ASSERT(!arena->next);
870     JS_ASSERT(!gen->arena.next);
871     JS_ASSERT(cx->stackPool.current != &gen->arena);
872     cx->stackPool.current = arena->next = &gen->arena;
873 
874     /* Push gen->frame around the interpreter activation. */
875     fp = cx->fp;
876     cx->fp = &gen->frame;
877     gen->frame.down = fp;
878     ok = js_Interpret(cx, gen->frame.pc, &junk);
879     cx->fp = fp;
880     gen->frame.down = NULL;
881 
882     /* Retract the stack pool and sanitize gen->arena. */
883     JS_ASSERT(!gen->arena.next);
884     JS_ASSERT(arena->next == &gen->arena);
885     JS_ASSERT(cx->stackPool.current == &gen->arena);
886     cx->stackPool.current = arena;
887     arena->next = NULL;
888 
889     if (gen->frame.flags & JSFRAME_YIELDING) {
890         /* Yield cannot fail, throw or be called on closing. */
891         JS_ASSERT(ok);
892         JS_ASSERT(!cx->throwing);
893         JS_ASSERT(gen->state == JSGEN_RUNNING);
894         JS_ASSERT(op != JSGENOP_CLOSE);
895         gen->frame.flags &= ~JSFRAME_YIELDING;
896         gen->state = JSGEN_OPEN;
897         *rval = gen->frame.rval;
898         return JS_TRUE;
899     }
900 
901     gen->state = JSGEN_CLOSED;
902 
903     if (ok) {
904         /* Returned, explicitly or by falling off the end. */
905         if (op == JSGENOP_CLOSE)
906             return JS_TRUE;
907         return js_ThrowStopIteration(cx, obj);
908     }
909 
910     /*
911      * An error, silent termination by branch callback or an exception.
912      * Propagate the condition to the caller.
913      */
914     return JS_FALSE;
915 }
916 
917 /*
918  * Execute gen's close hook after the GC detects that the object has become
919  * unreachable.
920  */
921 JSBool
js_CloseGeneratorObject(JSContext * cx,JSGenerator * gen)922 js_CloseGeneratorObject(JSContext *cx, JSGenerator *gen)
923 {
924     /* We pass null as rval since SendToGenerator never uses it with CLOSE. */
925     return SendToGenerator(cx, JSGENOP_CLOSE, gen->obj, gen, JSVAL_VOID, NULL);
926 }
927 
928 /*
929  * Common subroutine of generator_(next|send|throw|close) methods.
930  */
931 static JSBool
generator_op(JSContext * cx,JSGeneratorOp op,JSObject * obj,uintN argc,jsval * argv,jsval * rval)932 generator_op(JSContext *cx, JSGeneratorOp op,
933              JSObject *obj, uintN argc, jsval *argv, jsval *rval)
934 {
935     JSGenerator *gen;
936     JSString *str;
937     jsval arg;
938 
939     if (!JS_InstanceOf(cx, obj, &js_GeneratorClass, argv))
940         return JS_FALSE;
941 
942     gen = (JSGenerator *) JS_GetPrivate(cx, obj);
943     if (gen == NULL) {
944         /* This happens when obj is the generator prototype. See bug 352885. */
945         goto closed_generator;
946     }
947 
948     switch (gen->state) {
949       case JSGEN_NEWBORN:
950         switch (op) {
951           case JSGENOP_NEXT:
952           case JSGENOP_THROW:
953             break;
954 
955           case JSGENOP_SEND:
956             if (!JSVAL_IS_VOID(argv[0])) {
957                 str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK,
958                                                  argv[0], NULL);
959                 if (str) {
960                     JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL,
961                                            JSMSG_BAD_GENERATOR_SEND,
962                                            JSSTRING_CHARS(str));
963                 }
964                 return JS_FALSE;
965             }
966             break;
967 
968           default:
969             JS_ASSERT(op == JSGENOP_CLOSE);
970             gen->state = JSGEN_CLOSED;
971             return JS_TRUE;
972         }
973         break;
974 
975       case JSGEN_OPEN:
976         break;
977 
978       case JSGEN_RUNNING:
979       case JSGEN_CLOSING:
980         str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, argv[-1],
981                                          JS_GetFunctionId(gen->frame.fun));
982         if (str) {
983             JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL,
984                                    JSMSG_NESTING_GENERATOR,
985                                    JSSTRING_CHARS(str));
986         }
987         return JS_FALSE;
988 
989       default:
990         JS_ASSERT(gen->state == JSGEN_CLOSED);
991 
992       closed_generator:
993         switch (op) {
994           case JSGENOP_NEXT:
995           case JSGENOP_SEND:
996             return js_ThrowStopIteration(cx, obj);
997           case JSGENOP_THROW:
998             JS_SetPendingException(cx, argv[0]);
999             return JS_FALSE;
1000           default:
1001             JS_ASSERT(op == JSGENOP_CLOSE);
1002             return JS_TRUE;
1003         }
1004     }
1005 
1006     arg = (op == JSGENOP_SEND || op == JSGENOP_THROW)
1007           ? argv[0]
1008           : JSVAL_VOID;
1009     if (!SendToGenerator(cx, op, obj, gen, arg, rval))
1010         return JS_FALSE;
1011     return JS_TRUE;
1012 }
1013 
1014 static JSBool
generator_send(JSContext * cx,JSObject * obj,uintN argc,jsval * argv,jsval * rval)1015 generator_send(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1016                jsval *rval)
1017 {
1018     return generator_op(cx, JSGENOP_SEND, obj, argc, argv, rval);
1019 }
1020 
1021 static JSBool
generator_next(JSContext * cx,JSObject * obj,uintN argc,jsval * argv,jsval * rval)1022 generator_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1023                jsval *rval)
1024 {
1025     return generator_op(cx, JSGENOP_NEXT, obj, argc, argv, rval);
1026 }
1027 
1028 static JSBool
generator_throw(JSContext * cx,JSObject * obj,uintN argc,jsval * argv,jsval * rval)1029 generator_throw(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1030                 jsval *rval)
1031 {
1032     return generator_op(cx, JSGENOP_THROW, obj, argc, argv, rval);
1033 }
1034 
1035 static JSBool
generator_close(JSContext * cx,JSObject * obj,uintN argc,jsval * argv,jsval * rval)1036 generator_close(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1037                 jsval *rval)
1038 {
1039     return generator_op(cx, JSGENOP_CLOSE, obj, argc, argv, rval);
1040 }
1041 
1042 static JSFunctionSpec generator_methods[] = {
1043     {js_iterator_str, iterator_self,     0,JSPROP_READONLY|JSPROP_PERMANENT,0},
1044     {js_next_str,     generator_next,    0,JSPROP_READONLY|JSPROP_PERMANENT,0},
1045     {js_send_str,     generator_send,    1,JSPROP_READONLY|JSPROP_PERMANENT,0},
1046     {js_throw_str,    generator_throw,   1,JSPROP_READONLY|JSPROP_PERMANENT,0},
1047     {js_close_str,    generator_close,   0,JSPROP_READONLY|JSPROP_PERMANENT,0},
1048     {0,0,0,0,0}
1049 };
1050 
1051 #endif /* JS_HAS_GENERATORS */
1052 
1053 JSObject *
js_InitIteratorClasses(JSContext * cx,JSObject * obj)1054 js_InitIteratorClasses(JSContext *cx, JSObject *obj)
1055 {
1056     JSObject *proto, *stop;
1057 
1058     /* Idempotency required: we initialize several things, possibly lazily. */
1059     if (!js_GetClassObject(cx, obj, JSProto_StopIteration, &stop))
1060         return NULL;
1061     if (stop)
1062         return stop;
1063 
1064     proto = JS_InitClass(cx, obj, NULL, &js_IteratorClass, Iterator, 2,
1065                          NULL, iterator_methods, NULL, NULL);
1066     if (!proto)
1067         return NULL;
1068     proto->slots[JSSLOT_ITER_STATE] = JSVAL_NULL;
1069 
1070 #if JS_HAS_GENERATORS
1071     /* Initialize the generator internals if configured. */
1072     if (!JS_InitClass(cx, obj, NULL, &js_GeneratorClass, NULL, 0,
1073                       NULL, generator_methods, NULL, NULL)) {
1074         return NULL;
1075     }
1076 #endif
1077 
1078     return JS_InitClass(cx, obj, NULL, &js_StopIterationClass, NULL, 0,
1079                         NULL, NULL, NULL, NULL);
1080 }
1081