/[jscoverage]/trunk/js/jsiter.cpp
ViewVC logotype

Contents of /trunk/js/jsiter.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 399 - (show annotations)
Tue Dec 9 03:37:47 2008 UTC (11 years ago) by siliconforks
File size: 31596 byte(s)
Use SpiderMonkey from Firefox 3.1b2.

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

  ViewVC Help
Powered by ViewVC 1.1.24