/js/lib/Socket.IO-node/support/expresso/deps/jscoverage/js/jsiter.cpp
C++ | 1050 lines | 758 code | 131 blank | 161 comment | 169 complexity | 643f56208cf9fcbfd4c9348ef4603d00 MD5 | raw file
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 78static JSBool 79CloseGenerator(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 */ 87void 88js_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 116JSClass 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 125static JSBool 126InitNativeIterator(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 167static JSBool 168Iterator(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 194static JSBool 195NewKeyValuePair(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 212static JSBool 213IteratorNextImpl(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 268JSBool 269js_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 279static JSBool 280iterator_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 299static JSBool 300iterator_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 308static 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 314uintN 315js_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 */ 326JS_FRIEND_API(JSBool) 327js_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 437JS_FRIEND_API(JSBool) JS_FASTCALL 438js_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 459static JSBool 460CallEnumeratorNext(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 591JS_FRIEND_API(JSBool) 592js_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 629static JSBool 630stopiter_hasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp) 631{ 632 *bp = js_ValueIsStopIteration(v); 633 return JS_TRUE; 634} 635 636JSClass 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 651static void 652generator_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 668static void 669generator_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 693JSClass 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 */ 711JSObject * 712js_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 806typedef 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 */ 817static JSBool 818SendToGenerator(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 907static JSBool 908CloseGenerator(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 */ 928static JSBool 929generator_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 988static JSBool 989generator_send(JSContext *cx, uintN argc, jsval *vp) 990{ 991 return generator_op(cx, JSGENOP_SEND, vp, argc); 992} 993 994static JSBool 995generator_next(JSContext *cx, uintN argc, jsval *vp) 996{ 997 return generator_op(cx, JSGENOP_NEXT, vp, argc); 998} 999 1000static JSBool 1001generator_throw(JSContext *cx, uintN argc, jsval *vp) 1002{ 1003 return generator_op(cx, JSGENOP_THROW, vp, argc); 1004} 1005 1006static JSBool 1007generator_close(JSContext *cx, uintN argc, jsval *vp) 1008{ 1009 return generator_op(cx, JSGENOP_CLOSE, vp, argc); 1010} 1011 1012static 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 1023JSObject * 1024js_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}