Back to index

lightning-sunbird  0.9+nobinonly
jsiter.c
Go to the documentation of this file.
00001 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
00002  * vim: set ts=8 sw=4 et tw=78:
00003  *
00004  * ***** BEGIN LICENSE BLOCK *****
00005  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00006  *
00007  * The contents of this file are subject to the Mozilla Public License Version
00008  * 1.1 (the "License"); you may not use this file except in compliance with
00009  * the License. You may obtain a copy of the License at
00010  * http://www.mozilla.org/MPL/
00011  *
00012  * Software distributed under the License is distributed on an "AS IS" basis,
00013  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00014  * for the specific language governing rights and limitations under the
00015  * License.
00016  *
00017  * The Original Code is Mozilla Communicator client code, released
00018  * March 31, 1998.
00019  *
00020  * The Initial Developer of the Original Code is
00021  * Netscape Communications Corporation.
00022  * Portions created by the Initial Developer are Copyright (C) 1998
00023  * the Initial Developer. All Rights Reserved.
00024  *
00025  * Contributor(s):
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either of the GNU General Public License Version 2 or later (the "GPL"),
00029  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 /*
00042  * JavaScript iterators.
00043  */
00044 #include "jsstddef.h"
00045 #include <string.h>     /* for memcpy */
00046 #include "jstypes.h"
00047 #include "jsutil.h"
00048 #include "jsarena.h"
00049 #include "jsapi.h"
00050 #include "jsarray.h"
00051 #include "jsatom.h"
00052 #include "jsbool.h"
00053 #include "jscntxt.h"
00054 #include "jsconfig.h"
00055 #include "jsexn.h"
00056 #include "jsfun.h"
00057 #include "jsgc.h"
00058 #include "jsinterp.h"
00059 #include "jsiter.h"
00060 #include "jslock.h"
00061 #include "jsnum.h"
00062 #include "jsobj.h"
00063 #include "jsopcode.h"
00064 #include "jsscope.h"
00065 #include "jsscript.h"
00066 
00067 #if JS_HAS_XML_SUPPORT
00068 #include "jsxml.h"
00069 #endif
00070 
00071 extern const char js_throw_str[]; /* from jsscan.h */
00072 
00073 #define JSSLOT_ITER_STATE       (JSSLOT_PRIVATE)
00074 #define JSSLOT_ITER_FLAGS       (JSSLOT_PRIVATE + 1)
00075 
00076 #if JSSLOT_ITER_FLAGS >= JS_INITIAL_NSLOTS
00077 #error JS_INITIAL_NSLOTS must be greater than JSSLOT_ITER_FLAGS.
00078 #endif
00079 
00080 /*
00081  * Shared code to close iterator's state either through an explicit call or
00082  * when GC detects that the iterator is no longer reachable.
00083  */
00084 void
00085 js_CloseIteratorState(JSContext *cx, JSObject *iterobj)
00086 {
00087     jsval *slots;
00088     jsval state, parent;
00089     JSObject *iterable;
00090 
00091     JS_ASSERT(JS_InstanceOf(cx, iterobj, &js_IteratorClass, NULL));
00092     slots = iterobj->slots;
00093 
00094     /* Avoid double work if js_CloseNativeIterator was called on obj. */
00095     state = slots[JSSLOT_ITER_STATE];
00096     if (JSVAL_IS_NULL(state))
00097         return;
00098 
00099     /* Protect against failure to fully initialize obj. */
00100     parent = slots[JSSLOT_PARENT];
00101     if (!JSVAL_IS_PRIMITIVE(parent)) {
00102         iterable = JSVAL_TO_OBJECT(parent);
00103 #if JS_HAS_XML_SUPPORT
00104         if ((JSVAL_TO_INT(slots[JSSLOT_ITER_FLAGS]) & JSITER_FOREACH) &&
00105             OBJECT_IS_XML(cx, iterable)) {
00106             ((JSXMLObjectOps *) iterable->map->ops)->
00107                 enumerateValues(cx, iterable, JSENUMERATE_DESTROY, &state,
00108                                 NULL, NULL);
00109         } else
00110 #endif
00111             OBJ_ENUMERATE(cx, iterable, JSENUMERATE_DESTROY, &state, NULL);
00112     }
00113     slots[JSSLOT_ITER_STATE] = JSVAL_NULL;
00114 }
00115 
00116 JSClass js_IteratorClass = {
00117     "Iterator",
00118     JSCLASS_HAS_RESERVED_SLOTS(2) | /* slots for state and flags */
00119     JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator),
00120     JS_PropertyStub,  JS_PropertyStub,  JS_PropertyStub,  JS_PropertyStub,
00121     JS_EnumerateStub, JS_ResolveStub,   JS_ConvertStub,   JS_FinalizeStub,
00122     JSCLASS_NO_OPTIONAL_MEMBERS
00123 };
00124 
00125 static JSBool
00126 InitNativeIterator(JSContext *cx, JSObject *iterobj, JSObject *obj, uintN flags)
00127 {
00128     jsval state;
00129     JSBool ok;
00130 
00131     JS_ASSERT(JSVAL_TO_PRIVATE(iterobj->slots[JSSLOT_CLASS]) ==
00132               &js_IteratorClass);
00133 
00134     /* Initialize iterobj in case of enumerate hook failure. */
00135     iterobj->slots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(obj);
00136     iterobj->slots[JSSLOT_ITER_STATE] = JSVAL_NULL;
00137     iterobj->slots[JSSLOT_ITER_FLAGS] = INT_TO_JSVAL(flags);
00138     if (!js_RegisterCloseableIterator(cx, iterobj))
00139         return JS_FALSE;
00140     if (!obj)
00141         return JS_TRUE;
00142 
00143     ok =
00144 #if JS_HAS_XML_SUPPORT
00145          ((flags & JSITER_FOREACH) && OBJECT_IS_XML(cx, obj))
00146          ? ((JSXMLObjectOps *) obj->map->ops)->
00147                enumerateValues(cx, obj, JSENUMERATE_INIT, &state, NULL, NULL)
00148          :
00149 #endif
00150            OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &state, NULL);
00151     if (!ok)
00152         return JS_FALSE;
00153 
00154     iterobj->slots[JSSLOT_ITER_STATE] = state;
00155     if (flags & JSITER_ENUMERATE) {
00156         /*
00157          * The enumerating iterator needs the original object to suppress
00158          * enumeration of deleted or shadowed prototype properties. Since the
00159          * enumerator never escapes to scripts, we use the prototype slot to
00160          * store the original object.
00161          */
00162         JS_ASSERT(obj != iterobj);
00163         iterobj->slots[JSSLOT_PROTO] = OBJECT_TO_JSVAL(obj);
00164     }
00165     return JS_TRUE;
00166 }
00167 
00168 static JSBool
00169 Iterator(JSContext *cx, JSObject *iterobj, uintN argc, jsval *argv, jsval *rval)
00170 {
00171     JSBool keyonly;
00172     uintN flags;
00173     JSObject *obj;
00174 
00175     keyonly = JS_FALSE;
00176     if (!js_ValueToBoolean(cx, argv[1], &keyonly))
00177         return JS_FALSE;
00178     flags = keyonly ? 0 : JSITER_FOREACH;
00179 
00180     if (cx->fp->flags & JSFRAME_CONSTRUCTING) {
00181         /* XXX work around old valueOf call hidden beneath js_ValueToObject */
00182         if (!JSVAL_IS_PRIMITIVE(argv[0])) {
00183             obj = JSVAL_TO_OBJECT(argv[0]);
00184         } else {
00185             obj = js_ValueToNonNullObject(cx, argv[0]);
00186             if (!obj)
00187                 return JS_FALSE;
00188             argv[0] = OBJECT_TO_JSVAL(obj);
00189         }
00190         return InitNativeIterator(cx, iterobj, obj, flags);
00191     }
00192 
00193     *rval = argv[0];
00194     return js_ValueToIterator(cx, flags, rval);
00195 }
00196 
00197 static JSBool
00198 NewKeyValuePair(JSContext *cx, jsid key, jsval val, jsval *rval)
00199 {
00200     jsval vec[2];
00201     JSTempValueRooter tvr;
00202     JSObject *aobj;
00203 
00204     vec[0] = ID_TO_VALUE(key);
00205     vec[1] = val;
00206 
00207     JS_PUSH_TEMP_ROOT(cx, 2, vec, &tvr);
00208     aobj = js_NewArrayObject(cx, 2, vec);
00209     *rval = OBJECT_TO_JSVAL(aobj);
00210     JS_POP_TEMP_ROOT(cx, &tvr);
00211 
00212     return aobj != NULL;
00213 }
00214 
00215 static JSBool
00216 IteratorNextImpl(JSContext *cx, JSObject *obj, jsval *rval)
00217 {
00218     JSObject *iterable;
00219     jsval state;
00220     uintN flags;
00221     JSBool foreach, ok;
00222     jsid id;
00223 
00224     JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_IteratorClass);
00225 
00226     iterable = OBJ_GET_PARENT(cx, obj);
00227     JS_ASSERT(iterable);
00228     state = OBJ_GET_SLOT(cx, obj, JSSLOT_ITER_STATE);
00229     if (JSVAL_IS_NULL(state))
00230         goto stop;
00231 
00232     flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, obj, JSSLOT_ITER_FLAGS));
00233     JS_ASSERT(!(flags & JSITER_ENUMERATE));
00234     foreach = (flags & JSITER_FOREACH) != 0;
00235     ok =
00236 #if JS_HAS_XML_SUPPORT
00237          (foreach && OBJECT_IS_XML(cx, iterable))
00238          ? ((JSXMLObjectOps *) iterable->map->ops)->
00239                enumerateValues(cx, iterable, JSENUMERATE_NEXT, &state,
00240                                &id, rval)
00241          :
00242 #endif
00243            OBJ_ENUMERATE(cx, iterable, JSENUMERATE_NEXT, &state, &id);
00244     if (!ok)
00245         return JS_FALSE;
00246 
00247     OBJ_SET_SLOT(cx, obj, JSSLOT_ITER_STATE, state);
00248     if (JSVAL_IS_NULL(state))
00249         goto stop;
00250 
00251     if (foreach) {
00252 #if JS_HAS_XML_SUPPORT
00253         if (!OBJECT_IS_XML(cx, iterable) &&
00254             !OBJ_GET_PROPERTY(cx, iterable, id, rval)) {
00255             return JS_FALSE;
00256         }
00257 #endif
00258         if (!NewKeyValuePair(cx, id, *rval, rval))
00259             return JS_FALSE;
00260     } else {
00261         *rval = ID_TO_VALUE(id);
00262     }
00263     return JS_TRUE;
00264 
00265   stop:
00266     JS_ASSERT(OBJ_GET_SLOT(cx, obj, JSSLOT_ITER_STATE) == JSVAL_NULL);
00267     *rval = JSVAL_HOLE;
00268     return JS_TRUE;
00269 }
00270 
00271 static JSBool
00272 js_ThrowStopIteration(JSContext *cx, JSObject *obj)
00273 {
00274     jsval v;
00275 
00276     JS_ASSERT(!JS_IsExceptionPending(cx));
00277     if (js_FindClassObject(cx, NULL, INT_TO_JSID(JSProto_StopIteration), &v))
00278         JS_SetPendingException(cx, v);
00279     return JS_FALSE;
00280 }
00281 
00282 static JSBool
00283 iterator_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
00284               jsval *rval)
00285 {
00286     if (!JS_InstanceOf(cx, obj, &js_IteratorClass, argv))
00287         return JS_FALSE;
00288 
00289     if (!IteratorNextImpl(cx, obj, rval))
00290         return JS_FALSE;
00291 
00292     if (*rval == JSVAL_HOLE) {
00293         *rval = JSVAL_NULL;
00294         js_ThrowStopIteration(cx, obj);
00295         return JS_FALSE;
00296     }
00297     return JS_TRUE;
00298 }
00299 
00300 static JSBool
00301 iterator_self(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
00302               jsval *rval)
00303 {
00304     *rval = OBJECT_TO_JSVAL(obj);
00305     return JS_TRUE;
00306 }
00307 
00308 static JSFunctionSpec iterator_methods[] = {
00309     {js_iterator_str, iterator_self, 0,JSPROP_READONLY|JSPROP_PERMANENT,0},
00310     {js_next_str,     iterator_next, 0,JSPROP_READONLY|JSPROP_PERMANENT,0},
00311     {0,0,0,0,0}
00312 };
00313 
00314 uintN
00315 js_GetNativeIteratorFlags(JSContext *cx, JSObject *iterobj)
00316 {
00317     if (OBJ_GET_CLASS(cx, iterobj) != &js_IteratorClass)
00318         return 0;
00319     return JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
00320 }
00321 
00322 void
00323 js_CloseNativeIterator(JSContext *cx, JSObject *iterobj)
00324 {
00325     uintN flags;
00326 
00327     /*
00328      * If this iterator is not an instance of the native default iterator
00329      * class, leave it to be GC'ed.
00330      */
00331     if (!JS_InstanceOf(cx, iterobj, &js_IteratorClass, NULL))
00332         return;
00333 
00334     /*
00335      * If this iterator was not created by js_ValueToIterator called from the
00336      * for-in loop code in js_Interpret, leave it to be GC'ed.
00337      */
00338     flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
00339     if (!(flags & JSITER_ENUMERATE))
00340         return;
00341 
00342     js_CloseIteratorState(cx, iterobj);
00343 }
00344 
00345 /*
00346  * Call ToObject(v).__iterator__(keyonly) if ToObject(v).__iterator__ exists.
00347  * Otherwise construct the defualt iterator.
00348  */
00349 JSBool
00350 js_ValueToIterator(JSContext *cx, uintN flags, jsval *vp)
00351 {
00352     JSObject *obj;
00353     JSTempValueRooter tvr;
00354     const JSAtom *atom;
00355     JSBool ok;
00356     JSObject *iterobj;
00357     jsval arg;
00358     JSString *str;
00359 
00360     JS_ASSERT(!(flags & ~(JSITER_ENUMERATE |
00361                           JSITER_FOREACH |
00362                           JSITER_KEYVALUE)));
00363 
00364     /* JSITER_KEYVALUE must always come with JSITER_FOREACH */
00365     JS_ASSERT(!(flags & JSITER_KEYVALUE) || (flags & JSITER_FOREACH));
00366 
00367     /* XXX work around old valueOf call hidden beneath js_ValueToObject */
00368     if (!JSVAL_IS_PRIMITIVE(*vp)) {
00369         obj = JSVAL_TO_OBJECT(*vp);
00370     } else {
00371         /*
00372          * Enumerating over null and undefined gives an empty enumerator.
00373          * This is contrary to ECMA-262 9.9 ToObject, invoked from step 3 of
00374          * the first production in 12.6.4 and step 4 of the second production,
00375          * but it's "web JS" compatible.
00376          */
00377         if ((flags & JSITER_ENUMERATE)) {
00378             if (!js_ValueToObject(cx, *vp, &obj))
00379                 return JS_FALSE;
00380             if (!obj)
00381                 goto default_iter;
00382         } else {
00383             obj = js_ValueToNonNullObject(cx, *vp);
00384             if (!obj)
00385                 return JS_FALSE;
00386         }
00387     }
00388 
00389     JS_ASSERT(obj);
00390     JS_PUSH_TEMP_ROOT_OBJECT(cx, obj, &tvr);
00391 
00392     atom = cx->runtime->atomState.iteratorAtom;
00393 #if JS_HAS_XML_SUPPORT
00394     if (OBJECT_IS_XML(cx, obj)) {
00395         if (!js_GetXMLFunction(cx, obj, ATOM_TO_JSID(atom), vp))
00396             goto bad;
00397     } else
00398 #endif
00399     {
00400         if (!OBJ_GET_PROPERTY(cx, obj, ATOM_TO_JSID(atom), vp))
00401             goto bad;
00402     }
00403 
00404     if (JSVAL_IS_VOID(*vp)) {
00405       default_iter:
00406         /*
00407          * Fail over to the default enumerating native iterator.
00408          *
00409          * Create iterobj with a NULL parent to ensure that we use the correct
00410          * scope chain to lookup the iterator's constructor. Since we use the
00411          * parent slot to keep track of the iterable, we must fix it up after.
00412          */
00413         iterobj = js_NewObject(cx, &js_IteratorClass, NULL, NULL);
00414         if (!iterobj)
00415             goto bad;
00416 
00417         /* Store iterobj in *vp to protect it from GC (callers must root vp). */
00418         *vp = OBJECT_TO_JSVAL(iterobj);
00419 
00420         if (!InitNativeIterator(cx, iterobj, obj, flags))
00421             goto bad;
00422     } else {
00423         arg = BOOLEAN_TO_JSVAL((flags & JSITER_FOREACH) == 0);
00424         if (!js_InternalInvoke(cx, obj, *vp, JSINVOKE_ITERATOR, 1, &arg, vp))
00425             goto bad;
00426         if (JSVAL_IS_PRIMITIVE(*vp)) {
00427             str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, *vp, NULL);
00428             if (str) {
00429                 JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL,
00430                                        JSMSG_BAD_ITERATOR_RETURN,
00431                                        JSSTRING_CHARS(str),
00432                                        JSSTRING_CHARS(ATOM_TO_STRING(atom)));
00433             }
00434             goto bad;
00435         }
00436     }
00437 
00438     ok = JS_TRUE;
00439   out:
00440     if (obj)
00441         JS_POP_TEMP_ROOT(cx, &tvr);
00442     return ok;
00443   bad:
00444     ok = JS_FALSE;
00445     goto out;
00446 }
00447 
00448 static JSBool
00449 CallEnumeratorNext(JSContext *cx, JSObject *iterobj, uintN flags, jsval *rval)
00450 {
00451     JSObject *obj, *origobj;
00452     jsval state;
00453     JSBool foreach;
00454     jsid id;
00455     JSObject *obj2;
00456     JSBool cond;
00457     JSClass *clasp;
00458     JSExtendedClass *xclasp;
00459     JSProperty *prop;
00460     JSString *str;
00461 
00462     JS_ASSERT(flags & JSITER_ENUMERATE);
00463     JS_ASSERT(JSVAL_TO_PRIVATE(iterobj->slots[JSSLOT_CLASS]) ==
00464               &js_IteratorClass);
00465 
00466     obj = JSVAL_TO_OBJECT(iterobj->slots[JSSLOT_PARENT]);
00467     origobj = JSVAL_TO_OBJECT(iterobj->slots[JSSLOT_PROTO]);
00468     state = iterobj->slots[JSSLOT_ITER_STATE];
00469     if (JSVAL_IS_NULL(state))
00470         goto stop;
00471 
00472     foreach = (flags & JSITER_FOREACH) != 0;
00473 #if JS_HAS_XML_SUPPORT
00474     /*
00475      * Treat an XML object specially only when it starts the prototype chain.
00476      * Otherwise we need to do the usual deleted and shadowed property checks.
00477      */
00478     if (obj == origobj && OBJECT_IS_XML(cx, obj)) {
00479         if (foreach) {
00480             JSXMLObjectOps *xmlops = (JSXMLObjectOps *) obj->map->ops;
00481 
00482             if (!xmlops->enumerateValues(cx, obj, JSENUMERATE_NEXT, &state,
00483                                          &id, rval)) {
00484                 return JS_FALSE;
00485             }
00486         } else {
00487             if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_NEXT, &state, &id))
00488                 return JS_FALSE;
00489         }
00490         iterobj->slots[JSSLOT_ITER_STATE] = state;
00491         if (JSVAL_IS_NULL(state))
00492             goto stop;
00493     } else
00494 #endif
00495     {
00496       restart:
00497         if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_NEXT, &state, &id))
00498             return JS_TRUE;
00499 
00500         iterobj->slots[JSSLOT_ITER_STATE] = state;
00501         if (JSVAL_IS_NULL(state)) {
00502 #if JS_HAS_XML_SUPPORT
00503             if (OBJECT_IS_XML(cx, obj)) {
00504                 /*
00505                  * We just finished enumerating an XML obj that is present on
00506                  * the prototype chain of a non-XML origobj. Stop further
00507                  * prototype chain searches because XML objects don't
00508                  * enumerate prototypes.
00509                  */
00510                 JS_ASSERT(origobj != obj);
00511                 JS_ASSERT(!OBJECT_IS_XML(cx, origobj));
00512             } else
00513 #endif
00514             {
00515                 obj = OBJ_GET_PROTO(cx, obj);
00516                 if (obj) {
00517                     iterobj->slots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(obj);
00518                     if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &state, NULL))
00519                         return JS_FALSE;
00520                     iterobj->slots[JSSLOT_ITER_STATE] = state;
00521                     if (!JSVAL_IS_NULL(state))
00522                         goto restart;
00523                 }
00524             }
00525             goto stop;
00526         }
00527 
00528         /* Skip properties not in obj when looking from origobj. */
00529         if (!OBJ_LOOKUP_PROPERTY(cx, origobj, id, &obj2, &prop))
00530             return JS_FALSE;
00531         if (!prop)
00532             goto restart;
00533         OBJ_DROP_PROPERTY(cx, obj2, prop);
00534 
00535         /*
00536          * If the id was found in a prototype object or an unrelated object
00537          * (specifically, not in an inner object for obj), skip it. This step
00538          * means that all OBJ_LOOKUP_PROPERTY implementations must return an
00539          * object further along on the prototype chain, or else possibly an
00540          * object returned by the JSExtendedClass.outerObject optional hook.
00541          */
00542         if (obj != obj2) {
00543             cond = JS_FALSE;
00544             clasp = OBJ_GET_CLASS(cx, obj2);
00545             if (clasp->flags & JSCLASS_IS_EXTENDED) {
00546                 xclasp = (JSExtendedClass *) clasp;
00547                 cond = xclasp->outerObject &&
00548                     xclasp->outerObject(cx, obj2) == obj;
00549             }
00550             if (!cond)
00551                 goto restart;
00552         }
00553 
00554         if (foreach) {
00555             /* Get property querying the original object. */
00556             if (!OBJ_GET_PROPERTY(cx, origobj, id, rval))
00557                 return JS_FALSE;
00558         }
00559     }
00560 
00561     if (foreach) {
00562         if (flags & JSITER_KEYVALUE) {
00563             if (!NewKeyValuePair(cx, id, *rval, rval))
00564                 return JS_FALSE;
00565         }
00566     } else {
00567         /* Make rval a string for uniformity and compatibility. */
00568         if (JSID_IS_ATOM(id)) {
00569             *rval = ATOM_KEY(JSID_TO_ATOM(id));
00570         }
00571 #if JS_HAS_XML_SUPPORT
00572         else if (JSID_IS_OBJECT(id)) {
00573             str = js_ValueToString(cx, OBJECT_JSID_TO_JSVAL(id));
00574             if (!str)
00575                 return JS_FALSE;
00576             *rval = STRING_TO_JSVAL(str);
00577         }
00578 #endif
00579         else {
00580             str = js_NumberToString(cx, (jsdouble)JSID_TO_INT(id));
00581             if (!str)
00582                 return JS_FALSE;
00583             *rval = STRING_TO_JSVAL(str);
00584         }
00585     }
00586     return JS_TRUE;
00587 
00588   stop:
00589     JS_ASSERT(iterobj->slots[JSSLOT_ITER_STATE] == JSVAL_NULL);
00590     *rval = JSVAL_HOLE;
00591     return JS_TRUE;
00592 }
00593 
00594 JSBool
00595 js_CallIteratorNext(JSContext *cx, JSObject *iterobj, jsval *rval)
00596 {
00597     uintN flags;
00598 
00599     /* Fast path for native iterators */
00600     if (OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass) {
00601         flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
00602         if (flags & JSITER_ENUMERATE)
00603             return CallEnumeratorNext(cx, iterobj, flags, rval);
00604 
00605         /*
00606          * Call next directly as all the methods of the native iterator are
00607          * read-only and permanent.
00608          */
00609         if (!IteratorNextImpl(cx, iterobj, rval))
00610             return JS_FALSE;
00611     } else {
00612         jsid id = ATOM_TO_JSID(cx->runtime->atomState.nextAtom);
00613 
00614         if (!JS_GetMethodById(cx, iterobj, id, &iterobj, rval))
00615             return JS_FALSE;
00616         if (!js_InternalCall(cx, iterobj, *rval, 0, NULL, rval)) {
00617             /* Check for StopIteration. */
00618             if (!cx->throwing ||
00619                 JSVAL_IS_PRIMITIVE(cx->exception) ||
00620                 OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(cx->exception))
00621                     != &js_StopIterationClass) {
00622                 return JS_FALSE;
00623             }
00624 
00625             /* Inline JS_ClearPendingException(cx). */
00626             cx->throwing = JS_FALSE;
00627             cx->exception = JSVAL_VOID;
00628             *rval = JSVAL_HOLE;
00629             return JS_TRUE;
00630         }
00631     }
00632 
00633     return JS_TRUE;
00634 }
00635 
00636 static JSBool
00637 stopiter_hasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
00638 {
00639     *bp = !JSVAL_IS_PRIMITIVE(v) &&
00640           OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == &js_StopIterationClass;
00641     return JS_TRUE;
00642 }
00643 
00644 JSClass js_StopIterationClass = {
00645     js_StopIteration_str,
00646     JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration),
00647     JS_PropertyStub,  JS_PropertyStub,
00648     JS_PropertyStub,  JS_PropertyStub,
00649     JS_EnumerateStub, JS_ResolveStub,
00650     JS_ConvertStub,   JS_FinalizeStub,
00651     NULL,             NULL,
00652     NULL,             NULL,
00653     NULL,             stopiter_hasInstance,
00654     NULL,             NULL
00655 };
00656 
00657 #if JS_HAS_GENERATORS
00658 
00659 static void
00660 generator_finalize(JSContext *cx, JSObject *obj)
00661 {
00662     JSGenerator *gen;
00663 
00664     gen = (JSGenerator *) JS_GetPrivate(cx, obj);
00665     if (gen) {
00666         /*
00667          * gen can be open on shutdown when close hooks are ignored or when
00668          * the embedding cancels scheduled close hooks.
00669          */
00670         JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_CLOSED ||
00671                   gen->state == JSGEN_OPEN);
00672         JS_free(cx, gen);
00673     }
00674 }
00675 
00676 static uint32
00677 generator_mark(JSContext *cx, JSObject *obj, void *arg)
00678 {
00679     JSGenerator *gen;
00680 
00681     gen = (JSGenerator *) JS_GetPrivate(cx, obj);
00682     if (gen) {
00683         /*
00684          * We must mark argv[-2], as js_MarkStackFrame will not.  Note that
00685          * js_MarkStackFrame will mark thisp (argv[-1]) and actual arguments,
00686          * plus any missing formals and local GC roots.
00687          */
00688         JS_ASSERT(!JSVAL_IS_PRIMITIVE(gen->frame.argv[-2]));
00689         GC_MARK(cx, JSVAL_TO_GCTHING(gen->frame.argv[-2]), "generator");
00690         js_MarkStackFrame(cx, &gen->frame);
00691     }
00692     return 0;
00693 }
00694 
00695 JSClass js_GeneratorClass = {
00696     js_Generator_str,
00697     JSCLASS_HAS_PRIVATE | JSCLASS_IS_ANONYMOUS |
00698     JSCLASS_HAS_CACHED_PROTO(JSProto_Generator),
00699     JS_PropertyStub,  JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
00700     JS_EnumerateStub, JS_ResolveStub,  JS_ConvertStub,  generator_finalize,
00701     NULL,             NULL,            NULL,            NULL,
00702     NULL,             NULL,            generator_mark,  NULL
00703 };
00704 
00705 /*
00706  * Called from the JSOP_GENERATOR case in the interpreter, with fp referring
00707  * to the frame by which the generator function was activated.  Create a new
00708  * JSGenerator object, which contains its own JSStackFrame that we populate
00709  * from *fp.  We know that upon return, the JSOP_GENERATOR opcode will return
00710  * from the activation in fp, so we can steal away fp->callobj and fp->argsobj
00711  * if they are non-null.
00712  */
00713 JSObject *
00714 js_NewGenerator(JSContext *cx, JSStackFrame *fp)
00715 {
00716     JSObject *obj;
00717     uintN argc, nargs, nvars, depth, nslots;
00718     JSGenerator *gen;
00719     jsval *newsp;
00720 
00721     /* After the following return, failing control flow must goto bad. */
00722     obj = js_NewObject(cx, &js_GeneratorClass, NULL, NULL);
00723     if (!obj)
00724         return NULL;
00725 
00726     /* Load and compute stack slot counts. */
00727     argc = fp->argc;
00728     nargs = JS_MAX(argc, fp->fun->nargs);
00729     nvars = fp->nvars;
00730     depth = fp->script->depth;
00731     nslots = 2 + nargs + nvars + 2 * depth;
00732 
00733     /* Allocate obj's private data struct. */
00734     gen = (JSGenerator *)
00735           JS_malloc(cx, sizeof(JSGenerator) + (nslots - 1) * sizeof(jsval));
00736     if (!gen)
00737         goto bad;
00738 
00739     gen->obj = obj;
00740 
00741     /* Steal away objects reflecting fp and point them at gen->frame. */
00742     gen->frame.callobj = fp->callobj;
00743     if (fp->callobj) {
00744         JS_SetPrivate(cx, fp->callobj, &gen->frame);
00745         fp->callobj = NULL;
00746     }
00747     gen->frame.argsobj = fp->argsobj;
00748     if (fp->argsobj) {
00749         JS_SetPrivate(cx, fp->argsobj, &gen->frame);
00750         fp->argsobj = NULL;
00751     }
00752 
00753     /* These two references can be shared with fp until it goes away. */
00754     gen->frame.varobj = fp->varobj;
00755     gen->frame.thisp = fp->thisp;
00756 
00757     /* Copy call-invariant script and function references. */
00758     gen->frame.script = fp->script;
00759     gen->frame.callee = fp->callee;
00760     gen->frame.fun = fp->fun;
00761 
00762     /* Use newsp to carve space out of gen->stack. */
00763     newsp = gen->stack;
00764     gen->arena.next = NULL;
00765     gen->arena.base = (jsuword) newsp;
00766     gen->arena.limit = gen->arena.avail = (jsuword) (newsp + nslots);
00767 
00768 #define COPY_STACK_ARRAY(vec,cnt,num)                                         \
00769     JS_BEGIN_MACRO                                                            \
00770         gen->frame.cnt = cnt;                                                 \
00771         gen->frame.vec = newsp;                                               \
00772         newsp += (num);                                                       \
00773         memcpy(gen->frame.vec, fp->vec, (num) * sizeof(jsval));               \
00774     JS_END_MACRO
00775 
00776     /* Copy argv, rval, and vars. */
00777     *newsp++ = fp->argv[-2];
00778     *newsp++ = fp->argv[-1];
00779     COPY_STACK_ARRAY(argv, argc, nargs);
00780     gen->frame.rval = fp->rval;
00781     COPY_STACK_ARRAY(vars, nvars, nvars);
00782 
00783 #undef COPY_STACK_ARRAY
00784 
00785     /* Initialize or copy virtual machine state. */
00786     gen->frame.down = NULL;
00787     gen->frame.annotation = NULL;
00788     gen->frame.scopeChain = fp->scopeChain;
00789     gen->frame.pc = fp->pc;
00790 
00791     /* Allocate generating pc and operand stack space. */
00792     gen->frame.spbase = gen->frame.sp = newsp + depth;
00793 
00794     /* Copy remaining state (XXX sharp* and xml* should be local vars). */
00795     gen->frame.sharpDepth = 0;
00796     gen->frame.sharpArray = NULL;
00797     gen->frame.flags = fp->flags | JSFRAME_GENERATOR;
00798     gen->frame.dormantNext = NULL;
00799     gen->frame.xmlNamespace = NULL;
00800     gen->frame.blockChain = NULL;
00801 
00802     /* Note that gen is newborn. */
00803     gen->state = JSGEN_NEWBORN;
00804 
00805     if (!JS_SetPrivate(cx, obj, gen)) {
00806         JS_free(cx, gen);
00807         goto bad;
00808     }
00809 
00810     /*
00811      * Register with GC to ensure that suspended finally blocks will be
00812      * executed.
00813      */
00814     js_RegisterGenerator(cx, gen);
00815     return obj;
00816 
00817   bad:
00818     cx->weakRoots.newborn[GCX_OBJECT] = NULL;
00819     return NULL;
00820 }
00821 
00822 typedef enum JSGeneratorOp {
00823     JSGENOP_NEXT,
00824     JSGENOP_SEND,
00825     JSGENOP_THROW,
00826     JSGENOP_CLOSE
00827 } JSGeneratorOp;
00828 
00829 /*
00830  * Start newborn or restart yielding generator and perform the requested
00831  * operation inside its frame.
00832  */
00833 static JSBool
00834 SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj,
00835                 JSGenerator *gen, jsval arg, jsval *rval)
00836 {
00837     JSStackFrame *fp;
00838     jsval junk;
00839     JSArena *arena;
00840     JSBool ok;
00841 
00842     JS_ASSERT(gen->state ==  JSGEN_NEWBORN || gen->state == JSGEN_OPEN);
00843     switch (op) {
00844       case JSGENOP_NEXT:
00845       case JSGENOP_SEND:
00846         if (gen->state == JSGEN_OPEN) {
00847             /*
00848              * Store the argument to send as the result of the yield
00849              * expression.
00850              */
00851             gen->frame.sp[-1] = arg;
00852         }
00853         gen->state = JSGEN_RUNNING;
00854         break;
00855 
00856       case JSGENOP_THROW:
00857         JS_SetPendingException(cx, arg);
00858         gen->state = JSGEN_RUNNING;
00859         break;
00860 
00861       default:
00862         JS_ASSERT(op == JSGENOP_CLOSE);
00863         JS_SetPendingException(cx, JSVAL_ARETURN);
00864         gen->state = JSGEN_CLOSING;
00865         break;
00866     }
00867 
00868     /* Extend the current stack pool with gen->arena. */
00869     arena = cx->stackPool.current;
00870     JS_ASSERT(!arena->next);
00871     JS_ASSERT(!gen->arena.next);
00872     JS_ASSERT(cx->stackPool.current != &gen->arena);
00873     cx->stackPool.current = arena->next = &gen->arena;
00874 
00875     /* Push gen->frame around the interpreter activation. */
00876     fp = cx->fp;
00877     cx->fp = &gen->frame;
00878     gen->frame.down = fp;
00879     ok = js_Interpret(cx, gen->frame.pc, &junk);
00880     cx->fp = fp;
00881     gen->frame.down = NULL;
00882 
00883     /* Retract the stack pool and sanitize gen->arena. */
00884     JS_ASSERT(!gen->arena.next);
00885     JS_ASSERT(arena->next == &gen->arena);
00886     JS_ASSERT(cx->stackPool.current == &gen->arena);
00887     cx->stackPool.current = arena;
00888     arena->next = NULL;
00889 
00890     if (gen->frame.flags & JSFRAME_YIELDING) {
00891         /* Yield cannot fail, throw or be called on closing. */
00892         JS_ASSERT(ok);
00893         JS_ASSERT(!cx->throwing);
00894         JS_ASSERT(gen->state == JSGEN_RUNNING);
00895         JS_ASSERT(op != JSGENOP_CLOSE);
00896         gen->frame.flags &= ~JSFRAME_YIELDING;
00897         gen->state = JSGEN_OPEN;
00898         *rval = gen->frame.rval;
00899         return JS_TRUE;
00900     }
00901 
00902     gen->state = JSGEN_CLOSED;
00903 
00904     if (ok) {
00905         /* Returned, explicitly or by falling off the end. */
00906         if (op == JSGENOP_CLOSE)
00907             return JS_TRUE;
00908         return js_ThrowStopIteration(cx, obj);
00909     }
00910 
00911     /*
00912      * An error, silent termination by branch callback or an exception.
00913      * Propagate the condition to the caller.
00914      */
00915     return JS_FALSE;
00916 }
00917 
00918 /*
00919  * Execute gen's close hook after the GC detects that the object has become
00920  * unreachable.
00921  */
00922 JSBool
00923 js_CloseGeneratorObject(JSContext *cx, JSGenerator *gen)
00924 {
00925     /* We pass null as rval since SendToGenerator never uses it with CLOSE. */
00926     return SendToGenerator(cx, JSGENOP_CLOSE, gen->obj, gen, JSVAL_VOID, NULL);
00927 }
00928 
00929 /*
00930  * Common subroutine of generator_(next|send|throw|close) methods.
00931  */
00932 static JSBool
00933 generator_op(JSContext *cx, JSGeneratorOp op,
00934              JSObject *obj, uintN argc, jsval *argv, jsval *rval)
00935 {
00936     JSGenerator *gen;
00937     JSString *str;
00938     jsval arg;
00939 
00940     if (!JS_InstanceOf(cx, obj, &js_GeneratorClass, argv))
00941         return JS_FALSE;
00942 
00943     gen = (JSGenerator *) JS_GetPrivate(cx, obj);
00944     if (gen == NULL) {
00945         /* This happens when obj is the generator prototype. See bug 352885. */
00946         goto closed_generator;
00947     }
00948 
00949     switch (gen->state) {
00950       case JSGEN_NEWBORN:
00951         switch (op) {
00952           case JSGENOP_NEXT:
00953           case JSGENOP_THROW:
00954             break;
00955 
00956           case JSGENOP_SEND:
00957             if (!JSVAL_IS_VOID(argv[0])) {
00958                 str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK,
00959                                                  argv[0], NULL);
00960                 if (str) {
00961                     JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL,
00962                                            JSMSG_BAD_GENERATOR_SEND,
00963                                            JSSTRING_CHARS(str));
00964                 }
00965                 return JS_FALSE;
00966             }
00967             break;
00968 
00969           default:
00970             JS_ASSERT(op == JSGENOP_CLOSE);
00971             gen->state = JSGEN_CLOSED;
00972             return JS_TRUE;
00973         }
00974         break;
00975 
00976       case JSGEN_OPEN:
00977         break;
00978 
00979       case JSGEN_RUNNING:
00980       case JSGEN_CLOSING:
00981         str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, argv[-1],
00982                                          JS_GetFunctionId(gen->frame.fun));
00983         if (str) {
00984             JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL,
00985                                    JSMSG_NESTING_GENERATOR,
00986                                    JSSTRING_CHARS(str));
00987         }
00988         return JS_FALSE;
00989 
00990       default:
00991         JS_ASSERT(gen->state == JSGEN_CLOSED);
00992 
00993       closed_generator:
00994         switch (op) {
00995           case JSGENOP_NEXT:
00996           case JSGENOP_SEND:
00997             return js_ThrowStopIteration(cx, obj);
00998           case JSGENOP_THROW:
00999             JS_SetPendingException(cx, argv[0]);
01000             return JS_FALSE;
01001           default:
01002             JS_ASSERT(op == JSGENOP_CLOSE);
01003             return JS_TRUE;
01004         }
01005     }
01006 
01007     arg = (op == JSGENOP_SEND || op == JSGENOP_THROW)
01008           ? argv[0]
01009           : JSVAL_VOID;
01010     if (!SendToGenerator(cx, op, obj, gen, arg, rval))
01011         return JS_FALSE;
01012     return JS_TRUE;
01013 }
01014 
01015 static JSBool
01016 generator_send(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
01017                jsval *rval)
01018 {
01019     return generator_op(cx, JSGENOP_SEND, obj, argc, argv, rval);
01020 }
01021 
01022 static JSBool
01023 generator_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
01024                jsval *rval)
01025 {
01026     return generator_op(cx, JSGENOP_NEXT, obj, argc, argv, rval);
01027 }
01028 
01029 static JSBool
01030 generator_throw(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
01031                 jsval *rval)
01032 {
01033     return generator_op(cx, JSGENOP_THROW, obj, argc, argv, rval);
01034 }
01035 
01036 static JSBool
01037 generator_close(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
01038                 jsval *rval)
01039 {
01040     return generator_op(cx, JSGENOP_CLOSE, obj, argc, argv, rval);
01041 }
01042 
01043 static JSFunctionSpec generator_methods[] = {
01044     {js_iterator_str, iterator_self,     0,JSPROP_READONLY|JSPROP_PERMANENT,0},
01045     {js_next_str,     generator_next,    0,JSPROP_READONLY|JSPROP_PERMANENT,0},
01046     {js_send_str,     generator_send,    1,JSPROP_READONLY|JSPROP_PERMANENT,0},
01047     {js_throw_str,    generator_throw,   1,JSPROP_READONLY|JSPROP_PERMANENT,0},
01048     {js_close_str,    generator_close,   0,JSPROP_READONLY|JSPROP_PERMANENT,0},
01049     {0,0,0,0,0}
01050 };
01051 
01052 #endif /* JS_HAS_GENERATORS */
01053 
01054 JSObject *
01055 js_InitIteratorClasses(JSContext *cx, JSObject *obj)
01056 {
01057     JSObject *proto, *stop;
01058 
01059     /* Idempotency required: we initialize several things, possibly lazily. */
01060     if (!js_GetClassObject(cx, obj, JSProto_StopIteration, &stop))
01061         return NULL;
01062     if (stop)
01063         return stop;
01064 
01065     proto = JS_InitClass(cx, obj, NULL, &js_IteratorClass, Iterator, 2,
01066                          NULL, iterator_methods, NULL, NULL);
01067     if (!proto)
01068         return NULL;
01069     proto->slots[JSSLOT_ITER_STATE] = JSVAL_NULL;
01070 
01071 #if JS_HAS_GENERATORS
01072     /* Initialize the generator internals if configured. */
01073     if (!JS_InitClass(cx, obj, NULL, &js_GeneratorClass, NULL, 0,
01074                       NULL, generator_methods, NULL, NULL)) {
01075         return NULL;
01076     }
01077 #endif
01078 
01079     return JS_InitClass(cx, obj, NULL, &js_StopIterationClass, NULL, 0,
01080                         NULL, NULL, NULL, NULL);
01081 }