Code

fix 1243587 and misc fixes
[inkscape.git] / src / dom / js / jsarray.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set sw=4 ts=8 et tw=80:
3  *
4  * ***** BEGIN LICENSE BLOCK *****
5  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6  *
7  * The contents of this file are subject to the Mozilla Public License Version
8  * 1.1 (the "License"); you may not use this file except in compliance with
9  * the License. You may obtain a copy of the License at
10  * http://www.mozilla.org/MPL/
11  *
12  * Software distributed under the License is distributed on an "AS IS" basis,
13  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14  * for the specific language governing rights and limitations under the
15  * License.
16  *
17  * The Original Code is Mozilla Communicator client code, released
18  * March 31, 1998.
19  *
20  * The Initial Developer of the Original Code is
21  * Netscape Communications Corporation.
22  * Portions created by the Initial Developer are Copyright (C) 1998
23  * the Initial Developer. All Rights Reserved.
24  *
25  * Contributor(s):
26  *
27  * Alternatively, the contents of this file may be used under the terms of
28  * either of the GNU General Public License Version 2 or later (the "GPL"),
29  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30  * in which case the provisions of the GPL or the LGPL are applicable instead
31  * of those above. If you wish to allow use of your version of this file only
32  * under the terms of either the GPL or the LGPL, and not to allow others to
33  * use your version of this file under the terms of the MPL, indicate your
34  * decision by deleting the provisions above and replace them with the notice
35  * and other provisions required by the GPL or the LGPL. If you do not delete
36  * the provisions above, a recipient may use your version of this file under
37  * the terms of any one of the MPL, the GPL or the LGPL.
38  *
39  * ***** END LICENSE BLOCK ***** */
41 /*
42  * JS array class.
43  */
44 #include "jsstddef.h"
45 #include <stdlib.h>
46 #include <string.h>
47 #include "jstypes.h"
48 #include "jsutil.h" /* Added by JSIFY */
49 #include "jsapi.h"
50 #include "jsarray.h"
51 #include "jsatom.h"
52 #include "jsbool.h"
53 #include "jscntxt.h"
54 #include "jsconfig.h"
55 #include "jsfun.h"
56 #include "jsgc.h"
57 #include "jsinterp.h"
58 #include "jslock.h"
59 #include "jsnum.h"
60 #include "jsobj.h"
61 #include "jsstr.h"
63 /* 2^32 - 1 as a number and a string */
64 #define MAXINDEX 4294967295u
65 #define MAXSTR   "4294967295"
67 /*
68  * Determine if the id represents an array index or an XML property index.
69  *
70  * An id is an array index according to ECMA by (15.4):
71  *
72  * "Array objects give special treatment to a certain class of property names.
73  * A property name P (in the form of a string value) is an array index if and
74  * only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal
75  * to 2^32-1."
76  *
77  * In our implementation, it would be sufficient to check for JSVAL_IS_INT(id)
78  * except that by using signed 32-bit integers we miss the top half of the
79  * valid range. This function checks the string representation itself; note
80  * that calling a standard conversion routine might allow strings such as
81  * "08" or "4.0" as array indices, which they are not.
82  */
83 JSBool
84 js_IdIsIndex(jsval id, jsuint *indexp)
85 {
86     JSString *str;
87     jschar *cp;
89     if (JSVAL_IS_INT(id)) {
90         jsint i;
91         i = JSVAL_TO_INT(id);
92         if (i < 0)
93             return JS_FALSE;
94         *indexp = (jsuint)i;
95         return JS_TRUE;
96     }
98     /* NB: id should be a string, but jsxml.c may call us with an object id. */
99     if (!JSVAL_IS_STRING(id))
100         return JS_FALSE;
102     str = JSVAL_TO_STRING(id);
103     cp = JSSTRING_CHARS(str);
104     if (JS7_ISDEC(*cp) && JSSTRING_LENGTH(str) < sizeof(MAXSTR)) {
105         jsuint index = JS7_UNDEC(*cp++);
106         jsuint oldIndex = 0;
107         jsuint c = 0;
108         if (index != 0) {
109             while (JS7_ISDEC(*cp)) {
110                 oldIndex = index;
111                 c = JS7_UNDEC(*cp);
112                 index = 10*index + c;
113                 cp++;
114             }
115         }
117         /* Ensure that all characters were consumed and we didn't overflow. */
118         if (*cp == 0 &&
119              (oldIndex < (MAXINDEX / 10) ||
120               (oldIndex == (MAXINDEX / 10) && c < (MAXINDEX % 10))))
121         {
122             *indexp = index;
123             return JS_TRUE;
124         }
125     }
126     return JS_FALSE;
129 static JSBool
130 ValueIsLength(JSContext *cx, jsval v, jsuint *lengthp)
132     jsint i;
133     jsdouble d;
135     if (JSVAL_IS_INT(v)) {
136         i = JSVAL_TO_INT(v);
137         if (i < 0) {
138             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
139                                  JSMSG_BAD_ARRAY_LENGTH);
140             return JS_FALSE;
141         }
142         *lengthp = (jsuint) i;
143         return JS_TRUE;
144     }
146     if (!js_ValueToNumber(cx, v, &d)) {
147         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
148                              JSMSG_BAD_ARRAY_LENGTH);
149         return JS_FALSE;
150     }
151     if (!js_DoubleToECMAUint32(cx, d, (uint32 *)lengthp)) {
152         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
153                              JSMSG_BAD_ARRAY_LENGTH);
154         return JS_FALSE;
155     }
156     if (JSDOUBLE_IS_NaN(d) || d != *lengthp) {
157         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
158                              JSMSG_BAD_ARRAY_LENGTH);
159         return JS_FALSE;
160     }
161     return JS_TRUE;
164 JSBool
165 js_GetLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp)
167     JSTempValueRooter tvr;
168     jsid id;
169     JSBool ok;
170     jsint i;
172     JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
173     id = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
174     ok = OBJ_GET_PROPERTY(cx, obj, id, &tvr.u.value);
175     if (ok) {
176         /*
177          * Short-circuit, because js_ValueToECMAUint32 fails when called
178          * during init time.
179          */
180         if (JSVAL_IS_INT(tvr.u.value)) {
181             i = JSVAL_TO_INT(tvr.u.value);
182             *lengthp = (jsuint)i;       /* jsuint cast does ToUint32 */
183         } else {
184             ok = js_ValueToECMAUint32(cx, tvr.u.value, (uint32 *)lengthp);
185         }
186     }
187     JS_POP_TEMP_ROOT(cx, &tvr);
188     return ok;
191 static JSBool
192 IndexToValue(JSContext *cx, jsuint index, jsval *vp)
194     if (index <= JSVAL_INT_MAX) {
195         *vp = INT_TO_JSVAL(index);
196         return JS_TRUE;
197     }
198     return js_NewDoubleValue(cx, (jsdouble)index, vp);
201 static JSBool
202 IndexToId(JSContext *cx, jsuint index, jsid *idp)
204     JSString *str;
205     JSAtom *atom;
207     if (index <= JSVAL_INT_MAX) {
208         *idp = INT_TO_JSID(index);
209     } else {
210         str = js_NumberToString(cx, (jsdouble)index);
211         if (!str)
212             return JS_FALSE;
213         atom = js_AtomizeString(cx, str, 0);
214         if (!atom)
215             return JS_FALSE;
216         *idp = ATOM_TO_JSID(atom);
218     }
219     return JS_TRUE;
222 static JSBool
223 PropertyExists(JSContext *cx, JSObject *obj, jsid id, JSBool *foundp)
225     JSObject *obj2;
226     JSProperty *prop;
228     if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop))
229         return JS_FALSE;
231     *foundp = prop != NULL;
232     if (*foundp) {
233         OBJ_DROP_PROPERTY(cx, obj2, prop);
234     }
236     return JS_TRUE;
239 #define JSID_HOLE       JSVAL_NULL
241 static JSBool
242 IndexToExistingId(JSContext *cx, JSObject *obj, jsuint index, jsid *idp)
244     JSBool exists;
246     if (!IndexToId(cx, index, idp))
247         return JS_FALSE;
248     if (!PropertyExists(cx, obj, *idp, &exists))
249         return JS_FALSE;
250     if (!exists)
251         *idp = JSID_HOLE;
252     return JS_TRUE;
255 JSBool
256 js_SetLengthProperty(JSContext *cx, JSObject *obj, jsuint length)
258     jsval v;
259     jsid id;
261     if (!IndexToValue(cx, length, &v))
262         return JS_FALSE;
263     id = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
264     return OBJ_SET_PROPERTY(cx, obj, id, &v);
267 JSBool
268 js_HasLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp)
270     JSErrorReporter older;
271     JSTempValueRooter tvr;
272     jsid id;
273     JSBool ok;
275     older = JS_SetErrorReporter(cx, NULL);
276     JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
277     id = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
278     ok = OBJ_GET_PROPERTY(cx, obj, id, &tvr.u.value);
279     JS_SetErrorReporter(cx, older);
280     if (ok)
281         ok = ValueIsLength(cx, tvr.u.value, lengthp);
282     JS_POP_TEMP_ROOT(cx, &tvr);
283     return ok;
286 /*
287  * This get function is specific to Array.prototype.length and other array
288  * instance length properties.  It calls back through the class get function
289  * in case some magic happens there (see call_getProperty in jsfun.c).
290  */
291 static JSBool
292 array_length_getter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
294     return OBJ_GET_CLASS(cx, obj)->getProperty(cx, obj, id, vp);
297 static JSBool
298 array_length_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
300     jsuint newlen, oldlen, slot;
301     jsid id2;
302     jsval junk;
304     if (!ValueIsLength(cx, *vp, &newlen))
305         return JS_FALSE;
306     if (!js_GetLengthProperty(cx, obj, &oldlen))
307         return JS_FALSE;
308     slot = oldlen;
309     while (slot > newlen) {
310         --slot;
311         if (!IndexToId(cx, slot, &id2))
312             return JS_FALSE;
313         if (!OBJ_DELETE_PROPERTY(cx, obj, id2, &junk))
314             return JS_FALSE;
315     }
316     return IndexToValue(cx, newlen, vp);
319 static JSBool
320 array_addProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
322     jsuint index, length;
324     if (!js_IdIsIndex(id, &index))
325         return JS_TRUE;
326     if (!js_GetLengthProperty(cx, obj, &length))
327         return JS_FALSE;
328     if (index >= length) {
329         length = index + 1;
330         return js_SetLengthProperty(cx, obj, length);
331     }
332     return JS_TRUE;
335 static JSBool
336 array_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
338     jsuint length;
340     if (JS_VERSION_IS_1_2(cx)) {
341         if (!js_GetLengthProperty(cx, obj, &length))
342             return JS_FALSE;
343         switch (type) {
344           case JSTYPE_NUMBER:
345             return IndexToValue(cx, length, vp);
346           case JSTYPE_BOOLEAN:
347             *vp = BOOLEAN_TO_JSVAL(length > 0);
348             return JS_TRUE;
349           default:
350             return JS_TRUE;
351         }
352     }
353     return js_TryValueOf(cx, obj, type, vp);
356 JSClass js_ArrayClass = {
357     "Array",
358     0,
359     array_addProperty, JS_PropertyStub,   JS_PropertyStub,   JS_PropertyStub,
360     JS_EnumerateStub,  JS_ResolveStub,    array_convert,     JS_FinalizeStub,
361     JSCLASS_NO_OPTIONAL_MEMBERS
362 };
364 static JSBool
365 array_join_sub(JSContext *cx, JSObject *obj, JSString *sep, JSBool literalize,
366                jsval *rval, JSBool localeString)
368     JSBool ok;
369     jsuint length, index;
370     jschar *chars, *ochars;
371     size_t nchars, growth, seplen, tmplen;
372     const jschar *sepstr;
373     JSString *str;
374     JSHashEntry *he;
375     JSTempValueRooter tvr;
376     JSAtom *atom;
377     int stackDummy;
379     if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) {
380         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED);
381         return JS_FALSE;
382     }
384     ok = js_GetLengthProperty(cx, obj, &length);
385     if (!ok)
386         return JS_FALSE;
388     he = js_EnterSharpObject(cx, obj, NULL, &chars);
389     if (!he)
390         return JS_FALSE;
391     if (literalize) {
392         if (IS_SHARP(he)) {
393 #if JS_HAS_SHARP_VARS
394             nchars = js_strlen(chars);
395 #else
396             chars[0] = '[';
397             chars[1] = ']';
398             chars[2] = 0;
399             nchars = 2;
400 #endif
401             goto make_string;
402         }
404         /*
405          * Allocate 1 + 3 + 1 for "[", the worst-case closing ", ]", and the
406          * terminating 0.
407          */
408         growth = (1 + 3 + 1) * sizeof(jschar);
409         if (!chars) {
410             nchars = 0;
411             chars = (jschar *) malloc(growth);
412             if (!chars)
413                 goto done;
414         } else {
415             MAKE_SHARP(he);
416             nchars = js_strlen(chars);
417             chars = (jschar *)
418                 realloc((ochars = chars), nchars * sizeof(jschar) + growth);
419             if (!chars) {
420                 free(ochars);
421                 goto done;
422             }
423         }
424         chars[nchars++] = '[';
425     } else {
426         /*
427          * Free any sharp variable definition in chars.  Normally, we would
428          * MAKE_SHARP(he) so that only the first sharp variable annotation is
429          * a definition, and all the rest are references, but in the current
430          * case of (!literalize), we don't need chars at all.
431          */
432         if (chars)
433             JS_free(cx, chars);
434         chars = NULL;
435         nchars = 0;
437         /* Return the empty string on a cycle as well as on empty join. */
438         if (IS_BUSY(he) || length == 0) {
439             js_LeaveSharpObject(cx, NULL);
440             *rval = JS_GetEmptyStringValue(cx);
441             return ok;
442         }
444         /* Flag he as BUSY so we can distinguish a cycle from a join-point. */
445         MAKE_BUSY(he);
446     }
447     sepstr = NULL;
448     seplen = JSSTRING_LENGTH(sep);
450     /* Use rval to locally root each element value as we loop and convert. */
451 #define v (*rval)
453     v = JSVAL_NULL;
454     for (index = 0; index < length; index++) {
455         ok = JS_GetElement(cx, obj, index, &v);
456         if (!ok)
457             goto done;
459         if ((!literalize || JS_VERSION_IS_1_2(cx)) &&
460             (JSVAL_IS_VOID(v) || JSVAL_IS_NULL(v))) {
461             str = cx->runtime->emptyString;
462         } else {
463             if (localeString) {
464                 atom = cx->runtime->atomState.toLocaleStringAtom;
465                 JS_PUSH_TEMP_ROOT_OBJECT(cx, NULL, &tvr);
466                 ok = js_ValueToObject(cx, v, &tvr.u.object) &&
467                      js_TryMethod(cx, tvr.u.object, atom, 0, NULL, &v);
468                 JS_POP_TEMP_ROOT(cx, &tvr);
469                 if (!ok)
470                     goto done;
471                 str = js_ValueToString(cx, v);
472             } else {
473                 str = (literalize ? js_ValueToSource : js_ValueToString)(cx, v);
474             }
475             if (!str) {
476                 ok = JS_FALSE;
477                 goto done;
478             }
479         }
481         /* Allocate 3 + 1 at end for ", ", closing bracket, and zero. */
482         tmplen = JSSTRING_LENGTH(str);
483         growth = (nchars + (sepstr ? seplen : 0) + tmplen + 3 + 1);
484         if (nchars > growth || tmplen > growth ||
485             growth > (size_t)-1 / sizeof(jschar)) {
486             if (chars) {
487                 free(chars);
488                 chars = NULL;
489             }
490             JS_ReportOutOfMemory(cx);
491             goto done;
492         }
493         growth *= sizeof(jschar);
494         if (!chars) {
495             chars = (jschar *) malloc(growth);
496             if (!chars)
497                 goto done;
498         } else {
499             chars = (jschar *) realloc((ochars = chars), growth);
500             if (!chars) {
501                 free(ochars);
502                 goto done;
503             }
504         }
506         if (sepstr) {
507             js_strncpy(&chars[nchars], sepstr, seplen);
508             nchars += seplen;
509         }
510         sepstr = JSSTRING_CHARS(sep);
512         js_strncpy(&chars[nchars], JSSTRING_CHARS(str), tmplen);
513         nchars += tmplen;
514     }
516   done:
517     if (literalize) {
518         if (chars) {
519             if (JSVAL_IS_VOID(v)) {
520                 chars[nchars++] = ',';
521                 chars[nchars++] = ' ';
522             }
523             chars[nchars++] = ']';
524         }
525     } else {
526         CLEAR_BUSY(he);
527     }
528     js_LeaveSharpObject(cx, NULL);
529     if (!ok) {
530         if (chars)
531             free(chars);
532         return ok;
533     }
535 #undef v
537   make_string:
538     if (!chars) {
539         JS_ReportOutOfMemory(cx);
540         return JS_FALSE;
541     }
542     chars[nchars] = 0;
543     str = js_NewString(cx, chars, nchars, 0);
544     if (!str) {
545         free(chars);
546         return JS_FALSE;
547     }
548     *rval = STRING_TO_JSVAL(str);
549     return JS_TRUE;
552 static jschar   comma_space_ucstr[] = {',', ' ', 0};
553 static jschar   comma_ucstr[]       = {',', 0};
554 static JSString comma_space         = {2, comma_space_ucstr};
555 static JSString comma               = {1, comma_ucstr};
557 #if JS_HAS_TOSOURCE
558 static JSBool
559 array_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
560                jsval *rval)
562     return array_join_sub(cx, obj, &comma_space, JS_TRUE, rval, JS_FALSE);
564 #endif
566 static JSBool
567 array_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
568                jsval *rval)
570     JSBool literalize;
572     /*
573      * JS1.2 arrays convert to array literals, with a comma followed by a space
574      * between each element.
575      */
576     literalize = JS_VERSION_IS_1_2(cx);
577     return array_join_sub(cx, obj, literalize ? &comma_space : &comma,
578                           literalize, rval, JS_FALSE);
581 static JSBool
582 array_toLocaleString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
583                jsval *rval)
585     /*
586      *  Passing comma here as the separator. Need a way to get a
587      *  locale-specific version.
588      */
589     return array_join_sub(cx, obj, &comma, JS_FALSE, rval, JS_TRUE);
592 static JSBool
593 InitArrayElements(JSContext *cx, JSObject *obj, jsuint length, jsval *vector)
595     jsuint index;
596     jsid id;
598     for (index = 0; index < length; index++) {
599         JS_ASSERT(vector[index] != JSVAL_HOLE);
601         if (!IndexToId(cx, index, &id))
602             return JS_FALSE;
603         if (!OBJ_SET_PROPERTY(cx, obj, id, &vector[index]))
604             return JS_FALSE;
605     }
606     return JS_TRUE;
609 static JSBool
610 InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, jsval *vector)
612     jsval v;
613     jsid id;
615     if (!IndexToValue(cx, length, &v))
616         return JS_FALSE;
617     id = ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
618     if (!OBJ_DEFINE_PROPERTY(cx, obj, id, v,
619                              array_length_getter, array_length_setter,
620                              JSPROP_PERMANENT,
621                              NULL)) {
622           return JS_FALSE;
623     }
624     if (!vector)
625         return JS_TRUE;
626     return InitArrayElements(cx, obj, length, vector);
629 #if JS_HAS_SOME_PERL_FUN
630 /*
631  * Perl-inspired join, reverse, and sort.
632  */
633 static JSBool
634 array_join(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
636     JSString *str;
638     if (JSVAL_IS_VOID(argv[0]))
639         return array_join_sub(cx, obj, &comma, JS_FALSE, rval, JS_FALSE);
640     str = js_ValueToString(cx, argv[0]);
641     if (!str)
642         return JS_FALSE;
643     argv[0] = STRING_TO_JSVAL(str);
644     return array_join_sub(cx, obj, str, JS_FALSE, rval, JS_FALSE);
647 static JSBool
648 array_reverse(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
649               jsval *rval)
651     jsuint len, half, i;
652     jsid id, id2;
653     jsval *tmproot, *tmproot2;
654     JSBool idexists, id2exists, ok;
656     if (!js_GetLengthProperty(cx, obj, &len))
657         return JS_FALSE;
659     /*
660      * When len > JSVAL_INT_MAX + 1 the loop below accesses indexes greater
661      * than JSVAL_INT_MAX. For such indexes the corresponding ids are atoms.
662      * We use JS_KEEP_ATOMS to protect them against GC since OBJ_GET_PROPERTY
663      * can potentially execute an arbitrary script. See bug 341956.
664      *
665      * After this point control must flow through label out: to exit.
666      */
667     if (len > JSVAL_INT_MAX + 1)
668         JS_KEEP_ATOMS(cx->runtime);
670     /*
671      * Use argv[argc] and argv[argc + 1] as local roots to hold temporarily
672      * array elements for GC-safe swap.
673      */
674     tmproot = argv + argc;
675     tmproot2 = argv + argc + 1;
676     half = len / 2;
677     for (i = 0; i < half; i++) {
678         /*
679          * Get both values while checking for holes to make sure they don't
680          * get filled.
681          */
682         if (!IndexToId(cx, i, &id))
683             goto bad;
684         if (!PropertyExists(cx, obj, id, &idexists))
685             goto bad;
686         if (idexists && !OBJ_GET_PROPERTY(cx, obj, id, tmproot))
687             goto bad;
689         if (!IndexToId(cx, len - i - 1, &id2))
690             goto bad;
691         if (!PropertyExists(cx, obj, id2, &id2exists))
692             goto bad;
693         if (id2exists && !OBJ_GET_PROPERTY(cx, obj, id2, tmproot2))
694             goto bad;
696         /* Exchange the values. */
697         if (idexists) {
698             if (!OBJ_SET_PROPERTY(cx, obj, id2, tmproot))
699                 goto bad;
700         } else {
701             if (!OBJ_DELETE_PROPERTY(cx, obj, id2, tmproot))
702                 goto bad;
703         }
704         if (id2exists) {
705             if (!OBJ_SET_PROPERTY(cx, obj, id, tmproot2))
706                 goto bad;
707         } else {
708             if (!OBJ_DELETE_PROPERTY(cx, obj, id, tmproot2))
709                 goto bad;
710         }
711     }
712     ok = JS_TRUE;
714   out:
715     if (len > JSVAL_INT_MAX + 1)
716         JS_UNKEEP_ATOMS(cx->runtime);
717     *rval = OBJECT_TO_JSVAL(obj);
718     return ok;
720   bad:
721     ok = JS_FALSE;
722     goto out;
725 typedef struct HSortArgs {
726     void         *vec;
727     size_t       elsize;
728     void         *pivot;
729     JSComparator cmp;
730     void         *arg;
731     JSBool       fastcopy;
732 } HSortArgs;
734 static int
735 sort_compare(const void *a, const void *b, void *arg);
737 static int
738 sort_compare_strings(const void *a, const void *b, void *arg);
740 static void
741 HeapSortHelper(JSBool building, HSortArgs *hsa, size_t lo, size_t hi)
743     void *pivot, *vec, *vec2, *arg, *a, *b;
744     size_t elsize;
745     JSComparator cmp;
746     JSBool fastcopy;
747     size_t j, hiDiv2;
749     pivot = hsa->pivot;
750     vec = hsa->vec;
751     elsize = hsa->elsize;
752     vec2 =  (char *)vec - 2 * elsize;
753     cmp = hsa->cmp;
754     arg = hsa->arg;
756     fastcopy = hsa->fastcopy;
757 #define MEMCPY(p,q,n) \
758     (fastcopy ? (void)(*(jsval*)(p) = *(jsval*)(q)) : (void)memcpy(p, q, n))
760     if (lo == 1) {
761         j = 2;
762         b = (char *)vec + elsize;
763         if (j < hi && cmp(vec, b, arg) < 0)
764             j++;
765         a = (char *)vec + (hi - 1) * elsize;
766         b = (char *)vec2 + j * elsize;
768         /*
769          * During sorting phase b points to a member of heap that cannot be
770          * bigger then biggest of vec[0] and vec[1], and cmp(a, b, arg) <= 0
771          * always holds.
772          */
773         if ((building || hi == 2) && cmp(a, b, arg) >= 0)
774             return;
776         MEMCPY(pivot, a, elsize);
777         MEMCPY(a, b, elsize);
778         lo = j;
779     } else {
780         a = (char *)vec2 + lo * elsize;
781         MEMCPY(pivot, a, elsize);
782     }
784     hiDiv2 = hi/2;
785     while (lo <= hiDiv2) {
786         j = lo + lo;
787         a = (char *)vec2 + j * elsize;
788         b = (char *)vec + (j - 1) * elsize;
789         if (j < hi && cmp(a, b, arg) < 0)
790             j++;
791         b = (char *)vec2 + j * elsize;
792         if (cmp(pivot, b, arg) >= 0)
793             break;
795         a = (char *)vec2 + lo * elsize;
796         MEMCPY(a, b, elsize);
797         lo = j;
798     }
800     a = (char *)vec2 + lo * elsize;
801     MEMCPY(a, pivot, elsize);
802 #undef MEMCPY
805 void
806 js_HeapSort(void *vec, size_t nel, void *pivot, size_t elsize,
807             JSComparator cmp, void *arg)
809     HSortArgs hsa;
810     size_t i;
812     hsa.vec = vec;
813     hsa.elsize = elsize;
814     hsa.pivot = pivot;
815     hsa.cmp = cmp;
816     hsa.arg = arg;
817     hsa.fastcopy = (cmp == sort_compare || cmp == sort_compare_strings);
819     for (i = nel/2; i != 0; i--)
820         HeapSortHelper(JS_TRUE, &hsa, i, nel);
821     while (nel > 2)
822         HeapSortHelper(JS_FALSE, &hsa, 1, --nel);
825 typedef struct CompareArgs {
826     JSContext   *context;
827     jsval       fval;
828     jsval       *localroot;     /* need one local root, for sort_compare */
829     JSBool      status;
830 } CompareArgs;
832 static int
833 sort_compare(const void *a, const void *b, void *arg)
835     jsval av = *(const jsval *)a, bv = *(const jsval *)b;
836     CompareArgs *ca = (CompareArgs *) arg;
837     JSContext *cx = ca->context;
838     jsdouble cmp = -1;
839     jsval fval, argv[2], special;
840     JSBool ok;
842     fval = ca->fval;
844     /*
845      * By ECMA 262, 15.4.4.11, existence of the property with value undefined
846      * takes precedence over an undefined property (which we call a "hole").
847      */
848     if (av == JSVAL_HOLE || bv == JSVAL_HOLE)
849         special = JSVAL_HOLE;
850     else if (av == JSVAL_VOID || bv == JSVAL_VOID)
851         special = JSVAL_VOID;
852     else
853         special = JSVAL_NULL;
855     if (special != JSVAL_NULL) {
856         if (av == bv)
857             cmp = 0;
858         else if (av != special)
859             cmp = -1;
860         else
861             cmp = 1;
862     } else if (fval == JSVAL_NULL) {
863         JSString *astr, *bstr;
865         if (av == bv) {
866             cmp = 0;
867         } else {
868             /*
869              * Set our local root to astr in case the second js_ValueToString
870              * displaces the newborn root in cx, and the GC nests under that
871              * call.  Don't bother guarding the local root store with an astr
872              * non-null test.  If we tag null as a string, the GC will untag,
873              * null-test, and avoid dereferencing null.
874              */
875             astr = js_ValueToString(cx, av);
876             *ca->localroot = STRING_TO_JSVAL(astr);
877             if (astr && (bstr = js_ValueToString(cx, bv)))
878                 cmp = js_CompareStrings(astr, bstr);
879             else
880                 ca->status = JS_FALSE;
881         }
882     } else {
883         argv[0] = av;
884         argv[1] = bv;
885         ok = js_InternalCall(cx,
886                              OBJ_GET_PARENT(cx, JSVAL_TO_OBJECT(fval)),
887                              fval, 2, argv, ca->localroot);
888         if (ok) {
889             ok = js_ValueToNumber(cx, *ca->localroot, &cmp);
891             /* Clamp cmp to -1, 0, 1. */
892             if (ok) {
893                 if (JSDOUBLE_IS_NaN(cmp)) {
894                     /*
895                      * XXX report some kind of error here?  ECMA talks about
896                      * 'consistent compare functions' that don't return NaN,
897                      * but is silent about what the result should be.  So we
898                      * currently ignore it.
899                      */
900                     cmp = 0;
901                 } else if (cmp != 0) {
902                     cmp = cmp > 0 ? 1 : -1;
903                 }
904             }
905         }
906         if (!ok)
907             ca->status = ok;
908     }
909     return (int)cmp;
912 static int
913 sort_compare_strings(const void *a, const void *b, void *arg)
915     jsval av = *(const jsval *)a, bv = *(const jsval *)b;
917     return (int) js_CompareStrings(JSVAL_TO_STRING(av), JSVAL_TO_STRING(bv));
920 static JSBool
921 array_sort(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
923     jsval fval, *vec, *pivotroot;
924     CompareArgs ca;
925     jsuint len, newlen, i;
926     JSStackFrame *fp;
927     jsid id;
928     size_t nbytes;
930     /*
931      * Optimize the default compare function case if all of obj's elements
932      * have values of type string.
933      */
934     JSBool all_strings;
936     if (argc > 0) {
937         if (JSVAL_IS_PRIMITIVE(argv[0])) {
938             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
939                                  JSMSG_BAD_SORT_ARG);
940             return JS_FALSE;
941         }
942         fval = argv[0];
943         all_strings = JS_FALSE; /* non-default compare function */
944     } else {
945         fval = JSVAL_NULL;
946         all_strings = JS_TRUE;  /* check for all string values */
947     }
949     if (!js_GetLengthProperty(cx, obj, &len))
950         return JS_FALSE;
951     if (len == 0) {
952         *rval = OBJECT_TO_JSVAL(obj);
953         return JS_TRUE;
954     }
956     /*
957      * We need a temporary array of len jsvals to hold elements of the array.
958      * Check that its size does not overflow size_t, which would allow for
959      * indexing beyond the end of the malloc'd vector.
960      */
961     if (len > ((size_t) -1) / sizeof(jsval)) {
962         JS_ReportOutOfMemory(cx);
963         return JS_FALSE;
964     }
965     nbytes = ((size_t) len) * sizeof(jsval);
967     vec = (jsval *) JS_malloc(cx, nbytes);
968     if (!vec)
969         return JS_FALSE;
971     /* Root vec, clearing it first in case a GC nests while we're filling it. */
972     memset(vec, 0, nbytes);
973     fp = cx->fp;
974     fp->vars = vec;
975     fp->nvars = len;
977     newlen = 0;
978     for (i = 0; i < len; i++) {
979         ca.status = IndexToExistingId(cx, obj, i, &id);
980         if (!ca.status)
981             goto out;
983         if (id == JSID_HOLE) {
984             vec[i] = JSVAL_HOLE;
985             all_strings = JS_FALSE;
986             continue;
987         }
988         newlen++;
990         ca.status = OBJ_GET_PROPERTY(cx, obj, id, &vec[i]);
991         if (!ca.status)
992             goto out;
994         /* We know JSVAL_IS_STRING yields 0 or 1, so avoid a branch via &=. */
995         all_strings &= JSVAL_IS_STRING(vec[i]);
996     }
998     ca.context = cx;
999     ca.fval = fval;
1000     ca.localroot = argv + argc;       /* local GC root for temporary string */
1001     pivotroot    = argv + argc + 1;   /* local GC root for pivot val */
1002     ca.status = JS_TRUE;
1003     js_HeapSort(vec, (size_t) len, pivotroot, sizeof(jsval),
1004                 all_strings ? sort_compare_strings : sort_compare,
1005                 &ca);
1007     if (ca.status) {
1008         ca.status = InitArrayElements(cx, obj, newlen, vec);
1009         if (ca.status)
1010             *rval = OBJECT_TO_JSVAL(obj);
1012         /* Re-create any holes that sorted to the end of the array. */
1013         while (len > newlen) {
1014             jsval junk;
1016             if (!IndexToId(cx, --len, &id))
1017                 return JS_FALSE;
1018             if (!OBJ_DELETE_PROPERTY(cx, obj, id, &junk))
1019                 return JS_FALSE;
1020         }
1021     }
1023 out:
1024     if (vec)
1025         JS_free(cx, vec);
1026     return ca.status;
1028 #endif /* JS_HAS_SOME_PERL_FUN */
1030 #if JS_HAS_MORE_PERL_FUN
1031 /*
1032  * Perl-inspired push, pop, shift, unshift, and splice methods.
1033  */
1034 static JSBool
1035 array_push(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1037     jsuint length;
1038     uintN i;
1039     jsid id;
1041     if (!js_GetLengthProperty(cx, obj, &length))
1042         return JS_FALSE;
1043     for (i = 0; i < argc; i++) {
1044         if (!IndexToId(cx, length + i, &id))
1045             return JS_FALSE;
1046         if (!OBJ_SET_PROPERTY(cx, obj, id, &argv[i]))
1047             return JS_FALSE;
1048     }
1050     /*
1051      * If JS1.2, follow Perl4 by returning the last thing pushed.  Otherwise,
1052      * return the new array length.
1053      */
1054     length += argc;
1055     if (JS_VERSION_IS_1_2(cx)) {
1056         *rval = argc ? argv[argc-1] : JSVAL_VOID;
1057     } else {
1058         if (!IndexToValue(cx, length, rval))
1059             return JS_FALSE;
1060     }
1061     return js_SetLengthProperty(cx, obj, length);
1064 static JSBool
1065 array_pop(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1067     jsuint index;
1068     jsid id;
1069     JSBool ok;
1070     jsval junk;
1072     if (!js_GetLengthProperty(cx, obj, &index))
1073         return JS_FALSE;
1074     if (index > 0) {
1075         index--;
1077         /* Get the to-be-deleted property's value into rval. */
1078         if (!IndexToId(cx, index, &id))
1079             return JS_FALSE;
1081         /* See comments in array_reverse. */
1082         if (index > JSVAL_INT_MAX)
1083             JS_KEEP_ATOMS(cx->runtime);
1084         ok = OBJ_GET_PROPERTY(cx, obj, id, rval) &&
1085              OBJ_DELETE_PROPERTY(cx, obj, id, &junk);
1086         if (index > JSVAL_INT_MAX)
1087             JS_UNKEEP_ATOMS(cx->runtime);
1088         if (!ok)
1089             return JS_FALSE;
1090     }
1091     return js_SetLengthProperty(cx, obj, index);
1094 static JSBool
1095 array_shift(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1097     jsuint length, i;
1098     jsid id, id2;
1099     jsval junk;
1101     if (!js_GetLengthProperty(cx, obj, &length))
1102         return JS_FALSE;
1103     if (length > 0) {
1104         length--;
1105         id = JSVAL_ZERO;
1107         /* Get the to-be-deleted property's value into rval ASAP. */
1108         if (!OBJ_GET_PROPERTY(cx, obj, id, rval))
1109             return JS_FALSE;
1111         /*
1112          * Slide down the array above the first element.
1113          */
1114         if (length > 0) {
1115             for (i = 1; i <= length; i++) {
1116                 if (!IndexToId(cx, i, &id))
1117                     return JS_FALSE;
1118                 if (!OBJ_GET_PROPERTY(cx, obj, id, &argv[0]))
1119                     return JS_FALSE;
1121                 /* Get id after value to avoid nested GC hazards. */
1122                 if (!IndexToId(cx, i - 1, &id2))
1123                     return JS_FALSE;
1124                 if (!OBJ_SET_PROPERTY(cx, obj, id2, &argv[0]))
1125                     return JS_FALSE;
1126             }
1127         }
1129         /*
1130          * Delete the only or the last element. We recreate id when it is an
1131          * atom to protect against a nested GC during the last iteration.
1132          */
1133         if (length > JSVAL_INT_MAX && !IndexToId(cx, length, &id))
1134             return JS_FALSE;
1135         if (!OBJ_DELETE_PROPERTY(cx, obj, id, &junk))
1136             return JS_FALSE;
1137     }
1138     return js_SetLengthProperty(cx, obj, length);
1141 static JSBool
1142 array_unshift(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1143               jsval *rval)
1145     jsuint length, last;
1146     uintN i;
1147     jsid id, id2;
1148     jsval *vp, junk;
1150     if (!js_GetLengthProperty(cx, obj, &length))
1151         return JS_FALSE;
1152     if (argc > 0) {
1153         /* Slide up the array to make room for argc at the bottom. */
1154         if (length > 0) {
1155             last = length;
1156             vp = argv + argc;
1157             while (last--) {
1158                 if (!IndexToExistingId(cx, obj, last, &id))
1159                     return JS_FALSE;
1160                 if (id != JSID_HOLE) {
1161                     if (!OBJ_GET_PROPERTY(cx, obj, id, vp))
1162                         return JS_FALSE;
1163                 }
1165                 /* Get id after value to avoid nested GC hazards. */
1166                 if (!IndexToId(cx, last + argc, &id2))
1167                     return JS_FALSE;
1168                 if (id == JSID_HOLE) {
1169                     if (!OBJ_DELETE_PROPERTY(cx, obj, id2, &junk))
1170                         return JS_FALSE;
1171                 } else {
1172                     if (!OBJ_SET_PROPERTY(cx, obj, id2, vp))
1173                         return JS_FALSE;
1174                 }
1175             }
1176         }
1178         /* Copy from argv to the bottom of the array. */
1179         for (i = 0; i < argc; i++) {
1180             if (!IndexToId(cx, i, &id))
1181                 return JS_FALSE;
1182             if (!OBJ_SET_PROPERTY(cx, obj, id, &argv[i]))
1183                 return JS_FALSE;
1184         }
1186         /* Follow Perl by returning the new array length. */
1187         length += argc;
1188         if (!js_SetLengthProperty(cx, obj, length))
1189             return JS_FALSE;
1190     }
1191     return IndexToValue(cx, length, rval);
1194 static JSBool
1195 array_splice(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1197     jsval *vp, junk;
1198     jsuint length, begin, end, count, delta, last;
1199     jsdouble d;
1200     jsid id, id2;
1201     JSObject *obj2;
1202     uintN i;
1204     /*
1205      * Nothing to do if no args.  Otherwise point vp at our one explicit local
1206      * root and get length.
1207      */
1208     if (argc == 0)
1209         return JS_TRUE;
1210     vp = argv + argc;
1211     if (!js_GetLengthProperty(cx, obj, &length))
1212         return JS_FALSE;
1214     /* Convert the first argument into a starting index. */
1215     if (!js_ValueToNumber(cx, *argv, &d))
1216         return JS_FALSE;
1217     d = js_DoubleToInteger(d);
1218     if (d < 0) {
1219         d += length;
1220         if (d < 0)
1221             d = 0;
1222     } else if (d > length) {
1223         d = length;
1224     }
1225     begin = (jsuint)d; /* d has been clamped to uint32 */
1226     argc--;
1227     argv++;
1229     /* Convert the second argument from a count into a fencepost index. */
1230     delta = length - begin;
1231     if (argc == 0) {
1232         count = delta;
1233         end = length;
1234     } else {
1235         if (!js_ValueToNumber(cx, *argv, &d))
1236             return JS_FALSE;
1237         d = js_DoubleToInteger(d);
1238         if (d < 0)
1239             d = 0;
1240         else if (d > delta)
1241             d = delta;
1242         count = (jsuint)d;
1243         end = begin + count;
1244         argc--;
1245         argv++;
1246     }
1248     if (count == 1 && JS_VERSION_IS_1_2(cx)) {
1249         /*
1250          * JS lacks "list context", whereby in Perl one turns the single
1251          * scalar that's spliced out into an array just by assigning it to
1252          * @single instead of $single, or by using it as Perl push's first
1253          * argument, for instance.
1254          *
1255          * JS1.2 emulated Perl too closely and returned a non-Array for
1256          * the single-splice-out case, requiring callers to test and wrap
1257          * in [] if necessary.  So JS1.3, default, and other versions all
1258          * return an array of length 1 for uniformity.
1259          */
1260         if (!IndexToId(cx, begin, &id))
1261             return JS_FALSE;
1262         if (!OBJ_GET_PROPERTY(cx, obj, id, rval))
1263             return JS_FALSE;
1264     } else {
1265         if (!JS_VERSION_IS_1_2(cx) || count > 0) {
1266             /*
1267              * Create a new array value to return.  Our ECMA v2 proposal specs
1268              * that splice always returns an array value, even when given no
1269              * arguments.  We think this is best because it eliminates the need
1270              * for callers to do an extra test to handle the empty splice case.
1271              */
1272             obj2 = js_NewArrayObject(cx, 0, NULL);
1273             if (!obj2)
1274                 return JS_FALSE;
1275             *rval = OBJECT_TO_JSVAL(obj2);
1277             /* If there are elements to remove, put them into the return value. */
1278             if (count > 0) {
1279                 for (last = begin; last < end; last++) {
1280                     if (!IndexToExistingId(cx, obj, last, &id))
1281                         return JS_FALSE;
1282                     if (id == JSID_HOLE)
1283                         continue;       /* don't fill holes in the new array */
1284                     if (!OBJ_GET_PROPERTY(cx, obj, id, vp))
1285                         return JS_FALSE;
1287                     /* Get id after value to avoid nested GC hazards. */
1288                     if (!IndexToId(cx, last - begin, &id2))
1289                         return JS_FALSE;
1290                     if (!OBJ_SET_PROPERTY(cx, obj2, id2, vp))
1291                         return JS_FALSE;
1292                 }
1294                 if (!js_SetLengthProperty(cx, obj2, end - begin))
1295                     return JS_FALSE;
1296             }
1297         }
1298     }
1300     /* Find the direction (up or down) to copy and make way for argv. */
1301     if (argc > count) {
1302         delta = (jsuint)argc - count;
1303         last = length;
1304         /* (uint) end could be 0, so can't use vanilla >= test */
1305         while (last-- > end) {
1306             if (!IndexToExistingId(cx, obj, last, &id))
1307                 return JS_FALSE;
1308             if (id != JSID_HOLE) {
1309                 if (!OBJ_GET_PROPERTY(cx, obj, id, vp))
1310                     return JS_FALSE;
1311             }
1313             /* Get id after value to avoid nested GC hazards. */
1314             if (!IndexToId(cx, last + delta, &id2))
1315                 return JS_FALSE;
1316             if (id != JSID_HOLE) {
1317                 if (!OBJ_SET_PROPERTY(cx, obj, id2, vp))
1318                     return JS_FALSE;
1319             } else {
1320                 if (!OBJ_DELETE_PROPERTY(cx, obj, id2, &junk))
1321                     return JS_FALSE;
1322             }
1323         }
1324         length += delta;
1325     } else if (argc < count) {
1326         delta = count - (jsuint)argc;
1327         for (last = end; last < length; last++) {
1328             if (!IndexToExistingId(cx, obj, last, &id))
1329                 return JS_FALSE;
1330             if (id != JSID_HOLE) {
1331                 if (!OBJ_GET_PROPERTY(cx, obj, id, vp))
1332                     return JS_FALSE;
1333             }
1335             /* Get id after value to avoid nested GC hazards. */
1336             if (!IndexToId(cx, last - delta, &id2))
1337                 return JS_FALSE;
1338             if (id != JSID_HOLE) {
1339                 if (!OBJ_SET_PROPERTY(cx, obj, id2, vp))
1340                     return JS_FALSE;
1341             } else {
1342                 if (!OBJ_DELETE_PROPERTY(cx, obj, id2, &junk))
1343                     return JS_FALSE;
1344             }
1345         }
1346         length -= delta;
1347     }
1349     /* Copy from argv into the hole to complete the splice. */
1350     for (i = 0; i < argc; i++) {
1351         if (!IndexToId(cx, begin + i, &id))
1352             return JS_FALSE;
1353         if (!OBJ_SET_PROPERTY(cx, obj, id, &argv[i]))
1354             return JS_FALSE;
1355     }
1357     /* Update length in case we deleted elements from the end. */
1358     return js_SetLengthProperty(cx, obj, length);
1360 #endif /* JS_HAS_MORE_PERL_FUN */
1362 #if JS_HAS_SEQUENCE_OPS
1363 /*
1364  * Python-esque sequence operations.
1365  */
1366 static JSBool
1367 array_concat(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1369     jsval *vp, v;
1370     JSObject *nobj, *aobj;
1371     jsuint length, alength, slot;
1372     uintN i;
1373     jsid id, id2;
1375     /* Hoist the explicit local root address computation. */
1376     vp = argv + argc;
1378     /* Treat obj as the first argument; see ECMA 15.4.4.4. */
1379     --argv;
1380     JS_ASSERT(obj == JSVAL_TO_OBJECT(argv[0]));
1382     /* Create a new Array object and store it in the rval local root. */
1383     nobj = js_NewArrayObject(cx, 0, NULL);
1384     if (!nobj)
1385         return JS_FALSE;
1386     *rval = OBJECT_TO_JSVAL(nobj);
1388     /* Loop over [0, argc] to concat args into nobj, expanding all Arrays. */
1389     length = 0;
1390     for (i = 0; i <= argc; i++) {
1391         v = argv[i];
1392         if (JSVAL_IS_OBJECT(v)) {
1393             aobj = JSVAL_TO_OBJECT(v);
1394             if (aobj && OBJ_GET_CLASS(cx, aobj) == &js_ArrayClass) {
1395                 if (!OBJ_GET_PROPERTY(cx, aobj,
1396                                       ATOM_TO_JSID(cx->runtime->atomState
1397                                                    .lengthAtom),
1398                                       vp)) {
1399                     return JS_FALSE;
1400                 }
1401                 if (!ValueIsLength(cx, *vp, &alength))
1402                     return JS_FALSE;
1403                 for (slot = 0; slot < alength; slot++) {
1404                     if (!IndexToExistingId(cx, aobj, slot, &id))
1405                         return JS_FALSE;
1406                     if (id == JSID_HOLE) {
1407                         /*
1408                          * Per ECMA 262, 15.4.4.4, step 9, ignore non-existent
1409                          * properties.
1410                          */
1411                         continue;
1412                     }
1413                     if (!OBJ_GET_PROPERTY(cx, aobj, id, vp))
1414                         return JS_FALSE;
1416                     /* Get id after value to avoid nested GC hazards. */
1417                     if (!IndexToId(cx, length + slot, &id2))
1418                         return JS_FALSE;
1419                     if (!OBJ_SET_PROPERTY(cx, nobj, id2, vp))
1420                         return JS_FALSE;
1421                 }
1422                 length += alength;
1423                 continue;
1424             }
1425         }
1427         *vp = v;
1428         if (!IndexToId(cx, length, &id))
1429             return JS_FALSE;
1430         if (!OBJ_SET_PROPERTY(cx, nobj, id, vp))
1431             return JS_FALSE;
1432         length++;
1433     }
1435     return js_SetLengthProperty(cx, nobj, length);
1438 static JSBool
1439 array_slice(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1441     jsval *vp;
1442     JSObject *nobj;
1443     jsuint length, begin, end, slot;
1444     jsdouble d;
1445     jsid id, id2;
1447     /* Hoist the explicit local root address computation. */
1448     vp = argv + argc;
1450     /* Create a new Array object and store it in the rval local root. */
1451     nobj = js_NewArrayObject(cx, 0, NULL);
1452     if (!nobj)
1453         return JS_FALSE;
1454     *rval = OBJECT_TO_JSVAL(nobj);
1456     if (!js_GetLengthProperty(cx, obj, &length))
1457         return JS_FALSE;
1458     begin = 0;
1459     end = length;
1461     if (argc > 0) {
1462         if (!js_ValueToNumber(cx, argv[0], &d))
1463             return JS_FALSE;
1464         d = js_DoubleToInteger(d);
1465         if (d < 0) {
1466             d += length;
1467             if (d < 0)
1468                 d = 0;
1469         } else if (d > length) {
1470             d = length;
1471         }
1472         begin = (jsuint)d;
1474         if (argc > 1) {
1475             if (!js_ValueToNumber(cx, argv[1], &d))
1476                 return JS_FALSE;
1477             d = js_DoubleToInteger(d);
1478             if (d < 0) {
1479                 d += length;
1480                 if (d < 0)
1481                     d = 0;
1482             } else if (d > length) {
1483                 d = length;
1484             }
1485             end = (jsuint)d;
1486         }
1487     }
1489     if (begin > end)
1490         begin = end;
1492     for (slot = begin; slot < end; slot++) {
1493         if (!IndexToExistingId(cx, obj, slot, &id))
1494             return JS_FALSE;
1495         if (id == JSID_HOLE)
1496             continue;
1497         if (!OBJ_GET_PROPERTY(cx, obj, id, vp))
1498             return JS_FALSE;
1500         /* Get id after value to avoid nested GC hazards. */
1501         if (!IndexToId(cx, slot - begin, &id2))
1502             return JS_FALSE;
1503         if (!OBJ_SET_PROPERTY(cx, nobj, id2, vp))
1504             return JS_FALSE;
1505     }
1506     return js_SetLengthProperty(cx, nobj, end - begin);
1508 #endif /* JS_HAS_SEQUENCE_OPS */
1510 #if JS_HAS_ARRAY_EXTRAS
1512 static JSBool
1513 array_indexOfHelper(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1514                     jsval *rval, JSBool isLast)
1516     jsuint length, i, stop;
1517     jsint direction;
1519     if (!js_GetLengthProperty(cx, obj, &length))
1520         return JS_FALSE;
1521     if (length == 0)
1522         goto not_found;
1524     if (argc <= 1) {
1525         i = isLast ? length - 1 : 0;
1526     } else {
1527         jsdouble start;
1529         if (!js_ValueToNumber(cx, argv[1], &start))
1530             return JS_FALSE;
1531         start = js_DoubleToInteger(start);
1532         if (start < 0) {
1533             start += length;
1534             i = (start < 0) ? 0 : (jsuint)start;
1535         } else if (start >= length) {
1536             i = length - 1;
1537         } else {
1538             i = (jsuint)start;
1539         }
1540     }
1542     if (isLast) {
1543         stop = 0;
1544         direction = -1;
1545     } else {
1546         stop = length - 1;
1547         direction = 1;
1548     }
1550     for (;;) {
1551         jsid id;
1552         jsval v;
1554         if (!IndexToExistingId(cx, obj, (jsuint)i, &id))
1555             return JS_FALSE;
1556         if (id != JSID_HOLE) {
1557             if (!OBJ_GET_PROPERTY(cx, obj, id, &v))
1558                 return JS_FALSE;
1559             if (js_StrictlyEqual(v, argv[0]))
1560                 return js_NewNumberValue(cx, i, rval);
1561         }
1563         if (i == stop)
1564             goto not_found;
1565         i += direction;
1566     }
1568   not_found:
1569     *rval = INT_TO_JSVAL(-1);
1570     return JS_TRUE;
1573 static JSBool
1574 array_indexOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1575               jsval *rval)
1577     return array_indexOfHelper(cx, obj, argc, argv, rval, JS_FALSE);
1580 static JSBool
1581 array_lastIndexOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1582                   jsval *rval)
1584     return array_indexOfHelper(cx, obj, argc, argv, rval, JS_TRUE);
1587 /* Order is important; extras that use a caller's predicate must follow MAP. */
1588 typedef enum ArrayExtraMode {
1589     FOREACH,
1590     MAP,
1591     FILTER,
1592     SOME,
1593     EVERY
1594 } ArrayExtraMode;
1596 static JSBool
1597 array_extra(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval,
1598             ArrayExtraMode mode)
1600     jsval *vp, *sp, *origsp, *oldsp;
1601     jsuint length, newlen, i;
1602     JSObject *callable, *thisp, *newarr;
1603     void *mark;
1604     JSStackFrame *fp;
1605     JSBool ok, b;
1607     /* Hoist the explicit local root address computation. */
1608     vp = argv + argc;
1610     if (!js_GetLengthProperty(cx, obj, &length))
1611         return JS_FALSE;
1613     /*
1614      * First, get or compute our callee, so that we error out consistently
1615      * when passed a non-callable object.
1616      */
1617     callable = js_ValueToCallableObject(cx, &argv[0], 0);
1618     if (!callable)
1619         return JS_FALSE;
1621     /*
1622      * Set our initial return condition, used for zero-length array cases
1623      * (and pre-size our map return to match our known length, for all cases).
1624      */
1625 #ifdef __GNUC__ /* quell GCC overwarning */
1626     newlen = 0;
1627     newarr = NULL;
1628     ok = JS_TRUE;
1629 #endif
1630     switch (mode) {
1631       case MAP:
1632       case FILTER:
1633         newlen = (mode == MAP) ? length : 0;
1634         newarr = js_NewArrayObject(cx, newlen, NULL);
1635         if (!newarr)
1636             return JS_FALSE;
1637         *rval = OBJECT_TO_JSVAL(newarr);
1638         break;
1639       case SOME:
1640         *rval = JSVAL_FALSE;
1641         break;
1642       case EVERY:
1643         *rval = JSVAL_TRUE;
1644         break;
1645       case FOREACH:
1646         break;
1647     }
1649     if (length == 0)
1650         return JS_TRUE;
1652     if (argc > 1) {
1653         if (!js_ValueToObject(cx, argv[1], &thisp))
1654             return JS_FALSE;
1655         argv[1] = OBJECT_TO_JSVAL(thisp);
1656     } else {
1657         thisp = NULL;
1658     }
1660     /* We call with 3 args (value, index, array), plus room for rval. */
1661     origsp = js_AllocStack(cx, 2 + 3 + 1, &mark);
1662     if (!origsp)
1663         return JS_FALSE;
1665     /* Lift current frame to include our args. */
1666     fp = cx->fp;
1667     oldsp = fp->sp;
1669     for (i = 0; i < length; i++) {
1670         jsid id;
1671         jsval rval2;
1673         ok = IndexToExistingId(cx, obj, i, &id);
1674         if (!ok)
1675             break;
1676         if (id == JSID_HOLE)
1677             continue;
1678         ok = OBJ_GET_PROPERTY(cx, obj, id, vp);
1679         if (!ok)
1680             break;
1682         /*
1683          * Push callable and 'this', then args. We must do this for every
1684          * iteration around the loop since js_Invoke uses origsp[0] for rval
1685          * storage and some native functions use origsp[1] for local rooting.
1686          */
1687         sp = origsp;
1688         *sp++ = OBJECT_TO_JSVAL(callable);
1689         *sp++ = OBJECT_TO_JSVAL(thisp);
1690         *sp++ = *vp;
1691         *sp++ = INT_TO_JSVAL(i);
1692         *sp++ = OBJECT_TO_JSVAL(obj);
1694         /* Do the call. */
1695         fp->sp = sp;
1696         ok = js_Invoke(cx, 3, JSINVOKE_INTERNAL);
1697         rval2 = fp->sp[-1];
1698         fp->sp = oldsp;
1699         if (!ok)
1700             break;
1702         if (mode > MAP) {
1703             if (rval2 == JSVAL_NULL) {
1704                 b = JS_FALSE;
1705             } else if (JSVAL_IS_BOOLEAN(rval2)) {
1706                 b = JSVAL_TO_BOOLEAN(rval2);
1707             } else {
1708                 ok = js_ValueToBoolean(cx, rval2, &b);
1709                 if (!ok)
1710                     goto out;
1711             }
1712         }
1714         switch (mode) {
1715           case FOREACH:
1716             break;
1717           case MAP:
1718             /*
1719              * We reconstruct id once again when it is a GC thing since scripts
1720              * can trigger GC that collects it. See bug 341956.
1721              */
1722             if (i > JSVAL_INT_MAX) {
1723                 ok = IndexToId(cx, i, &id);
1724                 if (!ok)
1725                     goto out;
1726             }
1727             ok = OBJ_SET_PROPERTY(cx, newarr, id, &rval2);
1728             if (!ok)
1729                 goto out;
1730             break;
1731           case FILTER:
1732             if (!b)
1733                 break;
1734             /* Filter passed *vp, push as result. */
1735             ok = IndexToId(cx, newlen++, &id);
1736             if (!ok)
1737                 goto out;
1738             ok = OBJ_SET_PROPERTY(cx, newarr, id, vp);
1739             if (!ok)
1740                 goto out;
1741             break;
1742           case SOME:
1743             if (b) {
1744                 *rval = JSVAL_TRUE;
1745                 goto out;
1746             }
1747             break;
1748           case EVERY:
1749             if (!b) {
1750                 *rval = JSVAL_FALSE;
1751                 goto out;
1752             }
1753             break;
1754         }
1755     }
1757  out:
1758     js_FreeStack(cx, mark);
1759     if (ok && mode == FILTER)
1760         ok = js_SetLengthProperty(cx, newarr, newlen);
1761     return ok;
1764 static JSBool
1765 array_forEach(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1766               jsval *rval)
1768     return array_extra(cx, obj, argc, argv, rval, FOREACH);
1771 static JSBool
1772 array_map(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1773           jsval *rval)
1775     return array_extra(cx, obj, argc, argv, rval, MAP);
1778 static JSBool
1779 array_filter(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1780              jsval *rval)
1782     return array_extra(cx, obj, argc, argv, rval, FILTER);
1785 static JSBool
1786 array_some(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1787            jsval *rval)
1789     return array_extra(cx, obj, argc, argv, rval, SOME);
1792 static JSBool
1793 array_every(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1794            jsval *rval)
1796     return array_extra(cx, obj, argc, argv, rval, EVERY);
1798 #endif
1800 static JSFunctionSpec array_methods[] = {
1801 #if JS_HAS_TOSOURCE
1802     {js_toSource_str,       array_toSource,         0,0,0},
1803 #endif
1804     {js_toString_str,       array_toString,         0,0,0},
1805     {js_toLocaleString_str, array_toLocaleString,   0,0,0},
1807     /* Perl-ish methods. */
1808 #if JS_HAS_SOME_PERL_FUN
1809     {"join",                array_join,             1,JSFUN_GENERIC_NATIVE,0},
1810     {"reverse",             array_reverse,          0,JSFUN_GENERIC_NATIVE,2},
1811     {"sort",                array_sort,             1,JSFUN_GENERIC_NATIVE,2},
1812 #endif
1813 #if JS_HAS_MORE_PERL_FUN
1814     {"push",                array_push,             1,JSFUN_GENERIC_NATIVE,0},
1815     {"pop",                 array_pop,              0,JSFUN_GENERIC_NATIVE,0},
1816     {"shift",               array_shift,            0,JSFUN_GENERIC_NATIVE,1},
1817     {"unshift",             array_unshift,          1,JSFUN_GENERIC_NATIVE,1},
1818     {"splice",              array_splice,           2,JSFUN_GENERIC_NATIVE,1},
1819 #endif
1821     /* Python-esque sequence methods. */
1822 #if JS_HAS_SEQUENCE_OPS
1823     {"concat",              array_concat,           1,JSFUN_GENERIC_NATIVE,1},
1824     {"slice",               array_slice,            2,JSFUN_GENERIC_NATIVE,1},
1825 #endif
1827 #if JS_HAS_ARRAY_EXTRAS
1828     {"indexOf",             array_indexOf,          1,JSFUN_GENERIC_NATIVE,0},
1829     {"lastIndexOf",         array_lastIndexOf,      1,JSFUN_GENERIC_NATIVE,0},
1830     {"forEach",             array_forEach,          1,JSFUN_GENERIC_NATIVE,1},
1831     {"map",                 array_map,              1,JSFUN_GENERIC_NATIVE,1},
1832     {"filter",              array_filter,           1,JSFUN_GENERIC_NATIVE,1},
1833     {"some",                array_some,             1,JSFUN_GENERIC_NATIVE,1},
1834     {"every",               array_every,            1,JSFUN_GENERIC_NATIVE,1},
1835 #endif
1837     {0,0,0,0,0}
1838 };
1840 static JSBool
1841 Array(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1843     jsuint length;
1844     jsval *vector;
1846     /* If called without new, replace obj with a new Array object. */
1847     if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) {
1848         obj = js_NewObject(cx, &js_ArrayClass, NULL, NULL);
1849         if (!obj)
1850             return JS_FALSE;
1851         *rval = OBJECT_TO_JSVAL(obj);
1852     }
1854     if (argc == 0) {
1855         length = 0;
1856         vector = NULL;
1857     } else if (JS_VERSION_IS_1_2(cx)) {
1858         length = (jsuint) argc;
1859         vector = argv;
1860     } else if (argc > 1) {
1861         length = (jsuint) argc;
1862         vector = argv;
1863     } else if (!JSVAL_IS_NUMBER(argv[0])) {
1864         length = 1;
1865         vector = argv;
1866     } else {
1867         if (!ValueIsLength(cx, argv[0], &length))
1868             return JS_FALSE;
1869         vector = NULL;
1870     }
1871     return InitArrayObject(cx, obj, length, vector);
1874 JSObject *
1875 js_InitArrayClass(JSContext *cx, JSObject *obj)
1877     JSObject *proto;
1879     proto = JS_InitClass(cx, obj, NULL, &js_ArrayClass, Array, 1,
1880                          NULL, array_methods, NULL, NULL);
1882     /* Initialize the Array prototype object so it gets a length property. */
1883     if (!proto || !InitArrayObject(cx, proto, 0, NULL))
1884         return NULL;
1885     return proto;
1888 JSObject *
1889 js_NewArrayObject(JSContext *cx, jsuint length, jsval *vector)
1891     JSObject *obj;
1893     obj = js_NewObject(cx, &js_ArrayClass, NULL, NULL);
1894     if (!obj)
1895         return NULL;
1896     if (!InitArrayObject(cx, obj, length, vector)) {
1897         cx->newborn[GCX_OBJECT] = NULL;
1898         return NULL;
1899     }
1900     return obj;