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