Bug 550784 - [OOPP] Flash deadlocks during script evals that trigger focus related...
[mozilla-central.git] / js / src / jsarray.cpp
blob026ddee28ea2a21cb2e8562c7eeee03d458d865d
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set sw=4 ts=8 et tw=78:
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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.
17 * The Original Code is Mozilla Communicator client code, released
18 * March 31, 1998.
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.
25 * Contributor(s):
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.
39 * ***** END LICENSE BLOCK ***** */
42 * JS array class.
44 * Array objects begin as "dense" arrays, optimized for index-only property
45 * access over a vector of slots (obj->dslots) with high load factor. Array
46 * methods optimize for denseness by testing that the object's class is
47 * &js_ArrayClass, and can then directly manipulate the slots for efficiency.
49 * We track these pieces of metadata for arrays in dense mode:
50 * - the array's length property as a uint32, in JSSLOT_ARRAY_LENGTH,
51 * - the number of indices that are filled (non-holes), in JSSLOT_ARRAY_COUNT,
52 * - the net number of slots starting at dslots (capacity), in dslots[-1] if
53 * dslots is non-NULL.
55 * In dense mode, holes in the array are represented by JSVAL_HOLE. The final
56 * slot in fslots is unused.
58 * NB: the capacity and length of a dense array are entirely unrelated! The
59 * length may be greater than, less than, or equal to the capacity. See
60 * array_length_setter for an explanation of how the first, most surprising
61 * case may occur.
63 * Arrays are converted to use js_SlowArrayClass when any of these conditions
64 * are met:
65 * - the load factor (COUNT / capacity) is less than 0.25, and there are
66 * more than MIN_SPARSE_INDEX slots total
67 * - a property is set that is not indexed (and not "length"); or
68 * - a property is defined that has non-default property attributes.
70 * Dense arrays do not track property creation order, so unlike other native
71 * objects and slow arrays, enumerating an array does not necessarily visit the
72 * properties in the order they were created. We could instead maintain the
73 * scope to track property enumeration order, but still use the fast slot
74 * access. That would have the same memory cost as just using a
75 * js_SlowArrayClass, but have the same performance characteristics as a dense
76 * array for slot accesses, at some cost in code complexity.
78 #include <stdlib.h>
79 #include <string.h>
80 #include "jstypes.h"
81 #include "jsstdint.h"
82 #include "jsutil.h" /* Added by JSIFY */
83 #include "jsapi.h"
84 #include "jsarray.h"
85 #include "jsatom.h"
86 #include "jsbit.h"
87 #include "jsbool.h"
88 #include "jstracer.h"
89 #include "jsbuiltins.h"
90 #include "jscntxt.h"
91 #include "jsversion.h"
92 #include "jsdbgapi.h" /* for js_TraceWatchPoints */
93 #include "jsdtoa.h"
94 #include "jsfun.h"
95 #include "jsgc.h"
96 #include "jsinterp.h"
97 #include "jslock.h"
98 #include "jsnum.h"
99 #include "jsobj.h"
100 #include "jsscope.h"
101 #include "jsstr.h"
102 #include "jsstaticcheck.h"
103 #include "jsvector.h"
105 #include "jsatominlines.h"
107 using namespace js;
109 /* 2^32 - 1 as a number and a string */
110 #define MAXINDEX 4294967295u
111 #define MAXSTR "4294967295"
113 /* Small arrays are dense, no matter what. */
114 #define MIN_SPARSE_INDEX 256
116 static inline bool
117 INDEX_TOO_BIG(jsuint index)
119 return index > JS_BIT(29) - 1;
122 #define INDEX_TOO_SPARSE(array, index) \
123 (INDEX_TOO_BIG(index) || \
124 ((index) > js_DenseArrayCapacity(array) && (index) >= MIN_SPARSE_INDEX && \
125 (index) > (uint32)((array)->fslots[JSSLOT_ARRAY_COUNT] + 1) * 4))
127 JS_STATIC_ASSERT(sizeof(JSScopeProperty) > 4 * sizeof(jsval));
129 #define ENSURE_SLOW_ARRAY(cx, obj) \
130 (OBJ_GET_CLASS(cx, obj) == &js_SlowArrayClass || js_MakeArraySlow(cx, obj))
133 * Determine if the id represents an array index or an XML property index.
135 * An id is an array index according to ECMA by (15.4):
137 * "Array objects give special treatment to a certain class of property names.
138 * A property name P (in the form of a string value) is an array index if and
139 * only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal
140 * to 2^32-1."
142 * In our implementation, it would be sufficient to check for JSVAL_IS_INT(id)
143 * except that by using signed 32-bit integers we miss the top half of the
144 * valid range. This function checks the string representation itself; note
145 * that calling a standard conversion routine might allow strings such as
146 * "08" or "4.0" as array indices, which they are not.
148 JSBool
149 js_IdIsIndex(jsval id, jsuint *indexp)
151 JSString *str;
152 jschar *cp;
154 if (JSVAL_IS_INT(id)) {
155 jsint i;
156 i = JSVAL_TO_INT(id);
157 if (i < 0)
158 return JS_FALSE;
159 *indexp = (jsuint)i;
160 return JS_TRUE;
163 /* NB: id should be a string, but jsxml.c may call us with an object id. */
164 if (!JSVAL_IS_STRING(id))
165 return JS_FALSE;
167 str = JSVAL_TO_STRING(id);
168 cp = str->chars();
169 if (JS7_ISDEC(*cp) && str->length() < sizeof(MAXSTR)) {
170 jsuint index = JS7_UNDEC(*cp++);
171 jsuint oldIndex = 0;
172 jsuint c = 0;
173 if (index != 0) {
174 while (JS7_ISDEC(*cp)) {
175 oldIndex = index;
176 c = JS7_UNDEC(*cp);
177 index = 10*index + c;
178 cp++;
182 /* Ensure that all characters were consumed and we didn't overflow. */
183 if (*cp == 0 &&
184 (oldIndex < (MAXINDEX / 10) ||
185 (oldIndex == (MAXINDEX / 10) && c < (MAXINDEX % 10))))
187 *indexp = index;
188 return JS_TRUE;
191 return JS_FALSE;
194 static jsuint
195 ValueIsLength(JSContext *cx, jsval* vp)
197 jsint i;
198 jsdouble d;
199 jsuint length;
201 if (JSVAL_IS_INT(*vp)) {
202 i = JSVAL_TO_INT(*vp);
203 if (i < 0)
204 goto error;
205 return (jsuint) i;
208 d = js_ValueToNumber(cx, vp);
209 if (JSVAL_IS_NULL(*vp))
210 goto error;
212 if (JSDOUBLE_IS_NaN(d))
213 goto error;
214 length = (jsuint) d;
215 if (d != (jsdouble) length)
216 goto error;
217 return length;
219 error:
220 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
221 JSMSG_BAD_ARRAY_LENGTH);
222 *vp = JSVAL_NULL;
223 return 0;
226 JSBool
227 js_GetLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp)
229 if (OBJ_IS_ARRAY(cx, obj)) {
230 *lengthp = obj->fslots[JSSLOT_ARRAY_LENGTH];
231 return JS_TRUE;
234 JSAutoTempValueRooter tvr(cx, JSVAL_NULL);
235 if (!obj->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.lengthAtom), tvr.addr()))
236 return JS_FALSE;
238 if (JSVAL_IS_INT(tvr.value())) {
239 *lengthp = jsuint(jsint(JSVAL_TO_INT(tvr.value()))); /* jsuint cast does ToUint32 */
240 return JS_TRUE;
243 *lengthp = js_ValueToECMAUint32(cx, tvr.addr());
244 return !JSVAL_IS_NULL(tvr.value());
247 static JSBool
248 IndexToValue(JSContext *cx, jsdouble index, jsval *vp)
250 return js_NewWeaklyRootedNumber(cx, index, vp);
253 JSBool JS_FASTCALL
254 js_IndexToId(JSContext *cx, jsuint index, jsid *idp)
256 JSString *str;
258 if (index <= JSVAL_INT_MAX) {
259 *idp = INT_TO_JSID(index);
260 return JS_TRUE;
262 str = js_NumberToString(cx, index);
263 if (!str)
264 return JS_FALSE;
265 return js_ValueToStringId(cx, STRING_TO_JSVAL(str), idp);
268 static JSBool
269 BigIndexToId(JSContext *cx, JSObject *obj, jsuint index, JSBool createAtom,
270 jsid *idp)
272 jschar buf[10], *start;
273 JSClass *clasp;
274 JSAtom *atom;
275 JS_STATIC_ASSERT((jsuint)-1 == 4294967295U);
277 JS_ASSERT(index > JSVAL_INT_MAX);
279 start = JS_ARRAY_END(buf);
280 do {
281 --start;
282 *start = (jschar)('0' + index % 10);
283 index /= 10;
284 } while (index != 0);
287 * Skip the atomization if the class is known to store atoms corresponding
288 * to big indexes together with elements. In such case we know that the
289 * array does not have an element at the given index if its atom does not
290 * exist. Fast arrays (clasp == &js_ArrayClass) don't use atoms for
291 * any indexes, though it would be rare to see them have a big index
292 * in any case.
294 if (!createAtom &&
295 ((clasp = OBJ_GET_CLASS(cx, obj)) == &js_SlowArrayClass ||
296 clasp == &js_ArgumentsClass ||
297 clasp == &js_ObjectClass)) {
298 atom = js_GetExistingStringAtom(cx, start, JS_ARRAY_END(buf) - start);
299 if (!atom) {
300 *idp = JSVAL_VOID;
301 return JS_TRUE;
303 } else {
304 atom = js_AtomizeChars(cx, start, JS_ARRAY_END(buf) - start, 0);
305 if (!atom)
306 return JS_FALSE;
309 *idp = ATOM_TO_JSID(atom);
310 return JS_TRUE;
313 static JSBool
314 ResizeSlots(JSContext *cx, JSObject *obj, uint32 oldlen, uint32 newlen,
315 bool initializeAllSlots = true)
317 jsval *slots, *newslots;
319 if (newlen == 0) {
320 if (obj->dslots) {
321 cx->free(obj->dslots - 1);
322 obj->dslots = NULL;
324 return JS_TRUE;
327 if (newlen > MAX_DSLOTS_LENGTH) {
328 js_ReportAllocationOverflow(cx);
329 return JS_FALSE;
332 slots = obj->dslots ? obj->dslots - 1 : NULL;
333 newslots = (jsval *) cx->realloc(slots, (size_t(newlen) + 1) * sizeof(jsval));
334 if (!newslots)
335 return JS_FALSE;
337 obj->dslots = newslots + 1;
338 js_SetDenseArrayCapacity(obj, newlen);
340 if (initializeAllSlots) {
341 for (slots = obj->dslots + oldlen; slots < obj->dslots + newlen; slots++)
342 *slots = JSVAL_HOLE;
345 return JS_TRUE;
349 * When a dense array with CAPACITY_DOUBLING_MAX or fewer slots needs to grow,
350 * double its capacity, to push() N elements in amortized O(N) time.
352 * Above this limit, grow by 12.5% each time. Speed is still amortized O(N),
353 * with a higher constant factor, and we waste less space.
355 #define CAPACITY_DOUBLING_MAX (1024 * 1024)
358 * Round up all large allocations to a multiple of this (1MB), so as not to
359 * waste space if malloc gives us 1MB-sized chunks (as jemalloc does).
361 #define CAPACITY_CHUNK (1024 * 1024 / sizeof(jsval))
363 static JSBool
364 EnsureCapacity(JSContext *cx, JSObject *obj, uint32 newcap,
365 bool initializeAllSlots = true)
367 uint32 oldcap = js_DenseArrayCapacity(obj);
369 if (newcap > oldcap) {
371 * If this overflows uint32, newcap is very large. nextsize will end
372 * up being less than newcap, the code below will thus disregard it,
373 * and ResizeSlots will fail.
375 * The way we use dslots[-1] forces a few +1s and -1s here. For
376 * example, (oldcap * 2 + 1) produces the sequence 7, 15, 31, 63, ...
377 * which makes the total allocation size (with dslots[-1]) a power
378 * of two.
380 uint32 nextsize = (oldcap <= CAPACITY_DOUBLING_MAX)
381 ? oldcap * 2 + 1
382 : oldcap + (oldcap >> 3);
384 uint32 actualCapacity = JS_MAX(newcap, nextsize);
385 if (actualCapacity >= CAPACITY_CHUNK)
386 actualCapacity = JS_ROUNDUP(actualCapacity + 1, CAPACITY_CHUNK) - 1; /* -1 for dslots[-1] */
387 else if (actualCapacity < ARRAY_CAPACITY_MIN)
388 actualCapacity = ARRAY_CAPACITY_MIN;
389 if (!ResizeSlots(cx, obj, oldcap, actualCapacity, initializeAllSlots))
390 return JS_FALSE;
391 if (!initializeAllSlots) {
393 * Initialize the slots caller didn't actually ask for.
395 for (jsval *slots = obj->dslots + newcap;
396 slots < obj->dslots + actualCapacity;
397 slots++) {
398 *slots = JSVAL_HOLE;
402 return JS_TRUE;
405 static bool
406 ReallyBigIndexToId(JSContext* cx, jsdouble index, jsid* idp)
408 JSAutoTempValueRooter dval(cx);
409 if (!js_NewDoubleInRootedValue(cx, index, dval.addr()) ||
410 !js_ValueToStringId(cx, dval.value(), idp)) {
411 return JS_FALSE;
413 return JS_TRUE;
416 static bool
417 IndexToId(JSContext* cx, JSObject* obj, jsdouble index, JSBool* hole, jsid* idp,
418 JSBool createAtom = JS_FALSE)
420 if (index <= JSVAL_INT_MAX) {
421 *idp = INT_TO_JSID(int(index));
422 return JS_TRUE;
425 if (index <= jsuint(-1)) {
426 if (!BigIndexToId(cx, obj, jsuint(index), createAtom, idp))
427 return JS_FALSE;
428 if (hole && JSVAL_IS_VOID(*idp))
429 *hole = JS_TRUE;
430 return JS_TRUE;
433 return ReallyBigIndexToId(cx, index, idp);
437 * If the property at the given index exists, get its value into location
438 * pointed by vp and set *hole to false. Otherwise set *hole to true and *vp
439 * to JSVAL_VOID. This function assumes that the location pointed by vp is
440 * properly rooted and can be used as GC-protected storage for temporaries.
442 static JSBool
443 GetArrayElement(JSContext *cx, JSObject *obj, jsdouble index, JSBool *hole,
444 jsval *vp)
446 JS_ASSERT(index >= 0);
447 if (OBJ_IS_DENSE_ARRAY(cx, obj) && index < js_DenseArrayCapacity(obj) &&
448 (*vp = obj->dslots[jsuint(index)]) != JSVAL_HOLE) {
449 *hole = JS_FALSE;
450 return JS_TRUE;
453 JSAutoTempIdRooter idr(cx);
455 *hole = JS_FALSE;
456 if (!IndexToId(cx, obj, index, hole, idr.addr()))
457 return JS_FALSE;
458 if (*hole) {
459 *vp = JSVAL_VOID;
460 return JS_TRUE;
463 JSObject *obj2;
464 JSProperty *prop;
465 if (!obj->lookupProperty(cx, idr.id(), &obj2, &prop))
466 return JS_FALSE;
467 if (!prop) {
468 *hole = JS_TRUE;
469 *vp = JSVAL_VOID;
470 } else {
471 obj2->dropProperty(cx, prop);
472 if (!obj->getProperty(cx, idr.id(), vp))
473 return JS_FALSE;
474 *hole = JS_FALSE;
476 return JS_TRUE;
480 * Set the value of the property at the given index to v assuming v is rooted.
482 static JSBool
483 SetArrayElement(JSContext *cx, JSObject *obj, jsdouble index, jsval v)
485 JS_ASSERT(index >= 0);
487 if (OBJ_IS_DENSE_ARRAY(cx, obj)) {
488 /* Predicted/prefetched code should favor the remains-dense case. */
489 if (index <= jsuint(-1)) {
490 jsuint idx = jsuint(index);
491 if (!INDEX_TOO_SPARSE(obj, idx)) {
492 JS_ASSERT(idx + 1 > idx);
493 if (!EnsureCapacity(cx, obj, idx + 1))
494 return JS_FALSE;
495 if (idx >= uint32(obj->fslots[JSSLOT_ARRAY_LENGTH]))
496 obj->fslots[JSSLOT_ARRAY_LENGTH] = idx + 1;
497 if (obj->dslots[idx] == JSVAL_HOLE)
498 obj->fslots[JSSLOT_ARRAY_COUNT]++;
499 obj->dslots[idx] = v;
500 return JS_TRUE;
504 if (!js_MakeArraySlow(cx, obj))
505 return JS_FALSE;
508 JSAutoTempIdRooter idr(cx);
510 if (!IndexToId(cx, obj, index, NULL, idr.addr(), JS_TRUE))
511 return JS_FALSE;
512 JS_ASSERT(!JSVAL_IS_VOID(idr.id()));
514 return obj->setProperty(cx, idr.id(), &v);
517 static JSBool
518 DeleteArrayElement(JSContext *cx, JSObject *obj, jsdouble index)
520 JS_ASSERT(index >= 0);
521 if (OBJ_IS_DENSE_ARRAY(cx, obj)) {
522 if (index <= jsuint(-1)) {
523 jsuint idx = jsuint(index);
524 if (!INDEX_TOO_SPARSE(obj, idx) && idx < js_DenseArrayCapacity(obj)) {
525 if (obj->dslots[idx] != JSVAL_HOLE)
526 obj->fslots[JSSLOT_ARRAY_COUNT]--;
527 obj->dslots[idx] = JSVAL_HOLE;
528 return JS_TRUE;
531 return JS_TRUE;
534 JSAutoTempIdRooter idr(cx);
536 if (!IndexToId(cx, obj, index, NULL, idr.addr()))
537 return JS_FALSE;
538 if (JSVAL_IS_VOID(idr.id()))
539 return JS_TRUE;
541 jsval junk;
542 return obj->deleteProperty(cx, idr.id(), &junk);
546 * When hole is true, delete the property at the given index. Otherwise set
547 * its value to v assuming v is rooted.
549 static JSBool
550 SetOrDeleteArrayElement(JSContext *cx, JSObject *obj, jsdouble index,
551 JSBool hole, jsval v)
553 if (hole) {
554 JS_ASSERT(JSVAL_IS_VOID(v));
555 return DeleteArrayElement(cx, obj, index);
557 return SetArrayElement(cx, obj, index, v);
560 JSBool
561 js_SetLengthProperty(JSContext *cx, JSObject *obj, jsdouble length)
563 jsval v;
564 jsid id;
566 if (!IndexToValue(cx, length, &v))
567 return JS_FALSE;
568 id = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
569 return obj->setProperty(cx, id, &v);
572 JSBool
573 js_HasLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp)
575 JSErrorReporter older = JS_SetErrorReporter(cx, NULL);
576 JSAutoTempValueRooter tvr(cx, JSVAL_NULL);
577 jsid id = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
578 JSBool ok = obj->getProperty(cx, id, tvr.addr());
579 JS_SetErrorReporter(cx, older);
580 if (!ok)
581 return false;
583 *lengthp = ValueIsLength(cx, tvr.addr());
584 return !JSVAL_IS_NULL(tvr.value());
587 JSBool
588 js_IsArrayLike(JSContext *cx, JSObject *obj, JSBool *answerp, jsuint *lengthp)
590 JSClass *clasp;
592 clasp = OBJ_GET_CLASS(cx, js_GetWrappedObject(cx, obj));
593 *answerp = (clasp == &js_ArgumentsClass || clasp == &js_ArrayClass ||
594 clasp == &js_SlowArrayClass);
595 if (!*answerp) {
596 *lengthp = 0;
597 return JS_TRUE;
599 return js_GetLengthProperty(cx, obj, lengthp);
603 * The 'length' property of all native Array instances is a shared permanent
604 * property of Array.prototype, so it appears to be a direct property of each
605 * array instance delegating to that Array.prototype. It accesses the private
606 * slot reserved by js_ArrayClass.
608 * Since SpiderMonkey supports cross-class prototype-based delegation, we have
609 * to be careful about the length getter and setter being called on an object
610 * not of Array class. For the getter, we search obj's prototype chain for the
611 * array that caused this getter to be invoked. In the setter case to overcome
612 * the JSPROP_SHARED attribute, we must define a shadowing length property.
614 static JSBool
615 array_length_getter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
617 do {
618 if (OBJ_IS_ARRAY(cx, obj))
619 return IndexToValue(cx, obj->fslots[JSSLOT_ARRAY_LENGTH], vp);
620 } while ((obj = OBJ_GET_PROTO(cx, obj)) != NULL);
621 return JS_TRUE;
624 static JSBool
625 array_length_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
627 jsuint newlen, oldlen, gap, index;
628 jsval junk;
629 JSObject *iter;
630 JSTempValueRooter tvr;
631 JSBool ok;
633 if (!OBJ_IS_ARRAY(cx, obj)) {
634 jsid lengthId = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
636 return obj->defineProperty(cx, lengthId, *vp, NULL, NULL, JSPROP_ENUMERATE);
639 newlen = ValueIsLength(cx, vp);
640 if (JSVAL_IS_NULL(*vp))
641 return JS_FALSE;
642 oldlen = obj->fslots[JSSLOT_ARRAY_LENGTH];
644 if (oldlen == newlen)
645 return JS_TRUE;
647 if (!IndexToValue(cx, newlen, vp))
648 return JS_FALSE;
650 if (oldlen < newlen) {
651 obj->fslots[JSSLOT_ARRAY_LENGTH] = newlen;
652 return JS_TRUE;
655 if (OBJ_IS_DENSE_ARRAY(cx, obj)) {
656 /* Don't reallocate if we're not actually shrinking our slots. */
657 jsuint capacity = js_DenseArrayCapacity(obj);
658 if (capacity > newlen && !ResizeSlots(cx, obj, capacity, newlen))
659 return JS_FALSE;
660 } else if (oldlen - newlen < (1 << 24)) {
661 do {
662 --oldlen;
663 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
664 !DeleteArrayElement(cx, obj, oldlen)) {
665 return JS_FALSE;
667 } while (oldlen != newlen);
668 } else {
670 * We are going to remove a lot of indexes in a presumably sparse
671 * array. So instead of looping through indexes between newlen and
672 * oldlen, we iterate through all properties and remove those that
673 * correspond to indexes in the half-open range [newlen, oldlen). See
674 * bug 322135.
676 iter = JS_NewPropertyIterator(cx, obj);
677 if (!iter)
678 return JS_FALSE;
680 /* Protect iter against GC under JSObject::deleteProperty. */
681 JS_PUSH_TEMP_ROOT_OBJECT(cx, iter, &tvr);
682 gap = oldlen - newlen;
683 for (;;) {
684 ok = (JS_CHECK_OPERATION_LIMIT(cx) &&
685 JS_NextProperty(cx, iter, &id));
686 if (!ok)
687 break;
688 if (JSVAL_IS_VOID(id))
689 break;
690 if (js_IdIsIndex(id, &index) && index - newlen < gap) {
691 ok = obj->deleteProperty(cx, id, &junk);
692 if (!ok)
693 break;
696 JS_POP_TEMP_ROOT(cx, &tvr);
697 if (!ok)
698 return JS_FALSE;
701 obj->fslots[JSSLOT_ARRAY_LENGTH] = newlen;
702 return JS_TRUE;
706 * We have only indexed properties up to capacity (excepting holes), plus the
707 * length property. For all else, we delegate to the prototype.
709 static inline bool
710 IsDenseArrayId(JSContext *cx, JSObject *obj, jsid id)
712 JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj));
714 uint32 i;
715 return id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom) ||
716 (js_IdIsIndex(id, &i) &&
717 obj->fslots[JSSLOT_ARRAY_LENGTH] != 0 &&
718 i < js_DenseArrayCapacity(obj) &&
719 obj->dslots[i] != JSVAL_HOLE);
722 static JSBool
723 array_lookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
724 JSProperty **propp)
726 if (!OBJ_IS_DENSE_ARRAY(cx, obj))
727 return js_LookupProperty(cx, obj, id, objp, propp);
729 if (IsDenseArrayId(cx, obj, id)) {
730 *propp = (JSProperty *) id;
731 *objp = obj;
732 return JS_TRUE;
735 JSObject *proto = STOBJ_GET_PROTO(obj);
736 if (!proto) {
737 *objp = NULL;
738 *propp = NULL;
739 return JS_TRUE;
741 return proto->lookupProperty(cx, id, objp, propp);
744 static void
745 array_dropProperty(JSContext *cx, JSObject *obj, JSProperty *prop)
747 JS_ASSERT(IsDenseArrayId(cx, obj, (jsid) prop));
750 JSBool
751 js_GetDenseArrayElementValue(JSContext *cx, JSObject *obj, JSProperty *prop,
752 jsval *vp)
754 jsid id = (jsid) prop;
755 JS_ASSERT(IsDenseArrayId(cx, obj, id));
757 uint32 i;
758 if (!js_IdIsIndex(id, &i)) {
759 JS_ASSERT(id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom));
760 return IndexToValue(cx, obj->fslots[JSSLOT_ARRAY_LENGTH], vp);
762 *vp = obj->dslots[i];
763 return JS_TRUE;
766 static JSBool
767 array_getProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
769 uint32 i;
771 if (id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom))
772 return IndexToValue(cx, obj->fslots[JSSLOT_ARRAY_LENGTH], vp);
774 if (id == ATOM_TO_JSID(cx->runtime->atomState.protoAtom)) {
775 *vp = OBJECT_TO_JSVAL(obj->getProto());
776 return JS_TRUE;
779 if (!OBJ_IS_DENSE_ARRAY(cx, obj))
780 return js_GetProperty(cx, obj, id, vp);
782 if (!js_IdIsIndex(ID_TO_VALUE(id), &i) || i >= js_DenseArrayCapacity(obj) ||
783 obj->dslots[i] == JSVAL_HOLE) {
784 JSObject *obj2;
785 JSProperty *prop;
786 JSScopeProperty *sprop;
788 JSObject *proto = STOBJ_GET_PROTO(obj);
789 if (!proto) {
790 *vp = JSVAL_VOID;
791 return JS_TRUE;
794 *vp = JSVAL_VOID;
795 if (js_LookupPropertyWithFlags(cx, proto, id, cx->resolveFlags,
796 &obj2, &prop) < 0)
797 return JS_FALSE;
799 if (prop) {
800 if (OBJ_IS_NATIVE(obj2)) {
801 sprop = (JSScopeProperty *) prop;
802 if (!js_NativeGet(cx, obj, obj2, sprop, JSGET_METHOD_BARRIER, vp))
803 return JS_FALSE;
805 obj2->dropProperty(cx, prop);
807 return JS_TRUE;
810 *vp = obj->dslots[i];
811 return JS_TRUE;
814 static JSBool
815 slowarray_addProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
817 jsuint index, length;
819 if (!js_IdIsIndex(id, &index))
820 return JS_TRUE;
821 length = obj->fslots[JSSLOT_ARRAY_LENGTH];
822 if (index >= length)
823 obj->fslots[JSSLOT_ARRAY_LENGTH] = index + 1;
824 return JS_TRUE;
827 static JSBool
828 slowarray_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
829 jsval *statep, jsid *idp);
831 static JSType
832 array_typeOf(JSContext *cx, JSObject *obj)
834 return JSTYPE_OBJECT;
837 /* The same as js_ObjectOps except for the .enumerate and .call hooks. */
838 static JSObjectOps js_SlowArrayObjectOps = {
839 NULL,
840 js_LookupProperty, js_DefineProperty,
841 js_GetProperty, js_SetProperty,
842 js_GetAttributes, js_SetAttributes,
843 js_DeleteProperty, js_DefaultValue,
844 slowarray_enumerate, js_CheckAccess,
845 array_typeOf, js_TraceObject,
846 NULL, NATIVE_DROP_PROPERTY,
847 NULL, js_Construct,
848 js_HasInstance, js_Clear
851 static JSObjectOps *
852 slowarray_getObjectOps(JSContext *cx, JSClass *clasp)
854 return &js_SlowArrayObjectOps;
857 static JSBool
858 array_setProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
860 uint32 i;
862 if (id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom))
863 return array_length_setter(cx, obj, id, vp);
865 if (!OBJ_IS_DENSE_ARRAY(cx, obj))
866 return js_SetProperty(cx, obj, id, vp);
868 if (!js_IdIsIndex(id, &i) || INDEX_TOO_SPARSE(obj, i)) {
869 if (!js_MakeArraySlow(cx, obj))
870 return JS_FALSE;
871 return js_SetProperty(cx, obj, id, vp);
874 if (!EnsureCapacity(cx, obj, i + 1))
875 return JS_FALSE;
877 if (i >= (uint32)obj->fslots[JSSLOT_ARRAY_LENGTH])
878 obj->fslots[JSSLOT_ARRAY_LENGTH] = i + 1;
879 if (obj->dslots[i] == JSVAL_HOLE)
880 obj->fslots[JSSLOT_ARRAY_COUNT]++;
881 obj->dslots[i] = *vp;
882 return JS_TRUE;
885 JSBool
886 js_PrototypeHasIndexedProperties(JSContext *cx, JSObject *obj)
889 * Walk up the prototype chain and see if this indexed element already
890 * exists. If we hit the end of the prototype chain, it's safe to set the
891 * element on the original object.
893 while ((obj = obj->getProto()) != NULL) {
895 * If the prototype is a non-native object (possibly a dense array), or
896 * a native object (possibly a slow array) that has indexed properties,
897 * return true.
899 if (!OBJ_IS_NATIVE(obj))
900 return JS_TRUE;
901 if (OBJ_SCOPE(obj)->hadIndexedProperties())
902 return JS_TRUE;
904 return JS_FALSE;
907 #ifdef JS_TRACER
909 static inline JSBool FASTCALL
910 dense_grow(JSContext* cx, JSObject* obj, jsint i, jsval v)
913 * Let the interpreter worry about negative array indexes.
915 JS_ASSERT((MAX_DSLOTS_LENGTH > MAX_DSLOTS_LENGTH32) == (sizeof(jsval) != sizeof(uint32)));
916 if (MAX_DSLOTS_LENGTH > MAX_DSLOTS_LENGTH32) {
918 * Have to check for negative values bleeding through on 64-bit machines only,
919 * since we can't allocate large enough arrays for this on 32-bit machines.
921 if (i < 0)
922 return JS_FALSE;
926 * If needed, grow the array as long it remains dense, otherwise fall off trace.
928 jsuint u = jsuint(i);
929 jsuint capacity = js_DenseArrayCapacity(obj);
930 if ((u >= capacity) && (INDEX_TOO_SPARSE(obj, u) || !EnsureCapacity(cx, obj, u + 1)))
931 return JS_FALSE;
933 if (obj->dslots[u] == JSVAL_HOLE) {
934 if (js_PrototypeHasIndexedProperties(cx, obj))
935 return JS_FALSE;
937 if (u >= jsuint(obj->fslots[JSSLOT_ARRAY_LENGTH]))
938 obj->fslots[JSSLOT_ARRAY_LENGTH] = u + 1;
939 ++obj->fslots[JSSLOT_ARRAY_COUNT];
942 obj->dslots[u] = v;
943 return JS_TRUE;
947 JSBool FASTCALL
948 js_Array_dense_setelem(JSContext* cx, JSObject* obj, jsint i, jsval v)
950 JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj));
951 return dense_grow(cx, obj, i, v);
953 JS_DEFINE_CALLINFO_4(extern, BOOL, js_Array_dense_setelem, CONTEXT, OBJECT, INT32, JSVAL, 0, 0)
955 JSBool FASTCALL
956 js_Array_dense_setelem_int(JSContext* cx, JSObject* obj, jsint i, int32 j)
958 JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj));
960 jsval v;
961 if (JS_LIKELY(INT_FITS_IN_JSVAL(j))) {
962 v = INT_TO_JSVAL(j);
963 } else {
964 jsdouble d = (jsdouble)j;
965 if (!js_NewDoubleInRootedValue(cx, d, &v))
966 return JS_FALSE;
969 return dense_grow(cx, obj, i, v);
971 JS_DEFINE_CALLINFO_4(extern, BOOL, js_Array_dense_setelem_int, CONTEXT, OBJECT, INT32, INT32, 0, 0)
973 JSBool FASTCALL
974 js_Array_dense_setelem_double(JSContext* cx, JSObject* obj, jsint i, jsdouble d)
976 JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj));
978 jsval v;
979 jsint j;
981 if (JS_LIKELY(JSDOUBLE_IS_INT(d, j) && INT_FITS_IN_JSVAL(j))) {
982 v = INT_TO_JSVAL(j);
983 } else {
984 if (!js_NewDoubleInRootedValue(cx, d, &v))
985 return JS_FALSE;
988 return dense_grow(cx, obj, i, v);
990 JS_DEFINE_CALLINFO_4(extern, BOOL, js_Array_dense_setelem_double, CONTEXT, OBJECT, INT32, DOUBLE, 0, 0)
991 #endif
993 static JSBool
994 array_defineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value,
995 JSPropertyOp getter, JSPropertyOp setter, uintN attrs)
997 uint32 i = 0; // init to shut GCC up
998 JSBool isIndex;
1000 if (id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom))
1001 return JS_TRUE;
1003 isIndex = js_IdIsIndex(ID_TO_VALUE(id), &i);
1004 if (!isIndex || attrs != JSPROP_ENUMERATE || !OBJ_IS_DENSE_ARRAY(cx, obj) || INDEX_TOO_SPARSE(obj, i)) {
1005 if (!ENSURE_SLOW_ARRAY(cx, obj))
1006 return JS_FALSE;
1007 return js_DefineProperty(cx, obj, id, value, getter, setter, attrs);
1010 return array_setProperty(cx, obj, id, &value);
1013 static JSBool
1014 array_getAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
1015 uintN *attrsp)
1017 *attrsp = id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom)
1018 ? JSPROP_PERMANENT : JSPROP_ENUMERATE;
1019 return JS_TRUE;
1022 static JSBool
1023 array_setAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
1024 uintN *attrsp)
1026 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
1027 JSMSG_CANT_SET_ARRAY_ATTRS);
1028 return JS_FALSE;
1031 static JSBool
1032 array_deleteProperty(JSContext *cx, JSObject *obj, jsval id, jsval *rval)
1034 uint32 i;
1036 if (!OBJ_IS_DENSE_ARRAY(cx, obj))
1037 return js_DeleteProperty(cx, obj, id, rval);
1039 if (id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom)) {
1040 *rval = JSVAL_FALSE;
1041 return JS_TRUE;
1044 if (js_IdIsIndex(id, &i) && i < js_DenseArrayCapacity(obj) &&
1045 obj->dslots[i] != JSVAL_HOLE) {
1046 obj->fslots[JSSLOT_ARRAY_COUNT]--;
1047 obj->dslots[i] = JSVAL_HOLE;
1050 *rval = JSVAL_TRUE;
1051 return JS_TRUE;
1055 * JSObjectOps.enumerate implementation.
1057 * For a fast array, JSENUMERATE_INIT captures in the enumeration state both
1058 * the length of the array and the bitmap indicating the positions of holes in
1059 * the array. This ensures that adding or deleting array elements does not
1060 * affect the sequence of indexes JSENUMERATE_NEXT returns.
1062 * For a common case of an array without holes, to represent the state we pack
1063 * the (nextEnumerationIndex, arrayLength) pair as a pseudo-boolean jsval.
1064 * This is possible when length <= PACKED_UINT_PAIR_BITS. For arrays with
1065 * greater length or holes we allocate the JSIndexIterState structure and
1066 * store it as an int-tagged private pointer jsval. For a slow array we
1067 * delegate the enumeration implementation to js_Enumerate in
1068 * slowarray_enumerate.
1070 * Array mutations can turn a fast array into a slow one after the enumeration
1071 * starts. When this happens, slowarray_enumerate receives a state created
1072 * when the array was fast. To distinguish such fast state from a slow state,
1073 * which is an int-tagged pointer that js_Enumerate creates, we set not one
1074 * but two lowest bits when tagging a JSIndexIterState pointer -- see
1075 * INDEX_ITER_TAG usage below. Thus, when slowarray_enumerate receives a state
1076 * tagged with JSVAL_SPECIAL or with two lowest bits set, it knows that this
1077 * is a fast state so it calls array_enumerate to continue enumerating the
1078 * indexes present in the original fast array.
1081 #define PACKED_UINT_PAIR_BITS 14
1082 #define PACKED_UINT_PAIR_MASK JS_BITMASK(PACKED_UINT_PAIR_BITS)
1084 #define UINT_PAIR_TO_SPECIAL_JSVAL(i,j) \
1085 (JS_ASSERT((uint32) (i) <= PACKED_UINT_PAIR_MASK), \
1086 JS_ASSERT((uint32) (j) <= PACKED_UINT_PAIR_MASK), \
1087 ((jsval) (i) << (PACKED_UINT_PAIR_BITS + JSVAL_TAGBITS)) | \
1088 ((jsval) (j) << (JSVAL_TAGBITS)) | \
1089 (jsval) JSVAL_SPECIAL)
1091 #define SPECIAL_JSVAL_TO_UINT_PAIR(v,i,j) \
1092 (JS_ASSERT(JSVAL_IS_SPECIAL(v)), \
1093 (i) = (uint32) ((v) >> (PACKED_UINT_PAIR_BITS + JSVAL_TAGBITS)), \
1094 (j) = (uint32) ((v) >> JSVAL_TAGBITS) & PACKED_UINT_PAIR_MASK, \
1095 JS_ASSERT((i) <= PACKED_UINT_PAIR_MASK))
1097 JS_STATIC_ASSERT(PACKED_UINT_PAIR_BITS * 2 + JSVAL_TAGBITS <= JS_BITS_PER_WORD);
1099 typedef struct JSIndexIterState {
1100 uint32 index;
1101 uint32 length;
1102 JSBool hasHoles;
1105 * Variable-length bitmap representing array's holes. It must not be
1106 * accessed when hasHoles is false.
1108 jsbitmap holes[1];
1109 } JSIndexIterState;
1111 #define INDEX_ITER_TAG 3
1113 JS_STATIC_ASSERT(JSVAL_INT == 1);
1115 static JSBool
1116 array_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
1117 jsval *statep, jsid *idp)
1119 uint32 capacity, i;
1120 JSIndexIterState *ii;
1122 switch (enum_op) {
1123 case JSENUMERATE_INIT:
1124 JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj));
1125 capacity = js_DenseArrayCapacity(obj);
1126 if (idp)
1127 *idp = INT_TO_JSVAL(obj->fslots[JSSLOT_ARRAY_COUNT]);
1128 ii = NULL;
1129 for (i = 0; i != capacity; ++i) {
1130 if (obj->dslots[i] == JSVAL_HOLE) {
1131 if (!ii) {
1132 ii = (JSIndexIterState *)
1133 cx->malloc(offsetof(JSIndexIterState, holes) +
1134 JS_BITMAP_SIZE(capacity));
1135 if (!ii)
1136 return JS_FALSE;
1137 ii->hasHoles = JS_TRUE;
1138 memset(ii->holes, 0, JS_BITMAP_SIZE(capacity));
1140 JS_SET_BIT(ii->holes, i);
1143 if (!ii) {
1144 /* Array has no holes. */
1145 if (capacity <= PACKED_UINT_PAIR_MASK) {
1146 *statep = UINT_PAIR_TO_SPECIAL_JSVAL(0, capacity);
1147 break;
1149 ii = (JSIndexIterState *)
1150 cx->malloc(offsetof(JSIndexIterState, holes));
1151 if (!ii)
1152 return JS_FALSE;
1153 ii->hasHoles = JS_FALSE;
1155 ii->index = 0;
1156 ii->length = capacity;
1157 *statep = (jsval) ii | INDEX_ITER_TAG;
1158 JS_ASSERT(*statep & JSVAL_INT);
1159 break;
1161 case JSENUMERATE_NEXT:
1162 if (JSVAL_IS_SPECIAL(*statep)) {
1163 SPECIAL_JSVAL_TO_UINT_PAIR(*statep, i, capacity);
1164 if (i != capacity) {
1165 *idp = INT_TO_JSID(i);
1166 *statep = UINT_PAIR_TO_SPECIAL_JSVAL(i + 1, capacity);
1167 break;
1169 } else {
1170 JS_ASSERT((*statep & INDEX_ITER_TAG) == INDEX_ITER_TAG);
1171 ii = (JSIndexIterState *) (*statep & ~INDEX_ITER_TAG);
1172 i = ii->index;
1173 if (i != ii->length) {
1174 /* Skip holes if any. */
1175 if (ii->hasHoles) {
1176 while (JS_TEST_BIT(ii->holes, i) && ++i != ii->length)
1177 continue;
1179 if (i != ii->length) {
1180 ii->index = i + 1;
1181 return js_IndexToId(cx, i, idp);
1185 /* FALL THROUGH */
1187 case JSENUMERATE_DESTROY:
1188 if (!JSVAL_IS_SPECIAL(*statep)) {
1189 JS_ASSERT((*statep & INDEX_ITER_TAG) == INDEX_ITER_TAG);
1190 ii = (JSIndexIterState *) (*statep & ~INDEX_ITER_TAG);
1191 cx->free(ii);
1193 *statep = JSVAL_NULL;
1194 break;
1196 return JS_TRUE;
1199 static JSBool
1200 slowarray_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
1201 jsval *statep, jsid *idp)
1203 JSBool ok;
1205 /* Are we continuing an enumeration that started when we were dense? */
1206 if (enum_op != JSENUMERATE_INIT) {
1207 if (JSVAL_IS_SPECIAL(*statep) ||
1208 (*statep & INDEX_ITER_TAG) == INDEX_ITER_TAG) {
1209 return array_enumerate(cx, obj, enum_op, statep, idp);
1211 JS_ASSERT((*statep & INDEX_ITER_TAG) == JSVAL_INT);
1213 ok = js_Enumerate(cx, obj, enum_op, statep, idp);
1214 JS_ASSERT(*statep == JSVAL_NULL || (*statep & INDEX_ITER_TAG) == JSVAL_INT);
1215 return ok;
1218 static void
1219 array_finalize(JSContext *cx, JSObject *obj)
1221 if (obj->dslots)
1222 cx->free(obj->dslots - 1);
1223 obj->dslots = NULL;
1226 static void
1227 array_trace(JSTracer *trc, JSObject *obj)
1229 uint32 capacity;
1230 size_t i;
1231 jsval v;
1233 JS_ASSERT(obj->isDenseArray());
1234 obj->traceProtoAndParent(trc);
1236 capacity = js_DenseArrayCapacity(obj);
1237 for (i = 0; i < capacity; i++) {
1238 v = obj->dslots[i];
1239 if (JSVAL_IS_TRACEABLE(v)) {
1240 JS_SET_TRACING_INDEX(trc, "array_dslots", i);
1241 js_CallGCMarker(trc, JSVAL_TO_TRACEABLE(v), JSVAL_TRACE_KIND(v));
1246 extern JSObjectOps js_ArrayObjectOps;
1248 static const JSObjectMap SharedArrayMap(&js_ArrayObjectOps, JSObjectMap::SHAPELESS);
1250 JSObjectOps js_ArrayObjectOps = {
1251 &SharedArrayMap,
1252 array_lookupProperty, array_defineProperty,
1253 array_getProperty, array_setProperty,
1254 array_getAttributes, array_setAttributes,
1255 array_deleteProperty, js_DefaultValue,
1256 array_enumerate, js_CheckAccess,
1257 array_typeOf, array_trace,
1258 NULL, array_dropProperty,
1259 NULL, NULL,
1260 js_HasInstance, NULL
1263 static JSObjectOps *
1264 array_getObjectOps(JSContext *cx, JSClass *clasp)
1266 return &js_ArrayObjectOps;
1269 JSClass js_ArrayClass = {
1270 "Array",
1271 JSCLASS_HAS_RESERVED_SLOTS(2) |
1272 JSCLASS_HAS_CACHED_PROTO(JSProto_Array) |
1273 JSCLASS_NEW_ENUMERATE,
1274 JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
1275 JS_EnumerateStub, JS_ResolveStub, js_TryValueOf, array_finalize,
1276 array_getObjectOps, NULL, NULL, NULL,
1277 NULL, NULL, NULL, NULL
1280 JSClass js_SlowArrayClass = {
1281 "Array",
1282 JSCLASS_HAS_PRIVATE |
1283 JSCLASS_HAS_CACHED_PROTO(JSProto_Array),
1284 slowarray_addProperty, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
1285 JS_EnumerateStub, JS_ResolveStub, js_TryValueOf, NULL,
1286 slowarray_getObjectOps, NULL, NULL, NULL,
1287 NULL, NULL, NULL, NULL
1291 * Convert an array object from fast-and-dense to slow-and-flexible.
1293 JSBool
1294 js_MakeArraySlow(JSContext *cx, JSObject *obj)
1296 JS_ASSERT(obj->getClass() == &js_ArrayClass);
1299 * Create a native scope. All slow arrays other than Array.prototype get
1300 * the same initial shape.
1302 uint32 emptyShape;
1303 JSObject *arrayProto = obj->getProto();
1304 if (arrayProto->getClass() == &js_ObjectClass) {
1305 /* obj is Array.prototype. */
1306 emptyShape = js_GenerateShape(cx, false);
1307 } else {
1308 /* arrayProto is Array.prototype. */
1309 JS_ASSERT(arrayProto->getClass() == &js_SlowArrayClass);
1310 emptyShape = OBJ_SCOPE(arrayProto)->emptyScope->shape;
1312 JSScope *scope = JSScope::create(cx, &js_SlowArrayObjectOps, &js_SlowArrayClass, obj,
1313 emptyShape);
1314 if (!scope)
1315 return JS_FALSE;
1317 uint32 capacity = js_DenseArrayCapacity(obj);
1318 if (capacity) {
1319 scope->freeslot = STOBJ_NSLOTS(obj) + JS_INITIAL_NSLOTS;
1320 obj->dslots[-1] = JS_INITIAL_NSLOTS + capacity;
1321 } else {
1322 scope->freeslot = STOBJ_NSLOTS(obj);
1325 /* Create new properties pointing to existing values in dslots */
1326 for (uint32 i = 0; i < capacity; i++) {
1327 jsid id;
1328 JSScopeProperty *sprop;
1330 if (!JS_ValueToId(cx, INT_TO_JSVAL(i), &id))
1331 goto out_bad;
1333 if (obj->dslots[i] == JSVAL_HOLE) {
1334 obj->dslots[i] = JSVAL_VOID;
1335 continue;
1338 sprop = scope->addDataProperty(cx, id, JS_INITIAL_NSLOTS + i,
1339 JSPROP_ENUMERATE);
1340 if (!sprop)
1341 goto out_bad;
1345 * Render our formerly-reserved count property GC-safe. If length fits in
1346 * a jsval, set our slow/sparse COUNT to the current length as a jsval, so
1347 * we can tell when only named properties have been added to a dense array
1348 * to make it slow-but-not-sparse.
1350 * We do not need to make the length slot GC-safe as this slot is private
1351 * where the implementation can store an arbitrary value.
1354 JS_STATIC_ASSERT(JSSLOT_ARRAY_LENGTH == JSSLOT_PRIVATE);
1355 JS_ASSERT(js_SlowArrayClass.flags & JSCLASS_HAS_PRIVATE);
1356 uint32 length = uint32(obj->fslots[JSSLOT_ARRAY_LENGTH]);
1357 obj->fslots[JSSLOT_ARRAY_COUNT] = INT_FITS_IN_JSVAL(length)
1358 ? INT_TO_JSVAL(length)
1359 : JSVAL_VOID;
1362 /* Make sure we preserve any flags borrowing bits in classword. */
1363 obj->classword ^= (jsuword) &js_ArrayClass;
1364 obj->classword |= (jsuword) &js_SlowArrayClass;
1366 obj->map = scope;
1367 return JS_TRUE;
1369 out_bad:
1370 scope->destroy(cx);
1371 return JS_FALSE;
1374 /* Transfer ownership of buffer to returned string. */
1375 static inline JSBool
1376 BufferToString(JSContext *cx, JSCharBuffer &cb, jsval *rval)
1378 JSString *str = js_NewStringFromCharBuffer(cx, cb);
1379 if (!str)
1380 return false;
1381 *rval = STRING_TO_JSVAL(str);
1382 return true;
1385 #if JS_HAS_TOSOURCE
1386 static JSBool
1387 array_toSource(JSContext *cx, uintN argc, jsval *vp)
1389 JS_CHECK_RECURSION(cx, return false);
1391 JSObject *obj = JS_THIS_OBJECT(cx, vp);
1392 if (!obj ||
1393 (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass &&
1394 !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2))) {
1395 return false;
1398 /* Find joins or cycles in the reachable object graph. */
1399 jschar *sharpchars;
1400 JSHashEntry *he = js_EnterSharpObject(cx, obj, NULL, &sharpchars);
1401 if (!he)
1402 return false;
1403 bool initiallySharp = IS_SHARP(he);
1405 /* After this point, all paths exit through the 'out' label. */
1406 MUST_FLOW_THROUGH("out");
1407 bool ok = false;
1410 * This object will take responsibility for the jschar buffer until the
1411 * buffer is transferred to the returned JSString.
1413 JSCharBuffer cb(cx);
1415 /* Cycles/joins are indicated by sharp objects. */
1416 #if JS_HAS_SHARP_VARS
1417 if (IS_SHARP(he)) {
1418 JS_ASSERT(sharpchars != 0);
1419 cb.replaceRawBuffer(sharpchars, js_strlen(sharpchars));
1420 goto make_string;
1421 } else if (sharpchars) {
1422 MAKE_SHARP(he);
1423 cb.replaceRawBuffer(sharpchars, js_strlen(sharpchars));
1425 #else
1426 if (IS_SHARP(he)) {
1427 if (!js_AppendLiteral(cb, "[]"))
1428 goto out;
1429 cx->free(sharpchars);
1430 goto make_string;
1432 #endif
1434 if (!cb.append('['))
1435 goto out;
1437 jsuint length;
1438 if (!js_GetLengthProperty(cx, obj, &length))
1439 goto out;
1441 for (jsuint index = 0; index < length; index++) {
1442 /* Use vp to locally root each element value. */
1443 JSBool hole;
1444 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
1445 !GetArrayElement(cx, obj, index, &hole, vp)) {
1446 goto out;
1449 /* Get element's character string. */
1450 JSString *str;
1451 if (hole) {
1452 str = cx->runtime->emptyString;
1453 } else {
1454 str = js_ValueToSource(cx, *vp);
1455 if (!str)
1456 goto out;
1458 *vp = STRING_TO_JSVAL(str);
1459 const jschar *chars;
1460 size_t charlen;
1461 str->getCharsAndLength(chars, charlen);
1463 /* Append element to buffer. */
1464 if (!cb.append(chars, charlen))
1465 goto out;
1466 if (index + 1 != length) {
1467 if (!js_AppendLiteral(cb, ", "))
1468 goto out;
1469 } else if (hole) {
1470 if (!cb.append(','))
1471 goto out;
1475 /* Finalize the buffer. */
1476 if (!cb.append(']'))
1477 goto out;
1479 make_string:
1480 if (!BufferToString(cx, cb, vp))
1481 goto out;
1483 ok = true;
1485 out:
1486 if (!initiallySharp)
1487 js_LeaveSharpObject(cx, NULL);
1488 return ok;
1490 #endif
1492 static JSBool
1493 array_toString_sub(JSContext *cx, JSObject *obj, JSBool locale,
1494 JSString *sepstr, jsval *rval)
1496 JS_CHECK_RECURSION(cx, return false);
1499 * Use HashTable entry as the cycle indicator. On first visit, create the
1500 * entry, and, when leaving, remove the entry.
1502 typedef js::HashSet<JSObject *> ObjSet;
1503 ObjSet::AddPtr hashp = cx->busyArrays.lookupForAdd(obj);
1504 uint32 genBefore;
1505 if (!hashp) {
1506 /* Not in hash table, so not a cycle. */
1507 if (!cx->busyArrays.add(hashp, obj)) {
1508 JS_ReportOutOfMemory(cx);
1509 return false;
1511 genBefore = cx->busyArrays.generation();
1512 } else {
1513 /* Cycle, so return empty string. */
1514 *rval = ATOM_KEY(cx->runtime->atomState.emptyAtom);
1515 return true;
1518 JSAutoTempValueRooter tvr(cx, obj);
1520 /* After this point, all paths exit through the 'out' label. */
1521 MUST_FLOW_THROUGH("out");
1522 bool ok = false;
1524 /* Get characters to use for the separator. */
1525 static const jschar comma = ',';
1526 const jschar *sep;
1527 size_t seplen;
1528 if (sepstr) {
1529 sepstr->getCharsAndLength(sep, seplen);
1530 } else {
1531 sep = &comma;
1532 seplen = 1;
1536 * This object will take responsibility for the jschar buffer until the
1537 * buffer is transferred to the returned JSString.
1539 JSCharBuffer cb(cx);
1541 jsuint length;
1542 if (!js_GetLengthProperty(cx, obj, &length))
1543 goto out;
1545 for (jsuint index = 0; index < length; index++) {
1546 /* Use rval to locally root each element value. */
1547 JSBool hole;
1548 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
1549 !GetArrayElement(cx, obj, index, &hole, rval)) {
1550 goto out;
1553 /* Get element's character string. */
1554 if (!(hole || JSVAL_IS_VOID(*rval) || JSVAL_IS_NULL(*rval))) {
1555 if (locale) {
1556 /* Work on obj.toLocalString() instead. */
1557 JSObject *robj;
1559 if (!js_ValueToObject(cx, *rval, &robj))
1560 goto out;
1561 *rval = OBJECT_TO_JSVAL(robj);
1562 JSAtom *atom = cx->runtime->atomState.toLocaleStringAtom;
1563 if (!js_TryMethod(cx, robj, atom, 0, NULL, rval))
1564 goto out;
1567 if (!js_ValueToCharBuffer(cx, *rval, cb))
1568 goto out;
1571 /* Append the separator. */
1572 if (index + 1 != length) {
1573 if (!cb.append(sep, seplen))
1574 goto out;
1578 /* Finalize the buffer. */
1579 if (!BufferToString(cx, cb, rval))
1580 goto out;
1582 ok = true;
1584 out:
1585 if (genBefore == cx->busyArrays.generation())
1586 cx->busyArrays.remove(hashp);
1587 else
1588 cx->busyArrays.remove(obj);
1589 return ok;
1592 static JSBool
1593 array_toString(JSContext *cx, uintN argc, jsval *vp)
1595 JSObject *obj;
1597 obj = JS_THIS_OBJECT(cx, vp);
1598 if (!obj ||
1599 (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass &&
1600 !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2))) {
1601 return JS_FALSE;
1604 return array_toString_sub(cx, obj, JS_FALSE, NULL, vp);
1607 static JSBool
1608 array_toLocaleString(JSContext *cx, uintN argc, jsval *vp)
1610 JSObject *obj;
1612 obj = JS_THIS_OBJECT(cx, vp);
1613 if (!obj ||
1614 (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass &&
1615 !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2))) {
1616 return JS_FALSE;
1620 * Passing comma here as the separator. Need a way to get a
1621 * locale-specific version.
1623 return array_toString_sub(cx, obj, JS_TRUE, NULL, vp);
1626 enum TargetElementsType {
1627 TargetElementsAllHoles,
1628 TargetElementsMayContainValues
1631 enum SourceVectorType {
1632 SourceVectorAllValues,
1633 SourceVectorMayContainHoles
1636 static JSBool
1637 InitArrayElements(JSContext *cx, JSObject *obj, jsuint start, jsuint count, jsval *vector,
1638 TargetElementsType targetType, SourceVectorType vectorType)
1640 JS_ASSERT(count < MAXINDEX);
1643 * Optimize for dense arrays so long as adding the given set of elements
1644 * wouldn't otherwise make the array slow.
1646 if (OBJ_IS_DENSE_ARRAY(cx, obj) && !js_PrototypeHasIndexedProperties(cx, obj) &&
1647 start <= MAXINDEX - count && !INDEX_TOO_BIG(start + count)) {
1649 #ifdef DEBUG_jwalden
1651 /* Verify that overwriteType and writeType were accurate. */
1652 JSAutoTempIdRooter idr(cx, JSVAL_ZERO);
1653 for (jsuint i = 0; i < count; i++) {
1654 JS_ASSERT_IF(vectorType == SourceVectorAllValues, vector[i] != JSVAL_HOLE);
1656 jsdouble index = jsdouble(start) + i;
1657 if (targetType == TargetElementsAllHoles && index < jsuint(-1)) {
1658 JS_ASSERT(ReallyBigIndexToId(cx, index, idr.addr()));
1659 JSObject* obj2;
1660 JSProperty* prop;
1661 JS_ASSERT(obj->lookupProperty(cx, idr.id(), &obj2, &prop));
1662 JS_ASSERT(!prop);
1666 #endif
1668 jsuint newlen = start + count;
1669 JS_ASSERT(jsdouble(start) + count == jsdouble(newlen));
1670 if (!EnsureCapacity(cx, obj, newlen))
1671 return JS_FALSE;
1673 if (newlen > uint32(obj->fslots[JSSLOT_ARRAY_LENGTH]))
1674 obj->fslots[JSSLOT_ARRAY_LENGTH] = newlen;
1676 JS_ASSERT(count < size_t(-1) / sizeof(jsval));
1677 if (targetType == TargetElementsMayContainValues) {
1678 jsuint valueCount = 0;
1679 for (jsuint i = 0; i < count; i++) {
1680 if (obj->dslots[start + i] != JSVAL_HOLE)
1681 valueCount++;
1683 JS_ASSERT(uint32(obj->fslots[JSSLOT_ARRAY_COUNT]) >= valueCount);
1684 obj->fslots[JSSLOT_ARRAY_COUNT] -= valueCount;
1686 memcpy(obj->dslots + start, vector, sizeof(jsval) * count);
1687 if (vectorType == SourceVectorAllValues) {
1688 obj->fslots[JSSLOT_ARRAY_COUNT] += count;
1689 } else {
1690 jsuint valueCount = 0;
1691 for (jsuint i = 0; i < count; i++) {
1692 if (obj->dslots[start + i] != JSVAL_HOLE)
1693 valueCount++;
1695 obj->fslots[JSSLOT_ARRAY_COUNT] += valueCount;
1697 JS_ASSERT_IF(count != 0, obj->dslots[newlen - 1] != JSVAL_HOLE);
1698 return JS_TRUE;
1701 jsval* end = vector + count;
1702 while (vector != end && start < MAXINDEX) {
1703 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
1704 !SetArrayElement(cx, obj, start++, *vector++)) {
1705 return JS_FALSE;
1709 if (vector == end)
1710 return JS_TRUE;
1712 /* Finish out any remaining elements past the max array index. */
1713 if (OBJ_IS_DENSE_ARRAY(cx, obj) && !ENSURE_SLOW_ARRAY(cx, obj))
1714 return JS_FALSE;
1716 JS_ASSERT(start == MAXINDEX);
1717 jsval tmp[2] = {JSVAL_NULL, JSVAL_NULL};
1718 JSAutoTempValueRooter tvr(cx, JS_ARRAY_LENGTH(tmp), tmp);
1719 if (!js_NewDoubleInRootedValue(cx, MAXINDEX, &tmp[0]))
1720 return JS_FALSE;
1721 jsdouble *dp = JSVAL_TO_DOUBLE(tmp[0]);
1722 JS_ASSERT(*dp == MAXINDEX);
1723 JSAutoTempIdRooter idr(cx);
1724 do {
1725 tmp[1] = *vector++;
1726 if (!js_ValueToStringId(cx, tmp[0], idr.addr()) ||
1727 !obj->setProperty(cx, idr.id(), &tmp[1])) {
1728 return JS_FALSE;
1730 *dp += 1;
1731 } while (vector != end);
1733 return JS_TRUE;
1736 static JSBool
1737 InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, jsval *vector,
1738 JSBool holey = JS_FALSE)
1740 JS_ASSERT(OBJ_IS_ARRAY(cx, obj));
1742 obj->fslots[JSSLOT_ARRAY_LENGTH] = length;
1744 if (vector) {
1745 if (!EnsureCapacity(cx, obj, length))
1746 return JS_FALSE;
1748 jsuint count = length;
1749 if (!holey) {
1750 memcpy(obj->dslots, vector, length * sizeof (jsval));
1751 } else {
1752 for (jsuint i = 0; i < length; i++) {
1753 if (vector[i] == JSVAL_HOLE)
1754 --count;
1755 obj->dslots[i] = vector[i];
1758 obj->fslots[JSSLOT_ARRAY_COUNT] = count;
1759 } else {
1760 obj->fslots[JSSLOT_ARRAY_COUNT] = 0;
1762 return JS_TRUE;
1765 #ifdef JS_TRACER
1766 static JSString* FASTCALL
1767 Array_p_join(JSContext* cx, JSObject* obj, JSString *str)
1769 JSAutoTempValueRooter tvr(cx);
1770 if (!array_toString_sub(cx, obj, JS_FALSE, str, tvr.addr())) {
1771 SetBuiltinError(cx);
1772 return NULL;
1774 return JSVAL_TO_STRING(tvr.value());
1777 static JSString* FASTCALL
1778 Array_p_toString(JSContext* cx, JSObject* obj)
1780 JSAutoTempValueRooter tvr(cx);
1781 if (!array_toString_sub(cx, obj, JS_FALSE, NULL, tvr.addr())) {
1782 SetBuiltinError(cx);
1783 return NULL;
1785 return JSVAL_TO_STRING(tvr.value());
1787 #endif
1790 * Perl-inspired join, reverse, and sort.
1792 static JSBool
1793 array_join(JSContext *cx, uintN argc, jsval *vp)
1795 JSString *str;
1796 JSObject *obj;
1798 if (argc == 0 || JSVAL_IS_VOID(vp[2])) {
1799 str = NULL;
1800 } else {
1801 str = js_ValueToString(cx, vp[2]);
1802 if (!str)
1803 return JS_FALSE;
1804 vp[2] = STRING_TO_JSVAL(str);
1806 obj = JS_THIS_OBJECT(cx, vp);
1807 return obj && array_toString_sub(cx, obj, JS_FALSE, str, vp);
1810 static JSBool
1811 array_reverse(JSContext *cx, uintN argc, jsval *vp)
1813 jsuint len;
1814 JSObject *obj = JS_THIS_OBJECT(cx, vp);
1815 if (!obj || !js_GetLengthProperty(cx, obj, &len))
1816 return JS_FALSE;
1817 *vp = OBJECT_TO_JSVAL(obj);
1819 if (OBJ_IS_DENSE_ARRAY(cx, obj) && !js_PrototypeHasIndexedProperties(cx, obj)) {
1820 /* An empty array or an array with no elements is already reversed. */
1821 if (len == 0 || !obj->dslots)
1822 return JS_TRUE;
1825 * It's actually surprisingly complicated to reverse an array due to the
1826 * orthogonality of array length and array capacity while handling
1827 * leading and trailing holes correctly. Reversing seems less likely to
1828 * be a common operation than other array mass-mutation methods, so for
1829 * now just take a probably-small memory hit (in the absence of too many
1830 * holes in the array at its start) and ensure that the capacity is
1831 * sufficient to hold all the elements in the array if it were full.
1833 if (!EnsureCapacity(cx, obj, len))
1834 return JS_FALSE;
1836 jsval* lo = &obj->dslots[0];
1837 jsval* hi = &obj->dslots[len - 1];
1838 for (; lo < hi; lo++, hi--) {
1839 jsval tmp = *lo;
1840 *lo = *hi;
1841 *hi = tmp;
1845 * Per ECMA-262, don't update the length of the array, even if the new
1846 * array has trailing holes (and thus the original array began with
1847 * holes).
1849 return JS_TRUE;
1852 JSAutoTempValueRooter tvr(cx, JSVAL_NULL);
1853 for (jsuint i = 0, half = len / 2; i < half; i++) {
1854 JSBool hole, hole2;
1855 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
1856 !GetArrayElement(cx, obj, i, &hole, tvr.addr()) ||
1857 !GetArrayElement(cx, obj, len - i - 1, &hole2, vp) ||
1858 !SetOrDeleteArrayElement(cx, obj, len - i - 1, hole, tvr.value()) ||
1859 !SetOrDeleteArrayElement(cx, obj, i, hole2, *vp)) {
1860 return false;
1863 *vp = OBJECT_TO_JSVAL(obj);
1864 return true;
1867 typedef struct MSortArgs {
1868 size_t elsize;
1869 JSComparator cmp;
1870 void *arg;
1871 JSBool fastcopy;
1872 } MSortArgs;
1874 /* Helper function for js_MergeSort. */
1875 static JSBool
1876 MergeArrays(MSortArgs *msa, void *src, void *dest, size_t run1, size_t run2)
1878 void *arg, *a, *b, *c;
1879 size_t elsize, runtotal;
1880 int cmp_result;
1881 JSComparator cmp;
1882 JSBool fastcopy;
1884 runtotal = run1 + run2;
1886 elsize = msa->elsize;
1887 cmp = msa->cmp;
1888 arg = msa->arg;
1889 fastcopy = msa->fastcopy;
1891 #define CALL_CMP(a, b) \
1892 if (!cmp(arg, (a), (b), &cmp_result)) return JS_FALSE;
1894 /* Copy runs already in sorted order. */
1895 b = (char *)src + run1 * elsize;
1896 a = (char *)b - elsize;
1897 CALL_CMP(a, b);
1898 if (cmp_result <= 0) {
1899 memcpy(dest, src, runtotal * elsize);
1900 return JS_TRUE;
1903 #define COPY_ONE(p,q,n) \
1904 (fastcopy ? (void)(*(jsval*)(p) = *(jsval*)(q)) : (void)memcpy(p, q, n))
1906 a = src;
1907 c = dest;
1908 for (; runtotal != 0; runtotal--) {
1909 JSBool from_a = run2 == 0;
1910 if (!from_a && run1 != 0) {
1911 CALL_CMP(a,b);
1912 from_a = cmp_result <= 0;
1915 if (from_a) {
1916 COPY_ONE(c, a, elsize);
1917 run1--;
1918 a = (char *)a + elsize;
1919 } else {
1920 COPY_ONE(c, b, elsize);
1921 run2--;
1922 b = (char *)b + elsize;
1924 c = (char *)c + elsize;
1926 #undef COPY_ONE
1927 #undef CALL_CMP
1929 return JS_TRUE;
1933 * This sort is stable, i.e. sequence of equal elements is preserved.
1934 * See also bug #224128.
1936 JSBool
1937 js_MergeSort(void *src, size_t nel, size_t elsize,
1938 JSComparator cmp, void *arg, void *tmp)
1940 void *swap, *vec1, *vec2;
1941 MSortArgs msa;
1942 size_t i, j, lo, hi, run;
1943 JSBool fastcopy;
1944 int cmp_result;
1946 /* Avoid memcpy overhead for word-sized and word-aligned elements. */
1947 fastcopy = (elsize == sizeof(jsval) &&
1948 (((jsuword) src | (jsuword) tmp) & JSVAL_ALIGN) == 0);
1949 #define COPY_ONE(p,q,n) \
1950 (fastcopy ? (void)(*(jsval*)(p) = *(jsval*)(q)) : (void)memcpy(p, q, n))
1951 #define CALL_CMP(a, b) \
1952 if (!cmp(arg, (a), (b), &cmp_result)) return JS_FALSE;
1953 #define INS_SORT_INT 4
1956 * Apply insertion sort to small chunks to reduce the number of merge
1957 * passes needed.
1959 for (lo = 0; lo < nel; lo += INS_SORT_INT) {
1960 hi = lo + INS_SORT_INT;
1961 if (hi >= nel)
1962 hi = nel;
1963 for (i = lo + 1; i < hi; i++) {
1964 vec1 = (char *)src + i * elsize;
1965 vec2 = (char *)vec1 - elsize;
1966 for (j = i; j > lo; j--) {
1967 CALL_CMP(vec2, vec1);
1968 /* "<=" instead of "<" insures the sort is stable */
1969 if (cmp_result <= 0) {
1970 break;
1973 /* Swap elements, using "tmp" as tmp storage */
1974 COPY_ONE(tmp, vec2, elsize);
1975 COPY_ONE(vec2, vec1, elsize);
1976 COPY_ONE(vec1, tmp, elsize);
1977 vec1 = vec2;
1978 vec2 = (char *)vec1 - elsize;
1982 #undef CALL_CMP
1983 #undef COPY_ONE
1985 msa.elsize = elsize;
1986 msa.cmp = cmp;
1987 msa.arg = arg;
1988 msa.fastcopy = fastcopy;
1990 vec1 = src;
1991 vec2 = tmp;
1992 for (run = INS_SORT_INT; run < nel; run *= 2) {
1993 for (lo = 0; lo < nel; lo += 2 * run) {
1994 hi = lo + run;
1995 if (hi >= nel) {
1996 memcpy((char *)vec2 + lo * elsize, (char *)vec1 + lo * elsize,
1997 (nel - lo) * elsize);
1998 break;
2000 if (!MergeArrays(&msa, (char *)vec1 + lo * elsize,
2001 (char *)vec2 + lo * elsize, run,
2002 hi + run > nel ? nel - hi : run)) {
2003 return JS_FALSE;
2006 swap = vec1;
2007 vec1 = vec2;
2008 vec2 = swap;
2010 if (src != vec1)
2011 memcpy(src, tmp, nel * elsize);
2013 return JS_TRUE;
2016 typedef struct CompareArgs {
2017 JSContext *context;
2018 jsval fval;
2019 jsval *elemroot; /* stack needed for js_Invoke */
2020 } CompareArgs;
2022 static JS_REQUIRES_STACK JSBool
2023 sort_compare(void *arg, const void *a, const void *b, int *result)
2025 jsval av = *(const jsval *)a, bv = *(const jsval *)b;
2026 CompareArgs *ca = (CompareArgs *) arg;
2027 JSContext *cx = ca->context;
2028 jsval *invokevp, *sp;
2029 jsdouble cmp;
2032 * array_sort deals with holes and undefs on its own and they should not
2033 * come here.
2035 JS_ASSERT(!JSVAL_IS_VOID(av));
2036 JS_ASSERT(!JSVAL_IS_VOID(bv));
2038 if (!JS_CHECK_OPERATION_LIMIT(cx))
2039 return JS_FALSE;
2041 invokevp = ca->elemroot;
2042 sp = invokevp;
2043 *sp++ = ca->fval;
2044 *sp++ = JSVAL_NULL;
2045 *sp++ = av;
2046 *sp++ = bv;
2048 if (!js_Invoke(cx, 2, invokevp, 0))
2049 return JS_FALSE;
2051 cmp = js_ValueToNumber(cx, invokevp);
2052 if (JSVAL_IS_NULL(*invokevp))
2053 return JS_FALSE;
2055 /* Clamp cmp to -1, 0, 1. */
2056 *result = 0;
2057 if (!JSDOUBLE_IS_NaN(cmp) && cmp != 0)
2058 *result = cmp > 0 ? 1 : -1;
2061 * XXX else report some kind of error here? ECMA talks about 'consistent
2062 * compare functions' that don't return NaN, but is silent about what the
2063 * result should be. So we currently ignore it.
2066 return JS_TRUE;
2069 typedef JSBool (JS_REQUIRES_STACK *JSRedComparator)(void*, const void*,
2070 const void*, int *);
2072 static inline JS_IGNORE_STACK JSComparator
2073 comparator_stack_cast(JSRedComparator func)
2075 return func;
2078 static int
2079 sort_compare_strings(void *arg, const void *a, const void *b, int *result)
2081 jsval av = *(const jsval *)a, bv = *(const jsval *)b;
2083 JS_ASSERT(JSVAL_IS_STRING(av));
2084 JS_ASSERT(JSVAL_IS_STRING(bv));
2085 if (!JS_CHECK_OPERATION_LIMIT((JSContext *)arg))
2086 return JS_FALSE;
2088 *result = (int) js_CompareStrings(JSVAL_TO_STRING(av), JSVAL_TO_STRING(bv));
2089 return JS_TRUE;
2093 * The array_sort function below assumes JSVAL_NULL is zero in order to
2094 * perform initialization using memset. Other parts of SpiderMonkey likewise
2095 * "know" that JSVAL_NULL is zero; this static assertion covers all cases.
2097 JS_STATIC_ASSERT(JSVAL_NULL == 0);
2099 static JSBool
2100 array_sort(JSContext *cx, uintN argc, jsval *vp)
2102 jsval *argv, fval, *vec, *mergesort_tmp, v;
2103 JSObject *obj;
2104 CompareArgs ca;
2105 jsuint len, newlen, i, undefs;
2106 JSTempValueRooter tvr;
2107 JSBool hole;
2108 JSBool ok;
2109 size_t elemsize;
2110 JSString *str;
2113 * Optimize the default compare function case if all of obj's elements
2114 * have values of type string.
2116 JSBool all_strings;
2118 argv = JS_ARGV(cx, vp);
2119 if (argc > 0) {
2120 if (JSVAL_IS_PRIMITIVE(argv[0])) {
2121 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2122 JSMSG_BAD_SORT_ARG);
2123 return JS_FALSE;
2125 fval = argv[0]; /* non-default compare function */
2126 } else {
2127 fval = JSVAL_NULL;
2130 obj = JS_THIS_OBJECT(cx, vp);
2131 if (!obj || !js_GetLengthProperty(cx, obj, &len))
2132 return JS_FALSE;
2133 if (len == 0) {
2134 *vp = OBJECT_TO_JSVAL(obj);
2135 return JS_TRUE;
2139 * We need a temporary array of 2 * len jsvals to hold the array elements
2140 * and the scratch space for merge sort. Check that its size does not
2141 * overflow size_t, which would allow for indexing beyond the end of the
2142 * malloc'd vector.
2144 #if JS_BITS_PER_WORD == 32
2145 if ((size_t)len > ~(size_t)0 / (2 * sizeof(jsval))) {
2146 js_ReportAllocationOverflow(cx);
2147 return JS_FALSE;
2149 #endif
2150 vec = (jsval *) cx->malloc(2 * (size_t) len * sizeof(jsval));
2151 if (!vec)
2152 return JS_FALSE;
2155 * Initialize vec as a root. We will clear elements of vec one by
2156 * one while increasing tvr.count when we know that the property at
2157 * the corresponding index exists and its value must be rooted.
2159 * In this way when sorting a huge mostly sparse array we will not
2160 * access the tail of vec corresponding to properties that do not
2161 * exist, allowing OS to avoiding committing RAM. See bug 330812.
2163 * After this point control must flow through label out: to exit.
2165 JS_PUSH_TEMP_ROOT(cx, 0, vec, &tvr);
2168 * By ECMA 262, 15.4.4.11, a property that does not exist (which we
2169 * call a "hole") is always greater than an existing property with
2170 * value undefined and that is always greater than any other property.
2171 * Thus to sort holes and undefs we simply count them, sort the rest
2172 * of elements, append undefs after them and then make holes after
2173 * undefs.
2175 undefs = 0;
2176 newlen = 0;
2177 all_strings = JS_TRUE;
2178 for (i = 0; i < len; i++) {
2179 ok = JS_CHECK_OPERATION_LIMIT(cx);
2180 if (!ok)
2181 goto out;
2183 /* Clear vec[newlen] before including it in the rooted set. */
2184 vec[newlen] = JSVAL_NULL;
2185 tvr.count = newlen + 1;
2186 ok = GetArrayElement(cx, obj, i, &hole, &vec[newlen]);
2187 if (!ok)
2188 goto out;
2190 if (hole)
2191 continue;
2193 if (JSVAL_IS_VOID(vec[newlen])) {
2194 ++undefs;
2195 continue;
2198 /* We know JSVAL_IS_STRING yields 0 or 1, so avoid a branch via &=. */
2199 all_strings &= JSVAL_IS_STRING(vec[newlen]);
2201 ++newlen;
2204 if (newlen == 0) {
2205 /* The array has only holes and undefs. */
2206 ok = JS_TRUE;
2207 goto out;
2211 * The first newlen elements of vec are copied from the array object
2212 * (above). The remaining newlen positions are used as GC-rooted scratch
2213 * space for mergesort. We must clear the space before including it to
2214 * the root set covered by tvr.count. We assume JSVAL_NULL==0 to optimize
2215 * initialization using memset.
2217 mergesort_tmp = vec + newlen;
2218 memset(mergesort_tmp, 0, newlen * sizeof(jsval));
2219 tvr.count = newlen * 2;
2221 /* Here len == 2 * (newlen + undefs + number_of_holes). */
2222 if (fval == JSVAL_NULL) {
2224 * Sort using the default comparator converting all elements to
2225 * strings.
2227 if (all_strings) {
2228 elemsize = sizeof(jsval);
2229 } else {
2231 * To avoid string conversion on each compare we do it only once
2232 * prior to sorting. But we also need the space for the original
2233 * values to recover the sorting result. To reuse
2234 * sort_compare_strings we move the original values to the odd
2235 * indexes in vec, put the string conversion results in the even
2236 * indexes and pass 2 * sizeof(jsval) as an element size to the
2237 * sorting function. In this way sort_compare_strings will only
2238 * see the string values when it casts the compare arguments as
2239 * pointers to jsval.
2241 * This requires doubling the temporary storage including the
2242 * scratch space for the merge sort. Since vec already contains
2243 * the rooted scratch space for newlen elements at the tail, we
2244 * can use it to rearrange and convert to strings first and try
2245 * realloc only when we know that we successfully converted all
2246 * the elements.
2248 #if JS_BITS_PER_WORD == 32
2249 if ((size_t)newlen > ~(size_t)0 / (4 * sizeof(jsval))) {
2250 js_ReportAllocationOverflow(cx);
2251 ok = JS_FALSE;
2252 goto out;
2254 #endif
2257 * Rearrange and string-convert the elements of the vector from
2258 * the tail here and, after sorting, move the results back
2259 * starting from the start to prevent overwrite the existing
2260 * elements.
2262 i = newlen;
2263 do {
2264 --i;
2265 ok = JS_CHECK_OPERATION_LIMIT(cx);
2266 if (!ok)
2267 goto out;
2268 v = vec[i];
2269 str = js_ValueToString(cx, v);
2270 if (!str) {
2271 ok = JS_FALSE;
2272 goto out;
2274 vec[2 * i] = STRING_TO_JSVAL(str);
2275 vec[2 * i + 1] = v;
2276 } while (i != 0);
2278 JS_ASSERT(tvr.u.array == vec);
2279 vec = (jsval *) cx->realloc(vec,
2280 4 * (size_t) newlen * sizeof(jsval));
2281 if (!vec) {
2282 vec = tvr.u.array;
2283 ok = JS_FALSE;
2284 goto out;
2286 tvr.u.array = vec;
2287 mergesort_tmp = vec + 2 * newlen;
2288 memset(mergesort_tmp, 0, newlen * 2 * sizeof(jsval));
2289 tvr.count = newlen * 4;
2290 elemsize = 2 * sizeof(jsval);
2292 ok = js_MergeSort(vec, (size_t) newlen, elemsize,
2293 sort_compare_strings, cx, mergesort_tmp);
2294 if (!ok)
2295 goto out;
2296 if (!all_strings) {
2298 * We want to make the following loop fast and to unroot the
2299 * cached results of toString invocations before the operation
2300 * callback has a chance to run the GC. For this reason we do
2301 * not call JS_CHECK_OPERATION_LIMIT in the loop.
2303 i = 0;
2304 do {
2305 vec[i] = vec[2 * i + 1];
2306 } while (++i != newlen);
2308 } else {
2309 void *mark;
2311 LeaveTrace(cx);
2313 ca.context = cx;
2314 ca.fval = fval;
2315 ca.elemroot = js_AllocStack(cx, 2 + 2, &mark);
2316 if (!ca.elemroot) {
2317 ok = JS_FALSE;
2318 goto out;
2320 ok = js_MergeSort(vec, (size_t) newlen, sizeof(jsval),
2321 comparator_stack_cast(sort_compare),
2322 &ca, mergesort_tmp);
2323 js_FreeStack(cx, mark);
2324 if (!ok)
2325 goto out;
2329 * We no longer need to root the scratch space for the merge sort, so
2330 * unroot it now to make the job of a potential GC under InitArrayElements
2331 * easier.
2333 tvr.count = newlen;
2334 ok = InitArrayElements(cx, obj, 0, newlen, vec, TargetElementsMayContainValues,
2335 SourceVectorAllValues);
2336 if (!ok)
2337 goto out;
2339 out:
2340 JS_POP_TEMP_ROOT(cx, &tvr);
2341 cx->free(vec);
2342 if (!ok)
2343 return JS_FALSE;
2345 /* Set undefs that sorted after the rest of elements. */
2346 while (undefs != 0) {
2347 --undefs;
2348 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
2349 !SetArrayElement(cx, obj, newlen++, JSVAL_VOID)) {
2350 return JS_FALSE;
2354 /* Re-create any holes that sorted to the end of the array. */
2355 while (len > newlen) {
2356 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
2357 !DeleteArrayElement(cx, obj, --len)) {
2358 return JS_FALSE;
2361 *vp = OBJECT_TO_JSVAL(obj);
2362 return JS_TRUE;
2366 * Perl-inspired push, pop, shift, unshift, and splice methods.
2368 static JSBool
2369 array_push_slowly(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
2371 jsuint length;
2373 if (!js_GetLengthProperty(cx, obj, &length))
2374 return JS_FALSE;
2375 if (!InitArrayElements(cx, obj, length, argc, argv, TargetElementsMayContainValues,
2376 SourceVectorAllValues)) {
2377 return JS_FALSE;
2380 /* Per ECMA-262, return the new array length. */
2381 jsdouble newlength = length + jsdouble(argc);
2382 if (!IndexToValue(cx, newlength, rval))
2383 return JS_FALSE;
2384 return js_SetLengthProperty(cx, obj, newlength);
2387 static JSBool
2388 array_push1_dense(JSContext* cx, JSObject* obj, jsval v, jsval *rval)
2390 uint32 length = obj->fslots[JSSLOT_ARRAY_LENGTH];
2391 if (INDEX_TOO_SPARSE(obj, length)) {
2392 if (!js_MakeArraySlow(cx, obj))
2393 return JS_FALSE;
2394 return array_push_slowly(cx, obj, 1, &v, rval);
2397 if (!EnsureCapacity(cx, obj, length + 1))
2398 return JS_FALSE;
2399 obj->fslots[JSSLOT_ARRAY_LENGTH] = length + 1;
2401 JS_ASSERT(obj->dslots[length] == JSVAL_HOLE);
2402 obj->fslots[JSSLOT_ARRAY_COUNT]++;
2403 obj->dslots[length] = v;
2404 return IndexToValue(cx, obj->fslots[JSSLOT_ARRAY_LENGTH], rval);
2407 JSBool JS_FASTCALL
2408 js_ArrayCompPush(JSContext *cx, JSObject *obj, jsval v)
2410 JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj));
2411 uint32_t length = (uint32_t) obj->fslots[JSSLOT_ARRAY_LENGTH];
2412 JS_ASSERT(length <= js_DenseArrayCapacity(obj));
2414 if (length == js_DenseArrayCapacity(obj)) {
2415 if (length > JS_ARGS_LENGTH_MAX) {
2416 JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL,
2417 JSMSG_ARRAY_INIT_TOO_BIG);
2418 return JS_FALSE;
2421 if (!EnsureCapacity(cx, obj, length + 1))
2422 return JS_FALSE;
2424 obj->fslots[JSSLOT_ARRAY_LENGTH] = length + 1;
2425 obj->fslots[JSSLOT_ARRAY_COUNT]++;
2426 obj->dslots[length] = v;
2427 return JS_TRUE;
2429 JS_DEFINE_CALLINFO_3(extern, BOOL, js_ArrayCompPush, CONTEXT, OBJECT, JSVAL, 0, 0)
2431 #ifdef JS_TRACER
2432 static jsval FASTCALL
2433 Array_p_push1(JSContext* cx, JSObject* obj, jsval v)
2435 JSAutoTempValueRooter tvr(cx, v);
2436 if (OBJ_IS_DENSE_ARRAY(cx, obj)
2437 ? array_push1_dense(cx, obj, v, tvr.addr())
2438 : array_push_slowly(cx, obj, 1, tvr.addr(), tvr.addr())) {
2439 return tvr.value();
2441 SetBuiltinError(cx);
2442 return JSVAL_VOID;
2444 #endif
2446 static JSBool
2447 array_push(JSContext *cx, uintN argc, jsval *vp)
2449 JSObject *obj;
2451 /* Insist on one argument and obj of the expected class. */
2452 obj = JS_THIS_OBJECT(cx, vp);
2453 if (!obj)
2454 return JS_FALSE;
2455 if (argc != 1 || !OBJ_IS_DENSE_ARRAY(cx, obj))
2456 return array_push_slowly(cx, obj, argc, vp + 2, vp);
2458 return array_push1_dense(cx, obj, vp[2], vp);
2461 static JSBool
2462 array_pop_slowly(JSContext *cx, JSObject* obj, jsval *vp)
2464 jsuint index;
2465 JSBool hole;
2467 if (!js_GetLengthProperty(cx, obj, &index))
2468 return JS_FALSE;
2469 if (index == 0) {
2470 *vp = JSVAL_VOID;
2471 } else {
2472 index--;
2474 /* Get the to-be-deleted property's value into vp. */
2475 if (!GetArrayElement(cx, obj, index, &hole, vp))
2476 return JS_FALSE;
2477 if (!hole && !DeleteArrayElement(cx, obj, index))
2478 return JS_FALSE;
2480 return js_SetLengthProperty(cx, obj, index);
2483 static JSBool
2484 array_pop_dense(JSContext *cx, JSObject* obj, jsval *vp)
2486 jsuint index;
2487 JSBool hole;
2489 index = obj->fslots[JSSLOT_ARRAY_LENGTH];
2490 if (index == 0) {
2491 *vp = JSVAL_VOID;
2492 return JS_TRUE;
2494 index--;
2495 if (!GetArrayElement(cx, obj, index, &hole, vp))
2496 return JS_FALSE;
2497 if (!hole && !DeleteArrayElement(cx, obj, index))
2498 return JS_FALSE;
2499 obj->fslots[JSSLOT_ARRAY_LENGTH] = index;
2500 return JS_TRUE;
2503 #ifdef JS_TRACER
2504 static jsval FASTCALL
2505 Array_p_pop(JSContext* cx, JSObject* obj)
2507 JSAutoTempValueRooter tvr(cx);
2508 if (OBJ_IS_DENSE_ARRAY(cx, obj)
2509 ? array_pop_dense(cx, obj, tvr.addr())
2510 : array_pop_slowly(cx, obj, tvr.addr())) {
2511 return tvr.value();
2513 SetBuiltinError(cx);
2514 return JSVAL_VOID;
2516 #endif
2518 static JSBool
2519 array_pop(JSContext *cx, uintN argc, jsval *vp)
2521 JSObject *obj;
2523 obj = JS_THIS_OBJECT(cx, vp);
2524 if (!obj)
2525 return JS_FALSE;
2526 if (OBJ_IS_DENSE_ARRAY(cx, obj))
2527 return array_pop_dense(cx, obj, vp);
2528 return array_pop_slowly(cx, obj, vp);
2531 static JSBool
2532 array_shift(JSContext *cx, uintN argc, jsval *vp)
2534 JSObject *obj;
2535 jsuint length, i;
2536 JSBool hole;
2538 obj = JS_THIS_OBJECT(cx, vp);
2539 if (!obj || !js_GetLengthProperty(cx, obj, &length))
2540 return JS_FALSE;
2541 if (length == 0) {
2542 *vp = JSVAL_VOID;
2543 } else {
2544 length--;
2546 if (OBJ_IS_DENSE_ARRAY(cx, obj) && !js_PrototypeHasIndexedProperties(cx, obj) &&
2547 length < js_DenseArrayCapacity(obj)) {
2548 if (JS_LIKELY(obj->dslots != NULL)) {
2549 *vp = obj->dslots[0];
2550 if (*vp == JSVAL_HOLE)
2551 *vp = JSVAL_VOID;
2552 else
2553 obj->fslots[JSSLOT_ARRAY_COUNT]--;
2554 memmove(obj->dslots, obj->dslots + 1, length * sizeof(jsval));
2555 obj->dslots[length] = JSVAL_HOLE;
2556 } else {
2558 * We don't need to modify the indexed properties of an empty array
2559 * with an explicitly set non-zero length when shift() is called on
2560 * it, but note fallthrough to reduce the length by one.
2562 JS_ASSERT(obj->fslots[JSSLOT_ARRAY_COUNT] == 0);
2563 *vp = JSVAL_VOID;
2565 } else {
2566 /* Get the to-be-deleted property's value into vp ASAP. */
2567 if (!GetArrayElement(cx, obj, 0, &hole, vp))
2568 return JS_FALSE;
2570 /* Slide down the array above the first element. */
2571 JSAutoTempValueRooter tvr(cx, JSVAL_NULL);
2572 for (i = 0; i != length; i++) {
2573 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
2574 !GetArrayElement(cx, obj, i + 1, &hole, tvr.addr()) ||
2575 !SetOrDeleteArrayElement(cx, obj, i, hole, tvr.value())) {
2576 return JS_FALSE;
2580 /* Delete the only or last element when it exists. */
2581 if (!hole && !DeleteArrayElement(cx, obj, length))
2582 return JS_FALSE;
2585 return js_SetLengthProperty(cx, obj, length);
2588 static JSBool
2589 array_unshift(JSContext *cx, uintN argc, jsval *vp)
2591 JSObject *obj;
2592 jsval *argv;
2593 jsuint length;
2594 JSBool hole;
2595 jsdouble last, newlen;
2597 obj = JS_THIS_OBJECT(cx, vp);
2598 if (!obj || !js_GetLengthProperty(cx, obj, &length))
2599 return JS_FALSE;
2600 newlen = length;
2601 if (argc > 0) {
2602 /* Slide up the array to make room for argc at the bottom. */
2603 argv = JS_ARGV(cx, vp);
2604 if (length > 0) {
2605 if (OBJ_IS_DENSE_ARRAY(cx, obj) && !js_PrototypeHasIndexedProperties(cx, obj) &&
2606 !INDEX_TOO_SPARSE(obj, unsigned(newlen + argc))) {
2607 JS_ASSERT(newlen + argc == length + argc);
2608 if (!EnsureCapacity(cx, obj, length + argc))
2609 return JS_FALSE;
2610 memmove(obj->dslots + argc, obj->dslots, length * sizeof(jsval));
2611 for (uint32 i = 0; i < argc; i++)
2612 obj->dslots[i] = JSVAL_HOLE;
2613 } else {
2614 last = length;
2615 jsdouble upperIndex = last + argc;
2616 JSAutoTempValueRooter tvr(cx, JSVAL_NULL);
2617 do {
2618 --last, --upperIndex;
2619 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
2620 !GetArrayElement(cx, obj, last, &hole, tvr.addr()) ||
2621 !SetOrDeleteArrayElement(cx, obj, upperIndex, hole, tvr.value())) {
2622 return JS_FALSE;
2624 } while (last != 0);
2628 /* Copy from argv to the bottom of the array. */
2629 if (!InitArrayElements(cx, obj, 0, argc, argv, TargetElementsAllHoles, SourceVectorAllValues))
2630 return JS_FALSE;
2632 newlen += argc;
2633 if (!js_SetLengthProperty(cx, obj, newlen))
2634 return JS_FALSE;
2637 /* Follow Perl by returning the new array length. */
2638 return IndexToValue(cx, newlen, vp);
2641 static JSBool
2642 array_splice(JSContext *cx, uintN argc, jsval *vp)
2644 jsval *argv;
2645 JSObject *obj;
2646 jsuint length, begin, end, count, delta, last;
2647 jsdouble d;
2648 JSBool hole;
2649 JSObject *obj2;
2652 * Create a new array value to return. Our ECMA v2 proposal specs
2653 * that splice always returns an array value, even when given no
2654 * arguments. We think this is best because it eliminates the need
2655 * for callers to do an extra test to handle the empty splice case.
2657 obj2 = js_NewArrayObject(cx, 0, NULL);
2658 if (!obj2)
2659 return JS_FALSE;
2660 *vp = OBJECT_TO_JSVAL(obj2);
2662 /* Nothing to do if no args. Otherwise get length. */
2663 if (argc == 0)
2664 return JS_TRUE;
2665 argv = JS_ARGV(cx, vp);
2666 obj = JS_THIS_OBJECT(cx, vp);
2667 if (!obj || !js_GetLengthProperty(cx, obj, &length))
2668 return JS_FALSE;
2670 /* Convert the first argument into a starting index. */
2671 d = js_ValueToNumber(cx, argv);
2672 if (JSVAL_IS_NULL(*argv))
2673 return JS_FALSE;
2674 d = js_DoubleToInteger(d);
2675 if (d < 0) {
2676 d += length;
2677 if (d < 0)
2678 d = 0;
2679 } else if (d > length) {
2680 d = length;
2682 begin = (jsuint)d; /* d has been clamped to uint32 */
2683 argc--;
2684 argv++;
2686 /* Convert the second argument from a count into a fencepost index. */
2687 delta = length - begin;
2688 if (argc == 0) {
2689 count = delta;
2690 end = length;
2691 } else {
2692 d = js_ValueToNumber(cx, argv);
2693 if (JSVAL_IS_NULL(*argv))
2694 return JS_FALSE;
2695 d = js_DoubleToInteger(d);
2696 if (d < 0)
2697 d = 0;
2698 else if (d > delta)
2699 d = delta;
2700 count = (jsuint)d;
2701 end = begin + count;
2702 argc--;
2703 argv++;
2706 JSAutoTempValueRooter tvr(cx, JSVAL_NULL);
2708 /* If there are elements to remove, put them into the return value. */
2709 if (count > 0) {
2710 if (OBJ_IS_DENSE_ARRAY(cx, obj) && !js_PrototypeHasIndexedProperties(cx, obj) &&
2711 !js_PrototypeHasIndexedProperties(cx, obj2) &&
2712 end <= js_DenseArrayCapacity(obj)) {
2713 if (!InitArrayObject(cx, obj2, count, obj->dslots + begin,
2714 obj->fslots[JSSLOT_ARRAY_COUNT] !=
2715 obj->fslots[JSSLOT_ARRAY_LENGTH])) {
2716 return JS_FALSE;
2718 } else {
2719 for (last = begin; last < end; last++) {
2720 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
2721 !GetArrayElement(cx, obj, last, &hole, tvr.addr())) {
2722 return JS_FALSE;
2725 /* Copy tvr.value() to the new array unless it's a hole. */
2726 if (!hole && !SetArrayElement(cx, obj2, last - begin, tvr.value()))
2727 return JS_FALSE;
2730 if (!js_SetLengthProperty(cx, obj2, count))
2731 return JS_FALSE;
2735 /* Find the direction (up or down) to copy and make way for argv. */
2736 if (argc > count) {
2737 delta = (jsuint)argc - count;
2738 last = length;
2739 if (OBJ_IS_DENSE_ARRAY(cx, obj) && !js_PrototypeHasIndexedProperties(cx, obj) &&
2740 length <= js_DenseArrayCapacity(obj) &&
2741 (length == 0 || obj->dslots[length - 1] != JSVAL_HOLE)) {
2742 if (!EnsureCapacity(cx, obj, length + delta))
2743 return JS_FALSE;
2744 /* (uint) end could be 0, so we can't use a vanilla >= test. */
2745 while (last-- > end) {
2746 jsval srcval = obj->dslots[last];
2747 jsval* dest = &obj->dslots[last + delta];
2748 if (*dest == JSVAL_HOLE && srcval != JSVAL_HOLE)
2749 obj->fslots[JSSLOT_ARRAY_COUNT]++;
2750 *dest = srcval;
2752 obj->fslots[JSSLOT_ARRAY_LENGTH] += delta;
2753 } else {
2754 /* (uint) end could be 0, so we can't use a vanilla >= test. */
2755 while (last-- > end) {
2756 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
2757 !GetArrayElement(cx, obj, last, &hole, tvr.addr()) ||
2758 !SetOrDeleteArrayElement(cx, obj, last + delta, hole, tvr.value())) {
2759 return JS_FALSE;
2763 length += delta;
2764 } else if (argc < count) {
2765 delta = count - (jsuint)argc;
2766 if (OBJ_IS_DENSE_ARRAY(cx, obj) && !js_PrototypeHasIndexedProperties(cx, obj) &&
2767 length <= js_DenseArrayCapacity(obj)) {
2768 /* (uint) end could be 0, so we can't use a vanilla >= test. */
2769 for (last = end; last < length; last++) {
2770 jsval srcval = obj->dslots[last];
2771 jsval* dest = &obj->dslots[last - delta];
2772 if (*dest == JSVAL_HOLE && srcval != JSVAL_HOLE)
2773 obj->fslots[JSSLOT_ARRAY_COUNT]++;
2774 *dest = srcval;
2776 } else {
2777 for (last = end; last < length; last++) {
2778 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
2779 !GetArrayElement(cx, obj, last, &hole, tvr.addr()) ||
2780 !SetOrDeleteArrayElement(cx, obj, last - delta, hole, tvr.value())) {
2781 return JS_FALSE;
2785 length -= delta;
2789 * Copy from argv into the hole to complete the splice, and update length in
2790 * case we deleted elements from the end.
2792 return InitArrayElements(cx, obj, begin, argc, argv, TargetElementsMayContainValues,
2793 SourceVectorAllValues) &&
2794 js_SetLengthProperty(cx, obj, length);
2798 * Python-esque sequence operations.
2800 static JSBool
2801 array_concat(JSContext *cx, uintN argc, jsval *vp)
2803 jsval *argv, v;
2804 JSObject *aobj, *nobj;
2805 jsuint length, alength, slot;
2806 uintN i;
2807 JSBool hole;
2809 /* Treat our |this| object as the first argument; see ECMA 15.4.4.4. */
2810 argv = JS_ARGV(cx, vp) - 1;
2811 JS_ASSERT(JS_THIS_OBJECT(cx, vp) == JSVAL_TO_OBJECT(argv[0]));
2813 /* Create a new Array object and root it using *vp. */
2814 aobj = JS_THIS_OBJECT(cx, vp);
2815 if (OBJ_IS_DENSE_ARRAY(cx, aobj)) {
2817 * Clone aobj but pass the minimum of its length and capacity, to
2818 * handle a = [1,2,3]; a.length = 10000 "dense" cases efficiently. In
2819 * such a case we'll pass 8 (not 3) due to ARRAY_CAPACITY_MIN, which
2820 * will cause nobj to be over-allocated to 16. But in the normal case
2821 * where length is <= capacity, nobj and aobj will have the same
2822 * capacity.
2824 length = aobj->fslots[JSSLOT_ARRAY_LENGTH];
2825 jsuint capacity = js_DenseArrayCapacity(aobj);
2826 nobj = js_NewArrayObject(cx, JS_MIN(length, capacity), aobj->dslots,
2827 aobj->fslots[JSSLOT_ARRAY_COUNT] !=
2828 (jsval) length);
2829 if (!nobj)
2830 return JS_FALSE;
2831 nobj->fslots[JSSLOT_ARRAY_LENGTH] = length;
2832 *vp = OBJECT_TO_JSVAL(nobj);
2833 if (argc == 0)
2834 return JS_TRUE;
2835 argc--;
2836 argv++;
2837 } else {
2838 nobj = js_NewArrayObject(cx, 0, NULL);
2839 if (!nobj)
2840 return JS_FALSE;
2841 *vp = OBJECT_TO_JSVAL(nobj);
2842 length = 0;
2845 JSAutoTempValueRooter tvr(cx, JSVAL_NULL);
2847 /* Loop over [0, argc] to concat args into nobj, expanding all Arrays. */
2848 for (i = 0; i <= argc; i++) {
2849 if (!JS_CHECK_OPERATION_LIMIT(cx))
2850 return false;
2851 v = argv[i];
2852 if (!JSVAL_IS_PRIMITIVE(v)) {
2853 JSObject *wobj;
2855 aobj = JSVAL_TO_OBJECT(v);
2856 wobj = js_GetWrappedObject(cx, aobj);
2857 if (OBJ_IS_ARRAY(cx, wobj)) {
2858 jsid id = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
2859 if (!aobj->getProperty(cx, id, tvr.addr()))
2860 return false;
2861 alength = ValueIsLength(cx, tvr.addr());
2862 if (JSVAL_IS_NULL(tvr.value()))
2863 return false;
2864 for (slot = 0; slot < alength; slot++) {
2865 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
2866 !GetArrayElement(cx, aobj, slot, &hole, tvr.addr())) {
2867 return false;
2871 * Per ECMA 262, 15.4.4.4, step 9, ignore non-existent
2872 * properties.
2874 if (!hole &&
2875 !SetArrayElement(cx, nobj, length+slot, tvr.value())) {
2876 return false;
2879 length += alength;
2880 continue;
2884 if (!SetArrayElement(cx, nobj, length, v))
2885 return false;
2886 length++;
2889 return js_SetLengthProperty(cx, nobj, length);
2892 static JSBool
2893 array_slice(JSContext *cx, uintN argc, jsval *vp)
2895 jsval *argv;
2896 JSObject *nobj, *obj;
2897 jsuint length, begin, end, slot;
2898 jsdouble d;
2899 JSBool hole;
2901 argv = JS_ARGV(cx, vp);
2903 obj = JS_THIS_OBJECT(cx, vp);
2904 if (!obj || !js_GetLengthProperty(cx, obj, &length))
2905 return JS_FALSE;
2906 begin = 0;
2907 end = length;
2909 if (argc > 0) {
2910 d = js_ValueToNumber(cx, &argv[0]);
2911 if (JSVAL_IS_NULL(argv[0]))
2912 return JS_FALSE;
2913 d = js_DoubleToInteger(d);
2914 if (d < 0) {
2915 d += length;
2916 if (d < 0)
2917 d = 0;
2918 } else if (d > length) {
2919 d = length;
2921 begin = (jsuint)d;
2923 if (argc > 1) {
2924 d = js_ValueToNumber(cx, &argv[1]);
2925 if (JSVAL_IS_NULL(argv[1]))
2926 return JS_FALSE;
2927 d = js_DoubleToInteger(d);
2928 if (d < 0) {
2929 d += length;
2930 if (d < 0)
2931 d = 0;
2932 } else if (d > length) {
2933 d = length;
2935 end = (jsuint)d;
2939 if (begin > end)
2940 begin = end;
2942 if (OBJ_IS_DENSE_ARRAY(cx, obj) && end <= js_DenseArrayCapacity(obj) &&
2943 !js_PrototypeHasIndexedProperties(cx, obj)) {
2944 nobj = js_NewArrayObject(cx, end - begin, obj->dslots + begin,
2945 obj->fslots[JSSLOT_ARRAY_COUNT] !=
2946 obj->fslots[JSSLOT_ARRAY_LENGTH]);
2947 if (!nobj)
2948 return JS_FALSE;
2949 *vp = OBJECT_TO_JSVAL(nobj);
2950 return JS_TRUE;
2953 /* Create a new Array object and root it using *vp. */
2954 nobj = js_NewArrayObject(cx, 0, NULL);
2955 if (!nobj)
2956 return JS_FALSE;
2957 *vp = OBJECT_TO_JSVAL(nobj);
2959 JSAutoTempValueRooter tvr(cx, JSVAL_NULL);
2960 for (slot = begin; slot < end; slot++) {
2961 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
2962 !GetArrayElement(cx, obj, slot, &hole, tvr.addr())) {
2963 return JS_FALSE;
2965 if (!hole && !SetArrayElement(cx, nobj, slot - begin, tvr.value()))
2966 return JS_FALSE;
2969 return js_SetLengthProperty(cx, nobj, end - begin);
2972 #if JS_HAS_ARRAY_EXTRAS
2974 static JSBool
2975 array_indexOfHelper(JSContext *cx, JSBool isLast, uintN argc, jsval *vp)
2977 JSObject *obj;
2978 jsuint length, i, stop;
2979 jsval tosearch;
2980 jsint direction;
2981 JSBool hole;
2983 obj = JS_THIS_OBJECT(cx, vp);
2984 if (!obj || !js_GetLengthProperty(cx, obj, &length))
2985 return JS_FALSE;
2986 if (length == 0)
2987 goto not_found;
2989 if (argc <= 1) {
2990 i = isLast ? length - 1 : 0;
2991 tosearch = (argc != 0) ? vp[2] : JSVAL_VOID;
2992 } else {
2993 jsdouble start;
2995 tosearch = vp[2];
2996 start = js_ValueToNumber(cx, &vp[3]);
2997 if (JSVAL_IS_NULL(vp[3]))
2998 return JS_FALSE;
2999 start = js_DoubleToInteger(start);
3000 if (start < 0) {
3001 start += length;
3002 if (start < 0) {
3003 if (isLast)
3004 goto not_found;
3005 i = 0;
3006 } else {
3007 i = (jsuint)start;
3009 } else if (start >= length) {
3010 if (!isLast)
3011 goto not_found;
3012 i = length - 1;
3013 } else {
3014 i = (jsuint)start;
3018 if (isLast) {
3019 stop = 0;
3020 direction = -1;
3021 } else {
3022 stop = length - 1;
3023 direction = 1;
3026 for (;;) {
3027 if (!JS_CHECK_OPERATION_LIMIT(cx) ||
3028 !GetArrayElement(cx, obj, (jsuint)i, &hole, vp)) {
3029 return JS_FALSE;
3031 if (!hole && js_StrictlyEqual(cx, *vp, tosearch))
3032 return js_NewNumberInRootedValue(cx, i, vp);
3033 if (i == stop)
3034 goto not_found;
3035 i += direction;
3038 not_found:
3039 *vp = INT_TO_JSVAL(-1);
3040 return JS_TRUE;
3043 static JSBool
3044 array_indexOf(JSContext *cx, uintN argc, jsval *vp)
3046 return array_indexOfHelper(cx, JS_FALSE, argc, vp);
3049 static JSBool
3050 array_lastIndexOf(JSContext *cx, uintN argc, jsval *vp)
3052 return array_indexOfHelper(cx, JS_TRUE, argc, vp);
3055 /* Order is important; extras that take a predicate funarg must follow MAP. */
3056 typedef enum ArrayExtraMode {
3057 FOREACH,
3058 REDUCE,
3059 REDUCE_RIGHT,
3060 MAP,
3061 FILTER,
3062 SOME,
3063 EVERY
3064 } ArrayExtraMode;
3066 #define REDUCE_MODE(mode) ((mode) == REDUCE || (mode) == REDUCE_RIGHT)
3068 static JSBool
3069 array_extra(JSContext *cx, ArrayExtraMode mode, uintN argc, jsval *vp)
3071 JSObject *obj;
3072 jsuint length, newlen;
3073 jsval *argv, *elemroot, *invokevp, *sp;
3074 JSBool ok, cond, hole;
3075 JSObject *callable, *thisp, *newarr;
3076 jsint start, end, step, i;
3077 void *mark;
3079 obj = JS_THIS_OBJECT(cx, vp);
3080 if (!obj || !js_GetLengthProperty(cx, obj, &length))
3081 return JS_FALSE;
3084 * First, get or compute our callee, so that we error out consistently
3085 * when passed a non-callable object.
3087 if (argc == 0) {
3088 js_ReportMissingArg(cx, vp, 0);
3089 return JS_FALSE;
3091 argv = vp + 2;
3092 callable = js_ValueToCallableObject(cx, &argv[0], JSV2F_SEARCH_STACK);
3093 if (!callable)
3094 return JS_FALSE;
3097 * Set our initial return condition, used for zero-length array cases
3098 * (and pre-size our map return to match our known length, for all cases).
3100 #ifdef __GNUC__ /* quell GCC overwarning */
3101 newlen = 0;
3102 newarr = NULL;
3103 #endif
3104 start = 0, end = length, step = 1;
3106 switch (mode) {
3107 case REDUCE_RIGHT:
3108 start = length - 1, end = -1, step = -1;
3109 /* FALL THROUGH */
3110 case REDUCE:
3111 if (length == 0 && argc == 1) {
3112 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3113 JSMSG_EMPTY_ARRAY_REDUCE);
3114 return JS_FALSE;
3116 if (argc >= 2) {
3117 *vp = argv[1];
3118 } else {
3119 do {
3120 if (!GetArrayElement(cx, obj, start, &hole, vp))
3121 return JS_FALSE;
3122 start += step;
3123 } while (hole && start != end);
3125 if (hole && start == end) {
3126 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3127 JSMSG_EMPTY_ARRAY_REDUCE);
3128 return JS_FALSE;
3131 break;
3132 case MAP:
3133 case FILTER:
3134 newlen = (mode == MAP) ? length : 0;
3135 newarr = js_NewArrayObject(cx, newlen, NULL);
3136 if (!newarr)
3137 return JS_FALSE;
3138 *vp = OBJECT_TO_JSVAL(newarr);
3139 break;
3140 case SOME:
3141 *vp = JSVAL_FALSE;
3142 break;
3143 case EVERY:
3144 *vp = JSVAL_TRUE;
3145 break;
3146 case FOREACH:
3147 *vp = JSVAL_VOID;
3148 break;
3151 if (length == 0)
3152 return JS_TRUE;
3154 if (argc > 1 && !REDUCE_MODE(mode)) {
3155 if (!js_ValueToObject(cx, argv[1], &thisp))
3156 return JS_FALSE;
3157 argv[1] = OBJECT_TO_JSVAL(thisp);
3158 } else {
3159 thisp = NULL;
3163 * For all but REDUCE, we call with 3 args (value, index, array). REDUCE
3164 * requires 4 args (accum, value, index, array).
3166 LeaveTrace(cx);
3167 argc = 3 + REDUCE_MODE(mode);
3168 elemroot = js_AllocStack(cx, 1 + 2 + argc, &mark);
3169 if (!elemroot)
3170 return JS_FALSE;
3172 MUST_FLOW_THROUGH("out");
3173 ok = JS_TRUE;
3174 invokevp = elemroot + 1;
3176 for (i = start; i != end; i += step) {
3177 ok = JS_CHECK_OPERATION_LIMIT(cx) &&
3178 GetArrayElement(cx, obj, i, &hole, elemroot);
3179 if (!ok)
3180 goto out;
3181 if (hole)
3182 continue;
3185 * Push callable and 'this', then args. We must do this for every
3186 * iteration around the loop since js_Invoke uses spbase[0] for return
3187 * value storage, while some native functions use spbase[1] for local
3188 * rooting.
3190 sp = invokevp;
3191 *sp++ = OBJECT_TO_JSVAL(callable);
3192 *sp++ = OBJECT_TO_JSVAL(thisp);
3193 if (REDUCE_MODE(mode))
3194 *sp++ = *vp;
3195 *sp++ = *elemroot;
3196 *sp++ = INT_TO_JSVAL(i);
3197 *sp++ = OBJECT_TO_JSVAL(obj);
3199 /* Do the call. */
3200 ok = js_Invoke(cx, argc, invokevp, 0);
3201 if (!ok)
3202 break;
3204 if (mode > MAP)
3205 cond = js_ValueToBoolean(*invokevp);
3206 #ifdef __GNUC__ /* quell GCC overwarning */
3207 else
3208 cond = JS_FALSE;
3209 #endif
3211 switch (mode) {
3212 case FOREACH:
3213 break;
3214 case REDUCE:
3215 case REDUCE_RIGHT:
3216 *vp = *invokevp;
3217 break;
3218 case MAP:
3219 ok = SetArrayElement(cx, newarr, i, *invokevp);
3220 if (!ok)
3221 goto out;
3222 break;
3223 case FILTER:
3224 if (!cond)
3225 break;
3226 /* The filter passed *elemroot, so push it onto our result. */
3227 ok = SetArrayElement(cx, newarr, newlen++, *elemroot);
3228 if (!ok)
3229 goto out;
3230 break;
3231 case SOME:
3232 if (cond) {
3233 *vp = JSVAL_TRUE;
3234 goto out;
3236 break;
3237 case EVERY:
3238 if (!cond) {
3239 *vp = JSVAL_FALSE;
3240 goto out;
3242 break;
3246 out:
3247 js_FreeStack(cx, mark);
3248 if (ok && mode == FILTER)
3249 ok = js_SetLengthProperty(cx, newarr, newlen);
3250 return ok;
3253 static JSBool
3254 array_forEach(JSContext *cx, uintN argc, jsval *vp)
3256 return array_extra(cx, FOREACH, argc, vp);
3259 static JSBool
3260 array_map(JSContext *cx, uintN argc, jsval *vp)
3262 return array_extra(cx, MAP, argc, vp);
3265 static JSBool
3266 array_reduce(JSContext *cx, uintN argc, jsval *vp)
3268 return array_extra(cx, REDUCE, argc, vp);
3271 static JSBool
3272 array_reduceRight(JSContext *cx, uintN argc, jsval *vp)
3274 return array_extra(cx, REDUCE_RIGHT, argc, vp);
3277 static JSBool
3278 array_filter(JSContext *cx, uintN argc, jsval *vp)
3280 return array_extra(cx, FILTER, argc, vp);
3283 static JSBool
3284 array_some(JSContext *cx, uintN argc, jsval *vp)
3286 return array_extra(cx, SOME, argc, vp);
3289 static JSBool
3290 array_every(JSContext *cx, uintN argc, jsval *vp)
3292 return array_extra(cx, EVERY, argc, vp);
3294 #endif
3296 static JSBool
3297 array_isArray(JSContext *cx, uintN argc, jsval *vp)
3299 *vp = BOOLEAN_TO_JSVAL(argc > 0 &&
3300 !JSVAL_IS_PRIMITIVE(vp[2]) &&
3301 OBJ_IS_ARRAY(cx, js_GetWrappedObject(cx, JSVAL_TO_OBJECT(vp[2]))));
3302 return JS_TRUE;
3305 static JSPropertySpec array_props[] = {
3306 {js_length_str, -1, JSPROP_SHARED | JSPROP_PERMANENT,
3307 array_length_getter, array_length_setter},
3308 {0,0,0,0,0}
3311 JS_DEFINE_TRCINFO_1(array_toString,
3312 (2, (static, STRING_FAIL, Array_p_toString, CONTEXT, THIS, 0, 0)))
3313 JS_DEFINE_TRCINFO_1(array_join,
3314 (3, (static, STRING_FAIL, Array_p_join, CONTEXT, THIS, STRING, 0, 0)))
3315 JS_DEFINE_TRCINFO_1(array_push,
3316 (3, (static, JSVAL_FAIL, Array_p_push1, CONTEXT, THIS, JSVAL, 0, 0)))
3317 JS_DEFINE_TRCINFO_1(array_pop,
3318 (2, (static, JSVAL_FAIL, Array_p_pop, CONTEXT, THIS, 0, 0)))
3320 static JSFunctionSpec array_methods[] = {
3321 #if JS_HAS_TOSOURCE
3322 JS_FN(js_toSource_str, array_toSource, 0,0),
3323 #endif
3324 JS_TN(js_toString_str, array_toString, 0,0, &array_toString_trcinfo),
3325 JS_FN(js_toLocaleString_str,array_toLocaleString,0,0),
3327 /* Perl-ish methods. */
3328 JS_TN("join", array_join, 1,JSFUN_GENERIC_NATIVE, &array_join_trcinfo),
3329 JS_FN("reverse", array_reverse, 0,JSFUN_GENERIC_NATIVE),
3330 JS_FN("sort", array_sort, 1,JSFUN_GENERIC_NATIVE),
3331 JS_TN("push", array_push, 1,JSFUN_GENERIC_NATIVE, &array_push_trcinfo),
3332 JS_TN("pop", array_pop, 0,JSFUN_GENERIC_NATIVE, &array_pop_trcinfo),
3333 JS_FN("shift", array_shift, 0,JSFUN_GENERIC_NATIVE),
3334 JS_FN("unshift", array_unshift, 1,JSFUN_GENERIC_NATIVE),
3335 JS_FN("splice", array_splice, 2,JSFUN_GENERIC_NATIVE),
3337 /* Pythonic sequence methods. */
3338 JS_FN("concat", array_concat, 1,JSFUN_GENERIC_NATIVE),
3339 JS_FN("slice", array_slice, 2,JSFUN_GENERIC_NATIVE),
3341 #if JS_HAS_ARRAY_EXTRAS
3342 JS_FN("indexOf", array_indexOf, 1,JSFUN_GENERIC_NATIVE),
3343 JS_FN("lastIndexOf", array_lastIndexOf, 1,JSFUN_GENERIC_NATIVE),
3344 JS_FN("forEach", array_forEach, 1,JSFUN_GENERIC_NATIVE),
3345 JS_FN("map", array_map, 1,JSFUN_GENERIC_NATIVE),
3346 JS_FN("reduce", array_reduce, 1,JSFUN_GENERIC_NATIVE),
3347 JS_FN("reduceRight", array_reduceRight, 1,JSFUN_GENERIC_NATIVE),
3348 JS_FN("filter", array_filter, 1,JSFUN_GENERIC_NATIVE),
3349 JS_FN("some", array_some, 1,JSFUN_GENERIC_NATIVE),
3350 JS_FN("every", array_every, 1,JSFUN_GENERIC_NATIVE),
3351 #endif
3353 JS_FS_END
3356 static JSFunctionSpec array_static_methods[] = {
3357 JS_FN("isArray", array_isArray, 1,0),
3358 JS_FS_END
3361 JSBool
3362 js_Array(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
3364 jsuint length;
3365 jsval *vector;
3367 /* If called without new, replace obj with a new Array object. */
3368 if (!JS_IsConstructing(cx)) {
3369 obj = js_NewObject(cx, &js_ArrayClass, NULL, NULL);
3370 if (!obj)
3371 return JS_FALSE;
3372 *rval = OBJECT_TO_JSVAL(obj);
3375 if (argc == 0) {
3376 length = 0;
3377 vector = NULL;
3378 } else if (argc > 1) {
3379 length = (jsuint) argc;
3380 vector = argv;
3381 } else if (!JSVAL_IS_NUMBER(argv[0])) {
3382 length = 1;
3383 vector = argv;
3384 } else {
3385 length = ValueIsLength(cx, &argv[0]);
3386 if (JSVAL_IS_NULL(argv[0]))
3387 return JS_FALSE;
3388 vector = NULL;
3390 return InitArrayObject(cx, obj, length, vector);
3393 JS_STATIC_ASSERT(JSSLOT_PRIVATE == JSSLOT_ARRAY_LENGTH);
3394 JS_STATIC_ASSERT(JSSLOT_ARRAY_LENGTH + 1 == JSSLOT_ARRAY_COUNT);
3396 JSObject* JS_FASTCALL
3397 js_NewEmptyArray(JSContext* cx, JSObject* proto)
3399 JS_ASSERT(OBJ_IS_ARRAY(cx, proto));
3401 JSObject* obj = js_NewGCObject(cx);
3402 if (!obj)
3403 return NULL;
3405 /* Initialize all fields of JSObject. */
3406 obj->map = const_cast<JSObjectMap *>(&SharedArrayMap);
3407 obj->classword = jsuword(&js_ArrayClass);
3408 obj->setProto(proto);
3409 obj->setParent(proto->getParent());
3411 obj->fslots[JSSLOT_ARRAY_LENGTH] = 0;
3412 obj->fslots[JSSLOT_ARRAY_COUNT] = 0;
3413 for (unsigned i = JSSLOT_ARRAY_COUNT + 1; i != JS_INITIAL_NSLOTS; ++i)
3414 obj->fslots[i] = JSVAL_VOID;
3415 obj->dslots = NULL;
3416 return obj;
3418 #ifdef JS_TRACER
3419 JS_DEFINE_CALLINFO_2(extern, OBJECT, js_NewEmptyArray, CONTEXT, OBJECT, 0, 0)
3420 #endif
3422 JSObject* JS_FASTCALL
3423 js_NewEmptyArrayWithLength(JSContext* cx, JSObject* proto, int32 len)
3425 if (len < 0)
3426 return NULL;
3427 JSObject *obj = js_NewEmptyArray(cx, proto);
3428 if (!obj)
3429 return NULL;
3430 obj->fslots[JSSLOT_ARRAY_LENGTH] = len;
3431 return obj;
3433 #ifdef JS_TRACER
3434 JS_DEFINE_CALLINFO_3(extern, OBJECT, js_NewEmptyArrayWithLength, CONTEXT, OBJECT, INT32, 0, 0)
3435 #endif
3437 JSObject* JS_FASTCALL
3438 js_NewArrayWithSlots(JSContext* cx, JSObject* proto, uint32 len)
3440 JSObject* obj = js_NewEmptyArray(cx, proto);
3441 if (!obj)
3442 return NULL;
3443 obj->fslots[JSSLOT_ARRAY_LENGTH] = len;
3444 if (!ResizeSlots(cx, obj, 0, JS_MAX(len, ARRAY_CAPACITY_MIN)))
3445 return NULL;
3446 return obj;
3448 #ifdef JS_TRACER
3449 JS_DEFINE_CALLINFO_3(extern, OBJECT, js_NewArrayWithSlots, CONTEXT, OBJECT, UINT32, 0, 0)
3450 #endif
3452 JSObject *
3453 js_InitArrayClass(JSContext *cx, JSObject *obj)
3455 JSObject *proto = JS_InitClass(cx, obj, NULL, &js_ArrayClass, js_Array, 1,
3456 array_props, array_methods, NULL, array_static_methods);
3458 /* Initialize the Array prototype object so it gets a length property. */
3459 if (!proto || !InitArrayObject(cx, proto, 0, NULL))
3460 return NULL;
3461 return proto;
3464 JSObject *
3465 js_NewArrayObject(JSContext *cx, jsuint length, jsval *vector, JSBool holey)
3467 JSTempValueRooter tvr;
3468 JSObject *obj;
3470 obj = js_NewObject(cx, &js_ArrayClass, NULL, NULL);
3471 if (!obj)
3472 return NULL;
3475 * If this fails, the global object was not initialized and its class does
3476 * not have JSCLASS_IS_GLOBAL.
3478 JS_ASSERT(obj->getProto());
3480 JS_PUSH_TEMP_ROOT_OBJECT(cx, obj, &tvr);
3481 if (!InitArrayObject(cx, obj, length, vector, holey))
3482 obj = NULL;
3483 JS_POP_TEMP_ROOT(cx, &tvr);
3485 /* Set/clear newborn root, in case we lost it. */
3486 cx->weakRoots.finalizableNewborns[FINALIZE_OBJECT] = obj;
3487 return obj;
3490 JSObject *
3491 js_NewSlowArrayObject(JSContext *cx)
3493 JSObject *obj = js_NewObject(cx, &js_SlowArrayClass, NULL, NULL);
3494 if (obj)
3495 obj->fslots[JSSLOT_ARRAY_LENGTH] = 0;
3496 return obj;
3499 #ifdef DEBUG_ARRAYS
3500 JSBool
3501 js_ArrayInfo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
3503 uintN i;
3504 JSObject *array;
3506 for (i = 0; i < argc; i++) {
3507 char *bytes;
3509 bytes = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, argv[i],
3510 NULL);
3511 if (!bytes)
3512 return JS_FALSE;
3513 if (JSVAL_IS_PRIMITIVE(argv[i]) ||
3514 !OBJ_IS_ARRAY(cx, (array = JSVAL_TO_OBJECT(argv[i])))) {
3515 fprintf(stderr, "%s: not array\n", bytes);
3516 cx->free(bytes);
3517 continue;
3519 fprintf(stderr, "%s: %s (len %lu", bytes,
3520 OBJ_IS_DENSE_ARRAY(cx, array) ? "dense" : "sparse",
3521 array->fslots[JSSLOT_ARRAY_LENGTH]);
3522 if (OBJ_IS_DENSE_ARRAY(cx, array)) {
3523 fprintf(stderr, ", count %lu, capacity %lu",
3524 array->fslots[JSSLOT_ARRAY_COUNT],
3525 js_DenseArrayCapacity(array));
3527 fputs(")\n", stderr);
3528 cx->free(bytes);
3530 return JS_TRUE;
3532 #endif
3534 JS_FRIEND_API(JSBool)
3535 js_CoerceArrayToCanvasImageData(JSObject *obj, jsuint offset, jsuint count,
3536 JSUint8 *dest)
3538 uint32 length;
3540 if (!obj || !obj->isDenseArray())
3541 return JS_FALSE;
3543 length = obj->fslots[JSSLOT_ARRAY_LENGTH];
3544 if (length < offset + count)
3545 return JS_FALSE;
3547 JSUint8 *dp = dest;
3548 for (uintN i = offset; i < offset+count; i++) {
3549 jsval v = obj->dslots[i];
3550 if (JSVAL_IS_INT(v)) {
3551 jsint vi = JSVAL_TO_INT(v);
3552 if (jsuint(vi) > 255)
3553 vi = (vi < 0) ? 0 : 255;
3554 *dp++ = JSUint8(vi);
3555 } else if (JSVAL_IS_DOUBLE(v)) {
3556 jsdouble vd = *JSVAL_TO_DOUBLE(v);
3557 if (!(vd >= 0)) /* Not < so that NaN coerces to 0 */
3558 *dp++ = 0;
3559 else if (vd > 255)
3560 *dp++ = 255;
3561 else {
3562 jsdouble toTruncate = vd + 0.5;
3563 JSUint8 val = JSUint8(toTruncate);
3566 * now val is rounded to nearest, ties rounded up. We want
3567 * rounded to nearest ties to even, so check whether we had a
3568 * tie.
3570 if (val == toTruncate) {
3572 * It was a tie (since adding 0.5 gave us the exact integer
3573 * we want). Since we rounded up, we either already have an
3574 * even number or we have an odd number but the number we
3575 * want is one less. So just unconditionally masking out the
3576 * ones bit should do the trick to get us the value we
3577 * want.
3579 *dp++ = (val & ~1);
3580 } else {
3581 *dp++ = val;
3584 } else {
3585 return JS_FALSE;
3589 return JS_TRUE;
3592 JS_FRIEND_API(JSObject *)
3593 js_NewArrayObjectWithCapacity(JSContext *cx, jsuint capacity, jsval **vector)
3595 JSObject *obj = js_NewArrayObject(cx, capacity, NULL);
3596 if (!obj)
3597 return NULL;
3599 JSAutoTempValueRooter tvr(cx, obj);
3600 if (!EnsureCapacity(cx, obj, capacity, JS_FALSE))
3601 obj = NULL;
3603 /* Set/clear newborn root, in case we lost it. */
3604 cx->weakRoots.finalizableNewborns[FINALIZE_OBJECT] = obj;
3605 if (!obj)
3606 return NULL;
3608 obj->fslots[JSSLOT_ARRAY_COUNT] = capacity;
3609 *vector = obj->dslots;
3610 return obj;