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
17 * The Original Code is Mozilla Communicator client code, released
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.
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 ***** */
44 * Array objects begin as "dense" arrays, optimized for index-only property
45 * access over a vector of slots with high load factor. Array methods
46 * 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, accessible with
51 * getArrayLength(), setArrayLength().
52 * - The number of element slots (capacity), gettable with
53 * getDenseArrayCapacity().
55 * In dense mode, holes in the array are represented by
56 * MagicValue(JS_ARRAY_HOLE) invalid values.
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. The first
60 * case may occur when the user writes "new Array(100), in which case the
61 * length is 100 while the capacity remains 0 (indices below length and above
62 * capaicty must be treated as holes). See array_length_setter for another
63 * explanation of how the first case may occur.
65 * Arrays are converted to use js_SlowArrayClass when any of these conditions
67 * - there are more than MIN_SPARSE_INDEX slots total
68 * - the load factor (COUNT / capacity) is less than 0.25
69 * - a property is set that is not indexed (and not "length")
70 * - a property is defined that has non-default property attributes.
72 * Dense arrays do not track property creation order, so unlike other native
73 * objects and slow arrays, enumerating an array does not necessarily visit the
74 * properties in the order they were created. We could instead maintain the
75 * scope to track property enumeration order, but still use the fast slot
76 * access. That would have the same memory cost as just using a
77 * js_SlowArrayClass, but have the same performance characteristics as a dense
78 * array for slot accesses, at some cost in code complexity.
91 #include "jsbuiltins.h"
93 #include "jsversion.h"
94 #include "jsdbgapi.h" /* for js_TraceWatchPoints */
104 #include "jsstaticcheck.h"
105 #include "jsvector.h"
106 #include "jswrapper.h"
108 #include "jsatominlines.h"
109 #include "jscntxtinlines.h"
110 #include "jsinterpinlines.h"
111 #include "jsobjinlines.h"
114 using namespace js::gc
;
116 /* 2^32 - 1 as a number and a string */
117 #define MAXINDEX 4294967295u
118 #define MAXSTR "4294967295"
121 ENSURE_SLOW_ARRAY(JSContext
*cx
, JSObject
*obj
)
123 return obj
->getClass() == &js_SlowArrayClass
||
124 obj
->makeDenseArraySlow(cx
);
128 * Determine if the id represents an array index or an XML property index.
130 * An id is an array index according to ECMA by (15.4):
132 * "Array objects give special treatment to a certain class of property names.
133 * A property name P (in the form of a string value) is an array index if and
134 * only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal
137 * In our implementation, it would be sufficient to check for JSVAL_IS_INT(id)
138 * except that by using signed 31-bit integers we miss the top half of the
139 * valid range. This function checks the string representation itself; note
140 * that calling a standard conversion routine might allow strings such as
141 * "08" or "4.0" as array indices, which they are not.
143 * 'id' is passed as a jsboxedword since the given id need not necessarily hold
144 * an atomized string.
147 js_StringIsIndex(JSLinearString
*str
, jsuint
*indexp
)
149 const jschar
*cp
= str
->chars();
150 if (JS7_ISDEC(*cp
) && str
->length() < sizeof(MAXSTR
)) {
151 jsuint index
= JS7_UNDEC(*cp
++);
155 while (JS7_ISDEC(*cp
)) {
158 index
= 10*index
+ c
;
163 /* Ensure that all characters were consumed and we didn't overflow. */
165 (oldIndex
< (MAXINDEX
/ 10) ||
166 (oldIndex
== (MAXINDEX
/ 10) && c
< (MAXINDEX
% 10))))
176 ValueToLength(JSContext
*cx
, Value
* vp
, jsuint
* plength
)
179 int32_t i
= vp
->toInt32();
183 *plength
= (jsuint
)(i
);
188 if (!ValueToNumber(cx
, *vp
, &d
))
191 if (JSDOUBLE_IS_NaN(d
))
196 if (d
!= (jsdouble
) length
)
204 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, NULL
,
205 JSMSG_BAD_ARRAY_LENGTH
);
210 js_GetLengthProperty(JSContext
*cx
, JSObject
*obj
, jsuint
*lengthp
)
212 if (obj
->isArray()) {
213 *lengthp
= obj
->getArrayLength();
217 if (obj
->isArguments() && !obj
->isArgsLengthOverridden()) {
218 *lengthp
= obj
->getArgsInitialLength();
222 AutoValueRooter
tvr(cx
);
223 if (!obj
->getProperty(cx
, ATOM_TO_JSID(cx
->runtime
->atomState
.lengthAtom
), tvr
.addr()))
226 if (tvr
.value().isInt32()) {
227 *lengthp
= jsuint(jsint(tvr
.value().toInt32())); /* jsuint cast does ToUint32 */
231 JS_STATIC_ASSERT(sizeof(jsuint
) == sizeof(uint32_t));
232 return ValueToECMAUint32(cx
, tvr
.value(), (uint32_t *)lengthp
);
236 js_IndexToId(JSContext
*cx
, jsuint index
, jsid
*idp
)
240 if (index
<= JSID_INT_MAX
) {
241 *idp
= INT_TO_JSID(index
);
244 str
= js_NumberToString(cx
, index
);
247 return js_ValueToStringId(cx
, StringValue(str
), idp
);
251 BigIndexToId(JSContext
*cx
, JSObject
*obj
, jsuint index
, JSBool createAtom
,
254 jschar buf
[10], *start
;
257 JS_STATIC_ASSERT((jsuint
)-1 == 4294967295U);
259 JS_ASSERT(index
> JSID_INT_MAX
);
261 start
= JS_ARRAY_END(buf
);
264 *start
= (jschar
)('0' + index
% 10);
266 } while (index
!= 0);
269 * Skip the atomization if the class is known to store atoms corresponding
270 * to big indexes together with elements. In such case we know that the
271 * array does not have an element at the given index if its atom does not
272 * exist. Fast arrays (clasp == &js_ArrayClass) don't use atoms for
273 * any indexes, though it would be rare to see them have a big index
277 ((clasp
= obj
->getClass()) == &js_SlowArrayClass
||
278 clasp
== &js_ArgumentsClass
||
279 clasp
== &js_ObjectClass
)) {
280 atom
= js_GetExistingStringAtom(cx
, start
, JS_ARRAY_END(buf
) - start
);
286 atom
= js_AtomizeChars(cx
, start
, JS_ARRAY_END(buf
) - start
, 0);
291 *idp
= ATOM_TO_JSID(atom
);
296 JSObject::willBeSparseDenseArray(uintN requiredCapacity
, uintN newElementsHint
)
298 JS_ASSERT(isDenseArray());
299 JS_ASSERT(requiredCapacity
> MIN_SPARSE_INDEX
);
301 uintN cap
= numSlots();
302 JS_ASSERT(requiredCapacity
>= cap
);
304 if (requiredCapacity
>= JSObject::NSLOTS_LIMIT
)
307 uintN minimalDenseCount
= requiredCapacity
/ 4;
308 if (newElementsHint
>= minimalDenseCount
)
310 minimalDenseCount
-= newElementsHint
;
312 if (minimalDenseCount
> cap
)
315 Value
*elems
= getDenseArrayElements();
316 for (uintN i
= 0; i
< cap
; i
++) {
317 if (!elems
[i
].isMagic(JS_ARRAY_HOLE
) && !--minimalDenseCount
)
324 ReallyBigIndexToId(JSContext
* cx
, jsdouble index
, jsid
* idp
)
326 return js_ValueToStringId(cx
, DoubleValue(index
), idp
);
330 IndexToId(JSContext
* cx
, JSObject
* obj
, jsdouble index
, JSBool
* hole
, jsid
* idp
,
331 JSBool createAtom
= JS_FALSE
)
333 if (index
<= JSID_INT_MAX
) {
334 *idp
= INT_TO_JSID(int(index
));
338 if (index
<= jsuint(-1)) {
339 if (!BigIndexToId(cx
, obj
, jsuint(index
), createAtom
, idp
))
341 if (hole
&& JSID_IS_VOID(*idp
))
346 return ReallyBigIndexToId(cx
, index
, idp
);
350 * If the property at the given index exists, get its value into location
351 * pointed by vp and set *hole to false. Otherwise set *hole to true and *vp
352 * to JSVAL_VOID. This function assumes that the location pointed by vp is
353 * properly rooted and can be used as GC-protected storage for temporaries.
356 GetElement(JSContext
*cx
, JSObject
*obj
, jsdouble index
, JSBool
*hole
, Value
*vp
)
358 JS_ASSERT(index
>= 0);
359 if (obj
->isDenseArray() && index
< obj
->getDenseArrayCapacity() &&
360 !(*vp
= obj
->getDenseArrayElement(uint32(index
))).isMagic(JS_ARRAY_HOLE
)) {
364 if (obj
->isArguments() &&
365 index
< obj
->getArgsInitialLength() &&
366 !(*vp
= obj
->getArgsElement(uint32(index
))).isMagic(JS_ARGS_HOLE
)) {
368 JSStackFrame
*fp
= (JSStackFrame
*)obj
->getPrivate();
369 if (fp
!= JS_ARGUMENTS_OBJECT_ON_TRACE
) {
371 *vp
= fp
->canonicalActualArg(index
);
376 AutoIdRooter
idr(cx
);
379 if (!IndexToId(cx
, obj
, index
, hole
, idr
.addr()))
388 if (!obj
->lookupProperty(cx
, idr
.id(), &obj2
, &prop
))
394 if (!obj
->getProperty(cx
, idr
.id(), vp
))
404 GetElements(JSContext
*cx
, JSObject
*aobj
, jsuint length
, Value
*vp
)
406 if (aobj
->isDenseArray() && length
<= aobj
->getDenseArrayCapacity() &&
407 !js_PrototypeHasIndexedProperties(cx
, aobj
)) {
408 /* The prototype does not have indexed properties so hole = undefined */
409 Value
*srcbeg
= aobj
->getDenseArrayElements();
410 Value
*srcend
= srcbeg
+ length
;
411 for (Value
*dst
= vp
, *src
= srcbeg
; src
< srcend
; ++dst
, ++src
)
412 *dst
= src
->isMagic(JS_ARRAY_HOLE
) ? UndefinedValue() : *src
;
413 } else if (aobj
->isArguments() && !aobj
->isArgsLengthOverridden() &&
414 !js_PrototypeHasIndexedProperties(cx
, aobj
)) {
416 * Two cases, two loops: note how in the case of an active stack frame
417 * backing aobj, even though we copy from fp->argv, we still must check
418 * aobj->getArgsElement(i) for a hole, to handle a delete on the
419 * corresponding arguments element. See args_delProperty.
421 if (JSStackFrame
*fp
= (JSStackFrame
*) aobj
->getPrivate()) {
422 JS_ASSERT(fp
->numActualArgs() <= JS_ARGS_LENGTH_MAX
);
423 fp
->forEachCanonicalActualArg(CopyNonHoleArgsTo(aobj
, vp
));
425 Value
*srcbeg
= aobj
->getArgsElements();
426 Value
*srcend
= srcbeg
+ length
;
427 for (Value
*dst
= vp
, *src
= srcbeg
; src
< srcend
; ++dst
, ++src
)
428 *dst
= src
->isMagic(JS_ARGS_HOLE
) ? UndefinedValue() : *src
;
431 for (uintN i
= 0; i
< length
; i
++) {
432 if (!aobj
->getProperty(cx
, INT_TO_JSID(jsint(i
)), &vp
[i
]))
443 * Set the value of the property at the given index to v assuming v is rooted.
446 SetArrayElement(JSContext
*cx
, JSObject
*obj
, jsdouble index
, const Value
&v
)
448 JS_ASSERT(index
>= 0);
450 if (obj
->isDenseArray()) {
451 /* Predicted/prefetched code should favor the remains-dense case. */
452 JSObject::EnsureDenseResult result
= JSObject::ED_SPARSE
;
454 if (index
> jsuint(-1))
456 jsuint idx
= jsuint(index
);
457 result
= obj
->ensureDenseArrayElements(cx
, idx
, 1);
458 if (result
!= JSObject::ED_OK
)
460 if (idx
>= obj
->getArrayLength())
461 obj
->setArrayLength(idx
+ 1);
462 obj
->setDenseArrayElement(idx
, v
);
466 if (result
== JSObject::ED_FAILED
)
468 JS_ASSERT(result
== JSObject::ED_SPARSE
);
469 if (!obj
->makeDenseArraySlow(cx
))
473 AutoIdRooter
idr(cx
);
475 if (!IndexToId(cx
, obj
, index
, NULL
, idr
.addr(), JS_TRUE
))
477 JS_ASSERT(!JSID_IS_VOID(idr
.id()));
480 return obj
->setProperty(cx
, idr
.id(), &tmp
, true);
485 js_EnsureDenseArrayCapacity(JSContext
*cx
, JSObject
*obj
, jsint i
)
488 Class
*origObjClasp
= obj
->clasp
;
490 jsuint u
= jsuint(i
);
491 JSBool ret
= (obj
->ensureDenseArrayElements(cx
, u
, 1) == JSObject::ED_OK
);
493 /* Partially check the CallInfo's storeAccSet is correct. */
494 JS_ASSERT(obj
->clasp
== origObjClasp
);
497 /* This function and its callees do not touch any object's .clasp field. */
498 JS_DEFINE_CALLINFO_3(extern, BOOL
, js_EnsureDenseArrayCapacity
, CONTEXT
, OBJECT
, INT32
,
499 0, nanojit::ACCSET_STORE_ANY
& ~tjit::ACCSET_OBJ_CLASP
)
503 * Delete the element |index| from |obj|. If |strict|, do a strict
504 * deletion: throw if the property is not configurable.
506 * - Return 1 if the deletion succeeds (that is, ES5's [[Delete]] would
509 * - Return 0 if the deletion fails because the property is not
510 * configurable (that is, [[Delete]] would return false). Note that if
511 * |strict| is true we will throw, not return zero.
513 * - Return -1 if an exception occurs (that is, [[Delete]] would throw).
516 DeleteArrayElement(JSContext
*cx
, JSObject
*obj
, jsdouble index
, bool strict
)
518 JS_ASSERT(index
>= 0);
519 if (obj
->isDenseArray()) {
520 if (index
<= jsuint(-1)) {
521 jsuint idx
= jsuint(index
);
522 if (idx
< obj
->getDenseArrayCapacity()) {
523 obj
->setDenseArrayElement(idx
, MagicValue(JS_ARRAY_HOLE
));
524 if (!js_SuppressDeletedIndexProperties(cx
, obj
, idx
, idx
+1))
531 AutoIdRooter
idr(cx
);
533 if (!IndexToId(cx
, obj
, index
, NULL
, idr
.addr()))
535 if (JSID_IS_VOID(idr
.id()))
539 if (!obj
->deleteProperty(cx
, idr
.id(), &v
, strict
))
541 return v
.isTrue() ? 1 : 0;
545 * When hole is true, delete the property at the given index. Otherwise set
546 * its value to v assuming v is rooted.
549 SetOrDeleteArrayElement(JSContext
*cx
, JSObject
*obj
, jsdouble index
,
550 JSBool hole
, const Value
&v
)
553 JS_ASSERT(v
.isUndefined());
554 return DeleteArrayElement(cx
, obj
, index
, true) >= 0;
556 return SetArrayElement(cx
, obj
, index
, v
);
560 js_SetLengthProperty(JSContext
*cx
, JSObject
*obj
, jsdouble length
)
566 id
= ATOM_TO_JSID(cx
->runtime
->atomState
.lengthAtom
);
567 /* We don't support read-only array length yet. */
568 return obj
->setProperty(cx
, id
, &v
, false);
572 js_HasLengthProperty(JSContext
*cx
, JSObject
*obj
, jsuint
*lengthp
)
574 JSErrorReporter older
= JS_SetErrorReporter(cx
, NULL
);
575 AutoValueRooter
tvr(cx
);
576 jsid id
= ATOM_TO_JSID(cx
->runtime
->atomState
.lengthAtom
);
577 JSBool ok
= obj
->getProperty(cx
, id
, tvr
.addr());
578 JS_SetErrorReporter(cx
, older
);
582 if (!ValueToLength(cx
, tvr
.addr(), lengthp
))
589 * Since SpiderMonkey supports cross-class prototype-based delegation, we have
590 * to be careful about the length getter and setter being called on an object
591 * not of Array class. For the getter, we search obj's prototype chain for the
592 * array that caused this getter to be invoked. In the setter case to overcome
593 * the JSPROP_SHARED attribute, we must define a shadowing length property.
596 array_length_getter(JSContext
*cx
, JSObject
*obj
, jsid id
, Value
*vp
)
599 if (obj
->isArray()) {
600 vp
->setNumber(obj
->getArrayLength());
603 } while ((obj
= obj
->getProto()) != NULL
);
608 array_length_setter(JSContext
*cx
, JSObject
*obj
, jsid id
, JSBool strict
, Value
*vp
)
610 jsuint newlen
, oldlen
, gap
, index
;
613 if (!obj
->isArray()) {
614 jsid lengthId
= ATOM_TO_JSID(cx
->runtime
->atomState
.lengthAtom
);
616 return obj
->defineProperty(cx
, lengthId
, *vp
, NULL
, NULL
, JSPROP_ENUMERATE
);
619 if (!ValueToLength(cx
, vp
, &newlen
))
622 oldlen
= obj
->getArrayLength();
624 if (oldlen
== newlen
)
627 vp
->setNumber(newlen
);
628 if (oldlen
< newlen
) {
629 obj
->setArrayLength(newlen
);
633 if (obj
->isDenseArray()) {
635 * Don't reallocate if we're not actually shrinking our slots. If we do
636 * shrink slots here, ensureDenseArrayElements will fill all slots to the
637 * right of newlen with JS_ARRAY_HOLE. This permits us to disregard
638 * length when reading from arrays as long we are within the capacity.
640 jsuint oldcap
= obj
->getDenseArrayCapacity();
642 obj
->shrinkDenseArrayElements(cx
, newlen
);
643 obj
->setArrayLength(newlen
);
644 } else if (oldlen
- newlen
< (1 << 24)) {
647 if (!JS_CHECK_OPERATION_LIMIT(cx
)) {
648 obj
->setArrayLength(oldlen
+ 1);
651 int deletion
= DeleteArrayElement(cx
, obj
, oldlen
, strict
);
653 obj
->setArrayLength(oldlen
+ 1);
654 return deletion
>= 0;
656 } while (oldlen
!= newlen
);
657 obj
->setArrayLength(newlen
);
660 * We are going to remove a lot of indexes in a presumably sparse
661 * array. So instead of looping through indexes between newlen and
662 * oldlen, we iterate through all properties and remove those that
663 * correspond to indexes in the half-open range [newlen, oldlen). See
666 JSObject
*iter
= JS_NewPropertyIterator(cx
, obj
);
670 /* Protect iter against GC under JSObject::deleteProperty. */
671 AutoObjectRooter
tvr(cx
, iter
);
673 gap
= oldlen
- newlen
;
675 if (!JS_CHECK_OPERATION_LIMIT(cx
) || !JS_NextProperty(cx
, iter
, &id
))
677 if (JSID_IS_VOID(id
))
679 if (js_IdIsIndex(id
, &index
) && index
- newlen
< gap
&&
680 !obj
->deleteProperty(cx
, id
, &junk
, false)) {
684 obj
->setArrayLength(newlen
);
691 * We have only indexed properties up to capacity (excepting holes), plus the
692 * length property. For all else, we delegate to the prototype.
695 IsDenseArrayId(JSContext
*cx
, JSObject
*obj
, jsid id
)
697 JS_ASSERT(obj
->isDenseArray());
700 return JSID_IS_ATOM(id
, cx
->runtime
->atomState
.lengthAtom
) ||
701 (js_IdIsIndex(id
, &i
) &&
702 obj
->getArrayLength() != 0 &&
703 i
< obj
->getDenseArrayCapacity() &&
704 !obj
->getDenseArrayElement(i
).isMagic(JS_ARRAY_HOLE
));
708 array_lookupProperty(JSContext
*cx
, JSObject
*obj
, jsid id
, JSObject
**objp
,
711 if (!obj
->isDenseArray())
712 return js_LookupProperty(cx
, obj
, id
, objp
, propp
);
714 if (IsDenseArrayId(cx
, obj
, id
)) {
715 *propp
= (JSProperty
*) 1; /* non-null to indicate found */
720 JSObject
*proto
= obj
->getProto();
726 return proto
->lookupProperty(cx
, id
, objp
, propp
);
730 js_GetDenseArrayElementValue(JSContext
*cx
, JSObject
*obj
, jsid id
, Value
*vp
)
732 JS_ASSERT(obj
->isDenseArray());
735 if (!js_IdIsIndex(id
, &i
)) {
736 JS_ASSERT(JSID_IS_ATOM(id
, cx
->runtime
->atomState
.lengthAtom
));
737 vp
->setNumber(obj
->getArrayLength());
740 *vp
= obj
->getDenseArrayElement(i
);
745 array_getProperty(JSContext
*cx
, JSObject
*obj
, JSObject
*receiver
, jsid id
, Value
*vp
)
749 if (JSID_IS_ATOM(id
, cx
->runtime
->atomState
.lengthAtom
)) {
750 vp
->setNumber(obj
->getArrayLength());
754 if (JSID_IS_ATOM(id
, cx
->runtime
->atomState
.protoAtom
)) {
755 vp
->setObjectOrNull(obj
->getProto());
759 if (!obj
->isDenseArray())
760 return js_GetProperty(cx
, obj
, id
, vp
);
762 if (!js_IdIsIndex(id
, &i
) || i
>= obj
->getDenseArrayCapacity() ||
763 obj
->getDenseArrayElement(i
).isMagic(JS_ARRAY_HOLE
)) {
768 JSObject
*proto
= obj
->getProto();
775 if (js_LookupPropertyWithFlags(cx
, proto
, id
, cx
->resolveFlags
,
779 if (prop
&& obj2
->isNative()) {
780 shape
= (const Shape
*) prop
;
781 if (!js_NativeGet(cx
, obj
, obj2
, shape
, JSGET_METHOD_BARRIER
, vp
))
787 *vp
= obj
->getDenseArrayElement(i
);
792 slowarray_addProperty(JSContext
*cx
, JSObject
*obj
, jsid id
, Value
*vp
)
794 jsuint index
, length
;
796 if (!js_IdIsIndex(id
, &index
))
798 length
= obj
->getArrayLength();
800 obj
->setArrayLength(index
+ 1);
805 array_typeOf(JSContext
*cx
, JSObject
*obj
)
807 return JSTYPE_OBJECT
;
811 array_setProperty(JSContext
*cx
, JSObject
*obj
, jsid id
, Value
*vp
, JSBool strict
)
815 if (JSID_IS_ATOM(id
, cx
->runtime
->atomState
.lengthAtom
))
816 return array_length_setter(cx
, obj
, id
, strict
, vp
);
818 if (!obj
->isDenseArray())
819 return js_SetProperty(cx
, obj
, id
, vp
, strict
);
822 if (!js_IdIsIndex(id
, &i
))
824 if (js_PrototypeHasIndexedProperties(cx
, obj
))
827 JSObject::EnsureDenseResult result
= obj
->ensureDenseArrayElements(cx
, i
, 1);
828 if (result
!= JSObject::ED_OK
) {
829 if (result
== JSObject::ED_FAILED
)
831 JS_ASSERT(result
== JSObject::ED_SPARSE
);
835 if (i
>= obj
->getArrayLength())
836 obj
->setArrayLength(i
+ 1);
837 obj
->setDenseArrayElement(i
, *vp
);
841 if (!obj
->makeDenseArraySlow(cx
))
843 return js_SetProperty(cx
, obj
, id
, vp
, strict
);
847 js_PrototypeHasIndexedProperties(JSContext
*cx
, JSObject
*obj
)
850 * Walk up the prototype chain and see if this indexed element already
851 * exists. If we hit the end of the prototype chain, it's safe to set the
852 * element on the original object.
854 while ((obj
= obj
->getProto()) != NULL
) {
856 * If the prototype is a non-native object (possibly a dense array), or
857 * a native object (possibly a slow array) that has indexed properties,
860 if (!obj
->isNative())
862 if (obj
->isIndexed())
869 array_defineProperty(JSContext
*cx
, JSObject
*obj
, jsid id
, const Value
*value
,
870 PropertyOp getter
, StrictPropertyOp setter
, uintN attrs
)
872 if (JSID_IS_ATOM(id
, cx
->runtime
->atomState
.lengthAtom
))
875 if (!obj
->isDenseArray())
876 return js_DefineProperty(cx
, obj
, id
, value
, getter
, setter
, attrs
);
879 uint32 i
= 0; // init to shut GCC up
880 bool isIndex
= js_IdIsIndex(id
, &i
);
881 if (!isIndex
|| attrs
!= JSPROP_ENUMERATE
)
884 JSObject::EnsureDenseResult result
= obj
->ensureDenseArrayElements(cx
, i
, 1);
885 if (result
!= JSObject::ED_OK
) {
886 if (result
== JSObject::ED_FAILED
)
888 JS_ASSERT(result
== JSObject::ED_SPARSE
);
892 if (i
>= obj
->getArrayLength())
893 obj
->setArrayLength(i
+ 1);
894 obj
->setDenseArrayElement(i
, *value
);
898 if (!obj
->makeDenseArraySlow(cx
))
900 return js_DefineProperty(cx
, obj
, id
, value
, getter
, setter
, attrs
);
904 array_getAttributes(JSContext
*cx
, JSObject
*obj
, jsid id
, uintN
*attrsp
)
906 *attrsp
= JSID_IS_ATOM(id
, cx
->runtime
->atomState
.lengthAtom
)
907 ? JSPROP_PERMANENT
: JSPROP_ENUMERATE
;
912 array_setAttributes(JSContext
*cx
, JSObject
*obj
, jsid id
, uintN
*attrsp
)
914 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, NULL
,
915 JSMSG_CANT_SET_ARRAY_ATTRS
);
920 array_deleteProperty(JSContext
*cx
, JSObject
*obj
, jsid id
, Value
*rval
, JSBool strict
)
924 if (!obj
->isDenseArray())
925 return js_DeleteProperty(cx
, obj
, id
, rval
, strict
);
927 if (JSID_IS_ATOM(id
, cx
->runtime
->atomState
.lengthAtom
)) {
928 rval
->setBoolean(false);
932 if (js_IdIsIndex(id
, &i
) && i
< obj
->getDenseArrayCapacity())
933 obj
->setDenseArrayElement(i
, MagicValue(JS_ARRAY_HOLE
));
935 if (!js_SuppressDeletedProperty(cx
, obj
, id
))
938 rval
->setBoolean(true);
943 array_trace(JSTracer
*trc
, JSObject
*obj
)
945 JS_ASSERT(obj
->isDenseArray());
947 uint32 capacity
= obj
->getDenseArrayCapacity();
948 for (uint32 i
= 0; i
< capacity
; i
++)
949 MarkValue(trc
, obj
->getDenseArrayElement(i
), "dense_array_elems");
953 array_fix(JSContext
*cx
, JSObject
*obj
, bool *success
, AutoIdVector
*props
)
955 JS_ASSERT(obj
->isDenseArray());
958 * We must slowify dense arrays; otherwise, we'd need to detect assignments to holes,
959 * since that is effectively adding a new property to the array.
961 if (!obj
->makeDenseArraySlow(cx
) ||
962 !GetPropertyNames(cx
, obj
, JSITER_HIDDEN
| JSITER_OWNONLY
, props
))
969 Class js_ArrayClass
= {
972 JSCLASS_HAS_PRIVATE
|
973 JSCLASS_HAS_CACHED_PROTO(JSProto_Array
),
974 PropertyStub
, /* addProperty */
975 PropertyStub
, /* delProperty */
976 PropertyStub
, /* getProperty */
977 StrictPropertyStub
, /* setProperty */
982 NULL
, /* reserved0 */
983 NULL
, /* checkAccess */
985 NULL
, /* construct */
986 NULL
, /* xdrObject */
987 NULL
, /* hasInstance */
991 array_lookupProperty
,
992 array_defineProperty
,
997 array_deleteProperty
,
998 NULL
, /* enumerate */
1002 NULL
, /* thisObject */
1007 Class js_SlowArrayClass
= {
1009 JSCLASS_HAS_PRIVATE
|
1010 JSCLASS_HAS_CACHED_PROTO(JSProto_Array
),
1011 slowarray_addProperty
,
1012 PropertyStub
, /* delProperty */
1013 PropertyStub
, /* getProperty */
1014 StrictPropertyStub
, /* setProperty */
1021 AddLengthProperty(JSContext
*cx
, JSObject
*obj
)
1023 const jsid lengthId
= ATOM_TO_JSID(cx
->runtime
->atomState
.lengthAtom
);
1024 JS_ASSERT(!obj
->nativeLookup(lengthId
));
1026 return obj
->addProperty(cx
, lengthId
, array_length_getter
, array_length_setter
,
1027 SHAPE_INVALID_SLOT
, JSPROP_PERMANENT
| JSPROP_SHARED
, 0, 0);
1031 * Convert an array object from fast-and-dense to slow-and-flexible.
1034 JSObject::makeDenseArraySlow(JSContext
*cx
)
1036 JS_ASSERT(isDenseArray());
1039 * Save old map now, before calling InitScopeForObject. We'll have to undo
1040 * on error. This is gross, but a better way is not obvious.
1042 JSObjectMap
*oldMap
= map
;
1044 /* Create a native scope. */
1045 JSObject
*arrayProto
= getProto();
1046 js::gc::FinalizeKind kind
= js::gc::FinalizeKind(arena()->header()->thingKind
);
1047 if (!InitScopeForObject(cx
, this, &js_SlowArrayClass
, arrayProto
, kind
))
1050 uint32 capacity
= getDenseArrayCapacity();
1053 * Begin with the length property to share more of the property tree.
1054 * The getter/setter here will directly access the object's private value.
1056 if (!AddLengthProperty(cx
, this)) {
1062 * Create new properties pointing to existing elements. Pack the array to
1063 * remove holes, so that shapes use successive slots (as for other objects).
1066 for (uint32 i
= 0; i
< capacity
; i
++) {
1068 if (!ValueToId(cx
, Int32Value(i
), &id
)) {
1073 if (getDenseArrayElement(i
).isMagic(JS_ARRAY_HOLE
))
1076 setDenseArrayElement(next
, getDenseArrayElement(i
));
1078 if (!addDataProperty(cx
, id
, next
, JSPROP_ENUMERATE
)) {
1087 * Dense arrays with different numbers of slots but the same number of fixed
1088 * slots and the same non-hole indexes must use their fixed slots consistently.
1090 if (hasSlotsArray() && next
<= numFixedSlots())
1091 revertToFixedSlots(cx
);
1093 ClearValueRange(slots
+ next
, this->capacity
- next
, false);
1096 * Finally, update class. If |this| is Array.prototype, then js_InitClass
1097 * will create an emptyShape whose class is &js_SlowArrayClass, to ensure
1098 * that delegating instances can share shapes in the tree rooted at the
1099 * proto's empty shape.
1101 clasp
= &js_SlowArrayClass
;
1105 /* Transfer ownership of buffer to returned string. */
1106 static inline JSBool
1107 BufferToString(JSContext
*cx
, StringBuffer
&sb
, Value
*rval
)
1109 JSString
*str
= sb
.finishString();
1112 rval
->setString(str
);
1118 array_toSource(JSContext
*cx
, uintN argc
, Value
*vp
)
1120 JS_CHECK_RECURSION(cx
, return false);
1122 JSObject
*obj
= ToObject(cx
, &vp
[1]);
1125 if (!obj
->isSlowArray() && !InstanceOf(cx
, obj
, &js_ArrayClass
, vp
+ 2))
1128 /* Find joins or cycles in the reachable object graph. */
1130 JSHashEntry
*he
= js_EnterSharpObject(cx
, obj
, NULL
, &sharpchars
);
1133 bool initiallySharp
= IS_SHARP(he
);
1135 /* After this point, all paths exit through the 'out' label. */
1136 MUST_FLOW_THROUGH("out");
1140 * This object will take responsibility for the jschar buffer until the
1141 * buffer is transferred to the returned JSString.
1143 StringBuffer
sb(cx
);
1145 /* Cycles/joins are indicated by sharp objects. */
1146 #if JS_HAS_SHARP_VARS
1148 JS_ASSERT(sharpchars
!= 0);
1149 sb
.replaceRawBuffer(sharpchars
, js_strlen(sharpchars
));
1151 } else if (sharpchars
) {
1153 sb
.replaceRawBuffer(sharpchars
, js_strlen(sharpchars
));
1157 if (!sb
.append("[]"))
1159 cx
->free(sharpchars
);
1164 if (!sb
.append('['))
1168 if (!js_GetLengthProperty(cx
, obj
, &length
))
1171 for (jsuint index
= 0; index
< length
; index
++) {
1172 /* Use vp to locally root each element value. */
1174 if (!JS_CHECK_OPERATION_LIMIT(cx
) ||
1175 !GetElement(cx
, obj
, index
, &hole
, vp
)) {
1179 /* Get element's character string. */
1182 str
= cx
->runtime
->emptyString
;
1184 str
= js_ValueToSource(cx
, *vp
);
1190 const jschar
*chars
= str
->getChars(cx
);
1194 /* Append element to buffer. */
1195 if (!sb
.append(chars
, chars
+ str
->length()))
1197 if (index
+ 1 != length
) {
1198 if (!sb
.append(", "))
1201 if (!sb
.append(','))
1206 /* Finalize the buffer. */
1207 if (!sb
.append(']'))
1211 if (!BufferToString(cx
, sb
, vp
))
1217 if (!initiallySharp
)
1218 js_LeaveSharpObject(cx
, NULL
);
1224 array_toString_sub(JSContext
*cx
, JSObject
*obj
, JSBool locale
,
1225 JSString
*sepstr
, Value
*rval
)
1227 JS_CHECK_RECURSION(cx
, return false);
1229 /* Get characters to use for the separator. */
1230 static const jschar comma
= ',';
1234 seplen
= sepstr
->length();
1235 sep
= sepstr
->getChars(cx
);
1244 * Use HashTable entry as the cycle indicator. On first visit, create the
1245 * entry, and, when leaving, remove the entry.
1247 BusyArraysMap::AddPtr hashp
= cx
->busyArrays
.lookupForAdd(obj
);
1250 /* Not in hash table, so not a cycle. */
1251 if (!cx
->busyArrays
.add(hashp
, obj
))
1253 genBefore
= cx
->busyArrays
.generation();
1255 /* Cycle, so return empty string. */
1256 rval
->setString(ATOM_TO_STRING(cx
->runtime
->atomState
.emptyAtom
));
1260 AutoObjectRooter
tvr(cx
, obj
);
1262 /* After this point, all paths exit through the 'out' label. */
1263 MUST_FLOW_THROUGH("out");
1267 * This object will take responsibility for the jschar buffer until the
1268 * buffer is transferred to the returned JSString.
1270 StringBuffer
sb(cx
);
1273 if (!js_GetLengthProperty(cx
, obj
, &length
))
1276 for (jsuint index
= 0; index
< length
; index
++) {
1277 /* Use rval to locally root each element value. */
1279 if (!JS_CHECK_OPERATION_LIMIT(cx
) ||
1280 !GetElement(cx
, obj
, index
, &hole
, rval
)) {
1284 /* Get element's character string. */
1285 if (!(hole
|| rval
->isNullOrUndefined())) {
1287 /* Work on obj.toLocalString() instead. */
1290 if (!js_ValueToObjectOrNull(cx
, *rval
, &robj
))
1292 rval
->setObjectOrNull(robj
);
1293 JSAtom
*atom
= cx
->runtime
->atomState
.toLocaleStringAtom
;
1294 if (!js_TryMethod(cx
, robj
, atom
, 0, NULL
, rval
))
1298 if (!ValueToStringBuffer(cx
, *rval
, sb
))
1302 /* Append the separator. */
1303 if (index
+ 1 != length
) {
1304 if (!sb
.append(sep
, seplen
))
1309 /* Finalize the buffer. */
1310 if (!BufferToString(cx
, sb
, rval
))
1316 if (genBefore
== cx
->busyArrays
.generation())
1317 cx
->busyArrays
.remove(hashp
);
1319 cx
->busyArrays
.remove(obj
);
1323 /* ES5 15.4.4.2. NB: The algorithm here differs from the one in ES3. */
1325 array_toString(JSContext
*cx
, uintN argc
, Value
*vp
)
1327 JSObject
*obj
= ToObject(cx
, &vp
[1]);
1331 Value
&join
= vp
[0];
1332 if (!obj
->getProperty(cx
, ATOM_TO_JSID(cx
->runtime
->atomState
.joinAtom
), &join
))
1335 if (!js_IsCallable(join
)) {
1336 JSString
*str
= obj_toStringHelper(cx
, obj
);
1344 InvokeArgsGuard args
;
1345 if (!cx
->stack().pushInvokeArgs(cx
, 0, &args
))
1348 args
.callee() = join
;
1349 args
.thisv().setObject(*obj
);
1352 if (!Invoke(cx
, args
, 0))
1359 array_toLocaleString(JSContext
*cx
, uintN argc
, Value
*vp
)
1361 JSObject
*obj
= ToObject(cx
, &vp
[1]);
1366 * Passing comma here as the separator. Need a way to get a
1367 * locale-specific version.
1369 return array_toString_sub(cx
, obj
, JS_TRUE
, NULL
, vp
);
1373 InitArrayElements(JSContext
*cx
, JSObject
*obj
, jsuint start
, jsuint count
, Value
*vector
)
1375 JS_ASSERT(count
< MAXINDEX
);
1378 * Optimize for dense arrays so long as adding the given set of elements
1379 * wouldn't otherwise make the array slow.
1382 if (!obj
->isDenseArray())
1384 if (js_PrototypeHasIndexedProperties(cx
, obj
))
1387 JSObject::EnsureDenseResult result
= obj
->ensureDenseArrayElements(cx
, start
, count
);
1388 if (result
!= JSObject::ED_OK
) {
1389 if (result
== JSObject::ED_FAILED
)
1391 JS_ASSERT(result
== JSObject::ED_SPARSE
);
1394 jsuint newlen
= start
+ count
;
1395 if (newlen
> obj
->getArrayLength())
1396 obj
->setArrayLength(newlen
);
1398 JS_ASSERT(count
< uint32(-1) / sizeof(Value
));
1399 memcpy(obj
->getDenseArrayElements() + start
, vector
, sizeof(jsval
) * count
);
1400 JS_ASSERT_IF(count
!= 0, !obj
->getDenseArrayElement(newlen
- 1).isMagic(JS_ARRAY_HOLE
));
1404 Value
* end
= vector
+ count
;
1405 while (vector
!= end
&& start
< MAXINDEX
) {
1406 if (!JS_CHECK_OPERATION_LIMIT(cx
) ||
1407 !SetArrayElement(cx
, obj
, start
++, *vector
++)) {
1415 /* Finish out any remaining elements past the max array index. */
1416 if (obj
->isDenseArray() && !ENSURE_SLOW_ARRAY(cx
, obj
))
1419 JS_ASSERT(start
== MAXINDEX
);
1420 AutoValueRooter
tvr(cx
);
1421 AutoIdRooter
idr(cx
);
1422 Value idval
= DoubleValue(MAXINDEX
);
1424 *tvr
.addr() = *vector
++;
1425 if (!js_ValueToStringId(cx
, idval
, idr
.addr()) ||
1426 !obj
->setProperty(cx
, idr
.id(), tvr
.addr(), true)) {
1429 idval
.getDoubleRef() += 1;
1430 } while (vector
!= end
);
1436 InitArrayObject(JSContext
*cx
, JSObject
*obj
, jsuint length
, const Value
*vector
)
1438 JS_ASSERT(obj
->isArray());
1440 JS_ASSERT(obj
->isDenseArray());
1441 obj
->setArrayLength(length
);
1442 if (!vector
|| !length
)
1445 /* Avoid ensureDenseArrayElements to skip sparse array checks there. */
1446 if (!obj
->ensureSlots(cx
, length
))
1448 memcpy(obj
->getDenseArrayElements(), vector
, length
* sizeof(Value
));
1453 * Perl-inspired join, reverse, and sort.
1456 array_join(JSContext
*cx
, uintN argc
, Value
*vp
)
1459 if (argc
== 0 || vp
[2].isUndefined()) {
1462 str
= js_ValueToString(cx
, vp
[2]);
1465 vp
[2].setString(str
);
1467 JSObject
*obj
= ToObject(cx
, &vp
[1]);
1470 return array_toString_sub(cx
, obj
, JS_FALSE
, str
, vp
);
1474 array_reverse(JSContext
*cx
, uintN argc
, Value
*vp
)
1476 JSObject
*obj
= ToObject(cx
, &vp
[1]);
1481 if (!js_GetLengthProperty(cx
, obj
, &len
))
1483 vp
->setObject(*obj
);
1486 if (!obj
->isDenseArray())
1488 if (js_PrototypeHasIndexedProperties(cx
, obj
))
1491 /* An empty array or an array with no elements is already reversed. */
1492 if (len
== 0 || obj
->getDenseArrayCapacity() == 0)
1496 * It's actually surprisingly complicated to reverse an array due to the
1497 * orthogonality of array length and array capacity while handling
1498 * leading and trailing holes correctly. Reversing seems less likely to
1499 * be a common operation than other array mass-mutation methods, so for
1500 * now just take a probably-small memory hit (in the absence of too many
1501 * holes in the array at its start) and ensure that the capacity is
1502 * sufficient to hold all the elements in the array if it were full.
1504 JSObject::EnsureDenseResult result
= obj
->ensureDenseArrayElements(cx
, len
, 0);
1505 if (result
!= JSObject::ED_OK
) {
1506 if (result
== JSObject::ED_FAILED
)
1508 JS_ASSERT(result
== JSObject::ED_SPARSE
);
1512 uint32 lo
= 0, hi
= len
- 1;
1513 for (; lo
< hi
; lo
++, hi
--) {
1514 Value origlo
= obj
->getDenseArrayElement(lo
);
1515 Value orighi
= obj
->getDenseArrayElement(hi
);
1516 obj
->setDenseArrayElement(lo
, orighi
);
1517 if (orighi
.isMagic(JS_ARRAY_HOLE
) &&
1518 !js_SuppressDeletedProperty(cx
, obj
, INT_TO_JSID(lo
))) {
1521 obj
->setDenseArrayElement(hi
, origlo
);
1522 if (origlo
.isMagic(JS_ARRAY_HOLE
) &&
1523 !js_SuppressDeletedProperty(cx
, obj
, INT_TO_JSID(hi
))) {
1529 * Per ECMA-262, don't update the length of the array, even if the new
1530 * array has trailing holes (and thus the original array began with
1536 AutoValueRooter
tvr(cx
);
1537 for (jsuint i
= 0, half
= len
/ 2; i
< half
; i
++) {
1539 if (!JS_CHECK_OPERATION_LIMIT(cx
) ||
1540 !GetElement(cx
, obj
, i
, &hole
, tvr
.addr()) ||
1541 !GetElement(cx
, obj
, len
- i
- 1, &hole2
, vp
) ||
1542 !SetOrDeleteArrayElement(cx
, obj
, len
- i
- 1, hole
, tvr
.value()) ||
1543 !SetOrDeleteArrayElement(cx
, obj
, i
, hole2
, *vp
)) {
1547 vp
->setObject(*obj
);
1551 typedef struct MSortArgs
{
1558 /* Helper function for js_MergeSort. */
1560 MergeArrays(MSortArgs
*msa
, void *src
, void *dest
, size_t run1
, size_t run2
)
1562 void *arg
, *a
, *b
, *c
;
1563 size_t elsize
, runtotal
;
1568 runtotal
= run1
+ run2
;
1570 elsize
= msa
->elsize
;
1573 isValue
= msa
->isValue
;
1575 #define CALL_CMP(a, b) \
1576 if (!cmp(arg, (a), (b), &cmp_result)) return JS_FALSE;
1578 /* Copy runs already in sorted order. */
1579 b
= (char *)src
+ run1
* elsize
;
1580 a
= (char *)b
- elsize
;
1582 if (cmp_result
<= 0) {
1583 memcpy(dest
, src
, runtotal
* elsize
);
1587 #define COPY_ONE(p,q,n) \
1588 (isValue ? (void)(*(Value*)p = *(Value*)q) : (void)memcpy(p, q, n))
1592 for (; runtotal
!= 0; runtotal
--) {
1593 JSBool from_a
= run2
== 0;
1594 if (!from_a
&& run1
!= 0) {
1596 from_a
= cmp_result
<= 0;
1600 COPY_ONE(c
, a
, elsize
);
1602 a
= (char *)a
+ elsize
;
1604 COPY_ONE(c
, b
, elsize
);
1606 b
= (char *)b
+ elsize
;
1608 c
= (char *)c
+ elsize
;
1617 * This sort is stable, i.e. sequence of equal elements is preserved.
1618 * See also bug #224128.
1621 js_MergeSort(void *src
, size_t nel
, size_t elsize
,
1622 JSComparator cmp
, void *arg
, void *tmp
,
1623 JSMergeSortElemType elemType
)
1625 void *swap
, *vec1
, *vec2
;
1627 size_t i
, j
, lo
, hi
, run
;
1630 JS_ASSERT_IF(JS_SORTING_VALUES
, elsize
== sizeof(Value
));
1631 bool isValue
= elemType
== JS_SORTING_VALUES
;
1633 /* Avoid memcpy overhead for word-sized and word-aligned elements. */
1634 #define COPY_ONE(p,q,n) \
1635 (isValue ? (void)(*(Value*)p = *(Value*)q) : (void)memcpy(p, q, n))
1636 #define CALL_CMP(a, b) \
1637 if (!cmp(arg, (a), (b), &cmp_result)) return JS_FALSE;
1638 #define INS_SORT_INT 4
1641 * Apply insertion sort to small chunks to reduce the number of merge
1644 for (lo
= 0; lo
< nel
; lo
+= INS_SORT_INT
) {
1645 hi
= lo
+ INS_SORT_INT
;
1648 for (i
= lo
+ 1; i
< hi
; i
++) {
1649 vec1
= (char *)src
+ i
* elsize
;
1650 vec2
= (char *)vec1
- elsize
;
1651 for (j
= i
; j
> lo
; j
--) {
1652 CALL_CMP(vec2
, vec1
);
1653 /* "<=" instead of "<" insures the sort is stable */
1654 if (cmp_result
<= 0) {
1658 /* Swap elements, using "tmp" as tmp storage */
1659 COPY_ONE(tmp
, vec2
, elsize
);
1660 COPY_ONE(vec2
, vec1
, elsize
);
1661 COPY_ONE(vec1
, tmp
, elsize
);
1663 vec2
= (char *)vec1
- elsize
;
1670 msa
.elsize
= elsize
;
1673 msa
.isValue
= isValue
;
1677 for (run
= INS_SORT_INT
; run
< nel
; run
*= 2) {
1678 for (lo
= 0; lo
< nel
; lo
+= 2 * run
) {
1681 memcpy((char *)vec2
+ lo
* elsize
, (char *)vec1
+ lo
* elsize
,
1682 (nel
- lo
) * elsize
);
1685 if (!MergeArrays(&msa
, (char *)vec1
+ lo
* elsize
,
1686 (char *)vec2
+ lo
* elsize
, run
,
1687 hi
+ run
> nel
? nel
- hi
: run
)) {
1696 memcpy(src
, tmp
, nel
* elsize
);
1704 InvokeSessionGuard session
;
1706 CompareArgs(JSContext
*cx
)
1711 static JS_REQUIRES_STACK JSBool
1712 sort_compare(void *arg
, const void *a
, const void *b
, int *result
)
1714 const Value
*av
= (const Value
*)a
, *bv
= (const Value
*)b
;
1715 CompareArgs
*ca
= (CompareArgs
*) arg
;
1716 JSContext
*cx
= ca
->context
;
1719 * array_sort deals with holes and undefs on its own and they should not
1722 JS_ASSERT(!av
->isMagic() && !av
->isUndefined());
1723 JS_ASSERT(!av
->isMagic() && !bv
->isUndefined());
1725 if (!JS_CHECK_OPERATION_LIMIT(cx
))
1728 InvokeSessionGuard
&session
= ca
->session
;
1732 if (!session
.invoke(cx
))
1736 if (!ValueToNumber(cx
, session
.rval(), &cmp
))
1739 /* Clamp cmp to -1, 0, 1. */
1741 if (!JSDOUBLE_IS_NaN(cmp
) && cmp
!= 0)
1742 *result
= cmp
> 0 ? 1 : -1;
1745 * XXX else report some kind of error here? ECMA talks about 'consistent
1746 * compare functions' that don't return NaN, but is silent about what the
1747 * result should be. So we currently ignore it.
1753 typedef JSBool (JS_REQUIRES_STACK
*JSRedComparator
)(void*, const void*,
1754 const void*, int *);
1756 static inline JS_IGNORE_STACK JSComparator
1757 comparator_stack_cast(JSRedComparator func
)
1763 sort_compare_strings(void *arg
, const void *a
, const void *b
, int *result
)
1765 JSContext
*cx
= (JSContext
*)arg
;
1766 JSString
*astr
= ((const Value
*)a
)->toString();
1767 JSString
*bstr
= ((const Value
*)b
)->toString();
1768 return JS_CHECK_OPERATION_LIMIT(cx
) && CompareStrings(cx
, astr
, bstr
, result
);
1772 js::array_sort(JSContext
*cx
, uintN argc
, Value
*vp
)
1774 jsuint len
, newlen
, i
, undefs
;
1778 Value
*argv
= JS_ARGV(cx
, vp
);
1780 if (argc
> 0 && !argv
[0].isUndefined()) {
1781 if (argv
[0].isPrimitive()) {
1782 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, NULL
, JSMSG_BAD_SORT_ARG
);
1785 fval
= argv
[0]; /* non-default compare function */
1790 JSObject
*obj
= ToObject(cx
, &vp
[1]);
1793 if (!js_GetLengthProperty(cx
, obj
, &len
))
1796 vp
->setObject(*obj
);
1801 * We need a temporary array of 2 * len Value to hold the array elements
1802 * and the scratch space for merge sort. Check that its size does not
1803 * overflow size_t, which would allow for indexing beyond the end of the
1806 #if JS_BITS_PER_WORD == 32
1807 if (size_t(len
) > size_t(-1) / (2 * sizeof(Value
))) {
1808 js_ReportAllocationOverflow(cx
);
1814 * Initialize vec as a root. We will clear elements of vec one by
1815 * one while increasing the rooted amount of vec when we know that the
1816 * property at the corresponding index exists and its value must be rooted.
1818 * In this way when sorting a huge mostly sparse array we will not
1819 * access the tail of vec corresponding to properties that do not
1820 * exist, allowing OS to avoiding committing RAM. See bug 330812.
1823 Value
*vec
= (Value
*) cx
->malloc(2 * size_t(len
) * sizeof(Value
));
1827 DEFINE_LOCAL_CLASS_OF_STATIC_FUNCTION(AutoFreeVector
) {
1828 JSContext
*const cx
;
1831 AutoFreeVector(JSContext
*cx
, Value
*&vec
) : cx(cx
), vec(vec
) { }
1837 AutoArrayRooter
tvr(cx
, 0, vec
);
1840 * By ECMA 262, 15.4.4.11, a property that does not exist (which we
1841 * call a "hole") is always greater than an existing property with
1842 * value undefined and that is always greater than any other property.
1843 * Thus to sort holes and undefs we simply count them, sort the rest
1844 * of elements, append undefs after them and then make holes after
1849 bool allStrings
= true;
1850 for (i
= 0; i
< len
; i
++) {
1851 if (!JS_CHECK_OPERATION_LIMIT(cx
))
1854 /* Clear vec[newlen] before including it in the rooted set. */
1856 vec
[newlen
].setNull();
1857 tvr
.changeLength(newlen
+ 1);
1858 if (!GetElement(cx
, obj
, i
, &hole
, &vec
[newlen
]))
1864 if (vec
[newlen
].isUndefined()) {
1869 allStrings
= allStrings
&& vec
[newlen
].isString();
1875 vp
->setObject(*obj
);
1876 return true; /* The array has only holes and undefs. */
1880 * The first newlen elements of vec are copied from the array object
1881 * (above). The remaining newlen positions are used as GC-rooted scratch
1882 * space for mergesort. We must clear the space before including it to
1883 * the root set covered by tvr.count.
1885 Value
*mergesort_tmp
= vec
+ newlen
;
1886 MakeRangeGCSafe(mergesort_tmp
, newlen
);
1887 tvr
.changeLength(newlen
* 2);
1889 /* Here len == 2 * (newlen + undefs + number_of_holes). */
1890 if (fval
.isNull()) {
1892 * Sort using the default comparator converting all elements to
1896 elemsize
= sizeof(Value
);
1899 * To avoid string conversion on each compare we do it only once
1900 * prior to sorting. But we also need the space for the original
1901 * values to recover the sorting result. To reuse
1902 * sort_compare_strings we move the original values to the odd
1903 * indexes in vec, put the string conversion results in the even
1904 * indexes and pass 2 * sizeof(Value) as an element size to the
1905 * sorting function. In this way sort_compare_strings will only
1906 * see the string values when it casts the compare arguments as
1907 * pointers to Value.
1909 * This requires doubling the temporary storage including the
1910 * scratch space for the merge sort. Since vec already contains
1911 * the rooted scratch space for newlen elements at the tail, we
1912 * can use it to rearrange and convert to strings first and try
1913 * realloc only when we know that we successfully converted all
1916 #if JS_BITS_PER_WORD == 32
1917 if (size_t(newlen
) > size_t(-1) / (4 * sizeof(Value
))) {
1918 js_ReportAllocationOverflow(cx
);
1924 * Rearrange and string-convert the elements of the vector from
1925 * the tail here and, after sorting, move the results back
1926 * starting from the start to prevent overwrite the existing
1932 if (!JS_CHECK_OPERATION_LIMIT(cx
))
1934 const Value
&v
= vec
[i
];
1935 str
= js_ValueToString(cx
, v
);
1938 // Copying v must come first, because the following line overwrites v
1941 vec
[2 * i
].setString(str
);
1944 JS_ASSERT(tvr
.array
== vec
);
1945 vec
= (Value
*) cx
->realloc(vec
, 4 * size_t(newlen
) * sizeof(Value
));
1947 vec
= tvr
.array
; /* N.B. AutoFreeVector */
1950 mergesort_tmp
= vec
+ 2 * newlen
;
1951 MakeRangeGCSafe(mergesort_tmp
, 2 * newlen
);
1952 tvr
.changeArray(vec
, newlen
* 4);
1953 elemsize
= 2 * sizeof(Value
);
1955 if (!js_MergeSort(vec
, size_t(newlen
), elemsize
,
1956 sort_compare_strings
, cx
, mergesort_tmp
,
1957 JS_SORTING_GENERIC
)) {
1962 * We want to make the following loop fast and to unroot the
1963 * cached results of toString invocations before the operation
1964 * callback has a chance to run the GC. For this reason we do
1965 * not call JS_CHECK_OPERATION_LIMIT in the loop.
1969 vec
[i
] = vec
[2 * i
+ 1];
1970 } while (++i
!= newlen
);
1974 if (!ca
.session
.start(cx
, fval
, UndefinedValue(), 2))
1977 if (!js_MergeSort(vec
, size_t(newlen
), sizeof(Value
),
1978 comparator_stack_cast(sort_compare
),
1980 JS_SORTING_VALUES
)) {
1986 * We no longer need to root the scratch space for the merge sort, so
1987 * unroot it now to make the job of a potential GC under
1988 * InitArrayElements easier.
1990 tvr
.changeLength(newlen
);
1991 if (!InitArrayElements(cx
, obj
, 0, newlen
, vec
))
1995 /* Set undefs that sorted after the rest of elements. */
1996 while (undefs
!= 0) {
1998 if (!JS_CHECK_OPERATION_LIMIT(cx
) ||
1999 !SetArrayElement(cx
, obj
, newlen
++, UndefinedValue())) {
2004 /* Re-create any holes that sorted to the end of the array. */
2005 while (len
> newlen
) {
2006 if (!JS_CHECK_OPERATION_LIMIT(cx
) || DeleteArrayElement(cx
, obj
, --len
, true) < 0)
2009 vp
->setObject(*obj
);
2014 * Perl-inspired push, pop, shift, unshift, and splice methods.
2017 array_push_slowly(JSContext
*cx
, JSObject
*obj
, uintN argc
, Value
*argv
, Value
*rval
)
2021 if (!js_GetLengthProperty(cx
, obj
, &length
))
2023 if (!InitArrayElements(cx
, obj
, length
, argc
, argv
))
2026 /* Per ECMA-262, return the new array length. */
2027 jsdouble newlength
= length
+ jsdouble(argc
);
2028 rval
->setNumber(newlength
);
2029 return js_SetLengthProperty(cx
, obj
, newlength
);
2033 array_push1_dense(JSContext
* cx
, JSObject
* obj
, const Value
&v
, Value
*rval
)
2035 uint32 length
= obj
->getArrayLength();
2037 JSObject::EnsureDenseResult result
= obj
->ensureDenseArrayElements(cx
, length
, 1);
2038 if (result
!= JSObject::ED_OK
) {
2039 if (result
== JSObject::ED_FAILED
)
2041 JS_ASSERT(result
== JSObject::ED_SPARSE
);
2045 obj
->setArrayLength(length
+ 1);
2047 JS_ASSERT(obj
->getDenseArrayElement(length
).isMagic(JS_ARRAY_HOLE
));
2048 obj
->setDenseArrayElement(length
, v
);
2049 rval
->setNumber(obj
->getArrayLength());
2053 if (!obj
->makeDenseArraySlow(cx
))
2056 return array_push_slowly(cx
, obj
, 1, &tmp
, rval
);
2059 JS_ALWAYS_INLINE JSBool
2060 ArrayCompPushImpl(JSContext
*cx
, JSObject
*obj
, const Value
&v
)
2062 JS_ASSERT(obj
->isDenseArray());
2063 uint32_t length
= obj
->getArrayLength();
2064 JS_ASSERT(length
<= obj
->getDenseArrayCapacity());
2066 if (length
== obj
->getDenseArrayCapacity()) {
2067 if (length
> JS_ARGS_LENGTH_MAX
) {
2068 JS_ReportErrorNumberUC(cx
, js_GetErrorMessage
, NULL
,
2069 JSMSG_ARRAY_INIT_TOO_BIG
);
2074 * Array comprehension cannot add holes to the array and never leaks
2075 * the array before it is fully initialized. So we can use ensureSlots
2076 * instead of ensureDenseArrayElements.
2078 if (!obj
->ensureSlots(cx
, length
+ 1))
2081 obj
->setArrayLength(length
+ 1);
2082 obj
->setDenseArrayElement(length
, v
);
2087 js_ArrayCompPush(JSContext
*cx
, JSObject
*obj
, const Value
&vp
)
2089 return ArrayCompPushImpl(cx
, obj
, vp
);
2094 js_ArrayCompPush_tn(JSContext
*cx
, JSObject
*obj
, ValueArgType v
)
2096 TraceMonitor
*tm
= JS_TRACE_MONITOR_ON_TRACE(cx
);
2098 if (!ArrayCompPushImpl(cx
, obj
, ValueArgToConstRef(v
))) {
2099 SetBuiltinError(tm
);
2103 return WasBuiltinSuccessful(tm
);
2105 JS_DEFINE_CALLINFO_3(extern, BOOL_FAIL
, js_ArrayCompPush_tn
, CONTEXT
, OBJECT
,
2106 VALUE
, 0, nanojit::ACCSET_STORE_ANY
)
2110 array_push(JSContext
*cx
, uintN argc
, Value
*vp
)
2112 JSObject
*obj
= ToObject(cx
, &vp
[1]);
2116 /* Insist on one argument and obj of the expected class. */
2117 if (argc
!= 1 || !obj
->isDenseArray())
2118 return array_push_slowly(cx
, obj
, argc
, vp
+ 2, vp
);
2120 return array_push1_dense(cx
, obj
, vp
[2], vp
);
2124 array_pop_slowly(JSContext
*cx
, JSObject
* obj
, Value
*vp
)
2129 if (!js_GetLengthProperty(cx
, obj
, &index
))
2136 /* Get the to-be-deleted property's value into vp. */
2137 if (!GetElement(cx
, obj
, index
, &hole
, vp
))
2139 if (!hole
&& DeleteArrayElement(cx
, obj
, index
, true) < 0)
2142 return js_SetLengthProperty(cx
, obj
, index
);
2146 array_pop_dense(JSContext
*cx
, JSObject
* obj
, Value
*vp
)
2151 index
= obj
->getArrayLength();
2157 if (!GetElement(cx
, obj
, index
, &hole
, vp
))
2159 if (!hole
&& DeleteArrayElement(cx
, obj
, index
, true) < 0)
2161 obj
->setArrayLength(index
);
2166 array_pop(JSContext
*cx
, uintN argc
, Value
*vp
)
2168 JSObject
*obj
= ToObject(cx
, &vp
[1]);
2171 if (obj
->isDenseArray())
2172 return array_pop_dense(cx
, obj
, vp
);
2173 return array_pop_slowly(cx
, obj
, vp
);
2177 array_shift(JSContext
*cx
, uintN argc
, Value
*vp
)
2179 JSObject
*obj
= ToObject(cx
, &vp
[1]);
2184 if (!js_GetLengthProperty(cx
, obj
, &length
))
2192 if (obj
->isDenseArray() && !js_PrototypeHasIndexedProperties(cx
, obj
) &&
2193 length
< obj
->getDenseArrayCapacity()) {
2194 *vp
= obj
->getDenseArrayElement(0);
2195 if (vp
->isMagic(JS_ARRAY_HOLE
))
2197 Value
*elems
= obj
->getDenseArrayElements();
2198 memmove(elems
, elems
+ 1, length
* sizeof(jsval
));
2199 obj
->setDenseArrayElement(length
, MagicValue(JS_ARRAY_HOLE
));
2200 obj
->setArrayLength(length
);
2201 if (!js_SuppressDeletedProperty(cx
, obj
, INT_TO_JSID(length
)))
2206 /* Get the to-be-deleted property's value into vp ASAP. */
2208 if (!GetElement(cx
, obj
, 0, &hole
, vp
))
2211 /* Slide down the array above the first element. */
2212 AutoValueRooter
tvr(cx
);
2213 for (jsuint i
= 0; i
< length
; i
++) {
2214 if (!JS_CHECK_OPERATION_LIMIT(cx
) ||
2215 !GetElement(cx
, obj
, i
+ 1, &hole
, tvr
.addr()) ||
2216 !SetOrDeleteArrayElement(cx
, obj
, i
, hole
, tvr
.value())) {
2221 /* Delete the only or last element when it exists. */
2222 if (!hole
&& DeleteArrayElement(cx
, obj
, length
, true) < 0)
2225 return js_SetLengthProperty(cx
, obj
, length
);
2229 array_unshift(JSContext
*cx
, uintN argc
, Value
*vp
)
2233 jsdouble last
, newlen
;
2235 JSObject
*obj
= ToObject(cx
, &vp
[1]);
2240 if (!js_GetLengthProperty(cx
, obj
, &length
))
2244 /* Slide up the array to make room for argc at the bottom. */
2245 argv
= JS_ARGV(cx
, vp
);
2247 bool optimized
= false;
2249 if (!obj
->isDenseArray())
2251 if (js_PrototypeHasIndexedProperties(cx
, obj
))
2253 JSObject::EnsureDenseResult result
= obj
->ensureDenseArrayElements(cx
, length
, argc
);
2254 if (result
!= JSObject::ED_OK
) {
2255 if (result
== JSObject::ED_FAILED
)
2257 JS_ASSERT(result
== JSObject::ED_SPARSE
);
2260 Value
*elems
= obj
->getDenseArrayElements();
2261 memmove(elems
+ argc
, elems
, length
* sizeof(jsval
));
2262 for (uint32 i
= 0; i
< argc
; i
++)
2263 obj
->setDenseArrayElement(i
, MagicValue(JS_ARRAY_HOLE
));
2269 jsdouble upperIndex
= last
+ argc
;
2270 AutoValueRooter
tvr(cx
);
2272 --last
, --upperIndex
;
2273 if (!JS_CHECK_OPERATION_LIMIT(cx
) ||
2274 !GetElement(cx
, obj
, last
, &hole
, tvr
.addr()) ||
2275 !SetOrDeleteArrayElement(cx
, obj
, upperIndex
, hole
, tvr
.value())) {
2278 } while (last
!= 0);
2282 /* Copy from argv to the bottom of the array. */
2283 if (!InitArrayElements(cx
, obj
, 0, argc
, argv
))
2288 if (!js_SetLengthProperty(cx
, obj
, newlen
))
2291 /* Follow Perl by returning the new array length. */
2292 vp
->setNumber(newlen
);
2297 array_splice(JSContext
*cx
, uintN argc
, Value
*vp
)
2299 JSObject
*obj
= ToObject(cx
, &vp
[1]);
2303 jsuint length
, begin
, end
, count
, delta
, last
;
2306 /* Create a new array value to return. */
2307 JSObject
*obj2
= NewDenseEmptyArray(cx
);
2310 vp
->setObject(*obj2
);
2312 /* Nothing to do if no args. Otherwise get length. */
2315 Value
*argv
= JS_ARGV(cx
, vp
);
2316 if (!js_GetLengthProperty(cx
, obj
, &length
))
2318 jsuint origlength
= length
;
2320 /* Convert the first argument into a starting index. */
2322 if (!ValueToNumber(cx
, *argv
, &d
))
2324 d
= js_DoubleToInteger(d
);
2329 } else if (d
> length
) {
2332 begin
= (jsuint
)d
; /* d has been clamped to uint32 */
2336 /* Convert the second argument from a count into a fencepost index. */
2337 delta
= length
- begin
;
2342 if (!ValueToNumber(cx
, *argv
, &d
))
2344 d
= js_DoubleToInteger(d
);
2350 end
= begin
+ count
;
2355 AutoValueRooter
tvr(cx
);
2357 /* If there are elements to remove, put them into the return value. */
2359 if (obj
->isDenseArray() && !js_PrototypeHasIndexedProperties(cx
, obj
) &&
2360 end
<= obj
->getDenseArrayCapacity()) {
2361 if (!InitArrayObject(cx
, obj2
, count
, obj
->getDenseArrayElements() + begin
))
2364 for (last
= begin
; last
< end
; last
++) {
2365 if (!JS_CHECK_OPERATION_LIMIT(cx
) ||
2366 !GetElement(cx
, obj
, last
, &hole
, tvr
.addr())) {
2370 /* Copy tvr.value() to the new array unless it's a hole. */
2371 if (!hole
&& !SetArrayElement(cx
, obj2
, last
- begin
, tvr
.value()))
2375 if (!js_SetLengthProperty(cx
, obj2
, count
))
2380 /* Find the direction (up or down) to copy and make way for argv. */
2382 delta
= (jsuint
)argc
- count
;
2384 bool optimized
= false;
2386 if (!obj
->isDenseArray())
2388 if (js_PrototypeHasIndexedProperties(cx
, obj
))
2390 if (length
> obj
->getDenseArrayCapacity())
2392 if (length
!= 0 && obj
->getDenseArrayElement(length
- 1).isMagic(JS_ARRAY_HOLE
))
2394 JSObject::EnsureDenseResult result
= obj
->ensureDenseArrayElements(cx
, length
, delta
);
2395 if (result
!= JSObject::ED_OK
) {
2396 if (result
== JSObject::ED_FAILED
)
2398 JS_ASSERT(result
== JSObject::ED_SPARSE
);
2401 Value
*arraybeg
= obj
->getDenseArrayElements();
2402 Value
*srcbeg
= arraybeg
+ last
- 1;
2403 Value
*srcend
= arraybeg
+ end
- 1;
2404 Value
*dstbeg
= srcbeg
+ delta
;
2405 for (Value
*src
= srcbeg
, *dst
= dstbeg
; src
> srcend
; --src
, --dst
)
2408 obj
->setArrayLength(obj
->getArrayLength() + delta
);
2413 /* (uint) end could be 0, so we can't use a vanilla >= test. */
2414 while (last
-- > end
) {
2415 if (!JS_CHECK_OPERATION_LIMIT(cx
) ||
2416 !GetElement(cx
, obj
, last
, &hole
, tvr
.addr()) ||
2417 !SetOrDeleteArrayElement(cx
, obj
, last
+ delta
, hole
, tvr
.value())) {
2423 } else if (argc
< count
) {
2424 delta
= count
- (jsuint
)argc
;
2425 if (obj
->isDenseArray() && !js_PrototypeHasIndexedProperties(cx
, obj
) &&
2426 length
<= obj
->getDenseArrayCapacity()) {
2428 Value
*arraybeg
= obj
->getDenseArrayElements();
2429 Value
*srcbeg
= arraybeg
+ end
;
2430 Value
*srcend
= arraybeg
+ length
;
2431 Value
*dstbeg
= srcbeg
- delta
;
2432 for (Value
*src
= srcbeg
, *dst
= dstbeg
; src
< srcend
; ++src
, ++dst
)
2435 for (last
= end
; last
< length
; last
++) {
2436 if (!JS_CHECK_OPERATION_LIMIT(cx
) ||
2437 !GetElement(cx
, obj
, last
, &hole
, tvr
.addr()) ||
2438 !SetOrDeleteArrayElement(cx
, obj
, last
- delta
, hole
, tvr
.value())) {
2446 if (length
< origlength
&& !js_SuppressDeletedIndexProperties(cx
, obj
, length
, origlength
))
2450 * Copy from argv into the hole to complete the splice, and update length in
2451 * case we deleted elements from the end.
2453 return InitArrayElements(cx
, obj
, begin
, argc
, argv
) &&
2454 js_SetLengthProperty(cx
, obj
, length
);
2458 * Python-esque sequence operations.
2461 array_concat(JSContext
*cx
, uintN argc
, Value
*vp
)
2463 /* Treat our |this| object as the first argument; see ECMA 15.4.4.4. */
2464 Value
*p
= JS_ARGV(cx
, vp
) - 1;
2466 /* Create a new Array object and root it using *vp. */
2467 JSObject
*aobj
= ToObject(cx
, &vp
[1]);
2473 if (aobj
->isDenseArray()) {
2475 * Clone aobj but pass the minimum of its length and capacity, to
2476 * handle a = [1,2,3]; a.length = 10000 "dense" cases efficiently. In
2477 * the normal case where length is <= capacity, nobj and aobj will have
2478 * the same capacity.
2480 length
= aobj
->getArrayLength();
2481 jsuint capacity
= aobj
->getDenseArrayCapacity();
2482 nobj
= NewDenseCopiedArray(cx
, JS_MIN(length
, capacity
), aobj
->getDenseArrayElements());
2485 nobj
->setArrayLength(length
);
2486 vp
->setObject(*nobj
);
2492 nobj
= NewDenseEmptyArray(cx
);
2495 vp
->setObject(*nobj
);
2499 AutoValueRooter
tvr(cx
);
2501 /* Loop over [0, argc] to concat args into nobj, expanding all Arrays. */
2502 for (uintN i
= 0; i
<= argc
; i
++) {
2503 if (!JS_CHECK_OPERATION_LIMIT(cx
))
2505 const Value
&v
= p
[i
];
2507 aobj
= &v
.toObject();
2508 if (aobj
->isArray() ||
2509 (aobj
->isWrapper() && JSWrapper::wrappedObject(aobj
)->isArray())) {
2510 jsid id
= ATOM_TO_JSID(cx
->runtime
->atomState
.lengthAtom
);
2511 if (!aobj
->getProperty(cx
, id
, tvr
.addr()))
2514 if (!ValueToLength(cx
, tvr
.addr(), &alength
))
2516 for (jsuint slot
= 0; slot
< alength
; slot
++) {
2518 if (!JS_CHECK_OPERATION_LIMIT(cx
) ||
2519 !GetElement(cx
, aobj
, slot
, &hole
, tvr
.addr())) {
2524 * Per ECMA 262, 15.4.4.4, step 9, ignore nonexistent
2528 !SetArrayElement(cx
, nobj
, length
+slot
, tvr
.value())) {
2537 if (!SetArrayElement(cx
, nobj
, length
, v
))
2542 return js_SetLengthProperty(cx
, nobj
, length
);
2546 array_slice(JSContext
*cx
, uintN argc
, Value
*vp
)
2550 jsuint length
, begin
, end
, slot
;
2553 argv
= JS_ARGV(cx
, vp
);
2555 JSObject
*obj
= ToObject(cx
, &vp
[1]);
2559 if (!js_GetLengthProperty(cx
, obj
, &length
))
2566 if (!ValueToNumber(cx
, argv
[0], &d
))
2568 d
= js_DoubleToInteger(d
);
2573 } else if (d
> length
) {
2578 if (argc
> 1 && !argv
[1].isUndefined()) {
2579 if (!ValueToNumber(cx
, argv
[1], &d
))
2581 d
= js_DoubleToInteger(d
);
2586 } else if (d
> length
) {
2596 if (obj
->isDenseArray() && end
<= obj
->getDenseArrayCapacity() &&
2597 !js_PrototypeHasIndexedProperties(cx
, obj
)) {
2598 nobj
= NewDenseCopiedArray(cx
, end
- begin
, obj
->getDenseArrayElements() + begin
);
2601 vp
->setObject(*nobj
);
2605 /* Create a new Array object and root it using *vp. */
2606 nobj
= NewDenseAllocatedArray(cx
, end
- begin
);
2609 vp
->setObject(*nobj
);
2611 AutoValueRooter
tvr(cx
);
2612 for (slot
= begin
; slot
< end
; slot
++) {
2613 if (!JS_CHECK_OPERATION_LIMIT(cx
) ||
2614 !GetElement(cx
, obj
, slot
, &hole
, tvr
.addr())) {
2617 if (!hole
&& !SetArrayElement(cx
, nobj
, slot
- begin
, tvr
.value()))
2624 #if JS_HAS_ARRAY_EXTRAS
2627 array_indexOfHelper(JSContext
*cx
, JSBool isLast
, uintN argc
, Value
*vp
)
2629 jsuint length
, i
, stop
;
2634 JSObject
*obj
= ToObject(cx
, &vp
[1]);
2637 if (!js_GetLengthProperty(cx
, obj
, &length
))
2643 i
= isLast
? length
- 1 : 0;
2644 tosearch
= (argc
!= 0) ? vp
[2] : UndefinedValue();
2649 if (!ValueToNumber(cx
, vp
[3], &start
))
2651 start
= js_DoubleToInteger(start
);
2661 } else if (start
>= length
) {
2679 if (!JS_CHECK_OPERATION_LIMIT(cx
) ||
2680 !GetElement(cx
, obj
, (jsuint
)i
, &hole
, vp
)) {
2685 if (!StrictlyEqual(cx
, *vp
, tosearch
, &equal
))
2703 array_indexOf(JSContext
*cx
, uintN argc
, Value
*vp
)
2705 return array_indexOfHelper(cx
, JS_FALSE
, argc
, vp
);
2709 array_lastIndexOf(JSContext
*cx
, uintN argc
, Value
*vp
)
2711 return array_indexOfHelper(cx
, JS_TRUE
, argc
, vp
);
2714 /* Order is important; extras that take a predicate funarg must follow MAP. */
2715 typedef enum ArrayExtraMode
{
2725 #define REDUCE_MODE(mode) ((mode) == REDUCE || (mode) == REDUCE_RIGHT)
2728 array_extra(JSContext
*cx
, ArrayExtraMode mode
, uintN argc
, Value
*vp
)
2730 JSObject
*obj
= ToObject(cx
, &vp
[1]);
2735 if (!js_GetLengthProperty(cx
, obj
, &length
))
2739 * First, get or compute our callee, so that we error out consistently
2740 * when passed a non-callable object.
2743 js_ReportMissingArg(cx
, *vp
, 0);
2746 Value
*argv
= vp
+ 2;
2747 JSObject
*callable
= js_ValueToCallableObject(cx
, &argv
[0], JSV2F_SEARCH_STACK
);
2752 * Set our initial return condition, used for zero-length array cases
2753 * (and pre-size our map return to match our known length, for all cases).
2757 #ifdef __GNUC__ /* quell GCC overwarning */
2761 jsint start
= 0, end
= length
, step
= 1;
2765 start
= length
- 1, end
= -1, step
= -1;
2768 if (length
== 0 && argc
== 1) {
2769 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, NULL
,
2770 JSMSG_EMPTY_ARRAY_REDUCE
);
2778 if (!GetElement(cx
, obj
, start
, &hole
, vp
))
2781 } while (hole
&& start
!= end
);
2783 if (hole
&& start
== end
) {
2784 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, NULL
,
2785 JSMSG_EMPTY_ARRAY_REDUCE
);
2792 newlen
= (mode
== MAP
) ? length
: 0;
2793 newarr
= NewDenseAllocatedArray(cx
, newlen
);
2796 vp
->setObject(*newarr
);
2799 vp
->setBoolean(false);
2802 vp
->setBoolean(true);
2812 Value thisv
= (argc
> 1 && !REDUCE_MODE(mode
)) ? argv
[1] : UndefinedValue();
2815 * For all but REDUCE, we call with 3 args (value, index, array). REDUCE
2816 * requires 4 args (accum, value, index, array).
2818 argc
= 3 + REDUCE_MODE(mode
);
2820 InvokeSessionGuard session
;
2821 if (!session
.start(cx
, ObjectValue(*callable
), thisv
, argc
))
2824 MUST_FLOW_THROUGH("out");
2825 JSBool ok
= JS_TRUE
;
2828 Value objv
= ObjectValue(*obj
);
2829 AutoValueRooter
tvr(cx
);
2830 for (jsint i
= start
; i
!= end
; i
+= step
) {
2832 ok
= JS_CHECK_OPERATION_LIMIT(cx
) &&
2833 GetElement(cx
, obj
, i
, &hole
, tvr
.addr());
2840 * Push callable and 'this', then args. We must do this for every
2841 * iteration around the loop since Invoke clobbers its arguments.
2844 if (REDUCE_MODE(mode
))
2845 session
[argi
++] = *vp
;
2846 session
[argi
++] = tvr
.value();
2847 session
[argi
++] = Int32Value(i
);
2848 session
[argi
] = objv
;
2851 ok
= session
.invoke(cx
);
2855 const Value
&rval
= session
.rval();
2858 cond
= js_ValueToBoolean(rval
);
2859 #ifdef __GNUC__ /* quell GCC overwarning */
2872 ok
= SetArrayElement(cx
, newarr
, i
, rval
);
2879 /* The element passed the filter, so push it onto our result. */
2880 ok
= SetArrayElement(cx
, newarr
, newlen
++, tvr
.value());
2886 vp
->setBoolean(true);
2892 vp
->setBoolean(false);
2900 if (ok
&& mode
== FILTER
)
2901 ok
= js_SetLengthProperty(cx
, newarr
, newlen
);
2906 array_forEach(JSContext
*cx
, uintN argc
, Value
*vp
)
2908 return array_extra(cx
, FOREACH
, argc
, vp
);
2912 array_map(JSContext
*cx
, uintN argc
, Value
*vp
)
2914 return array_extra(cx
, MAP
, argc
, vp
);
2918 array_reduce(JSContext
*cx
, uintN argc
, Value
*vp
)
2920 return array_extra(cx
, REDUCE
, argc
, vp
);
2924 array_reduceRight(JSContext
*cx
, uintN argc
, Value
*vp
)
2926 return array_extra(cx
, REDUCE_RIGHT
, argc
, vp
);
2930 array_filter(JSContext
*cx
, uintN argc
, Value
*vp
)
2932 return array_extra(cx
, FILTER
, argc
, vp
);
2936 array_some(JSContext
*cx
, uintN argc
, Value
*vp
)
2938 return array_extra(cx
, SOME
, argc
, vp
);
2942 array_every(JSContext
*cx
, uintN argc
, Value
*vp
)
2944 return array_extra(cx
, EVERY
, argc
, vp
);
2949 array_isArray(JSContext
*cx
, uintN argc
, Value
*vp
)
2952 vp
->setBoolean(argc
> 0 &&
2954 ((obj
= &vp
[2].toObject())->isArray() ||
2955 (obj
->isWrapper() && JSWrapper::wrappedObject(obj
)->isArray())));
2959 static JSFunctionSpec array_methods
[] = {
2961 JS_FN(js_toSource_str
, array_toSource
, 0,0),
2963 JS_FN(js_toString_str
, array_toString
, 0,0),
2964 JS_FN(js_toLocaleString_str
,array_toLocaleString
,0,0),
2966 /* Perl-ish methods. */
2967 JS_FN("join", array_join
, 1,JSFUN_GENERIC_NATIVE
),
2968 JS_FN("reverse", array_reverse
, 0,JSFUN_GENERIC_NATIVE
),
2969 JS_FN("sort", array_sort
, 1,JSFUN_GENERIC_NATIVE
),
2970 JS_FN("push", array_push
, 1,JSFUN_GENERIC_NATIVE
),
2971 JS_FN("pop", array_pop
, 0,JSFUN_GENERIC_NATIVE
),
2972 JS_FN("shift", array_shift
, 0,JSFUN_GENERIC_NATIVE
),
2973 JS_FN("unshift", array_unshift
, 1,JSFUN_GENERIC_NATIVE
),
2974 JS_FN("splice", array_splice
, 2,JSFUN_GENERIC_NATIVE
),
2976 /* Pythonic sequence methods. */
2977 JS_FN("concat", array_concat
, 1,JSFUN_GENERIC_NATIVE
),
2978 JS_FN("slice", array_slice
, 2,JSFUN_GENERIC_NATIVE
),
2980 #if JS_HAS_ARRAY_EXTRAS
2981 JS_FN("indexOf", array_indexOf
, 1,JSFUN_GENERIC_NATIVE
),
2982 JS_FN("lastIndexOf", array_lastIndexOf
, 1,JSFUN_GENERIC_NATIVE
),
2983 JS_FN("forEach", array_forEach
, 1,JSFUN_GENERIC_NATIVE
),
2984 JS_FN("map", array_map
, 1,JSFUN_GENERIC_NATIVE
),
2985 JS_FN("reduce", array_reduce
, 1,JSFUN_GENERIC_NATIVE
),
2986 JS_FN("reduceRight", array_reduceRight
, 1,JSFUN_GENERIC_NATIVE
),
2987 JS_FN("filter", array_filter
, 1,JSFUN_GENERIC_NATIVE
),
2988 JS_FN("some", array_some
, 1,JSFUN_GENERIC_NATIVE
),
2989 JS_FN("every", array_every
, 1,JSFUN_GENERIC_NATIVE
),
2995 static JSFunctionSpec array_static_methods
[] = {
2996 JS_FN("isArray", array_isArray
, 1,0),
3001 js_Array(JSContext
*cx
, uintN argc
, Value
*vp
)
3006 obj
= NewDenseEmptyArray(cx
);
3007 } else if (argc
> 1) {
3008 obj
= NewDenseCopiedArray(cx
, argc
, vp
+ 2);
3009 } else if (!vp
[2].isNumber()) {
3010 obj
= NewDenseCopiedArray(cx
, 1, vp
+ 2);
3013 if (!ValueToLength(cx
, vp
+ 2, &length
))
3015 obj
= NewDenseUnallocatedArray(cx
, length
);
3020 vp
->setObject(*obj
);
3026 js_InitArrayClass(JSContext
*cx
, JSObject
*obj
)
3028 JSObject
*proto
= js_InitClass(cx
, obj
, NULL
, &js_ArrayClass
, js_Array
, 1,
3029 NULL
, array_methods
, NULL
, array_static_methods
);
3034 * Assert that js_InitClass used the correct (slow array, not dense array)
3035 * class for proto's emptyShape class.
3037 JS_ASSERT(proto
->emptyShapes
&& proto
->emptyShapes
[0]->getClass() == proto
->getClass());
3039 proto
->setArrayLength(0);
3044 * Array allocation functions.
3048 template<bool allocateCapacity
>
3049 static JS_ALWAYS_INLINE JSObject
*
3050 NewArray(JSContext
*cx
, jsuint length
, JSObject
*proto
)
3052 JS_ASSERT_IF(proto
, proto
->isArray());
3054 gc::FinalizeKind kind
= GuessObjectGCKind(length
, true);
3055 JSObject
*obj
= detail::NewObject
<WithProto::Class
, false>(cx
, &js_ArrayClass
, proto
, NULL
, kind
);
3059 obj
->setArrayLength(length
);
3061 if (allocateCapacity
&& !obj
->ensureSlots(cx
, length
))
3067 JSObject
* JS_FASTCALL
3068 NewDenseEmptyArray(JSContext
*cx
, JSObject
*proto
)
3070 return NewArray
<false>(cx
, 0, proto
);
3073 JSObject
* JS_FASTCALL
3074 NewDenseAllocatedArray(JSContext
*cx
, uint32 length
, JSObject
*proto
)
3076 return NewArray
<true>(cx
, length
, proto
);
3079 JSObject
* JS_FASTCALL
3080 NewDenseUnallocatedArray(JSContext
*cx
, uint32 length
, JSObject
*proto
)
3082 return NewArray
<false>(cx
, length
, proto
);
3086 NewDenseCopiedArray(JSContext
*cx
, uintN length
, Value
*vp
, JSObject
*proto
)
3088 JSObject
* obj
= NewArray
<true>(cx
, length
, proto
);
3092 JS_ASSERT(obj
->getDenseArrayCapacity() >= length
);
3095 memcpy(obj
->getDenseArrayElements(), vp
, length
* sizeof(Value
));
3101 JS_DEFINE_CALLINFO_2(extern, OBJECT
, NewDenseEmptyArray
, CONTEXT
, OBJECT
, 0,
3102 nanojit::ACCSET_STORE_ANY
)
3103 JS_DEFINE_CALLINFO_3(extern, OBJECT
, NewDenseAllocatedArray
, CONTEXT
, UINT32
, OBJECT
, 0,
3104 nanojit::ACCSET_STORE_ANY
)
3105 JS_DEFINE_CALLINFO_3(extern, OBJECT
, NewDenseUnallocatedArray
, CONTEXT
, UINT32
, OBJECT
, 0,
3106 nanojit::ACCSET_STORE_ANY
)
3112 NewSlowEmptyArray(JSContext
*cx
)
3114 JSObject
*obj
= NewNonFunction
<WithProto::Class
>(cx
, &js_SlowArrayClass
, NULL
, NULL
);
3115 if (!obj
|| !AddLengthProperty(cx
, obj
))
3118 obj
->setArrayLength(0);
3127 js_ArrayInfo(JSContext
*cx
, uintN argc
, jsval
*vp
)
3132 for (i
= 0; i
< argc
; i
++) {
3133 Value arg
= Valueify(JS_ARGV(cx
, vp
)[i
]);
3135 char *bytes
= DecompileValueGenerator(cx
, JSDVG_SEARCH_STACK
, arg
, NULL
);
3138 if (arg
.isPrimitive() ||
3139 !(array
= arg
.toObjectOrNull())->isArray()) {
3140 fprintf(stderr
, "%s: not array\n", bytes
);
3144 fprintf(stderr
, "%s: %s (len %u", bytes
,
3145 array
->isDenseArray() ? "dense" : "sparse",
3146 array
->getArrayLength());
3147 if (array
->isDenseArray()) {
3148 fprintf(stderr
, ", capacity %u",
3149 array
->getDenseArrayCapacity());
3151 fputs(")\n", stderr
);
3155 JS_SET_RVAL(cx
, vp
, JSVAL_VOID
);
3160 JS_FRIEND_API(JSBool
)
3161 js_CoerceArrayToCanvasImageData(JSObject
*obj
, jsuint offset
, jsuint count
,
3166 if (!obj
|| !obj
->isDenseArray())
3169 length
= obj
->getArrayLength();
3170 if (length
< offset
+ count
)
3174 for (uintN i
= offset
; i
< offset
+count
; i
++) {
3175 const Value
&v
= obj
->getDenseArrayElement(i
);
3177 jsint vi
= v
.toInt32();
3178 if (jsuint(vi
) > 255)
3179 vi
= (vi
< 0) ? 0 : 255;
3180 *dp
++ = JSUint8(vi
);
3181 } else if (v
.isDouble()) {
3182 jsdouble vd
= v
.toDouble();
3183 if (!(vd
>= 0)) /* Not < so that NaN coerces to 0 */
3188 jsdouble toTruncate
= vd
+ 0.5;
3189 JSUint8 val
= JSUint8(toTruncate
);
3192 * now val is rounded to nearest, ties rounded up. We want
3193 * rounded to nearest ties to even, so check whether we had a
3196 if (val
== toTruncate
) {
3198 * It was a tie (since adding 0.5 gave us the exact integer
3199 * we want). Since we rounded up, we either already have an
3200 * even number or we have an odd number but the number we
3201 * want is one less. So just unconditionally masking out the
3202 * ones bit should do the trick to get us the value we
3218 JS_FRIEND_API(JSBool
)
3219 js_IsDensePrimitiveArray(JSObject
*obj
)
3221 if (!obj
|| !obj
->isDenseArray())
3224 jsuint capacity
= obj
->getDenseArrayCapacity();
3225 for (jsuint i
= 0; i
< capacity
; i
++) {
3226 if (obj
->getDenseArrayElement(i
).isObject())
3233 JS_FRIEND_API(JSBool
)
3234 js_CloneDensePrimitiveArray(JSContext
*cx
, JSObject
*obj
, JSObject
**clone
)
3237 if (!obj
->isDenseArray()) {
3239 * This wasn't a dense array. Return JS_TRUE but a NULL clone to signal
3240 * that no exception was encountered.
3246 jsuint length
= obj
->getArrayLength();
3249 * Must use the minimum of original array's length and capacity, to handle
3250 * |a = [1,2,3]; a.length = 10000| "dense" cases efficiently. In the normal
3251 * case where length is <= capacity, the clone and original array will have
3252 * the same capacity.
3254 jsuint jsvalCount
= JS_MIN(obj
->getDenseArrayCapacity(), length
);
3256 js::AutoValueVector
vector(cx
);
3257 if (!vector
.reserve(jsvalCount
))
3260 for (jsuint i
= 0; i
< jsvalCount
; i
++) {
3261 const Value
&val
= obj
->getDenseArrayElement(i
);
3263 if (val
.isString()) {
3264 // Strings must be made immutable before being copied to a clone.
3265 if (!js_MakeStringImmutable(cx
, val
.toString()))
3267 } else if (val
.isObject()) {
3269 * This wasn't an array of primitives. Return JS_TRUE but a null
3270 * clone to signal that no exception was encountered.
3279 *clone
= NewDenseCopiedArray(cx
, jsvalCount
, vector
.begin());
3283 /* The length will be set to the JS_MIN, above, but length might be larger. */
3284 (*clone
)->setArrayLength(length
);