growth = (1 + 3 + 1) * sizeof(jschar);
extratail = 2; growth = (1 + extratail) * sizeof(jschar);
array_join_sub(JSContext *cx, JSObject *obj, JSString *sep, JSBool literalize,               jsval *rval, JSBool localeString){    JSBool ok;    jsuint length, index;    jschar *chars, *ochars;    size_t nchars, growth, seplen, tmplen;    const jschar *sepstr;    JSString *str;    JSHashEntry *he;    JSObject *obj2;    int stackDummy;    if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) {        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED);        return JS_FALSE;    }    ok = js_GetLengthProperty(cx, obj, &length);    if (!ok)        return JS_FALSE;    he = js_EnterSharpObject(cx, obj, NULL, &chars);    if (!he)        return JS_FALSE;    if (literalize) {        if (IS_SHARP(he)) {#if JS_HAS_SHARP_VARS            nchars = js_strlen(chars);#else            chars[0] = '[';            chars[1] = ']';            chars[2] = 0;            nchars = 2;#endif            goto make_string;        }        /*         * Allocate 1 + 3 + 1 for "[", the worst-case closing ", ]", and the         * terminating 0.         */        growth = (1 + 3 + 1) * sizeof(jschar);        if (!chars) {            nchars = 0;            chars = (jschar *) malloc(growth);            if (!chars)                goto done;        } else {            MAKE_SHARP(he);            nchars = js_strlen(chars);            chars = (jschar *)                realloc((ochars = chars), nchars * sizeof(jschar) + growth);            if (!chars) {                free(ochars);                goto done;            }        }        chars[nchars++] = '[';    } else {        /*         * Free any sharp variable definition in chars.  Normally, we would         * MAKE_SHARP(he) so that only the first sharp variable annotation is         * a definition, and all the rest are references, but in the current         * case of (!literalize), we don't need chars at all.         */        if (chars)            JS_free(cx, chars);        chars = NULL;        nchars = 0;        /* Return the empty string on a cycle as well as on empty join. */        if (IS_BUSY(he) || length == 0) {            js_LeaveSharpObject(cx, NULL);            *rval = JS_GetEmptyStringValue(cx);            return ok;        }        /* Flag he as BUSY so we can distinguish a cycle from a join-point. */        MAKE_BUSY(he);    }    sepstr = NULL;    seplen = JSSTRING_LENGTH(sep);    /* Use rval to locally root each element value as we loop and convert. */#define v (*rval)    v = JSVAL_NULL;    for (index = 0; index < length; index++) {        ok = JS_GetElement(cx, obj, index, &v);        if (!ok)            goto done;        if ((!literalize || JS_VERSION_IS_1_2(cx)) &&            (JSVAL_IS_VOID(v) || JSVAL_IS_NULL(v))) {            str = cx->runtime->emptyString;        } else {            if (localeString) {                if (!js_ValueToObject(cx, v, &obj2) ||                    !js_TryMethod(cx, obj2,                                  cx->runtime->atomState.toLocaleStringAtom,                                  0, NULL, &v)) {                    str = NULL;                } else {                    str = js_ValueToString(cx, v);                }            } else {                str = (literalize ? js_ValueToSource : js_ValueToString)(cx, v);            }            if (!str) {                ok = JS_FALSE;                goto done;            }        }        /* Allocate 3 + 1 at end for ", ", closing bracket, and zero. */        growth = (nchars + (sepstr ? seplen : 0) +                  JSSTRING_LENGTH(str) +                  3 + 1) * sizeof(jschar);        if (!chars) {            chars = (jschar *) malloc(growth);            if (!chars)                goto done;        } else {            chars = (jschar *) realloc((ochars = chars), growth);            if (!chars) {                free(ochars);                goto done;            }        }        if (sepstr) {            js_strncpy(&chars[nchars], sepstr, seplen);            nchars += seplen;        }        sepstr = JSSTRING_CHARS(sep);        tmplen = JSSTRING_LENGTH(str);        js_strncpy(&chars[nchars], JSSTRING_CHARS(str), tmplen);        nchars += tmplen;    }  done:    if (literalize) {        if (chars) {            if (JSVAL_IS_VOID(v)) {                chars[nchars++] = ',';                chars[nchars++] = ' ';            }            chars[nchars++] = ']';        }    } else {        CLEAR_BUSY(he);    }    js_LeaveSharpObject(cx, NULL);    if (!ok) {        if (chars)            free(chars);        return ok;    }#undef v  make_string:    if (!chars) {        JS_ReportOutOfMemory(cx);        return JS_FALSE;    }    chars[nchars] = 0;    str = js_NewString(cx, chars, nchars, 0);    if (!str) {        free(chars);        return JS_FALSE;    }    *rval = STRING_TO_JSVAL(str);    return JS_TRUE;}