Bug 322135: Optimizing large index access for array elements to prevent out-of-memory when calling Array functions for huge sparse arrays. r=mrbkap sr=brendan

This commit is contained in:
igor.bukanov%gmail.com 2006-08-08 20:25:30 +00:00
Родитель 75d60d71fd
Коммит 73dbbf8c4b
4 изменённых файлов: 290 добавлений и 351 удалений

Просмотреть файл

@ -64,9 +64,6 @@
#define MAXINDEX 4294967295u
#define MAXSTR "4294967295"
/* A useful value for identifying a hole in an array */
#define JSVAL_HOLE BOOLEAN_TO_JSVAL(2)
/*
* Determine if the id represents an array index or an XML property index.
*
@ -198,58 +195,140 @@ IndexToValue(JSContext *cx, jsuint index, jsval *vp)
}
static JSBool
IndexToId(JSContext *cx, jsuint index, jsid *idp)
BigIndexToId(JSContext *cx, JSObject *obj, jsuint index, JSBool createAtom,
jsid *idp)
{
JSString *str;
jschar buf[10], *start;
JSClass *clasp;
JSAtom *atom;
JS_STATIC_ASSERT((jsuint)-1 == 4294967295U);
if (index <= JSVAL_INT_MAX) {
*idp = INT_TO_JSID(index);
JS_ASSERT(index > JSVAL_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.
*/
if (!createAtom &&
((clasp = OBJ_GET_CLASS(cx, obj)) == &js_ArrayClass ||
clasp == &js_ArgumentsClass ||
clasp == &js_ObjectClass)) {
atom = js_GetExistingStringAtom(cx, start, JS_ARRAY_END(buf) - start);
if (!atom) {
*idp = JSVAL_VOID;
return JS_TRUE;
}
} else {
str = js_NumberToString(cx, (jsdouble)index);
if (!str)
return JS_FALSE;
atom = js_AtomizeString(cx, str, 0);
atom = js_AtomizeChars(cx, start, JS_ARRAY_END(buf) - start, 0);
if (!atom)
return JS_FALSE;
*idp = ATOM_TO_JSID(atom);
}
*idp = ATOM_TO_JSID(atom);
return JS_TRUE;
}
/*
* 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
GetArrayElement(JSContext *cx, JSObject *obj, jsuint index, JSBool *hole,
jsval *vp)
{
jsid id;
JSObject *obj2;
JSProperty *prop;
if (index <= JSVAL_INT_MAX) {
id = INT_TO_JSID(index);
} else {
if (!BigIndexToId(cx, obj, index, JS_FALSE, &id))
return JS_FALSE;
if (id == JSVAL_VOID) {
*hole = JS_TRUE;
*vp = JSVAL_VOID;
return JS_TRUE;
}
}
if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop))
return JS_FALSE;
if (!prop) {
*hole = JS_TRUE;
*vp = JSVAL_VOID;
} else {
OBJ_DROP_PROPERTY(cx, obj2, prop);
if (!OBJ_GET_PROPERTY(cx, obj, id, vp))
return JS_FALSE;
*hole = JS_FALSE;
}
return JS_TRUE;
}
/*
* Set the value of the property at the given index to v assuming v is rooted.
*/
static JSBool
PropertyExists(JSContext *cx, JSObject *obj, jsid id, JSBool *foundp)
SetArrayElement(JSContext *cx, JSObject *obj, jsuint index, jsval v)
{
JSObject *obj2;
JSProperty *prop;
jsid id;
if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop))
return JS_FALSE;
*foundp = prop != NULL;
if (*foundp)
OBJ_DROP_PROPERTY(cx, obj2, prop);
return JS_TRUE;
if (index <= JSVAL_INT_MAX) {
id = INT_TO_JSID(index);
} else {
if (!BigIndexToId(cx, obj, index, JS_TRUE, &id))
return JS_FALSE;
JS_ASSERT(id != JSVAL_VOID);
}
return OBJ_SET_PROPERTY(cx, obj, id, &v);
}
#define JSID_HOLE JSVAL_NULL
static JSBool
IndexToExistingId(JSContext *cx, JSObject *obj, jsuint index, jsid *idp)
DeleteArrayElement(JSContext *cx, JSObject *obj, jsuint index)
{
JSBool exists;
jsid id;
jsval junk;
if (!IndexToId(cx, index, idp))
return JS_FALSE;
if (!PropertyExists(cx, obj, *idp, &exists))
return JS_FALSE;
if (!exists)
*idp = JSID_HOLE;
return JS_TRUE;
if (index <= JSVAL_INT_MAX) {
id = INT_TO_JSID(index);
} else {
if (!BigIndexToId(cx, obj, index, JS_FALSE, &id))
return JS_FALSE;
if (id == JSVAL_VOID)
return JS_TRUE;
}
return OBJ_DELETE_PROPERTY(cx, obj, id, &junk);
}
/*
* 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, jsuint index,
JSBool hole, jsval v)
{
if (hole) {
JS_ASSERT(v == JSVAL_VOID);
return DeleteArrayElement(cx, obj, index);
} else {
return SetArrayElement(cx, obj, index, v);
}
}
JSBool
js_SetLengthProperty(JSContext *cx, JSObject *obj, jsuint length)
{
@ -322,9 +401,7 @@ array_length_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
if (oldlen - newlen < (1 << 24)) {
do {
--oldlen;
if (!IndexToId(cx, oldlen, &id2))
return JS_FALSE;
if (!OBJ_DELETE_PROPERTY(cx, obj, id2, &junk))
if (!DeleteArrayElement(cx, obj, oldlen))
return JS_FALSE;
} while (oldlen != newlen);
} else {
@ -407,12 +484,11 @@ static JSBool
array_join_sub(JSContext *cx, JSObject *obj, enum ArrayToStringOp op,
JSString *sep, jsval *rval)
{
JSBool ok;
JSBool ok, hole;
jsuint length, index;
jschar *chars, *ochars;
size_t nchars, growth, seplen, tmplen, extratail;
const jschar *sepstr;
jsid id;
JSString *str;
JSHashEntry *he;
JSObject *obj2;
@ -508,26 +584,11 @@ array_join_sub(JSContext *cx, JSObject *obj, enum ArrayToStringOp op,
#define v (*rval)
for (index = 0; index < length; index++) {
if (op != TO_SOURCE) {
ok = JS_GetElement(cx, obj, index, &v);
} else {
ok = IndexToExistingId(cx, obj, index, &id);
if (!ok)
goto done;
if (id == JSID_HOLE) {
str = cx->runtime->emptyString;
/* For tail holes always append single "," and not ", ". */
if (index + 1 == length)
seplen = 1;
goto got_str;
}
ok = OBJ_GET_PROPERTY(cx, obj, id, &v);
}
ok = GetArrayElement(cx, obj, index, &hole, &v);
if (!ok)
goto done;
if (op != TO_SOURCE && (JSVAL_IS_VOID(v) || JSVAL_IS_NULL(v))) {
if (hole ||
(op != TO_SOURCE && (JSVAL_IS_VOID(v) || JSVAL_IS_NULL(v)))) {
str = cx->runtime->emptyString;
} else {
if (op == TO_LOCALE_STRING) {
@ -551,11 +612,13 @@ array_join_sub(JSContext *cx, JSObject *obj, enum ArrayToStringOp op,
}
}
/* Do not append separator after the last element. */
/*
* Do not append separator after the last element unless it is a hole
* and we are in toSource. In that case we append single ",".
*/
if (index + 1 == length)
seplen = 0;
seplen = (hole && op == TO_SOURCE) ? 1 : 0;
got_str:
/* Allocate 1 at end for closing bracket and zero. */
tmplen = JSSTRING_LENGTH(str);
growth = nchars + tmplen + seplen + extratail;
@ -656,17 +719,11 @@ array_toLocaleString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
}
static JSBool
InitArrayElements(JSContext *cx, JSObject *obj, jsuint length, jsval *vector)
InitArrayElements(JSContext *cx, JSObject *obj, jsuint start, jsuint end,
jsval *vector)
{
jsuint index;
jsid id;
for (index = 0; index < length; index++) {
JS_ASSERT(vector[index] != JSVAL_HOLE);
if (!IndexToId(cx, index, &id))
return JS_FALSE;
if (!OBJ_SET_PROPERTY(cx, obj, id, &vector[index]))
while (start != end) {
if (!SetArrayElement(cx, obj, start++, *vector++))
return JS_FALSE;
}
return JS_TRUE;
@ -689,7 +746,7 @@ InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, jsval *vector)
}
if (!vector)
return JS_TRUE;
return InitArrayElements(cx, obj, length, vector);
return InitArrayElements(cx, obj, 0, length, vector);
}
/*
@ -716,24 +773,12 @@ array_reverse(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
jsval *rval)
{
jsuint len, half, i;
jsid id, id2;
JSBool hole, hole2;
jsval *tmproot, *tmproot2;
JSBool idexists, id2exists, ok;
if (!js_GetLengthProperty(cx, obj, &len))
return JS_FALSE;
/*
* When len > JSVAL_INT_MAX + 1 the loop below accesses indexes greater
* than JSVAL_INT_MAX. For such indexes the corresponding ids are atoms.
* We use JS_KEEP_ATOMS to protect them against GC since OBJ_GET_PROPERTY
* can potentially execute an arbitrary script. See bug 341956.
*
* After this point control must flow through label out: to exit.
*/
if (len > JSVAL_INT_MAX + 1)
JS_KEEP_ATOMS(cx->runtime);
/*
* Use argv[argc] and argv[argc + 1] as local roots to hold temporarily
* array elements for GC-safe swap.
@ -742,51 +787,15 @@ array_reverse(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
tmproot2 = argv + argc + 1;
half = len / 2;
for (i = 0; i < half; i++) {
/*
* Get both values while checking for holes to make sure they don't
* get filled.
*/
if (!IndexToId(cx, i, &id))
goto bad;
if (!PropertyExists(cx, obj, id, &idexists))
goto bad;
if (idexists && !OBJ_GET_PROPERTY(cx, obj, id, tmproot))
goto bad;
if (!IndexToId(cx, len - i - 1, &id2))
goto bad;
if (!PropertyExists(cx, obj, id2, &id2exists))
goto bad;
if (id2exists && !OBJ_GET_PROPERTY(cx, obj, id2, tmproot2))
goto bad;
/* Exchange the values. */
if (idexists) {
if (!OBJ_SET_PROPERTY(cx, obj, id2, tmproot))
goto bad;
} else {
if (!OBJ_DELETE_PROPERTY(cx, obj, id2, tmproot))
goto bad;
}
if (id2exists) {
if (!OBJ_SET_PROPERTY(cx, obj, id, tmproot2))
goto bad;
} else {
if (!OBJ_DELETE_PROPERTY(cx, obj, id, tmproot2))
goto bad;
if (!GetArrayElement(cx, obj, i, &hole, tmproot) ||
!GetArrayElement(cx, obj, len - i - 1, &hole2, tmproot2) ||
!SetOrDeleteArrayElement(cx, obj, len - i - 1, hole, *tmproot) ||
!SetOrDeleteArrayElement(cx, obj, i, hole2, *tmproot2)) {
return JS_FALSE;
}
}
ok = JS_TRUE;
out:
if (len > JSVAL_INT_MAX + 1)
JS_UNKEEP_ATOMS(cx->runtime);
*rval = OBJECT_TO_JSVAL(obj);
return ok;
bad:
ok = JS_FALSE;
goto out;
return JS_TRUE;
}
typedef struct HSortArgs {
@ -932,8 +941,6 @@ sort_compare(void *arg, const void *a, const void *b, int *result)
* array_sort deals with holes and undefs on its own and they should not
* come here.
*/
JS_ASSERT(av != JSVAL_HOLE);
JS_ASSERT(bv != JSVAL_HOLE);
JS_ASSERT(av != JSVAL_VOID);
JS_ASSERT(bv != JSVAL_VOID);
@ -1003,9 +1010,8 @@ array_sort(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
jsval fval, *vec, *pivotroot;
CompareArgs ca;
jsuint len, newlen, i, undefs;
JSStackFrame *fp;
jsid id;
JSBool ok;
JSTempValueRooter tvr;
JSBool hole, ok;
/*
* Optimize the default compare function case if all of obj's elements
@ -1043,23 +1049,22 @@ array_sort(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
return JS_FALSE;
}
/* After this point control must flow through label out: to exit. */
vec = (jsval *) JS_malloc(cx, ((size_t) len) * sizeof(jsval));
if (!vec)
return JS_FALSE;
/*
* Initialize vec as a root. We will clear elements of vec one by
* one while increasing fp->nvars when we know that the property at
* one while increasing tvr.count 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 for it. See bug 330812.
* exist, allowing OS to avoiding committing RAM. See bug 330812.
*
* After this point control must flow through label out: to exit.
*/
fp = cx->fp;
fp->vars = vec;
fp->nvars = 0;
JS_PUSH_TEMP_ROOT(cx, 0, vec, &tvr);
/*
* By ECMA 262, 15.4.4.11, a property that does not exist (which we
@ -1072,20 +1077,16 @@ array_sort(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
undefs = 0;
newlen = 0;
for (i = 0; i < len; i++) {
ok = IndexToExistingId(cx, obj, i, &id);
if (!ok)
goto out;
if (id == JSID_HOLE)
continue;
/* Clear vec[newlen] before including it in the rooted set. */
vec[newlen] = JSVAL_NULL;
fp->nvars = newlen + 1;
ok = OBJ_GET_PROPERTY(cx, obj, id, &vec[newlen]);
tvr.count = newlen + 1;
ok = GetArrayElement(cx, obj, i, &hole, &vec[newlen]);
if (!ok)
goto out;
if (hole)
continue;
if (vec[newlen] == JSVAL_VOID) {
++undefs;
continue;
@ -1097,7 +1098,19 @@ array_sort(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
++newlen;
}
/* Here len == newlen + undefs + number_of_holes. */
/*
* Here len == newlen + undefs + number_of_holes. We want to shrink the
* array to relieve the memory pressure from VM in case of many holes.
*/
if (newlen != len) {
vec = JS_realloc(cx, vec, newlen * sizeof vec[0]);
if (!vec) {
/* realloc that can not shrink. */
vec = tvr.u.array;
} else {
tvr.u.array = vec;
}
}
ca.context = cx;
ca.fval = fval;
@ -1109,24 +1122,26 @@ array_sort(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
if (!ok)
goto out;
while (undefs != 0) {
--undefs;
vec[newlen++] = JSVAL_VOID;
}
ok = InitArrayElements(cx, obj, newlen, vec);
ok = InitArrayElements(cx, obj, 0, newlen, vec);
if (!ok)
goto out;
out:
JS_POP_TEMP_ROOT(cx, &tvr);
JS_free(cx, vec);
if (!ok)
return JS_FALSE;
/* Set undefs that sorted after the rest of elements. */
while (undefs != 0) {
--undefs;
if (!SetArrayElement(cx, obj, newlen++, JSVAL_VOID))
return JS_FALSE;
}
/* Re-create any holes that sorted to the end of the array. */
while (len > newlen) {
jsval junk;
if (!IndexToId(cx, --len, &id))
return JS_FALSE;
if (!OBJ_DELETE_PROPERTY(cx, obj, id, &junk))
if (!DeleteArrayElement(cx, obj, --len))
return JS_FALSE;
}
*rval = OBJECT_TO_JSVAL(obj);
@ -1139,22 +1154,16 @@ array_sort(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
static JSBool
array_push(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
jsuint length;
uintN i;
jsid id;
jsuint length, newlength;
if (!js_GetLengthProperty(cx, obj, &length))
return JS_FALSE;
for (i = 0; i < argc; i++) {
if (!IndexToId(cx, length + i, &id))
return JS_FALSE;
if (!OBJ_SET_PROPERTY(cx, obj, id, &argv[i]))
return JS_FALSE;
}
newlength = length + argc;
if (!InitArrayElements(cx, obj, length, newlength, argv))
return JS_FALSE;
/* Per ECMA-262, return the new array length. */
length += argc;
if (!IndexToValue(cx, length, rval))
if (!IndexToValue(cx, newlength, rval))
return JS_FALSE;
return js_SetLengthProperty(cx, obj, length);
}
@ -1163,9 +1172,7 @@ static JSBool
array_pop(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
jsuint index;
jsid id;
JSBool ok;
jsval junk;
JSBool hole;
if (!js_GetLengthProperty(cx, obj, &index))
return JS_FALSE;
@ -1173,17 +1180,9 @@ array_pop(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
index--;
/* Get the to-be-deleted property's value into rval. */
if (!IndexToId(cx, index, &id))
if (!GetArrayElement(cx, obj, index, &hole, rval))
return JS_FALSE;
/* See comments in array_reverse. */
if (index > JSVAL_INT_MAX)
JS_KEEP_ATOMS(cx->runtime);
ok = OBJ_GET_PROPERTY(cx, obj, id, rval) &&
OBJ_DELETE_PROPERTY(cx, obj, id, &junk);
if (index > JSVAL_INT_MAX)
JS_UNKEEP_ATOMS(cx->runtime);
if (!ok)
if (!hole && !DeleteArrayElement(cx, obj, index))
return JS_FALSE;
}
return js_SetLengthProperty(cx, obj, index);
@ -1193,44 +1192,31 @@ static JSBool
array_shift(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
jsuint length, i;
jsid id, id2;
jsval junk;
JSBool hole;
if (!js_GetLengthProperty(cx, obj, &length))
return JS_FALSE;
if (length > 0) {
if (length == 0) {
*rval = JSVAL_VOID;
} else {
length--;
id = JSVAL_ZERO;
/* Get the to-be-deleted property's value into rval ASAP. */
if (!OBJ_GET_PROPERTY(cx, obj, id, rval))
if (!GetArrayElement(cx, obj, 0, &hole, rval))
return JS_FALSE;
/*
* Slide down the array above the first element.
*/
if (length > 0) {
for (i = 1; i <= length; i++) {
if (!IndexToId(cx, i, &id))
return JS_FALSE;
if (!OBJ_GET_PROPERTY(cx, obj, id, &argv[0]))
return JS_FALSE;
/* Get id after value to avoid nested GC hazards. */
if (!IndexToId(cx, i - 1, &id2))
return JS_FALSE;
if (!OBJ_SET_PROPERTY(cx, obj, id2, &argv[0]))
return JS_FALSE;
}
for (i = 0; i != length; i++) {
if (!GetArrayElement(cx, obj, i + 1, &hole, &argv[0]))
return JS_FALSE;
if (!SetOrDeleteArrayElement(cx, obj, i, hole, argv[0]))
return JS_FALSE;
}
/*
* Delete the only or the last element. We recreate id when it is an
* atom to protect against a nested GC during the last iteration.
*/
if (length > JSVAL_INT_MAX && !IndexToId(cx, length, &id))
return JS_FALSE;
if (!OBJ_DELETE_PROPERTY(cx, obj, id, &junk))
/* Delete the only or last element when it exist. */
if (!hole && !DeleteArrayElement(cx, obj, length))
return JS_FALSE;
}
return js_SetLengthProperty(cx, obj, length);
@ -1241,9 +1227,8 @@ array_unshift(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
jsval *rval)
{
jsuint length, last;
uintN i;
jsid id, id2;
jsval *vp, junk;
jsval *vp;
JSBool hole;
if (!js_GetLengthProperty(cx, obj, &length))
return JS_FALSE;
@ -1251,53 +1236,36 @@ array_unshift(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
/* Slide up the array to make room for argc at the bottom. */
if (length > 0) {
last = length;
vp = argv + argc;
while (last--) {
if (!IndexToExistingId(cx, obj, last, &id))
vp = argv + argc; /* local root */
do {
if (!GetArrayElement(cx, obj, last, &hole, vp) ||
!SetOrDeleteArrayElement(cx, obj, last + argc, hole, *vp)) {
return JS_FALSE;
if (id != JSID_HOLE) {
if (!OBJ_GET_PROPERTY(cx, obj, id, vp))
return JS_FALSE;
}
/* Get id after value to avoid nested GC hazards. */
if (!IndexToId(cx, last + argc, &id2))
return JS_FALSE;
if (id == JSID_HOLE) {
if (!OBJ_DELETE_PROPERTY(cx, obj, id2, &junk))
return JS_FALSE;
} else {
if (!OBJ_SET_PROPERTY(cx, obj, id2, vp))
return JS_FALSE;
}
}
} while (--last);
}
/* Copy from argv to the bottom of the array. */
for (i = 0; i < argc; i++) {
if (!IndexToId(cx, i, &id))
return JS_FALSE;
if (!OBJ_SET_PROPERTY(cx, obj, id, &argv[i]))
return JS_FALSE;
}
if (!InitArrayElements(cx, obj, 0, argc, argv))
return JS_FALSE;
/* Follow Perl by returning the new array length. */
length += argc;
if (!js_SetLengthProperty(cx, obj, length))
return JS_FALSE;
}
/* Follow Perl by returning the new array length. */
return IndexToValue(cx, length, rval);
}
static JSBool
array_splice(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
jsval *vp, junk;
jsval *vp;
jsuint length, begin, end, count, delta, last;
jsdouble d;
jsid id, id2;
JSBool hole;
JSObject *obj2;
uintN i;
/*
* Nothing to do if no args. Otherwise point vp at our one explicit local
@ -1358,17 +1326,11 @@ array_splice(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
/* If there are elements to remove, put them into the return value. */
if (count > 0) {
for (last = begin; last < end; last++) {
if (!IndexToExistingId(cx, obj, last, &id))
return JS_FALSE;
if (id == JSID_HOLE)
continue; /* don't fill holes in the new array */
if (!OBJ_GET_PROPERTY(cx, obj, id, vp))
if (!GetArrayElement(cx, obj, last, &hole, vp))
return JS_FALSE;
/* Get id after value to avoid nested GC hazards. */
if (!IndexToId(cx, last - begin, &id2))
return JS_FALSE;
if (!OBJ_SET_PROPERTY(cx, obj2, id2, vp))
/* Copy *vp to new array unless it's a hole. */
if (!hole && !SetArrayElement(cx, obj2, last - begin, *vp))
return JS_FALSE;
}
@ -1382,56 +1344,26 @@ array_splice(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
last = length;
/* (uint) end could be 0, so can't use vanilla >= test */
while (last-- > end) {
if (!IndexToExistingId(cx, obj, last, &id))
if (!GetArrayElement(cx, obj, last, &hole, vp) ||
!SetOrDeleteArrayElement(cx, obj, last + delta, hole, *vp)) {
return JS_FALSE;
if (id != JSID_HOLE) {
if (!OBJ_GET_PROPERTY(cx, obj, id, vp))
return JS_FALSE;
}
/* Get id after value to avoid nested GC hazards. */
if (!IndexToId(cx, last + delta, &id2))
return JS_FALSE;
if (id != JSID_HOLE) {
if (!OBJ_SET_PROPERTY(cx, obj, id2, vp))
return JS_FALSE;
} else {
if (!OBJ_DELETE_PROPERTY(cx, obj, id2, &junk))
return JS_FALSE;
}
}
length += delta;
} else if (argc < count) {
delta = count - (jsuint)argc;
for (last = end; last < length; last++) {
if (!IndexToExistingId(cx, obj, last, &id))
if (!GetArrayElement(cx, obj, last, &hole, vp) ||
!SetOrDeleteArrayElement(cx, obj, last - delta, hole, *vp)) {
return JS_FALSE;
if (id != JSID_HOLE) {
if (!OBJ_GET_PROPERTY(cx, obj, id, vp))
return JS_FALSE;
}
/* Get id after value to avoid nested GC hazards. */
if (!IndexToId(cx, last - delta, &id2))
return JS_FALSE;
if (id != JSID_HOLE) {
if (!OBJ_SET_PROPERTY(cx, obj, id2, vp))
return JS_FALSE;
} else {
if (!OBJ_DELETE_PROPERTY(cx, obj, id2, &junk))
return JS_FALSE;
}
}
length -= delta;
}
/* Copy from argv into the hole to complete the splice. */
for (i = 0; i < argc; i++) {
if (!IndexToId(cx, begin + i, &id))
return JS_FALSE;
if (!OBJ_SET_PROPERTY(cx, obj, id, &argv[i]))
return JS_FALSE;
}
if (!InitArrayElements(cx, obj, begin, begin + argc, argv))
return JS_FALSE;
/* Update length in case we deleted elements from the end. */
return js_SetLengthProperty(cx, obj, length);
@ -1447,7 +1379,7 @@ array_concat(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
JSObject *nobj, *aobj;
jsuint length, alength, slot;
uintN i;
jsid id, id2;
JSBool hole;
/* Hoist the explicit local root address computation. */
vp = argv + argc;
@ -1478,22 +1410,14 @@ array_concat(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
if (!ValueIsLength(cx, *vp, &alength))
return JS_FALSE;
for (slot = 0; slot < alength; slot++) {
if (!IndexToExistingId(cx, aobj, slot, &id))
return JS_FALSE;
if (id == JSID_HOLE) {
/*
* Per ECMA 262, 15.4.4.4, step 9, ignore non-existent
* properties.
*/
continue;
}
if (!OBJ_GET_PROPERTY(cx, aobj, id, vp))
if (!GetArrayElement(cx, aobj, slot, &hole, vp))
return JS_FALSE;
/* Get id after value to avoid nested GC hazards. */
if (!IndexToId(cx, length + slot, &id2))
return JS_FALSE;
if (!OBJ_SET_PROPERTY(cx, nobj, id2, vp))
/*
* Per ECMA 262, 15.4.4.4, step 9, ignore non-existent
* properties.
*/
if (!hole && !SetArrayElement(cx, nobj, length + slot, *vp))
return JS_FALSE;
}
length += alength;
@ -1501,10 +1425,7 @@ array_concat(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
}
}
*vp = v;
if (!IndexToId(cx, length, &id))
return JS_FALSE;
if (!OBJ_SET_PROPERTY(cx, nobj, id, vp))
if (!SetArrayElement(cx, nobj, length, v))
return JS_FALSE;
length++;
}
@ -1519,7 +1440,7 @@ array_slice(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
JSObject *nobj;
jsuint length, begin, end, slot;
jsdouble d;
jsid id, id2;
JSBool hole;
/* Hoist the explicit local root address computation. */
vp = argv + argc;
@ -1567,17 +1488,9 @@ array_slice(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
begin = end;
for (slot = begin; slot < end; slot++) {
if (!IndexToExistingId(cx, obj, slot, &id))
if (!GetArrayElement(cx, obj, slot, &hole, vp))
return JS_FALSE;
if (id == JSID_HOLE)
continue;
if (!OBJ_GET_PROPERTY(cx, obj, id, vp))
return JS_FALSE;
/* Get id after value to avoid nested GC hazards. */
if (!IndexToId(cx, slot - begin, &id2))
return JS_FALSE;
if (!OBJ_SET_PROPERTY(cx, nobj, id2, vp))
if (!hole && !SetArrayElement(cx, nobj, slot - begin, *vp))
return JS_FALSE;
}
return js_SetLengthProperty(cx, nobj, end - begin);
@ -1591,6 +1504,7 @@ array_indexOfHelper(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
{
jsuint length, i, stop;
jsint direction;
JSBool hole;
if (!js_GetLengthProperty(cx, obj, &length))
return JS_FALSE;
@ -1624,18 +1538,10 @@ array_indexOfHelper(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
}
for (;;) {
jsid id;
jsval v;
if (!IndexToExistingId(cx, obj, (jsuint)i, &id))
if (!GetArrayElement(cx, obj, (jsuint)i, &hole, rval))
return JS_FALSE;
if (id != JSID_HOLE) {
if (!OBJ_GET_PROPERTY(cx, obj, id, &v))
return JS_FALSE;
if (js_StrictlyEqual(v, argv[0]))
return js_NewNumberValue(cx, i, rval);
}
if (!hole && js_StrictlyEqual(*rval, argv[0]))
return js_NewNumberValue(cx, i, rval);
if (i == stop)
goto not_found;
i += direction;
@ -1678,7 +1584,7 @@ array_extra(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval,
JSObject *callable, *thisp, *newarr;
void *mark;
JSStackFrame *fp;
JSBool ok, cond;
JSBool ok, cond, hole;
/* Hoist the explicit local root address computation. */
vp = argv + argc;
@ -1743,17 +1649,11 @@ array_extra(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval,
oldsp = fp->sp;
for (i = 0; i < length; i++) {
jsid id;
jsval rval2;
ok = IndexToExistingId(cx, obj, i, &id);
ok = GetArrayElement(cx, obj, i, &hole, vp);
if (!ok)
break;
if (id == JSID_HOLE)
if (hole)
continue;
ok = OBJ_GET_PROPERTY(cx, obj, id, vp);
if (!ok)
break;
/*
* Push callable and 'this', then args. We must do this for every
@ -1770,18 +1670,18 @@ array_extra(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval,
/* Do the call. */
fp->sp = sp;
ok = js_Invoke(cx, 3, JSINVOKE_INTERNAL);
rval2 = fp->sp[-1];
*rval = fp->sp[-1];
fp->sp = oldsp;
if (!ok)
break;
if (mode > MAP) {
if (rval2 == JSVAL_NULL) {
if (*rval == JSVAL_NULL) {
cond = JS_FALSE;
} else if (JSVAL_IS_BOOLEAN(rval2)) {
cond = JSVAL_TO_BOOLEAN(rval2);
} else if (JSVAL_IS_BOOLEAN(*rval)) {
cond = JSVAL_TO_BOOLEAN(*rval);
} else {
ok = js_ValueToBoolean(cx, rval2, &cond);
ok = js_ValueToBoolean(cx, *rval, &cond);
if (!ok)
goto out;
}
@ -1791,16 +1691,7 @@ array_extra(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval,
case FOREACH:
break;
case MAP:
/*
* We reconstruct id once again when it is a GC thing since scripts
* can trigger GC that collects it. See bug 341956.
*/
if (i > JSVAL_INT_MAX) {
ok = IndexToId(cx, i, &id);
if (!ok)
goto out;
}
ok = OBJ_SET_PROPERTY(cx, newarr, id, &rval2);
ok = SetArrayElement(cx, newarr, i, *rval);
if (!ok)
goto out;
break;
@ -1808,10 +1699,7 @@ array_extra(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval,
if (!cond)
break;
/* Filter passed *vp, push as result. */
ok = IndexToId(cx, newlen++, &id);
if (!ok)
goto out;
ok = OBJ_SET_PROPERTY(cx, newarr, id, vp);
ok = SetArrayElement(cx, newarr, newlen++, *vp);
if (!ok)
goto out;
break;

Просмотреть файл

@ -742,6 +742,30 @@ js_AtomizeChars(JSContext *cx, const jschar *chars, size_t length, uintN flags)
return js_AtomizeString(cx, str, ATOM_TMPSTR | flags);
}
JSAtom *
js_GetExistingStringAtom(JSContext *cx, const jschar *chars, size_t length)
{
JSString *str;
char buf[2 * ALIGNMENT(JSString)];
JSHashNumber keyHash;
jsval key;
JSAtomState *state;
JSHashTable *table;
JSHashEntry **hep;
str = ALIGN(buf, JSString);
str->chars = (jschar *)chars;
str->length = length;
keyHash = js_HashString(str);
key = STRING_TO_JSVAL(str);
state = &cx->runtime->atomState;
JS_LOCK(&state->lock, cx);
table = state->table;
hep = JS_HashTableRawLookup(table, keyHash, (void *)key);
JS_UNLOCK(&state->lock, cx);
return (hep) ? (JSAtom *)*hep : NULL;
}
JSAtom *
js_AtomizeValue(JSContext *cx, jsval value, uintN flags)
{

Просмотреть файл

@ -396,6 +396,13 @@ js_Atomize(JSContext *cx, const char *bytes, size_t length, uintN flags);
extern JS_FRIEND_API(JSAtom *)
js_AtomizeChars(JSContext *cx, const jschar *chars, size_t length, uintN flags);
/*
* Return an existing atom for the given char array or null if the char
* sequence is currently not atomized.
*/
extern JSAtom *
js_GetExistingStringAtom(JSContext *cx, const jschar *chars, size_t length);
/*
* This variant handles all value tag types.
*/

Просмотреть файл

@ -439,6 +439,26 @@ typedef unsigned long JSUword;
#define JS_UNLIKELY(x) (x)
#endif
/***********************************************************************
** MACROS: JS_ARRAY_LENGTH
** JS_ARRAY_END
** DESCRIPTION:
** Macros to get the number of elements and the pointer to one past the
** last element of a C array. Use them like this:
**
** jschar buf[10], *s;
** JSString *str;
** ...
** for (s = buf; s != JS_ARRAY_END(buf); ++s) *s = ...;
** ...
** str = JS_NewStringCopyN(cx, buf, JS_ARRAY_LENGTH(buf));
** ...
**
***********************************************************************/
#define JS_ARRAY_LENGTH(array) (sizeof (array) / sizeof (array)[0])
#define JS_ARRAY_END(array) ((array) + JS_ARRAY_LENGTH(array))
JS_END_EXTERN_C
#endif /* jstypes_h___ */