summaryrefslogtreecommitdiff
path: root/js/src/jsarray.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jsarray.cpp')
-rw-r--r--js/src/jsarray.cpp3291
1 files changed, 3291 insertions, 0 deletions
diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp
new file mode 100644
index 0000000..7e5cdc0
--- /dev/null
+++ b/js/src/jsarray.cpp
@@ -0,0 +1,3291 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set sw=4 ts=8 et tw=78:
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * JS array class.
+ *
+ * Array objects begin as "dense" arrays, optimized for index-only property
+ * access over a vector of slots with high load factor. Array methods
+ * optimize for denseness by testing that the object's class is
+ * &js_ArrayClass, and can then directly manipulate the slots for efficiency.
+ *
+ * We track these pieces of metadata for arrays in dense mode:
+ * - The array's length property as a uint32, accessible with
+ * getArrayLength(), setArrayLength().
+ * - The number of element slots (capacity), gettable with
+ * getDenseArrayCapacity().
+ *
+ * In dense mode, holes in the array are represented by
+ * MagicValue(JS_ARRAY_HOLE) invalid values.
+ *
+ * NB: the capacity and length of a dense array are entirely unrelated! The
+ * length may be greater than, less than, or equal to the capacity. The first
+ * case may occur when the user writes "new Array(100), in which case the
+ * length is 100 while the capacity remains 0 (indices below length and above
+ * capaicty must be treated as holes). See array_length_setter for another
+ * explanation of how the first case may occur.
+ *
+ * Arrays are converted to use js_SlowArrayClass when any of these conditions
+ * are met:
+ * - there are more than MIN_SPARSE_INDEX slots total
+ * - the load factor (COUNT / capacity) is less than 0.25
+ * - a property is set that is not indexed (and not "length")
+ * - a property is defined that has non-default property attributes.
+ *
+ * Dense arrays do not track property creation order, so unlike other native
+ * objects and slow arrays, enumerating an array does not necessarily visit the
+ * properties in the order they were created. We could instead maintain the
+ * scope to track property enumeration order, but still use the fast slot
+ * access. That would have the same memory cost as just using a
+ * js_SlowArrayClass, but have the same performance characteristics as a dense
+ * array for slot accesses, at some cost in code complexity.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include "jstypes.h"
+#include "jsstdint.h"
+#include "jsutil.h"
+#include "jsapi.h"
+#include "jsarray.h"
+#include "jsatom.h"
+#include "jsbit.h"
+#include "jsbool.h"
+#include "jstracer.h"
+#include "jsbuiltins.h"
+#include "jscntxt.h"
+#include "jsversion.h"
+#include "jsfun.h"
+#include "jsgc.h"
+#include "jsinterp.h"
+#include "jsiter.h"
+#include "jslock.h"
+#include "jsnum.h"
+#include "jsobj.h"
+#include "jsscope.h"
+#include "jsstr.h"
+#include "jsstaticcheck.h"
+#include "jsvector.h"
+#include "jswrapper.h"
+
+#include "jsatominlines.h"
+#include "jscntxtinlines.h"
+#include "jsinterpinlines.h"
+#include "jsobjinlines.h"
+
+using namespace js;
+using namespace js::gc;
+
+/* 2^32 - 1 as a number and a string */
+#define MAXINDEX 4294967295u
+#define MAXSTR "4294967295"
+
+static inline bool
+ENSURE_SLOW_ARRAY(JSContext *cx, JSObject *obj)
+{
+ return obj->getClass() == &js_SlowArrayClass ||
+ obj->makeDenseArraySlow(cx);
+}
+
+/*
+ * Determine if the id represents an array index or an XML property index.
+ *
+ * An id is an array index according to ECMA by (15.4):
+ *
+ * "Array objects give special treatment to a certain class of property names.
+ * A property name P (in the form of a string value) is an array index if and
+ * only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal
+ * to 2^32-1."
+ *
+ * In our implementation, it would be sufficient to check for JSVAL_IS_INT(id)
+ * except that by using signed 31-bit integers we miss the top half of the
+ * valid range. This function checks the string representation itself; note
+ * that calling a standard conversion routine might allow strings such as
+ * "08" or "4.0" as array indices, which they are not.
+ *
+ * 'id' is passed as a jsboxedword since the given id need not necessarily hold
+ * an atomized string.
+ */
+bool
+js_StringIsIndex(JSLinearString *str, jsuint *indexp)
+{
+ const jschar *cp = str->chars();
+ if (JS7_ISDEC(*cp) && str->length() < sizeof(MAXSTR)) {
+ jsuint index = JS7_UNDEC(*cp++);
+ jsuint oldIndex = 0;
+ jsuint c = 0;
+ if (index != 0) {
+ while (JS7_ISDEC(*cp)) {
+ oldIndex = index;
+ c = JS7_UNDEC(*cp);
+ index = 10*index + c;
+ cp++;
+ }
+ }
+
+ /* Ensure that all characters were consumed and we didn't overflow. */
+ if (*cp == 0 &&
+ (oldIndex < (MAXINDEX / 10) ||
+ (oldIndex == (MAXINDEX / 10) && c < (MAXINDEX % 10))))
+ {
+ *indexp = index;
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool
+ValueToLength(JSContext *cx, Value* vp, jsuint* plength)
+{
+ if (vp->isInt32()) {
+ int32_t i = vp->toInt32();
+ if (i < 0)
+ goto error;
+
+ *plength = (jsuint)(i);
+ return true;
+ }
+
+ jsdouble d;
+ if (!ValueToNumber(cx, *vp, &d))
+ goto error;
+
+ if (JSDOUBLE_IS_NaN(d))
+ goto error;
+
+ jsuint length;
+ length = (jsuint) d;
+ if (d != (jsdouble) length)
+ goto error;
+
+
+ *plength = length;
+ return true;
+
+error:
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+ JSMSG_BAD_ARRAY_LENGTH);
+ return false;
+}
+
+JSBool
+js_GetLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp)
+{
+ if (obj->isArray()) {
+ *lengthp = obj->getArrayLength();
+ return true;
+ }
+
+ if (obj->isArguments() && !obj->isArgsLengthOverridden()) {
+ *lengthp = obj->getArgsInitialLength();
+ return true;
+ }
+
+ AutoValueRooter tvr(cx);
+ if (!obj->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.lengthAtom), tvr.addr()))
+ return false;
+
+ if (tvr.value().isInt32()) {
+ *lengthp = jsuint(jsint(tvr.value().toInt32())); /* jsuint cast does ToUint32 */
+ return true;
+ }
+
+ JS_STATIC_ASSERT(sizeof(jsuint) == sizeof(uint32_t));
+ return ValueToECMAUint32(cx, tvr.value(), (uint32_t *)lengthp);
+}
+
+JSBool JS_FASTCALL
+js_IndexToId(JSContext *cx, jsuint index, jsid *idp)
+{
+ JSString *str;
+
+ if (index <= JSID_INT_MAX) {
+ *idp = INT_TO_JSID(index);
+ return JS_TRUE;
+ }
+ str = js_NumberToString(cx, index);
+ if (!str)
+ return JS_FALSE;
+ return js_ValueToStringId(cx, StringValue(str), idp);
+}
+
+static JSBool
+BigIndexToId(JSContext *cx, JSObject *obj, jsuint index, JSBool createAtom,
+ jsid *idp)
+{
+ jschar buf[10], *start;
+ Class *clasp;
+ JSAtom *atom;
+ JS_STATIC_ASSERT((jsuint)-1 == 4294967295U);
+
+ JS_ASSERT(index > JSID_INT_MAX);
+
+ start = JS_ARRAY_END(buf);
+ do {
+ --start;
+ *start = (jschar)('0' + index % 10);
+ index /= 10;
+ } while (index != 0);
+
+ /*
+ * Skip the atomization if the class is known to store atoms corresponding
+ * to big indexes together with elements. In such case we know that the
+ * array does not have an element at the given index if its atom does not
+ * exist. Fast arrays (clasp == &js_ArrayClass) don't use atoms for
+ * any indexes, though it would be rare to see them have a big index
+ * in any case.
+ */
+ if (!createAtom &&
+ ((clasp = obj->getClass()) == &js_SlowArrayClass ||
+ clasp == &js_ArgumentsClass ||
+ clasp == &js_ObjectClass)) {
+ atom = js_GetExistingStringAtom(cx, start, JS_ARRAY_END(buf) - start);
+ if (!atom) {
+ *idp = JSID_VOID;
+ return JS_TRUE;
+ }
+ } else {
+ atom = js_AtomizeChars(cx, start, JS_ARRAY_END(buf) - start, 0);
+ if (!atom)
+ return JS_FALSE;
+ }
+
+ *idp = ATOM_TO_JSID(atom);
+ return JS_TRUE;
+}
+
+bool
+JSObject::willBeSparseDenseArray(uintN requiredCapacity, uintN newElementsHint)
+{
+ JS_ASSERT(isDenseArray());
+ JS_ASSERT(requiredCapacity > MIN_SPARSE_INDEX);
+
+ uintN cap = numSlots();
+ JS_ASSERT(requiredCapacity >= cap);
+
+ if (requiredCapacity >= JSObject::NSLOTS_LIMIT)
+ return true;
+
+ uintN minimalDenseCount = requiredCapacity / 4;
+ if (newElementsHint >= minimalDenseCount)
+ return false;
+ minimalDenseCount -= newElementsHint;
+
+ if (minimalDenseCount > cap)
+ return true;
+
+ Value *elems = getDenseArrayElements();
+ for (uintN i = 0; i < cap; i++) {
+ if (!elems[i].isMagic(JS_ARRAY_HOLE) && !--minimalDenseCount)
+ return false;
+ }
+ return true;
+}
+
+static bool
+ReallyBigIndexToId(JSContext* cx, jsdouble index, jsid* idp)
+{
+ return js_ValueToStringId(cx, DoubleValue(index), idp);
+}
+
+static bool
+IndexToId(JSContext* cx, JSObject* obj, jsdouble index, JSBool* hole, jsid* idp,
+ JSBool createAtom = JS_FALSE)
+{
+ if (index <= JSID_INT_MAX) {
+ *idp = INT_TO_JSID(int(index));
+ return JS_TRUE;
+ }
+
+ if (index <= jsuint(-1)) {
+ if (!BigIndexToId(cx, obj, jsuint(index), createAtom, idp))
+ return JS_FALSE;
+ if (hole && JSID_IS_VOID(*idp))
+ *hole = JS_TRUE;
+ return JS_TRUE;
+ }
+
+ return ReallyBigIndexToId(cx, index, idp);
+}
+
+/*
+ * If the property at the given index exists, get its value into location
+ * pointed by vp and set *hole to false. Otherwise set *hole to true and *vp
+ * to JSVAL_VOID. This function assumes that the location pointed by vp is
+ * properly rooted and can be used as GC-protected storage for temporaries.
+ */
+static JSBool
+GetElement(JSContext *cx, JSObject *obj, jsdouble index, JSBool *hole, Value *vp)
+{
+ JS_ASSERT(index >= 0);
+ if (obj->isDenseArray() && index < obj->getDenseArrayCapacity() &&
+ !(*vp = obj->getDenseArrayElement(uint32(index))).isMagic(JS_ARRAY_HOLE)) {
+ *hole = JS_FALSE;
+ return JS_TRUE;
+ }
+ if (obj->isArguments() &&
+ index < obj->getArgsInitialLength() &&
+ !(*vp = obj->getArgsElement(uint32(index))).isMagic(JS_ARGS_HOLE)) {
+ *hole = JS_FALSE;
+ JSStackFrame *fp = (JSStackFrame *)obj->getPrivate();
+ if (fp != JS_ARGUMENTS_OBJECT_ON_TRACE) {
+ if (fp)
+ *vp = fp->canonicalActualArg(index);
+ return JS_TRUE;
+ }
+ }
+
+ AutoIdRooter idr(cx);
+
+ *hole = JS_FALSE;
+ if (!IndexToId(cx, obj, index, hole, idr.addr()))
+ return JS_FALSE;
+ if (*hole) {
+ vp->setUndefined();
+ return JS_TRUE;
+ }
+
+ JSObject *obj2;
+ JSProperty *prop;
+ if (!obj->lookupProperty(cx, idr.id(), &obj2, &prop))
+ return JS_FALSE;
+ if (!prop) {
+ *hole = JS_TRUE;
+ vp->setUndefined();
+ } else {
+ if (!obj->getProperty(cx, idr.id(), vp))
+ return JS_FALSE;
+ *hole = JS_FALSE;
+ }
+ return JS_TRUE;
+}
+
+namespace js {
+
+bool
+GetElements(JSContext *cx, JSObject *aobj, jsuint length, Value *vp)
+{
+ if (aobj->isDenseArray() && length <= aobj->getDenseArrayCapacity() &&
+ !js_PrototypeHasIndexedProperties(cx, aobj)) {
+ /* The prototype does not have indexed properties so hole = undefined */
+ Value *srcbeg = aobj->getDenseArrayElements();
+ Value *srcend = srcbeg + length;
+ for (Value *dst = vp, *src = srcbeg; src < srcend; ++dst, ++src)
+ *dst = src->isMagic(JS_ARRAY_HOLE) ? UndefinedValue() : *src;
+ } else if (aobj->isArguments() && !aobj->isArgsLengthOverridden() &&
+ !js_PrototypeHasIndexedProperties(cx, aobj)) {
+ /*
+ * Two cases, two loops: note how in the case of an active stack frame
+ * backing aobj, even though we copy from fp->argv, we still must check
+ * aobj->getArgsElement(i) for a hole, to handle a delete on the
+ * corresponding arguments element. See args_delProperty.
+ */
+ if (JSStackFrame *fp = (JSStackFrame *) aobj->getPrivate()) {
+ JS_ASSERT(fp->numActualArgs() <= JS_ARGS_LENGTH_MAX);
+ fp->forEachCanonicalActualArg(CopyNonHoleArgsTo(aobj, vp));
+ } else {
+ Value *srcbeg = aobj->getArgsElements();
+ Value *srcend = srcbeg + length;
+ for (Value *dst = vp, *src = srcbeg; src < srcend; ++dst, ++src)
+ *dst = src->isMagic(JS_ARGS_HOLE) ? UndefinedValue() : *src;
+ }
+ } else {
+ for (uintN i = 0; i < length; i++) {
+ if (!aobj->getProperty(cx, INT_TO_JSID(jsint(i)), &vp[i]))
+ return JS_FALSE;
+ }
+ }
+
+ return true;
+}
+
+}
+
+/*
+ * Set the value of the property at the given index to v assuming v is rooted.
+ */
+static JSBool
+SetArrayElement(JSContext *cx, JSObject *obj, jsdouble index, const Value &v)
+{
+ JS_ASSERT(index >= 0);
+
+ if (obj->isDenseArray()) {
+ /* Predicted/prefetched code should favor the remains-dense case. */
+ JSObject::EnsureDenseResult result = JSObject::ED_SPARSE;
+ do {
+ if (index > jsuint(-1))
+ break;
+ jsuint idx = jsuint(index);
+ result = obj->ensureDenseArrayElements(cx, idx, 1);
+ if (result != JSObject::ED_OK)
+ break;
+ if (idx >= obj->getArrayLength())
+ obj->setArrayLength(idx + 1);
+ obj->setDenseArrayElement(idx, v);
+ return true;
+ } while (false);
+
+ if (result == JSObject::ED_FAILED)
+ return false;
+ JS_ASSERT(result == JSObject::ED_SPARSE);
+ if (!obj->makeDenseArraySlow(cx))
+ return JS_FALSE;
+ }
+
+ AutoIdRooter idr(cx);
+
+ if (!IndexToId(cx, obj, index, NULL, idr.addr(), JS_TRUE))
+ return JS_FALSE;
+ JS_ASSERT(!JSID_IS_VOID(idr.id()));
+
+ Value tmp = v;
+ return obj->setProperty(cx, idr.id(), &tmp, true);
+}
+
+#ifdef JS_TRACER
+JSBool JS_FASTCALL
+js_EnsureDenseArrayCapacity(JSContext *cx, JSObject *obj, jsint i)
+{
+#ifdef DEBUG
+ Class *origObjClasp = obj->clasp;
+#endif
+ jsuint u = jsuint(i);
+ JSBool ret = (obj->ensureDenseArrayElements(cx, u, 1) == JSObject::ED_OK);
+
+ /* Partially check the CallInfo's storeAccSet is correct. */
+ JS_ASSERT(obj->clasp == origObjClasp);
+ return ret;
+}
+/* This function and its callees do not touch any object's .clasp field. */
+JS_DEFINE_CALLINFO_3(extern, BOOL, js_EnsureDenseArrayCapacity, CONTEXT, OBJECT, INT32,
+ 0, nanojit::ACCSET_STORE_ANY & ~tjit::ACCSET_OBJ_CLASP)
+#endif
+
+/*
+ * Delete the element |index| from |obj|. If |strict|, do a strict
+ * deletion: throw if the property is not configurable.
+ *
+ * - Return 1 if the deletion succeeds (that is, ES5's [[Delete]] would
+ * return true)
+ *
+ * - Return 0 if the deletion fails because the property is not
+ * configurable (that is, [[Delete]] would return false). Note that if
+ * |strict| is true we will throw, not return zero.
+ *
+ * - Return -1 if an exception occurs (that is, [[Delete]] would throw).
+ */
+static int
+DeleteArrayElement(JSContext *cx, JSObject *obj, jsdouble index, bool strict)
+{
+ JS_ASSERT(index >= 0);
+ if (obj->isDenseArray()) {
+ if (index <= jsuint(-1)) {
+ jsuint idx = jsuint(index);
+ if (idx < obj->getDenseArrayCapacity()) {
+ obj->setDenseArrayElement(idx, MagicValue(JS_ARRAY_HOLE));
+ if (!js_SuppressDeletedIndexProperties(cx, obj, idx, idx+1))
+ return -1;
+ }
+ }
+ return 1;
+ }
+
+ AutoIdRooter idr(cx);
+
+ if (!IndexToId(cx, obj, index, NULL, idr.addr()))
+ return -1;
+ if (JSID_IS_VOID(idr.id()))
+ return 1;
+
+ Value v;
+ if (!obj->deleteProperty(cx, idr.id(), &v, strict))
+ return -1;
+ return v.isTrue() ? 1 : 0;
+}
+
+/*
+ * When hole is true, delete the property at the given index. Otherwise set
+ * its value to v assuming v is rooted.
+ */
+static JSBool
+SetOrDeleteArrayElement(JSContext *cx, JSObject *obj, jsdouble index,
+ JSBool hole, const Value &v)
+{
+ if (hole) {
+ JS_ASSERT(v.isUndefined());
+ return DeleteArrayElement(cx, obj, index, true) >= 0;
+ }
+ return SetArrayElement(cx, obj, index, v);
+}
+
+JSBool
+js_SetLengthProperty(JSContext *cx, JSObject *obj, jsdouble length)
+{
+ Value v;
+ jsid id;
+
+ v.setNumber(length);
+ id = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
+ /* We don't support read-only array length yet. */
+ return obj->setProperty(cx, id, &v, false);
+}
+
+JSBool
+js_HasLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp)
+{
+ JSErrorReporter older = JS_SetErrorReporter(cx, NULL);
+ AutoValueRooter tvr(cx);
+ jsid id = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
+ JSBool ok = obj->getProperty(cx, id, tvr.addr());
+ JS_SetErrorReporter(cx, older);
+ if (!ok)
+ return false;
+
+ if (!ValueToLength(cx, tvr.addr(), lengthp))
+ return false;
+
+ return true;
+}
+
+/*
+ * Since SpiderMonkey supports cross-class prototype-based delegation, we have
+ * to be careful about the length getter and setter being called on an object
+ * not of Array class. For the getter, we search obj's prototype chain for the
+ * array that caused this getter to be invoked. In the setter case to overcome
+ * the JSPROP_SHARED attribute, we must define a shadowing length property.
+ */
+static JSBool
+array_length_getter(JSContext *cx, JSObject *obj, jsid id, Value *vp)
+{
+ do {
+ if (obj->isArray()) {
+ vp->setNumber(obj->getArrayLength());
+ return JS_TRUE;
+ }
+ } while ((obj = obj->getProto()) != NULL);
+ return JS_TRUE;
+}
+
+static JSBool
+array_length_setter(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp)
+{
+ jsuint newlen, oldlen, gap, index;
+ Value junk;
+
+ if (!obj->isArray()) {
+ jsid lengthId = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
+
+ return obj->defineProperty(cx, lengthId, *vp, NULL, NULL, JSPROP_ENUMERATE);
+ }
+
+ if (!ValueToLength(cx, vp, &newlen))
+ return false;
+
+ oldlen = obj->getArrayLength();
+
+ if (oldlen == newlen)
+ return true;
+
+ vp->setNumber(newlen);
+ if (oldlen < newlen) {
+ obj->setArrayLength(newlen);
+ return true;
+ }
+
+ if (obj->isDenseArray()) {
+ /*
+ * Don't reallocate if we're not actually shrinking our slots. If we do
+ * shrink slots here, ensureDenseArrayElements will fill all slots to the
+ * right of newlen with JS_ARRAY_HOLE. This permits us to disregard
+ * length when reading from arrays as long we are within the capacity.
+ */
+ jsuint oldcap = obj->getDenseArrayCapacity();
+ if (oldcap > newlen)
+ obj->shrinkDenseArrayElements(cx, newlen);
+ obj->setArrayLength(newlen);
+ } else if (oldlen - newlen < (1 << 24)) {
+ do {
+ --oldlen;
+ if (!JS_CHECK_OPERATION_LIMIT(cx)) {
+ obj->setArrayLength(oldlen + 1);
+ return false;
+ }
+ int deletion = DeleteArrayElement(cx, obj, oldlen, strict);
+ if (deletion <= 0) {
+ obj->setArrayLength(oldlen + 1);
+ return deletion >= 0;
+ }
+ } while (oldlen != newlen);
+ obj->setArrayLength(newlen);
+ } else {
+ /*
+ * We are going to remove a lot of indexes in a presumably sparse
+ * array. So instead of looping through indexes between newlen and
+ * oldlen, we iterate through all properties and remove those that
+ * correspond to indexes in the half-open range [newlen, oldlen). See
+ * bug 322135.
+ */
+ JSObject *iter = JS_NewPropertyIterator(cx, obj);
+ if (!iter)
+ return false;
+
+ /* Protect iter against GC under JSObject::deleteProperty. */
+ AutoObjectRooter tvr(cx, iter);
+
+ gap = oldlen - newlen;
+ for (;;) {
+ if (!JS_CHECK_OPERATION_LIMIT(cx) || !JS_NextProperty(cx, iter, &id))
+ return false;
+ if (JSID_IS_VOID(id))
+ break;
+ if (js_IdIsIndex(id, &index) && index - newlen < gap &&
+ !obj->deleteProperty(cx, id, &junk, false)) {
+ return false;
+ }
+ }
+ obj->setArrayLength(newlen);
+ }
+
+ return true;
+}
+
+/*
+ * We have only indexed properties up to capacity (excepting holes), plus the
+ * length property. For all else, we delegate to the prototype.
+ */
+static inline bool
+IsDenseArrayId(JSContext *cx, JSObject *obj, jsid id)
+{
+ JS_ASSERT(obj->isDenseArray());
+
+ uint32 i;
+ return JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom) ||
+ (js_IdIsIndex(id, &i) &&
+ obj->getArrayLength() != 0 &&
+ i < obj->getDenseArrayCapacity() &&
+ !obj->getDenseArrayElement(i).isMagic(JS_ARRAY_HOLE));
+}
+
+static JSBool
+array_lookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
+ JSProperty **propp)
+{
+ if (!obj->isDenseArray())
+ return js_LookupProperty(cx, obj, id, objp, propp);
+
+ if (IsDenseArrayId(cx, obj, id)) {
+ *propp = (JSProperty *) 1; /* non-null to indicate found */
+ *objp = obj;
+ return JS_TRUE;
+ }
+
+ JSObject *proto = obj->getProto();
+ if (!proto) {
+ *objp = NULL;
+ *propp = NULL;
+ return JS_TRUE;
+ }
+ return proto->lookupProperty(cx, id, objp, propp);
+}
+
+JSBool
+js_GetDenseArrayElementValue(JSContext *cx, JSObject *obj, jsid id, Value *vp)
+{
+ JS_ASSERT(obj->isDenseArray());
+
+ uint32 i;
+ if (!js_IdIsIndex(id, &i)) {
+ JS_ASSERT(JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom));
+ vp->setNumber(obj->getArrayLength());
+ return JS_TRUE;
+ }
+ *vp = obj->getDenseArrayElement(i);
+ return JS_TRUE;
+}
+
+static JSBool
+array_getProperty(JSContext *cx, JSObject *obj, JSObject *receiver, jsid id, Value *vp)
+{
+ uint32 i;
+
+ if (JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom)) {
+ vp->setNumber(obj->getArrayLength());
+ return JS_TRUE;
+ }
+
+ if (JSID_IS_ATOM(id, cx->runtime->atomState.protoAtom)) {
+ vp->setObjectOrNull(obj->getProto());
+ return JS_TRUE;
+ }
+
+ if (!obj->isDenseArray())
+ return js_GetProperty(cx, obj, id, vp);
+
+ if (!js_IdIsIndex(id, &i) || i >= obj->getDenseArrayCapacity() ||
+ obj->getDenseArrayElement(i).isMagic(JS_ARRAY_HOLE)) {
+ JSObject *obj2;
+ JSProperty *prop;
+ const Shape *shape;
+
+ JSObject *proto = obj->getProto();
+ if (!proto) {
+ vp->setUndefined();
+ return JS_TRUE;
+ }
+
+ vp->setUndefined();
+ if (js_LookupPropertyWithFlags(cx, proto, id, cx->resolveFlags,
+ &obj2, &prop) < 0)
+ return JS_FALSE;
+
+ if (prop && obj2->isNative()) {
+ shape = (const Shape *) prop;
+ if (!js_NativeGet(cx, obj, obj2, shape, JSGET_METHOD_BARRIER, vp))
+ return JS_FALSE;
+ }
+ return JS_TRUE;
+ }
+
+ *vp = obj->getDenseArrayElement(i);
+ return JS_TRUE;
+}
+
+static JSBool
+slowarray_addProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp)
+{
+ jsuint index, length;
+
+ if (!js_IdIsIndex(id, &index))
+ return JS_TRUE;
+ length = obj->getArrayLength();
+ if (index >= length)
+ obj->setArrayLength(index + 1);
+ return JS_TRUE;
+}
+
+static JSType
+array_typeOf(JSContext *cx, JSObject *obj)
+{
+ return JSTYPE_OBJECT;
+}
+
+static JSBool
+array_setProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp, JSBool strict)
+{
+ uint32 i;
+
+ if (JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom))
+ return array_length_setter(cx, obj, id, strict, vp);
+
+ if (!obj->isDenseArray())
+ return js_SetProperty(cx, obj, id, vp, strict);
+
+ do {
+ if (!js_IdIsIndex(id, &i))
+ break;
+ if (js_PrototypeHasIndexedProperties(cx, obj))
+ break;
+
+ JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, i, 1);
+ if (result != JSObject::ED_OK) {
+ if (result == JSObject::ED_FAILED)
+ return false;
+ JS_ASSERT(result == JSObject::ED_SPARSE);
+ break;
+ }
+
+ if (i >= obj->getArrayLength())
+ obj->setArrayLength(i + 1);
+ obj->setDenseArrayElement(i, *vp);
+ return true;
+ } while (false);
+
+ if (!obj->makeDenseArraySlow(cx))
+ return false;
+ return js_SetProperty(cx, obj, id, vp, strict);
+}
+
+JSBool
+js_PrototypeHasIndexedProperties(JSContext *cx, JSObject *obj)
+{
+ /*
+ * Walk up the prototype chain and see if this indexed element already
+ * exists. If we hit the end of the prototype chain, it's safe to set the
+ * element on the original object.
+ */
+ while ((obj = obj->getProto()) != NULL) {
+ /*
+ * If the prototype is a non-native object (possibly a dense array), or
+ * a native object (possibly a slow array) that has indexed properties,
+ * return true.
+ */
+ if (!obj->isNative())
+ return JS_TRUE;
+ if (obj->isIndexed())
+ return JS_TRUE;
+ }
+ return JS_FALSE;
+}
+
+static JSBool
+array_defineProperty(JSContext *cx, JSObject *obj, jsid id, const Value *value,
+ PropertyOp getter, StrictPropertyOp setter, uintN attrs)
+{
+ if (JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom))
+ return JS_TRUE;
+
+ if (!obj->isDenseArray())
+ return js_DefineProperty(cx, obj, id, value, getter, setter, attrs);
+
+ do {
+ uint32 i = 0; // init to shut GCC up
+ bool isIndex = js_IdIsIndex(id, &i);
+ if (!isIndex || attrs != JSPROP_ENUMERATE)
+ break;
+
+ JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, i, 1);
+ if (result != JSObject::ED_OK) {
+ if (result == JSObject::ED_FAILED)
+ return false;
+ JS_ASSERT(result == JSObject::ED_SPARSE);
+ break;
+ }
+
+ if (i >= obj->getArrayLength())
+ obj->setArrayLength(i + 1);
+ obj->setDenseArrayElement(i, *value);
+ return true;
+ } while (false);
+
+ if (!obj->makeDenseArraySlow(cx))
+ return false;
+ return js_DefineProperty(cx, obj, id, value, getter, setter, attrs);
+}
+
+static JSBool
+array_getAttributes(JSContext *cx, JSObject *obj, jsid id, uintN *attrsp)
+{
+ *attrsp = JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom)
+ ? JSPROP_PERMANENT : JSPROP_ENUMERATE;
+ return JS_TRUE;
+}
+
+static JSBool
+array_setAttributes(JSContext *cx, JSObject *obj, jsid id, uintN *attrsp)
+{
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+ JSMSG_CANT_SET_ARRAY_ATTRS);
+ return JS_FALSE;
+}
+
+static JSBool
+array_deleteProperty(JSContext *cx, JSObject *obj, jsid id, Value *rval, JSBool strict)
+{
+ uint32 i;
+
+ if (!obj->isDenseArray())
+ return js_DeleteProperty(cx, obj, id, rval, strict);
+
+ if (JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom)) {
+ rval->setBoolean(false);
+ return JS_TRUE;
+ }
+
+ if (js_IdIsIndex(id, &i) && i < obj->getDenseArrayCapacity())
+ obj->setDenseArrayElement(i, MagicValue(JS_ARRAY_HOLE));
+
+ if (!js_SuppressDeletedProperty(cx, obj, id))
+ return false;
+
+ rval->setBoolean(true);
+ return JS_TRUE;
+}
+
+static void
+array_trace(JSTracer *trc, JSObject *obj)
+{
+ JS_ASSERT(obj->isDenseArray());
+
+ uint32 capacity = obj->getDenseArrayCapacity();
+ for (uint32 i = 0; i < capacity; i++)
+ MarkValue(trc, obj->getDenseArrayElement(i), "dense_array_elems");
+}
+
+static JSBool
+array_fix(JSContext *cx, JSObject *obj, bool *success, AutoIdVector *props)
+{
+ JS_ASSERT(obj->isDenseArray());
+
+ /*
+ * We must slowify dense arrays; otherwise, we'd need to detect assignments to holes,
+ * since that is effectively adding a new property to the array.
+ */
+ if (!obj->makeDenseArraySlow(cx) ||
+ !GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, props))
+ return false;
+
+ *success = true;
+ return true;
+}
+
+Class js_ArrayClass = {
+ "Array",
+ Class::NON_NATIVE |
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Array),
+ PropertyStub, /* addProperty */
+ PropertyStub, /* delProperty */
+ PropertyStub, /* getProperty */
+ StrictPropertyStub, /* setProperty */
+ EnumerateStub,
+ ResolveStub,
+ js_TryValueOf,
+ NULL,
+ NULL, /* reserved0 */
+ NULL, /* checkAccess */
+ NULL, /* call */
+ NULL, /* construct */
+ NULL, /* xdrObject */
+ NULL, /* hasInstance */
+ NULL, /* mark */
+ JS_NULL_CLASS_EXT,
+ {
+ array_lookupProperty,
+ array_defineProperty,
+ array_getProperty,
+ array_setProperty,
+ array_getAttributes,
+ array_setAttributes,
+ array_deleteProperty,
+ NULL, /* enumerate */
+ array_typeOf,
+ array_trace,
+ array_fix,
+ NULL, /* thisObject */
+ NULL, /* clear */
+ }
+};
+
+Class js_SlowArrayClass = {
+ "Array",
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Array),
+ slowarray_addProperty,
+ PropertyStub, /* delProperty */
+ PropertyStub, /* getProperty */
+ StrictPropertyStub, /* setProperty */
+ EnumerateStub,
+ ResolveStub,
+ js_TryValueOf
+};
+
+static bool
+AddLengthProperty(JSContext *cx, JSObject *obj)
+{
+ const jsid lengthId = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
+ JS_ASSERT(!obj->nativeLookup(lengthId));
+
+ return obj->addProperty(cx, lengthId, array_length_getter, array_length_setter,
+ SHAPE_INVALID_SLOT, JSPROP_PERMANENT | JSPROP_SHARED, 0, 0);
+}
+
+/*
+ * Convert an array object from fast-and-dense to slow-and-flexible.
+ */
+JSBool
+JSObject::makeDenseArraySlow(JSContext *cx)
+{
+ JS_ASSERT(isDenseArray());
+
+ /*
+ * Save old map now, before calling InitScopeForObject. We'll have to undo
+ * on error. This is gross, but a better way is not obvious.
+ */
+ JSObjectMap *oldMap = map;
+
+ /* Create a native scope. */
+ JSObject *arrayProto = getProto();
+ js::gc::FinalizeKind kind = js::gc::FinalizeKind(arena()->header()->thingKind);
+ if (!InitScopeForObject(cx, this, &js_SlowArrayClass, arrayProto, kind))
+ return false;
+
+ uint32 capacity = getDenseArrayCapacity();
+
+ /*
+ * Begin with the length property to share more of the property tree.
+ * The getter/setter here will directly access the object's private value.
+ */
+ if (!AddLengthProperty(cx, this)) {
+ setMap(oldMap);
+ return false;
+ }
+
+ /*
+ * Create new properties pointing to existing elements. Pack the array to
+ * remove holes, so that shapes use successive slots (as for other objects).
+ */
+ uint32 next = 0;
+ for (uint32 i = 0; i < capacity; i++) {
+ jsid id;
+ if (!ValueToId(cx, Int32Value(i), &id)) {
+ setMap(oldMap);
+ return false;
+ }
+
+ if (getDenseArrayElement(i).isMagic(JS_ARRAY_HOLE))
+ continue;
+
+ setDenseArrayElement(next, getDenseArrayElement(i));
+
+ if (!addDataProperty(cx, id, next, JSPROP_ENUMERATE)) {
+ setMap(oldMap);
+ return false;
+ }
+
+ next++;
+ }
+
+ /*
+ * Dense arrays with different numbers of slots but the same number of fixed
+ * slots and the same non-hole indexes must use their fixed slots consistently.
+ */
+ if (hasSlotsArray() && next <= numFixedSlots())
+ revertToFixedSlots(cx);
+
+ ClearValueRange(slots + next, this->capacity - next, false);
+
+ /*
+ * Finally, update class. If |this| is Array.prototype, then js_InitClass
+ * will create an emptyShape whose class is &js_SlowArrayClass, to ensure
+ * that delegating instances can share shapes in the tree rooted at the
+ * proto's empty shape.
+ */
+ clasp = &js_SlowArrayClass;
+ return true;
+}
+
+/* Transfer ownership of buffer to returned string. */
+static inline JSBool
+BufferToString(JSContext *cx, StringBuffer &sb, Value *rval)
+{
+ JSString *str = sb.finishString();
+ if (!str)
+ return false;
+ rval->setString(str);
+ return true;
+}
+
+#if JS_HAS_TOSOURCE
+static JSBool
+array_toSource(JSContext *cx, uintN argc, Value *vp)
+{
+ JS_CHECK_RECURSION(cx, return false);
+
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return false;
+ if (!obj->isSlowArray() && !InstanceOf(cx, obj, &js_ArrayClass, vp + 2))
+ return false;
+
+ /* Find joins or cycles in the reachable object graph. */
+ jschar *sharpchars;
+ JSHashEntry *he = js_EnterSharpObject(cx, obj, NULL, &sharpchars);
+ if (!he)
+ return false;
+ bool initiallySharp = IS_SHARP(he);
+
+ /* After this point, all paths exit through the 'out' label. */
+ MUST_FLOW_THROUGH("out");
+ bool ok = false;
+
+ /*
+ * This object will take responsibility for the jschar buffer until the
+ * buffer is transferred to the returned JSString.
+ */
+ StringBuffer sb(cx);
+
+ /* Cycles/joins are indicated by sharp objects. */
+#if JS_HAS_SHARP_VARS
+ if (IS_SHARP(he)) {
+ JS_ASSERT(sharpchars != 0);
+ sb.replaceRawBuffer(sharpchars, js_strlen(sharpchars));
+ goto make_string;
+ } else if (sharpchars) {
+ MAKE_SHARP(he);
+ sb.replaceRawBuffer(sharpchars, js_strlen(sharpchars));
+ }
+#else
+ if (IS_SHARP(he)) {
+ if (!sb.append("[]"))
+ goto out;
+ cx->free(sharpchars);
+ goto make_string;
+ }
+#endif
+
+ if (!sb.append('['))
+ goto out;
+
+ jsuint length;
+ if (!js_GetLengthProperty(cx, obj, &length))
+ goto out;
+
+ for (jsuint index = 0; index < length; index++) {
+ /* Use vp to locally root each element value. */
+ JSBool hole;
+ if (!JS_CHECK_OPERATION_LIMIT(cx) ||
+ !GetElement(cx, obj, index, &hole, vp)) {
+ goto out;
+ }
+
+ /* Get element's character string. */
+ JSString *str;
+ if (hole) {
+ str = cx->runtime->emptyString;
+ } else {
+ str = js_ValueToSource(cx, *vp);
+ if (!str)
+ goto out;
+ }
+ vp->setString(str);
+
+ const jschar *chars = str->getChars(cx);
+ if (!chars)
+ goto out;
+
+ /* Append element to buffer. */
+ if (!sb.append(chars, chars + str->length()))
+ goto out;
+ if (index + 1 != length) {
+ if (!sb.append(", "))
+ goto out;
+ } else if (hole) {
+ if (!sb.append(','))
+ goto out;
+ }
+ }
+
+ /* Finalize the buffer. */
+ if (!sb.append(']'))
+ goto out;
+
+ make_string:
+ if (!BufferToString(cx, sb, vp))
+ goto out;
+
+ ok = true;
+
+ out:
+ if (!initiallySharp)
+ js_LeaveSharpObject(cx, NULL);
+ return ok;
+}
+#endif
+
+static JSBool
+array_toString_sub(JSContext *cx, JSObject *obj, JSBool locale,
+ JSString *sepstr, Value *rval)
+{
+ JS_CHECK_RECURSION(cx, return false);
+
+ /* Get characters to use for the separator. */
+ static const jschar comma = ',';
+ const jschar *sep;
+ size_t seplen;
+ if (sepstr) {
+ seplen = sepstr->length();
+ sep = sepstr->getChars(cx);
+ if (!sep)
+ return false;
+ } else {
+ sep = &comma;
+ seplen = 1;
+ }
+
+ /*
+ * Use HashTable entry as the cycle indicator. On first visit, create the
+ * entry, and, when leaving, remove the entry.
+ */
+ BusyArraysMap::AddPtr hashp = cx->busyArrays.lookupForAdd(obj);
+ uint32 genBefore;
+ if (!hashp) {
+ /* Not in hash table, so not a cycle. */
+ if (!cx->busyArrays.add(hashp, obj))
+ return false;
+ genBefore = cx->busyArrays.generation();
+ } else {
+ /* Cycle, so return empty string. */
+ rval->setString(ATOM_TO_STRING(cx->runtime->atomState.emptyAtom));
+ return true;
+ }
+
+ AutoObjectRooter tvr(cx, obj);
+
+ /* After this point, all paths exit through the 'out' label. */
+ MUST_FLOW_THROUGH("out");
+ bool ok = false;
+
+ /*
+ * This object will take responsibility for the jschar buffer until the
+ * buffer is transferred to the returned JSString.
+ */
+ StringBuffer sb(cx);
+
+ jsuint length;
+ if (!js_GetLengthProperty(cx, obj, &length))
+ goto out;
+
+ for (jsuint index = 0; index < length; index++) {
+ /* Use rval to locally root each element value. */
+ JSBool hole;
+ if (!JS_CHECK_OPERATION_LIMIT(cx) ||
+ !GetElement(cx, obj, index, &hole, rval)) {
+ goto out;
+ }
+
+ /* Get element's character string. */
+ if (!(hole || rval->isNullOrUndefined())) {
+ if (locale) {
+ /* Work on obj.toLocalString() instead. */
+ JSObject *robj;
+
+ if (!js_ValueToObjectOrNull(cx, *rval, &robj))
+ goto out;
+ rval->setObjectOrNull(robj);
+ JSAtom *atom = cx->runtime->atomState.toLocaleStringAtom;
+ if (!js_TryMethod(cx, robj, atom, 0, NULL, rval))
+ goto out;
+ }
+
+ if (!ValueToStringBuffer(cx, *rval, sb))
+ goto out;
+ }
+
+ /* Append the separator. */
+ if (index + 1 != length) {
+ if (!sb.append(sep, seplen))
+ goto out;
+ }
+ }
+
+ /* Finalize the buffer. */
+ if (!BufferToString(cx, sb, rval))
+ goto out;
+
+ ok = true;
+
+ out:
+ if (genBefore == cx->busyArrays.generation())
+ cx->busyArrays.remove(hashp);
+ else
+ cx->busyArrays.remove(obj);
+ return ok;
+}
+
+/* ES5 15.4.4.2. NB: The algorithm here differs from the one in ES3. */
+static JSBool
+array_toString(JSContext *cx, uintN argc, Value *vp)
+{
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return false;
+
+ Value &join = vp[0];
+ if (!obj->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.joinAtom), &join))
+ return false;
+
+ if (!js_IsCallable(join)) {
+ JSString *str = obj_toStringHelper(cx, obj);
+ if (!str)
+ return false;
+ vp->setString(str);
+ return true;
+ }
+
+ LeaveTrace(cx);
+ InvokeArgsGuard args;
+ if (!cx->stack().pushInvokeArgs(cx, 0, &args))
+ return false;
+
+ args.callee() = join;
+ args.thisv().setObject(*obj);
+
+ /* Do the call. */
+ if (!Invoke(cx, args, 0))
+ return false;
+ *vp = args.rval();
+ return true;
+}
+
+static JSBool
+array_toLocaleString(JSContext *cx, uintN argc, Value *vp)
+{
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return false;
+
+ /*
+ * Passing comma here as the separator. Need a way to get a
+ * locale-specific version.
+ */
+ return array_toString_sub(cx, obj, JS_TRUE, NULL, vp);
+}
+
+static JSBool
+InitArrayElements(JSContext *cx, JSObject *obj, jsuint start, jsuint count, Value *vector)
+{
+ JS_ASSERT(count < MAXINDEX);
+
+ /*
+ * Optimize for dense arrays so long as adding the given set of elements
+ * wouldn't otherwise make the array slow.
+ */
+ do {
+ if (!obj->isDenseArray())
+ break;
+ if (js_PrototypeHasIndexedProperties(cx, obj))
+ break;
+
+ JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, start, count);
+ if (result != JSObject::ED_OK) {
+ if (result == JSObject::ED_FAILED)
+ return false;
+ JS_ASSERT(result == JSObject::ED_SPARSE);
+ break;
+ }
+ jsuint newlen = start + count;
+ if (newlen > obj->getArrayLength())
+ obj->setArrayLength(newlen);
+
+ JS_ASSERT(count < uint32(-1) / sizeof(Value));
+ memcpy(obj->getDenseArrayElements() + start, vector, sizeof(jsval) * count);
+ JS_ASSERT_IF(count != 0, !obj->getDenseArrayElement(newlen - 1).isMagic(JS_ARRAY_HOLE));
+ return true;
+ } while (false);
+
+ Value* end = vector + count;
+ while (vector != end && start < MAXINDEX) {
+ if (!JS_CHECK_OPERATION_LIMIT(cx) ||
+ !SetArrayElement(cx, obj, start++, *vector++)) {
+ return JS_FALSE;
+ }
+ }
+
+ if (vector == end)
+ return JS_TRUE;
+
+ /* Finish out any remaining elements past the max array index. */
+ if (obj->isDenseArray() && !ENSURE_SLOW_ARRAY(cx, obj))
+ return JS_FALSE;
+
+ JS_ASSERT(start == MAXINDEX);
+ AutoValueRooter tvr(cx);
+ AutoIdRooter idr(cx);
+ Value idval = DoubleValue(MAXINDEX);
+ do {
+ *tvr.addr() = *vector++;
+ if (!js_ValueToStringId(cx, idval, idr.addr()) ||
+ !obj->setProperty(cx, idr.id(), tvr.addr(), true)) {
+ return JS_FALSE;
+ }
+ idval.getDoubleRef() += 1;
+ } while (vector != end);
+
+ return JS_TRUE;
+}
+
+static JSBool
+InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, const Value *vector)
+{
+ JS_ASSERT(obj->isArray());
+
+ JS_ASSERT(obj->isDenseArray());
+ obj->setArrayLength(length);
+ if (!vector || !length)
+ return true;
+
+ /* Avoid ensureDenseArrayElements to skip sparse array checks there. */
+ if (!obj->ensureSlots(cx, length))
+ return false;
+ memcpy(obj->getDenseArrayElements(), vector, length * sizeof(Value));
+ return true;
+}
+
+/*
+ * Perl-inspired join, reverse, and sort.
+ */
+static JSBool
+array_join(JSContext *cx, uintN argc, Value *vp)
+{
+ JSString *str;
+ if (argc == 0 || vp[2].isUndefined()) {
+ str = NULL;
+ } else {
+ str = js_ValueToString(cx, vp[2]);
+ if (!str)
+ return JS_FALSE;
+ vp[2].setString(str);
+ }
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return false;
+ return array_toString_sub(cx, obj, JS_FALSE, str, vp);
+}
+
+static JSBool
+array_reverse(JSContext *cx, uintN argc, Value *vp)
+{
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return false;
+
+ jsuint len;
+ if (!js_GetLengthProperty(cx, obj, &len))
+ return false;
+ vp->setObject(*obj);
+
+ do {
+ if (!obj->isDenseArray())
+ break;
+ if (js_PrototypeHasIndexedProperties(cx, obj))
+ break;
+
+ /* An empty array or an array with no elements is already reversed. */
+ if (len == 0 || obj->getDenseArrayCapacity() == 0)
+ return true;
+
+ /*
+ * It's actually surprisingly complicated to reverse an array due to the
+ * orthogonality of array length and array capacity while handling
+ * leading and trailing holes correctly. Reversing seems less likely to
+ * be a common operation than other array mass-mutation methods, so for
+ * now just take a probably-small memory hit (in the absence of too many
+ * holes in the array at its start) and ensure that the capacity is
+ * sufficient to hold all the elements in the array if it were full.
+ */
+ JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, len, 0);
+ if (result != JSObject::ED_OK) {
+ if (result == JSObject::ED_FAILED)
+ return false;
+ JS_ASSERT(result == JSObject::ED_SPARSE);
+ break;
+ }
+
+ uint32 lo = 0, hi = len - 1;
+ for (; lo < hi; lo++, hi--) {
+ Value origlo = obj->getDenseArrayElement(lo);
+ Value orighi = obj->getDenseArrayElement(hi);
+ obj->setDenseArrayElement(lo, orighi);
+ if (orighi.isMagic(JS_ARRAY_HOLE) &&
+ !js_SuppressDeletedProperty(cx, obj, INT_TO_JSID(lo))) {
+ return false;
+ }
+ obj->setDenseArrayElement(hi, origlo);
+ if (origlo.isMagic(JS_ARRAY_HOLE) &&
+ !js_SuppressDeletedProperty(cx, obj, INT_TO_JSID(hi))) {
+ return false;
+ }
+ }
+
+ /*
+ * Per ECMA-262, don't update the length of the array, even if the new
+ * array has trailing holes (and thus the original array began with
+ * holes).
+ */
+ return true;
+ } while (false);
+
+ AutoValueRooter tvr(cx);
+ for (jsuint i = 0, half = len / 2; i < half; i++) {
+ JSBool hole, hole2;
+ if (!JS_CHECK_OPERATION_LIMIT(cx) ||
+ !GetElement(cx, obj, i, &hole, tvr.addr()) ||
+ !GetElement(cx, obj, len - i - 1, &hole2, vp) ||
+ !SetOrDeleteArrayElement(cx, obj, len - i - 1, hole, tvr.value()) ||
+ !SetOrDeleteArrayElement(cx, obj, i, hole2, *vp)) {
+ return false;
+ }
+ }
+ vp->setObject(*obj);
+ return true;
+}
+
+typedef struct MSortArgs {
+ size_t elsize;
+ JSComparator cmp;
+ void *arg;
+ JSBool isValue;
+} MSortArgs;
+
+/* Helper function for js_MergeSort. */
+static JSBool
+MergeArrays(MSortArgs *msa, void *src, void *dest, size_t run1, size_t run2)
+{
+ void *arg, *a, *b, *c;
+ size_t elsize, runtotal;
+ int cmp_result;
+ JSComparator cmp;
+ JSBool isValue;
+
+ runtotal = run1 + run2;
+
+ elsize = msa->elsize;
+ cmp = msa->cmp;
+ arg = msa->arg;
+ isValue = msa->isValue;
+
+#define CALL_CMP(a, b) \
+ if (!cmp(arg, (a), (b), &cmp_result)) return JS_FALSE;
+
+ /* Copy runs already in sorted order. */
+ b = (char *)src + run1 * elsize;
+ a = (char *)b - elsize;
+ CALL_CMP(a, b);
+ if (cmp_result <= 0) {
+ memcpy(dest, src, runtotal * elsize);
+ return JS_TRUE;
+ }
+
+#define COPY_ONE(p,q,n) \
+ (isValue ? (void)(*(Value*)p = *(Value*)q) : (void)memcpy(p, q, n))
+
+ a = src;
+ c = dest;
+ for (; runtotal != 0; runtotal--) {
+ JSBool from_a = run2 == 0;
+ if (!from_a && run1 != 0) {
+ CALL_CMP(a,b);
+ from_a = cmp_result <= 0;
+ }
+
+ if (from_a) {
+ COPY_ONE(c, a, elsize);
+ run1--;
+ a = (char *)a + elsize;
+ } else {
+ COPY_ONE(c, b, elsize);
+ run2--;
+ b = (char *)b + elsize;
+ }
+ c = (char *)c + elsize;
+ }
+#undef COPY_ONE
+#undef CALL_CMP
+
+ return JS_TRUE;
+}
+
+/*
+ * This sort is stable, i.e. sequence of equal elements is preserved.
+ * See also bug #224128.
+ */
+bool
+js_MergeSort(void *src, size_t nel, size_t elsize,
+ JSComparator cmp, void *arg, void *tmp,
+ JSMergeSortElemType elemType)
+{
+ void *swap, *vec1, *vec2;
+ MSortArgs msa;
+ size_t i, j, lo, hi, run;
+ int cmp_result;
+
+ JS_ASSERT_IF(JS_SORTING_VALUES, elsize == sizeof(Value));
+ bool isValue = elemType == JS_SORTING_VALUES;
+
+ /* Avoid memcpy overhead for word-sized and word-aligned elements. */
+#define COPY_ONE(p,q,n) \
+ (isValue ? (void)(*(Value*)p = *(Value*)q) : (void)memcpy(p, q, n))
+#define CALL_CMP(a, b) \
+ if (!cmp(arg, (a), (b), &cmp_result)) return JS_FALSE;
+#define INS_SORT_INT 4
+
+ /*
+ * Apply insertion sort to small chunks to reduce the number of merge
+ * passes needed.
+ */
+ for (lo = 0; lo < nel; lo += INS_SORT_INT) {
+ hi = lo + INS_SORT_INT;
+ if (hi >= nel)
+ hi = nel;
+ for (i = lo + 1; i < hi; i++) {
+ vec1 = (char *)src + i * elsize;
+ vec2 = (char *)vec1 - elsize;
+ for (j = i; j > lo; j--) {
+ CALL_CMP(vec2, vec1);
+ /* "<=" instead of "<" insures the sort is stable */
+ if (cmp_result <= 0) {
+ break;
+ }
+
+ /* Swap elements, using "tmp" as tmp storage */
+ COPY_ONE(tmp, vec2, elsize);
+ COPY_ONE(vec2, vec1, elsize);
+ COPY_ONE(vec1, tmp, elsize);
+ vec1 = vec2;
+ vec2 = (char *)vec1 - elsize;
+ }
+ }
+ }
+#undef CALL_CMP
+#undef COPY_ONE
+
+ msa.elsize = elsize;
+ msa.cmp = cmp;
+ msa.arg = arg;
+ msa.isValue = isValue;
+
+ vec1 = src;
+ vec2 = tmp;
+ for (run = INS_SORT_INT; run < nel; run *= 2) {
+ for (lo = 0; lo < nel; lo += 2 * run) {
+ hi = lo + run;
+ if (hi >= nel) {
+ memcpy((char *)vec2 + lo * elsize, (char *)vec1 + lo * elsize,
+ (nel - lo) * elsize);
+ break;
+ }
+ if (!MergeArrays(&msa, (char *)vec1 + lo * elsize,
+ (char *)vec2 + lo * elsize, run,
+ hi + run > nel ? nel - hi : run)) {
+ return JS_FALSE;
+ }
+ }
+ swap = vec1;
+ vec1 = vec2;
+ vec2 = swap;
+ }
+ if (src != vec1)
+ memcpy(src, tmp, nel * elsize);
+
+ return JS_TRUE;
+}
+
+struct CompareArgs
+{
+ JSContext *context;
+ InvokeSessionGuard session;
+
+ CompareArgs(JSContext *cx)
+ : context(cx)
+ {}
+};
+
+static JS_REQUIRES_STACK JSBool
+sort_compare(void *arg, const void *a, const void *b, int *result)
+{
+ const Value *av = (const Value *)a, *bv = (const Value *)b;
+ CompareArgs *ca = (CompareArgs *) arg;
+ JSContext *cx = ca->context;
+
+ /*
+ * array_sort deals with holes and undefs on its own and they should not
+ * come here.
+ */
+ JS_ASSERT(!av->isMagic() && !av->isUndefined());
+ JS_ASSERT(!av->isMagic() && !bv->isUndefined());
+
+ if (!JS_CHECK_OPERATION_LIMIT(cx))
+ return JS_FALSE;
+
+ InvokeSessionGuard &session = ca->session;
+ session[0] = *av;
+ session[1] = *bv;
+
+ if (!session.invoke(cx))
+ return JS_FALSE;
+
+ jsdouble cmp;
+ if (!ValueToNumber(cx, session.rval(), &cmp))
+ return JS_FALSE;
+
+ /* Clamp cmp to -1, 0, 1. */
+ *result = 0;
+ if (!JSDOUBLE_IS_NaN(cmp) && cmp != 0)
+ *result = cmp > 0 ? 1 : -1;
+
+ /*
+ * XXX else report some kind of error here? ECMA talks about 'consistent
+ * compare functions' that don't return NaN, but is silent about what the
+ * result should be. So we currently ignore it.
+ */
+
+ return JS_TRUE;
+}
+
+typedef JSBool (JS_REQUIRES_STACK *JSRedComparator)(void*, const void*,
+ const void*, int *);
+
+static inline JS_IGNORE_STACK JSComparator
+comparator_stack_cast(JSRedComparator func)
+{
+ return func;
+}
+
+static int
+sort_compare_strings(void *arg, const void *a, const void *b, int *result)
+{
+ JSContext *cx = (JSContext *)arg;
+ JSString *astr = ((const Value *)a)->toString();
+ JSString *bstr = ((const Value *)b)->toString();
+ return JS_CHECK_OPERATION_LIMIT(cx) && CompareStrings(cx, astr, bstr, result);
+}
+
+JSBool
+js::array_sort(JSContext *cx, uintN argc, Value *vp)
+{
+ jsuint len, newlen, i, undefs;
+ size_t elemsize;
+ JSString *str;
+
+ Value *argv = JS_ARGV(cx, vp);
+ Value fval;
+ if (argc > 0 && !argv[0].isUndefined()) {
+ if (argv[0].isPrimitive()) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_SORT_ARG);
+ return false;
+ }
+ fval = argv[0]; /* non-default compare function */
+ } else {
+ fval.setNull();
+ }
+
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return false;
+ if (!js_GetLengthProperty(cx, obj, &len))
+ return false;
+ if (len == 0) {
+ vp->setObject(*obj);
+ return true;
+ }
+
+ /*
+ * We need a temporary array of 2 * len Value to hold the array elements
+ * and the scratch space for merge sort. Check that its size does not
+ * overflow size_t, which would allow for indexing beyond the end of the
+ * malloc'd vector.
+ */
+#if JS_BITS_PER_WORD == 32
+ if (size_t(len) > size_t(-1) / (2 * sizeof(Value))) {
+ js_ReportAllocationOverflow(cx);
+ return false;
+ }
+#endif
+
+ /*
+ * Initialize vec as a root. We will clear elements of vec one by
+ * one while increasing the rooted amount of vec when we know that the
+ * property at the corresponding index exists and its value must be rooted.
+ *
+ * In this way when sorting a huge mostly sparse array we will not
+ * access the tail of vec corresponding to properties that do not
+ * exist, allowing OS to avoiding committing RAM. See bug 330812.
+ */
+ {
+ Value *vec = (Value *) cx->malloc(2 * size_t(len) * sizeof(Value));
+ if (!vec)
+ return false;
+
+ DEFINE_LOCAL_CLASS_OF_STATIC_FUNCTION(AutoFreeVector) {
+ JSContext *const cx;
+ Value *&vec;
+ public:
+ AutoFreeVector(JSContext *cx, Value *&vec) : cx(cx), vec(vec) { }
+ ~AutoFreeVector() {
+ cx->free(vec);
+ }
+ } free(cx, vec);
+
+ AutoArrayRooter tvr(cx, 0, vec);
+
+ /*
+ * By ECMA 262, 15.4.4.11, a property that does not exist (which we
+ * call a "hole") is always greater than an existing property with
+ * value undefined and that is always greater than any other property.
+ * Thus to sort holes and undefs we simply count them, sort the rest
+ * of elements, append undefs after them and then make holes after
+ * undefs.
+ */
+ undefs = 0;
+ newlen = 0;
+ bool allStrings = true;
+ for (i = 0; i < len; i++) {
+ if (!JS_CHECK_OPERATION_LIMIT(cx))
+ return false;
+
+ /* Clear vec[newlen] before including it in the rooted set. */
+ JSBool hole;
+ vec[newlen].setNull();
+ tvr.changeLength(newlen + 1);
+ if (!GetElement(cx, obj, i, &hole, &vec[newlen]))
+ return false;
+
+ if (hole)
+ continue;
+
+ if (vec[newlen].isUndefined()) {
+ ++undefs;
+ continue;
+ }
+
+ allStrings = allStrings && vec[newlen].isString();
+
+ ++newlen;
+ }
+
+ if (newlen == 0) {
+ vp->setObject(*obj);
+ return true; /* The array has only holes and undefs. */
+ }
+
+ /*
+ * The first newlen elements of vec are copied from the array object
+ * (above). The remaining newlen positions are used as GC-rooted scratch
+ * space for mergesort. We must clear the space before including it to
+ * the root set covered by tvr.count.
+ */
+ Value *mergesort_tmp = vec + newlen;
+ MakeRangeGCSafe(mergesort_tmp, newlen);
+ tvr.changeLength(newlen * 2);
+
+ /* Here len == 2 * (newlen + undefs + number_of_holes). */
+ if (fval.isNull()) {
+ /*
+ * Sort using the default comparator converting all elements to
+ * strings.
+ */
+ if (allStrings) {
+ elemsize = sizeof(Value);
+ } else {
+ /*
+ * To avoid string conversion on each compare we do it only once
+ * prior to sorting. But we also need the space for the original
+ * values to recover the sorting result. To reuse
+ * sort_compare_strings we move the original values to the odd
+ * indexes in vec, put the string conversion results in the even
+ * indexes and pass 2 * sizeof(Value) as an element size to the
+ * sorting function. In this way sort_compare_strings will only
+ * see the string values when it casts the compare arguments as
+ * pointers to Value.
+ *
+ * This requires doubling the temporary storage including the
+ * scratch space for the merge sort. Since vec already contains
+ * the rooted scratch space for newlen elements at the tail, we
+ * can use it to rearrange and convert to strings first and try
+ * realloc only when we know that we successfully converted all
+ * the elements.
+ */
+#if JS_BITS_PER_WORD == 32
+ if (size_t(newlen) > size_t(-1) / (4 * sizeof(Value))) {
+ js_ReportAllocationOverflow(cx);
+ return false;
+ }
+#endif
+
+ /*
+ * Rearrange and string-convert the elements of the vector from
+ * the tail here and, after sorting, move the results back
+ * starting from the start to prevent overwrite the existing
+ * elements.
+ */
+ i = newlen;
+ do {
+ --i;
+ if (!JS_CHECK_OPERATION_LIMIT(cx))
+ return false;
+ const Value &v = vec[i];
+ str = js_ValueToString(cx, v);
+ if (!str)
+ return false;
+ // Copying v must come first, because the following line overwrites v
+ // when i == 0.
+ vec[2 * i + 1] = v;
+ vec[2 * i].setString(str);
+ } while (i != 0);
+
+ JS_ASSERT(tvr.array == vec);
+ vec = (Value *) cx->realloc(vec, 4 * size_t(newlen) * sizeof(Value));
+ if (!vec) {
+ vec = tvr.array; /* N.B. AutoFreeVector */
+ return false;
+ }
+ mergesort_tmp = vec + 2 * newlen;
+ MakeRangeGCSafe(mergesort_tmp, 2 * newlen);
+ tvr.changeArray(vec, newlen * 4);
+ elemsize = 2 * sizeof(Value);
+ }
+ if (!js_MergeSort(vec, size_t(newlen), elemsize,
+ sort_compare_strings, cx, mergesort_tmp,
+ JS_SORTING_GENERIC)) {
+ return false;
+ }
+ if (!allStrings) {
+ /*
+ * We want to make the following loop fast and to unroot the
+ * cached results of toString invocations before the operation
+ * callback has a chance to run the GC. For this reason we do
+ * not call JS_CHECK_OPERATION_LIMIT in the loop.
+ */
+ i = 0;
+ do {
+ vec[i] = vec[2 * i + 1];
+ } while (++i != newlen);
+ }
+ } else {
+ CompareArgs ca(cx);
+ if (!ca.session.start(cx, fval, UndefinedValue(), 2))
+ return false;
+
+ if (!js_MergeSort(vec, size_t(newlen), sizeof(Value),
+ comparator_stack_cast(sort_compare),
+ &ca, mergesort_tmp,
+ JS_SORTING_VALUES)) {
+ return false;
+ }
+ }
+
+ /*
+ * We no longer need to root the scratch space for the merge sort, so
+ * unroot it now to make the job of a potential GC under
+ * InitArrayElements easier.
+ */
+ tvr.changeLength(newlen);
+ if (!InitArrayElements(cx, obj, 0, newlen, vec))
+ return false;
+ }
+
+ /* Set undefs that sorted after the rest of elements. */
+ while (undefs != 0) {
+ --undefs;
+ if (!JS_CHECK_OPERATION_LIMIT(cx) ||
+ !SetArrayElement(cx, obj, newlen++, UndefinedValue())) {
+ return false;
+ }
+ }
+
+ /* Re-create any holes that sorted to the end of the array. */
+ while (len > newlen) {
+ if (!JS_CHECK_OPERATION_LIMIT(cx) || DeleteArrayElement(cx, obj, --len, true) < 0)
+ return false;
+ }
+ vp->setObject(*obj);
+ return true;
+}
+
+/*
+ * Perl-inspired push, pop, shift, unshift, and splice methods.
+ */
+static JSBool
+array_push_slowly(JSContext *cx, JSObject *obj, uintN argc, Value *argv, Value *rval)
+{
+ jsuint length;
+
+ if (!js_GetLengthProperty(cx, obj, &length))
+ return JS_FALSE;
+ if (!InitArrayElements(cx, obj, length, argc, argv))
+ return JS_FALSE;
+
+ /* Per ECMA-262, return the new array length. */
+ jsdouble newlength = length + jsdouble(argc);
+ rval->setNumber(newlength);
+ return js_SetLengthProperty(cx, obj, newlength);
+}
+
+static JSBool
+array_push1_dense(JSContext* cx, JSObject* obj, const Value &v, Value *rval)
+{
+ uint32 length = obj->getArrayLength();
+ do {
+ JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, length, 1);
+ if (result != JSObject::ED_OK) {
+ if (result == JSObject::ED_FAILED)
+ return false;
+ JS_ASSERT(result == JSObject::ED_SPARSE);
+ break;
+ }
+
+ obj->setArrayLength(length + 1);
+
+ JS_ASSERT(obj->getDenseArrayElement(length).isMagic(JS_ARRAY_HOLE));
+ obj->setDenseArrayElement(length, v);
+ rval->setNumber(obj->getArrayLength());
+ return true;
+ } while (false);
+
+ if (!obj->makeDenseArraySlow(cx))
+ return false;
+ Value tmp = v;
+ return array_push_slowly(cx, obj, 1, &tmp, rval);
+}
+
+JS_ALWAYS_INLINE JSBool
+ArrayCompPushImpl(JSContext *cx, JSObject *obj, const Value &v)
+{
+ uint32 length = obj->getArrayLength();
+ if (obj->isSlowArray()) {
+ /* This can happen in one evil case. See bug 630377. */
+ jsid id;
+ return js_IndexToId(cx, length, &id) &&
+ js_DefineProperty(cx, obj, id, &v, NULL, NULL, JSPROP_ENUMERATE);
+ }
+
+ JS_ASSERT(obj->isDenseArray());
+ JS_ASSERT(length <= obj->getDenseArrayCapacity());
+
+ if (length == obj->getDenseArrayCapacity()) {
+ if (length > JS_ARGS_LENGTH_MAX) {
+ JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL,
+ JSMSG_ARRAY_INIT_TOO_BIG);
+ return false;
+ }
+
+ /*
+ * An array comprehension cannot add holes to the array. So we can use
+ * ensureSlots instead of ensureDenseArrayElements.
+ */
+ if (!obj->ensureSlots(cx, length + 1))
+ return false;
+ }
+ obj->setArrayLength(length + 1);
+ obj->setDenseArrayElement(length, v);
+ return true;
+}
+
+JSBool
+js_ArrayCompPush(JSContext *cx, JSObject *obj, const Value &vp)
+{
+ return ArrayCompPushImpl(cx, obj, vp);
+}
+
+#ifdef JS_TRACER
+JSBool JS_FASTCALL
+js_ArrayCompPush_tn(JSContext *cx, JSObject *obj, ValueArgType v)
+{
+ TraceMonitor *tm = JS_TRACE_MONITOR_ON_TRACE(cx);
+
+ if (!ArrayCompPushImpl(cx, obj, ValueArgToConstRef(v))) {
+ SetBuiltinError(tm);
+ return JS_FALSE;
+ }
+
+ return WasBuiltinSuccessful(tm);
+}
+JS_DEFINE_CALLINFO_3(extern, BOOL_FAIL, js_ArrayCompPush_tn, CONTEXT, OBJECT,
+ VALUE, 0, nanojit::ACCSET_STORE_ANY)
+#endif
+
+static JSBool
+array_push(JSContext *cx, uintN argc, Value *vp)
+{
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return false;
+
+ /* Insist on one argument and obj of the expected class. */
+ if (argc != 1 || !obj->isDenseArray())
+ return array_push_slowly(cx, obj, argc, vp + 2, vp);
+
+ return array_push1_dense(cx, obj, vp[2], vp);
+}
+
+static JSBool
+array_pop_slowly(JSContext *cx, JSObject* obj, Value *vp)
+{
+ jsuint index;
+ JSBool hole;
+
+ if (!js_GetLengthProperty(cx, obj, &index))
+ return JS_FALSE;
+ if (index == 0) {
+ vp->setUndefined();
+ } else {
+ index--;
+
+ /* Get the to-be-deleted property's value into vp. */
+ if (!GetElement(cx, obj, index, &hole, vp))
+ return JS_FALSE;
+ if (!hole && DeleteArrayElement(cx, obj, index, true) < 0)
+ return JS_FALSE;
+ }
+ return js_SetLengthProperty(cx, obj, index);
+}
+
+static JSBool
+array_pop_dense(JSContext *cx, JSObject* obj, Value *vp)
+{
+ jsuint index;
+ JSBool hole;
+
+ index = obj->getArrayLength();
+ if (index == 0) {
+ vp->setUndefined();
+ return JS_TRUE;
+ }
+ index--;
+ if (!GetElement(cx, obj, index, &hole, vp))
+ return JS_FALSE;
+ if (!hole && DeleteArrayElement(cx, obj, index, true) < 0)
+ return JS_FALSE;
+ obj->setArrayLength(index);
+ return JS_TRUE;
+}
+
+static JSBool
+array_pop(JSContext *cx, uintN argc, Value *vp)
+{
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return false;
+ if (obj->isDenseArray())
+ return array_pop_dense(cx, obj, vp);
+ return array_pop_slowly(cx, obj, vp);
+}
+
+static JSBool
+array_shift(JSContext *cx, uintN argc, Value *vp)
+{
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return JS_FALSE;
+
+ jsuint length;
+ if (!js_GetLengthProperty(cx, obj, &length))
+ return JS_FALSE;
+
+ if (length == 0) {
+ vp->setUndefined();
+ } else {
+ length--;
+
+ if (obj->isDenseArray() && !js_PrototypeHasIndexedProperties(cx, obj) &&
+ length < obj->getDenseArrayCapacity()) {
+ *vp = obj->getDenseArrayElement(0);
+ if (vp->isMagic(JS_ARRAY_HOLE))
+ vp->setUndefined();
+ Value *elems = obj->getDenseArrayElements();
+ memmove(elems, elems + 1, length * sizeof(jsval));
+ obj->setDenseArrayElement(length, MagicValue(JS_ARRAY_HOLE));
+ obj->setArrayLength(length);
+ if (!js_SuppressDeletedProperty(cx, obj, INT_TO_JSID(length)))
+ return JS_FALSE;
+ return JS_TRUE;
+ }
+
+ /* Get the to-be-deleted property's value into vp ASAP. */
+ JSBool hole;
+ if (!GetElement(cx, obj, 0, &hole, vp))
+ return JS_FALSE;
+
+ /* Slide down the array above the first element. */
+ AutoValueRooter tvr(cx);
+ for (jsuint i = 0; i < length; i++) {
+ if (!JS_CHECK_OPERATION_LIMIT(cx) ||
+ !GetElement(cx, obj, i + 1, &hole, tvr.addr()) ||
+ !SetOrDeleteArrayElement(cx, obj, i, hole, tvr.value())) {
+ return JS_FALSE;
+ }
+ }
+
+ /* Delete the only or last element when it exists. */
+ if (!hole && DeleteArrayElement(cx, obj, length, true) < 0)
+ return JS_FALSE;
+ }
+ return js_SetLengthProperty(cx, obj, length);
+}
+
+static JSBool
+array_unshift(JSContext *cx, uintN argc, Value *vp)
+{
+ Value *argv;
+ JSBool hole;
+ jsdouble last, newlen;
+
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return false;
+
+ jsuint length;
+ if (!js_GetLengthProperty(cx, obj, &length))
+ return JS_FALSE;
+ newlen = length;
+ if (argc > 0) {
+ /* Slide up the array to make room for argc at the bottom. */
+ argv = JS_ARGV(cx, vp);
+ if (length > 0) {
+ bool optimized = false;
+ do {
+ if (!obj->isDenseArray())
+ break;
+ if (js_PrototypeHasIndexedProperties(cx, obj))
+ break;
+ JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, length, argc);
+ if (result != JSObject::ED_OK) {
+ if (result == JSObject::ED_FAILED)
+ return false;
+ JS_ASSERT(result == JSObject::ED_SPARSE);
+ break;
+ }
+ Value *elems = obj->getDenseArrayElements();
+ memmove(elems + argc, elems, length * sizeof(jsval));
+ for (uint32 i = 0; i < argc; i++)
+ obj->setDenseArrayElement(i, MagicValue(JS_ARRAY_HOLE));
+ optimized = true;
+ } while (false);
+
+ if (!optimized) {
+ last = length;
+ jsdouble upperIndex = last + argc;
+ AutoValueRooter tvr(cx);
+ do {
+ --last, --upperIndex;
+ if (!JS_CHECK_OPERATION_LIMIT(cx) ||
+ !GetElement(cx, obj, last, &hole, tvr.addr()) ||
+ !SetOrDeleteArrayElement(cx, obj, upperIndex, hole, tvr.value())) {
+ return JS_FALSE;
+ }
+ } while (last != 0);
+ }
+ }
+
+ /* Copy from argv to the bottom of the array. */
+ if (!InitArrayElements(cx, obj, 0, argc, argv))
+ return JS_FALSE;
+
+ newlen += argc;
+ }
+ if (!js_SetLengthProperty(cx, obj, newlen))
+ return JS_FALSE;
+
+ /* Follow Perl by returning the new array length. */
+ vp->setNumber(newlen);
+ return JS_TRUE;
+}
+
+static JSBool
+array_splice(JSContext *cx, uintN argc, Value *vp)
+{
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return false;
+
+ jsuint length, begin, end, count, delta, last;
+ JSBool hole;
+
+ /* Create a new array value to return. */
+ JSObject *obj2 = NewDenseEmptyArray(cx);
+ if (!obj2)
+ return JS_FALSE;
+ vp->setObject(*obj2);
+
+ /* Nothing to do if no args. Otherwise get length. */
+ if (argc == 0)
+ return JS_TRUE;
+ Value *argv = JS_ARGV(cx, vp);
+ if (!js_GetLengthProperty(cx, obj, &length))
+ return JS_FALSE;
+ jsuint origlength = length;
+
+ /* Convert the first argument into a starting index. */
+ jsdouble d;
+ if (!ValueToNumber(cx, *argv, &d))
+ return JS_FALSE;
+ d = js_DoubleToInteger(d);
+ if (d < 0) {
+ d += length;
+ if (d < 0)
+ d = 0;
+ } else if (d > length) {
+ d = length;
+ }
+ begin = (jsuint)d; /* d has been clamped to uint32 */
+ argc--;
+ argv++;
+
+ /* Convert the second argument from a count into a fencepost index. */
+ delta = length - begin;
+ if (argc == 0) {
+ count = delta;
+ end = length;
+ } else {
+ if (!ValueToNumber(cx, *argv, &d))
+ return JS_FALSE;
+ d = js_DoubleToInteger(d);
+ if (d < 0)
+ d = 0;
+ else if (d > delta)
+ d = delta;
+ count = (jsuint)d;
+ end = begin + count;
+ argc--;
+ argv++;
+ }
+
+ AutoValueRooter tvr(cx);
+
+ /* If there are elements to remove, put them into the return value. */
+ if (count > 0) {
+ if (obj->isDenseArray() && !js_PrototypeHasIndexedProperties(cx, obj) &&
+ end <= obj->getDenseArrayCapacity()) {
+ if (!InitArrayObject(cx, obj2, count, obj->getDenseArrayElements() + begin))
+ return JS_FALSE;
+ } else {
+ for (last = begin; last < end; last++) {
+ if (!JS_CHECK_OPERATION_LIMIT(cx) ||
+ !GetElement(cx, obj, last, &hole, tvr.addr())) {
+ return JS_FALSE;
+ }
+
+ /* Copy tvr.value() to the new array unless it's a hole. */
+ if (!hole && !SetArrayElement(cx, obj2, last - begin, tvr.value()))
+ return JS_FALSE;
+ }
+
+ if (!js_SetLengthProperty(cx, obj2, count))
+ return JS_FALSE;
+ }
+ }
+
+ /* Find the direction (up or down) to copy and make way for argv. */
+ if (argc > count) {
+ delta = (jsuint)argc - count;
+ last = length;
+ bool optimized = false;
+ do {
+ if (!obj->isDenseArray())
+ break;
+ if (js_PrototypeHasIndexedProperties(cx, obj))
+ break;
+ if (length > obj->getDenseArrayCapacity())
+ break;
+ if (length != 0 && obj->getDenseArrayElement(length - 1).isMagic(JS_ARRAY_HOLE))
+ break;
+ JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, length, delta);
+ if (result != JSObject::ED_OK) {
+ if (result == JSObject::ED_FAILED)
+ return false;
+ JS_ASSERT(result == JSObject::ED_SPARSE);
+ break;
+ }
+ Value *arraybeg = obj->getDenseArrayElements();
+ Value *srcbeg = arraybeg + last - 1;
+ Value *srcend = arraybeg + end - 1;
+ Value *dstbeg = srcbeg + delta;
+ for (Value *src = srcbeg, *dst = dstbeg; src > srcend; --src, --dst)
+ *dst = *src;
+
+ obj->setArrayLength(obj->getArrayLength() + delta);
+ optimized = true;
+ } while (false);
+
+ if (!optimized) {
+ /* (uint) end could be 0, so we can't use a vanilla >= test. */
+ while (last-- > end) {
+ if (!JS_CHECK_OPERATION_LIMIT(cx) ||
+ !GetElement(cx, obj, last, &hole, tvr.addr()) ||
+ !SetOrDeleteArrayElement(cx, obj, last + delta, hole, tvr.value())) {
+ return JS_FALSE;
+ }
+ }
+ }
+ length += delta;
+ } else if (argc < count) {
+ delta = count - (jsuint)argc;
+ if (obj->isDenseArray() && !js_PrototypeHasIndexedProperties(cx, obj) &&
+ length <= obj->getDenseArrayCapacity()) {
+
+ Value *arraybeg = obj->getDenseArrayElements();
+ Value *srcbeg = arraybeg + end;
+ Value *srcend = arraybeg + length;
+ Value *dstbeg = srcbeg - delta;
+ for (Value *src = srcbeg, *dst = dstbeg; src < srcend; ++src, ++dst)
+ *dst = *src;
+ } else {
+ for (last = end; last < length; last++) {
+ if (!JS_CHECK_OPERATION_LIMIT(cx) ||
+ !GetElement(cx, obj, last, &hole, tvr.addr()) ||
+ !SetOrDeleteArrayElement(cx, obj, last - delta, hole, tvr.value())) {
+ return JS_FALSE;
+ }
+ }
+ }
+ length -= delta;
+ }
+
+ if (length < origlength && !js_SuppressDeletedIndexProperties(cx, obj, length, origlength))
+ return JS_FALSE;
+
+ /*
+ * Copy from argv into the hole to complete the splice, and update length in
+ * case we deleted elements from the end.
+ */
+ return InitArrayElements(cx, obj, begin, argc, argv) &&
+ js_SetLengthProperty(cx, obj, length);
+}
+
+/*
+ * Python-esque sequence operations.
+ */
+static JSBool
+array_concat(JSContext *cx, uintN argc, Value *vp)
+{
+ /* Treat our |this| object as the first argument; see ECMA 15.4.4.4. */
+ Value *p = JS_ARGV(cx, vp) - 1;
+
+ /* Create a new Array object and root it using *vp. */
+ JSObject *aobj = ToObject(cx, &vp[1]);
+ if (!aobj)
+ return false;
+
+ JSObject *nobj;
+ jsuint length;
+ if (aobj->isDenseArray()) {
+ /*
+ * Clone aobj but pass the minimum of its length and capacity, to
+ * handle a = [1,2,3]; a.length = 10000 "dense" cases efficiently. In
+ * the normal case where length is <= capacity, nobj and aobj will have
+ * the same capacity.
+ */
+ length = aobj->getArrayLength();
+ jsuint capacity = aobj->getDenseArrayCapacity();
+ nobj = NewDenseCopiedArray(cx, JS_MIN(length, capacity), aobj->getDenseArrayElements());
+ if (!nobj)
+ return JS_FALSE;
+ nobj->setArrayLength(length);
+ vp->setObject(*nobj);
+ if (argc == 0)
+ return JS_TRUE;
+ argc--;
+ p++;
+ } else {
+ nobj = NewDenseEmptyArray(cx);
+ if (!nobj)
+ return JS_FALSE;
+ vp->setObject(*nobj);
+ length = 0;
+ }
+
+ AutoValueRooter tvr(cx);
+
+ /* Loop over [0, argc] to concat args into nobj, expanding all Arrays. */
+ for (uintN i = 0; i <= argc; i++) {
+ if (!JS_CHECK_OPERATION_LIMIT(cx))
+ return false;
+ const Value &v = p[i];
+ if (v.isObject()) {
+ aobj = &v.toObject();
+ if (aobj->isArray() ||
+ (aobj->isWrapper() && JSWrapper::wrappedObject(aobj)->isArray())) {
+ jsid id = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
+ if (!aobj->getProperty(cx, id, tvr.addr()))
+ return false;
+ jsuint alength;
+ if (!ValueToLength(cx, tvr.addr(), &alength))
+ return false;
+ for (jsuint slot = 0; slot < alength; slot++) {
+ JSBool hole;
+ if (!JS_CHECK_OPERATION_LIMIT(cx) ||
+ !GetElement(cx, aobj, slot, &hole, tvr.addr())) {
+ return false;
+ }
+
+ /*
+ * Per ECMA 262, 15.4.4.4, step 9, ignore nonexistent
+ * properties.
+ */
+ if (!hole &&
+ !SetArrayElement(cx, nobj, length+slot, tvr.value())) {
+ return false;
+ }
+ }
+ length += alength;
+ continue;
+ }
+ }
+
+ if (!SetArrayElement(cx, nobj, length, v))
+ return false;
+ length++;
+ }
+
+ return js_SetLengthProperty(cx, nobj, length);
+}
+
+static JSBool
+array_slice(JSContext *cx, uintN argc, Value *vp)
+{
+ Value *argv;
+ JSObject *nobj;
+ jsuint length, begin, end, slot;
+ JSBool hole;
+
+ argv = JS_ARGV(cx, vp);
+
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return false;
+
+ if (!js_GetLengthProperty(cx, obj, &length))
+ return JS_FALSE;
+ begin = 0;
+ end = length;
+
+ if (argc > 0) {
+ jsdouble d;
+ if (!ValueToNumber(cx, argv[0], &d))
+ return JS_FALSE;
+ d = js_DoubleToInteger(d);
+ if (d < 0) {
+ d += length;
+ if (d < 0)
+ d = 0;
+ } else if (d > length) {
+ d = length;
+ }
+ begin = (jsuint)d;
+
+ if (argc > 1 && !argv[1].isUndefined()) {
+ if (!ValueToNumber(cx, argv[1], &d))
+ return JS_FALSE;
+ d = js_DoubleToInteger(d);
+ if (d < 0) {
+ d += length;
+ if (d < 0)
+ d = 0;
+ } else if (d > length) {
+ d = length;
+ }
+ end = (jsuint)d;
+ }
+ }
+
+ if (begin > end)
+ begin = end;
+
+ if (obj->isDenseArray() && end <= obj->getDenseArrayCapacity() &&
+ !js_PrototypeHasIndexedProperties(cx, obj)) {
+ nobj = NewDenseCopiedArray(cx, end - begin, obj->getDenseArrayElements() + begin);
+ if (!nobj)
+ return JS_FALSE;
+ vp->setObject(*nobj);
+ return JS_TRUE;
+ }
+
+ /* Create a new Array object and root it using *vp. */
+ nobj = NewDenseAllocatedArray(cx, end - begin);
+ if (!nobj)
+ return JS_FALSE;
+ vp->setObject(*nobj);
+
+ AutoValueRooter tvr(cx);
+ for (slot = begin; slot < end; slot++) {
+ if (!JS_CHECK_OPERATION_LIMIT(cx) ||
+ !GetElement(cx, obj, slot, &hole, tvr.addr())) {
+ return JS_FALSE;
+ }
+ if (!hole && !SetArrayElement(cx, nobj, slot - begin, tvr.value()))
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+#if JS_HAS_ARRAY_EXTRAS
+
+static JSBool
+array_indexOfHelper(JSContext *cx, JSBool isLast, uintN argc, Value *vp)
+{
+ jsuint length, i, stop;
+ Value tosearch;
+ jsint direction;
+ JSBool hole;
+
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return false;
+ if (!js_GetLengthProperty(cx, obj, &length))
+ return JS_FALSE;
+ if (length == 0)
+ goto not_found;
+
+ if (argc <= 1) {
+ i = isLast ? length - 1 : 0;
+ tosearch = (argc != 0) ? vp[2] : UndefinedValue();
+ } else {
+ jsdouble start;
+
+ tosearch = vp[2];
+ if (!ValueToNumber(cx, vp[3], &start))
+ return JS_FALSE;
+ start = js_DoubleToInteger(start);
+ if (start < 0) {
+ start += length;
+ if (start < 0) {
+ if (isLast)
+ goto not_found;
+ i = 0;
+ } else {
+ i = (jsuint)start;
+ }
+ } else if (start >= length) {
+ if (!isLast)
+ goto not_found;
+ i = length - 1;
+ } else {
+ i = (jsuint)start;
+ }
+ }
+
+ if (isLast) {
+ stop = 0;
+ direction = -1;
+ } else {
+ stop = length - 1;
+ direction = 1;
+ }
+
+ for (;;) {
+ if (!JS_CHECK_OPERATION_LIMIT(cx) ||
+ !GetElement(cx, obj, (jsuint)i, &hole, vp)) {
+ return JS_FALSE;
+ }
+ if (!hole) {
+ JSBool equal;
+ if (!StrictlyEqual(cx, *vp, tosearch, &equal))
+ return JS_FALSE;
+ if (equal) {
+ vp->setNumber(i);
+ return JS_TRUE;
+ }
+ }
+ if (i == stop)
+ goto not_found;
+ i += direction;
+ }
+
+ not_found:
+ vp->setInt32(-1);
+ return JS_TRUE;
+}
+
+static JSBool
+array_indexOf(JSContext *cx, uintN argc, Value *vp)
+{
+ return array_indexOfHelper(cx, JS_FALSE, argc, vp);
+}
+
+static JSBool
+array_lastIndexOf(JSContext *cx, uintN argc, Value *vp)
+{
+ return array_indexOfHelper(cx, JS_TRUE, argc, vp);
+}
+
+/* Order is important; extras that take a predicate funarg must follow MAP. */
+typedef enum ArrayExtraMode {
+ FOREACH,
+ REDUCE,
+ REDUCE_RIGHT,
+ MAP,
+ FILTER,
+ SOME,
+ EVERY
+} ArrayExtraMode;
+
+#define REDUCE_MODE(mode) ((mode) == REDUCE || (mode) == REDUCE_RIGHT)
+
+static JSBool
+array_extra(JSContext *cx, ArrayExtraMode mode, uintN argc, Value *vp)
+{
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj)
+ return false;
+
+ jsuint length;
+ if (!js_GetLengthProperty(cx, obj, &length))
+ return JS_FALSE;
+
+ /*
+ * First, get or compute our callee, so that we error out consistently
+ * when passed a non-callable object.
+ */
+ if (argc == 0) {
+ js_ReportMissingArg(cx, *vp, 0);
+ return JS_FALSE;
+ }
+ Value *argv = vp + 2;
+ JSObject *callable = js_ValueToCallableObject(cx, &argv[0], JSV2F_SEARCH_STACK);
+ if (!callable)
+ return JS_FALSE;
+
+ /*
+ * Set our initial return condition, used for zero-length array cases
+ * (and pre-size our map return to match our known length, for all cases).
+ */
+ jsuint newlen;
+ JSObject *newarr;
+#ifdef __GNUC__ /* quell GCC overwarning */
+ newlen = 0;
+ newarr = NULL;
+#endif
+ jsint start = 0, end = length, step = 1;
+
+ switch (mode) {
+ case REDUCE_RIGHT:
+ start = length - 1, end = -1, step = -1;
+ /* FALL THROUGH */
+ case REDUCE:
+ if (length == 0 && argc == 1) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+ JSMSG_EMPTY_ARRAY_REDUCE);
+ return JS_FALSE;
+ }
+ if (argc >= 2) {
+ *vp = argv[1];
+ } else {
+ JSBool hole;
+ do {
+ if (!GetElement(cx, obj, start, &hole, vp))
+ return JS_FALSE;
+ start += step;
+ } while (hole && start != end);
+
+ if (hole && start == end) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+ JSMSG_EMPTY_ARRAY_REDUCE);
+ return JS_FALSE;
+ }
+ }
+ break;
+ case MAP:
+ case FILTER:
+ newlen = (mode == MAP) ? length : 0;
+ newarr = NewDenseAllocatedArray(cx, newlen);
+ if (!newarr)
+ return JS_FALSE;
+ vp->setObject(*newarr);
+ break;
+ case SOME:
+ vp->setBoolean(false);
+ break;
+ case EVERY:
+ vp->setBoolean(true);
+ break;
+ case FOREACH:
+ vp->setUndefined();
+ break;
+ }
+
+ if (length == 0)
+ return JS_TRUE;
+
+ Value thisv = (argc > 1 && !REDUCE_MODE(mode)) ? argv[1] : UndefinedValue();
+
+ /*
+ * For all but REDUCE, we call with 3 args (value, index, array). REDUCE
+ * requires 4 args (accum, value, index, array).
+ */
+ argc = 3 + REDUCE_MODE(mode);
+
+ InvokeSessionGuard session;
+ if (!session.start(cx, ObjectValue(*callable), thisv, argc))
+ return JS_FALSE;
+
+ MUST_FLOW_THROUGH("out");
+ JSBool ok = JS_TRUE;
+ JSBool cond;
+
+ Value objv = ObjectValue(*obj);
+ AutoValueRooter tvr(cx);
+ for (jsint i = start; i != end; i += step) {
+ JSBool hole;
+ ok = JS_CHECK_OPERATION_LIMIT(cx) &&
+ GetElement(cx, obj, i, &hole, tvr.addr());
+ if (!ok)
+ goto out;
+ if (hole)
+ continue;
+
+ /*
+ * Push callable and 'this', then args. We must do this for every
+ * iteration around the loop since Invoke clobbers its arguments.
+ */
+ uintN argi = 0;
+ if (REDUCE_MODE(mode))
+ session[argi++] = *vp;
+ session[argi++] = tvr.value();
+ session[argi++] = Int32Value(i);
+ session[argi] = objv;
+
+ /* Do the call. */
+ ok = session.invoke(cx);
+ if (!ok)
+ break;
+
+ const Value &rval = session.rval();
+
+ if (mode > MAP)
+ cond = js_ValueToBoolean(rval);
+#ifdef __GNUC__ /* quell GCC overwarning */
+ else
+ cond = JS_FALSE;
+#endif
+
+ switch (mode) {
+ case FOREACH:
+ break;
+ case REDUCE:
+ case REDUCE_RIGHT:
+ *vp = rval;
+ break;
+ case MAP:
+ ok = SetArrayElement(cx, newarr, i, rval);
+ if (!ok)
+ goto out;
+ break;
+ case FILTER:
+ if (!cond)
+ break;
+ /* The element passed the filter, so push it onto our result. */
+ ok = SetArrayElement(cx, newarr, newlen++, tvr.value());
+ if (!ok)
+ goto out;
+ break;
+ case SOME:
+ if (cond) {
+ vp->setBoolean(true);
+ goto out;
+ }
+ break;
+ case EVERY:
+ if (!cond) {
+ vp->setBoolean(false);
+ goto out;
+ }
+ break;
+ }
+ }
+
+ out:
+ if (ok && mode == FILTER)
+ ok = js_SetLengthProperty(cx, newarr, newlen);
+ return ok;
+}
+
+static JSBool
+array_forEach(JSContext *cx, uintN argc, Value *vp)
+{
+ return array_extra(cx, FOREACH, argc, vp);
+}
+
+static JSBool
+array_map(JSContext *cx, uintN argc, Value *vp)
+{
+ return array_extra(cx, MAP, argc, vp);
+}
+
+static JSBool
+array_reduce(JSContext *cx, uintN argc, Value *vp)
+{
+ return array_extra(cx, REDUCE, argc, vp);
+}
+
+static JSBool
+array_reduceRight(JSContext *cx, uintN argc, Value *vp)
+{
+ return array_extra(cx, REDUCE_RIGHT, argc, vp);
+}
+
+static JSBool
+array_filter(JSContext *cx, uintN argc, Value *vp)
+{
+ return array_extra(cx, FILTER, argc, vp);
+}
+
+static JSBool
+array_some(JSContext *cx, uintN argc, Value *vp)
+{
+ return array_extra(cx, SOME, argc, vp);
+}
+
+static JSBool
+array_every(JSContext *cx, uintN argc, Value *vp)
+{
+ return array_extra(cx, EVERY, argc, vp);
+}
+#endif
+
+static JSBool
+array_isArray(JSContext *cx, uintN argc, Value *vp)
+{
+ JSObject *obj;
+ vp->setBoolean(argc > 0 &&
+ vp[2].isObject() &&
+ ((obj = &vp[2].toObject())->isArray() ||
+ (obj->isWrapper() && JSWrapper::wrappedObject(obj)->isArray())));
+ return true;
+}
+
+static JSFunctionSpec array_methods[] = {
+#if JS_HAS_TOSOURCE
+ JS_FN(js_toSource_str, array_toSource, 0,0),
+#endif
+ JS_FN(js_toString_str, array_toString, 0,0),
+ JS_FN(js_toLocaleString_str,array_toLocaleString,0,0),
+
+ /* Perl-ish methods. */
+ JS_FN("join", array_join, 1,JSFUN_GENERIC_NATIVE),
+ JS_FN("reverse", array_reverse, 0,JSFUN_GENERIC_NATIVE),
+ JS_FN("sort", array_sort, 1,JSFUN_GENERIC_NATIVE),
+ JS_FN("push", array_push, 1,JSFUN_GENERIC_NATIVE),
+ JS_FN("pop", array_pop, 0,JSFUN_GENERIC_NATIVE),
+ JS_FN("shift", array_shift, 0,JSFUN_GENERIC_NATIVE),
+ JS_FN("unshift", array_unshift, 1,JSFUN_GENERIC_NATIVE),
+ JS_FN("splice", array_splice, 2,JSFUN_GENERIC_NATIVE),
+
+ /* Pythonic sequence methods. */
+ JS_FN("concat", array_concat, 1,JSFUN_GENERIC_NATIVE),
+ JS_FN("slice", array_slice, 2,JSFUN_GENERIC_NATIVE),
+
+#if JS_HAS_ARRAY_EXTRAS
+ JS_FN("indexOf", array_indexOf, 1,JSFUN_GENERIC_NATIVE),
+ JS_FN("lastIndexOf", array_lastIndexOf, 1,JSFUN_GENERIC_NATIVE),
+ JS_FN("forEach", array_forEach, 1,JSFUN_GENERIC_NATIVE),
+ JS_FN("map", array_map, 1,JSFUN_GENERIC_NATIVE),
+ JS_FN("reduce", array_reduce, 1,JSFUN_GENERIC_NATIVE),
+ JS_FN("reduceRight", array_reduceRight, 1,JSFUN_GENERIC_NATIVE),
+ JS_FN("filter", array_filter, 1,JSFUN_GENERIC_NATIVE),
+ JS_FN("some", array_some, 1,JSFUN_GENERIC_NATIVE),
+ JS_FN("every", array_every, 1,JSFUN_GENERIC_NATIVE),
+#endif
+
+ JS_FS_END
+};
+
+static JSFunctionSpec array_static_methods[] = {
+ JS_FN("isArray", array_isArray, 1,0),
+ JS_FS_END
+};
+
+JSBool
+js_Array(JSContext *cx, uintN argc, Value *vp)
+{
+ JSObject *obj;
+
+ if (argc == 0) {
+ obj = NewDenseEmptyArray(cx);
+ } else if (argc > 1) {
+ obj = NewDenseCopiedArray(cx, argc, vp + 2);
+ } else if (!vp[2].isNumber()) {
+ obj = NewDenseCopiedArray(cx, 1, vp + 2);
+ } else {
+ jsuint length;
+ if (!ValueToLength(cx, vp + 2, &length))
+ return JS_FALSE;
+ obj = NewDenseUnallocatedArray(cx, length);
+ }
+
+ if (!obj)
+ return JS_FALSE;
+ vp->setObject(*obj);
+
+ return JS_TRUE;
+}
+
+JSObject *
+js_InitArrayClass(JSContext *cx, JSObject *obj)
+{
+ JSObject *proto = js_InitClass(cx, obj, NULL, &js_ArrayClass, js_Array, 1,
+ NULL, array_methods, NULL, array_static_methods);
+ if (!proto)
+ return NULL;
+
+ /*
+ * Assert that js_InitClass used the correct (slow array, not dense array)
+ * class for proto's emptyShape class.
+ */
+ JS_ASSERT(proto->emptyShapes && proto->emptyShapes[0]->getClass() == proto->getClass());
+
+ proto->setArrayLength(0);
+ return proto;
+}
+
+/*
+ * Array allocation functions.
+ */
+namespace js {
+
+template<bool allocateCapacity>
+static JS_ALWAYS_INLINE JSObject *
+NewArray(JSContext *cx, jsuint length, JSObject *proto)
+{
+ JS_ASSERT_IF(proto, proto->isArray());
+
+ gc::FinalizeKind kind = GuessObjectGCKind(length, true);
+ JSObject *obj = detail::NewObject<WithProto::Class, false>(cx, &js_ArrayClass, proto, NULL, kind);
+ if (!obj)
+ return NULL;
+
+ obj->setArrayLength(length);
+
+ if (allocateCapacity && !obj->ensureSlots(cx, length))
+ return NULL;
+
+ return obj;
+}
+
+JSObject * JS_FASTCALL
+NewDenseEmptyArray(JSContext *cx, JSObject *proto)
+{
+ return NewArray<false>(cx, 0, proto);
+}
+
+JSObject * JS_FASTCALL
+NewDenseAllocatedArray(JSContext *cx, uint32 length, JSObject *proto)
+{
+ return NewArray<true>(cx, length, proto);
+}
+
+JSObject * JS_FASTCALL
+NewDenseUnallocatedArray(JSContext *cx, uint32 length, JSObject *proto)
+{
+ return NewArray<false>(cx, length, proto);
+}
+
+JSObject *
+NewDenseCopiedArray(JSContext *cx, uintN length, Value *vp, JSObject *proto)
+{
+ JSObject* obj = NewArray<true>(cx, length, proto);
+ if (!obj)
+ return NULL;
+
+ JS_ASSERT(obj->getDenseArrayCapacity() >= length);
+
+ if (vp)
+ memcpy(obj->getDenseArrayElements(), vp, length * sizeof(Value));
+
+ return obj;
+}
+
+#ifdef JS_TRACER
+JS_DEFINE_CALLINFO_2(extern, OBJECT, NewDenseEmptyArray, CONTEXT, OBJECT, 0,
+ nanojit::ACCSET_STORE_ANY)
+JS_DEFINE_CALLINFO_3(extern, OBJECT, NewDenseAllocatedArray, CONTEXT, UINT32, OBJECT, 0,
+ nanojit::ACCSET_STORE_ANY)
+JS_DEFINE_CALLINFO_3(extern, OBJECT, NewDenseUnallocatedArray, CONTEXT, UINT32, OBJECT, 0,
+ nanojit::ACCSET_STORE_ANY)
+#endif
+
+
+
+JSObject *
+NewSlowEmptyArray(JSContext *cx)
+{
+ JSObject *obj = NewNonFunction<WithProto::Class>(cx, &js_SlowArrayClass, NULL, NULL);
+ if (!obj || !AddLengthProperty(cx, obj))
+ return NULL;
+
+ obj->setArrayLength(0);
+ return obj;
+}
+
+}
+
+
+#ifdef DEBUG
+JSBool
+js_ArrayInfo(JSContext *cx, uintN argc, jsval *vp)
+{
+ uintN i;
+ JSObject *array;
+
+ for (i = 0; i < argc; i++) {
+ Value arg = Valueify(JS_ARGV(cx, vp)[i]);
+
+ char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, arg, NULL);
+ if (!bytes)
+ return JS_FALSE;
+ if (arg.isPrimitive() ||
+ !(array = arg.toObjectOrNull())->isArray()) {
+ fprintf(stderr, "%s: not array\n", bytes);
+ cx->free(bytes);
+ continue;
+ }
+ fprintf(stderr, "%s: %s (len %u", bytes,
+ array->isDenseArray() ? "dense" : "sparse",
+ array->getArrayLength());
+ if (array->isDenseArray()) {
+ fprintf(stderr, ", capacity %u",
+ array->getDenseArrayCapacity());
+ }
+ fputs(")\n", stderr);
+ cx->free(bytes);
+ }
+
+ JS_SET_RVAL(cx, vp, JSVAL_VOID);
+ return true;
+}
+#endif
+
+JS_FRIEND_API(JSBool)
+js_CoerceArrayToCanvasImageData(JSObject *obj, jsuint offset, jsuint count,
+ JSUint8 *dest)
+{
+ uint32 length;
+
+ if (!obj || !obj->isDenseArray())
+ return JS_FALSE;
+
+ length = obj->getArrayLength();
+ if (length < offset + count)
+ return JS_FALSE;
+
+ JSUint8 *dp = dest;
+ for (uintN i = offset; i < offset+count; i++) {
+ const Value &v = obj->getDenseArrayElement(i);
+ if (v.isInt32()) {
+ jsint vi = v.toInt32();
+ if (jsuint(vi) > 255)
+ vi = (vi < 0) ? 0 : 255;
+ *dp++ = JSUint8(vi);
+ } else if (v.isDouble()) {
+ jsdouble vd = v.toDouble();
+ if (!(vd >= 0)) /* Not < so that NaN coerces to 0 */
+ *dp++ = 0;
+ else if (vd > 255)
+ *dp++ = 255;
+ else {
+ jsdouble toTruncate = vd + 0.5;
+ JSUint8 val = JSUint8(toTruncate);
+
+ /*
+ * now val is rounded to nearest, ties rounded up. We want
+ * rounded to nearest ties to even, so check whether we had a
+ * tie.
+ */
+ if (val == toTruncate) {
+ /*
+ * It was a tie (since adding 0.5 gave us the exact integer
+ * we want). Since we rounded up, we either already have an
+ * even number or we have an odd number but the number we
+ * want is one less. So just unconditionally masking out the
+ * ones bit should do the trick to get us the value we
+ * want.
+ */
+ *dp++ = (val & ~1);
+ } else {
+ *dp++ = val;
+ }
+ }
+ } else {
+ return JS_FALSE;
+ }
+ }
+
+ return JS_TRUE;
+}
+
+JS_FRIEND_API(JSBool)
+js_IsDensePrimitiveArray(JSObject *obj)
+{
+ if (!obj || !obj->isDenseArray())
+ return JS_FALSE;
+
+ jsuint capacity = obj->getDenseArrayCapacity();
+ for (jsuint i = 0; i < capacity; i++) {
+ if (obj->getDenseArrayElement(i).isObject())
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+JS_FRIEND_API(JSBool)
+js_CloneDensePrimitiveArray(JSContext *cx, JSObject *obj, JSObject **clone)
+{
+ JS_ASSERT(obj);
+ if (!obj->isDenseArray()) {
+ /*
+ * This wasn't a dense array. Return JS_TRUE but a NULL clone to signal
+ * that no exception was encountered.
+ */
+ *clone = NULL;
+ return JS_TRUE;
+ }
+
+ jsuint length = obj->getArrayLength();
+
+ /*
+ * Must use the minimum of original array's length and capacity, to handle
+ * |a = [1,2,3]; a.length = 10000| "dense" cases efficiently. In the normal
+ * case where length is <= capacity, the clone and original array will have
+ * the same capacity.
+ */
+ jsuint jsvalCount = JS_MIN(obj->getDenseArrayCapacity(), length);
+
+ js::AutoValueVector vector(cx);
+ if (!vector.reserve(jsvalCount))
+ return JS_FALSE;
+
+ for (jsuint i = 0; i < jsvalCount; i++) {
+ const Value &val = obj->getDenseArrayElement(i);
+
+ if (val.isString()) {
+ // Strings must be made immutable before being copied to a clone.
+ if (!js_MakeStringImmutable(cx, val.toString()))
+ return JS_FALSE;
+ } else if (val.isObject()) {
+ /*
+ * This wasn't an array of primitives. Return JS_TRUE but a null
+ * clone to signal that no exception was encountered.
+ */
+ *clone = NULL;
+ return JS_TRUE;
+ }
+
+ vector.append(val);
+ }
+
+ *clone = NewDenseCopiedArray(cx, jsvalCount, vector.begin());
+ if (!*clone)
+ return JS_FALSE;
+
+ /* The length will be set to the JS_MIN, above, but length might be larger. */
+ (*clone)->setArrayLength(length);
+ return JS_TRUE;
+}