Bug 617935: Check string lengths using StringBuffer. (r=lw)
authorChris Leary <cdleary@mozilla.com>
Wed, 12 Jan 2011 15:28:58 -0800
changeset 60571 7e5853562debba1c7b905cc1ce73a41695dbc748
parent 60570 6ef4c13f0941e7536238c099cba64dff202d3748
child 60572 99c9ed53df99bd42bac723cbd34f8c73be84c6d0
push id18037
push usercleary@mozilla.com
push dateFri, 14 Jan 2011 17:42:55 +0000
treeherdermozilla-central@4e0501a0c5e5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslw
bugs617935
milestone2.0b10pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 617935: Check string lengths using StringBuffer. (r=lw)
js/src/jsapi.cpp
js/src/jsarray.cpp
js/src/jsatom.cpp
js/src/jsbool.cpp
js/src/jsbool.h
js/src/jsnum.cpp
js/src/jsnum.h
js/src/json.cpp
js/src/json.h
js/src/jsprvtd.h
js/src/jsregexpinlines.h
js/src/jsscan.cpp
js/src/jsscan.h
js/src/jsstr.cpp
js/src/jsstr.h
js/src/jsstrinlines.h
js/src/jsvector.h
js/src/jsxml.cpp
js/src/jsxml.h
js/src/tests/ecma_5/RegExp/jstests.list
js/src/tests/ecma_5/RegExp/regress-617935.js
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5463,20 +5463,20 @@ JS_EncodeStringToBuffer(JSString *str, c
 }
 
 JS_PUBLIC_API(JSBool)
 JS_Stringify(JSContext *cx, jsval *vp, JSObject *replacer, jsval space,
              JSONWriteCallback callback, void *data)
 {
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, replacer, space);
-    JSCharBuffer cb(cx);
-    if (!js_Stringify(cx, Valueify(vp), replacer, Valueify(space), cb))
+    StringBuffer sb(cx);
+    if (!js_Stringify(cx, Valueify(vp), replacer, Valueify(space), sb))
         return false;
-    return callback(cb.begin(), cb.length(), data);
+    return callback(sb.begin(), sb.length(), data);
 }
 
 JS_PUBLIC_API(JSBool)
 JS_TryJSON(JSContext *cx, jsval *vp)
 {
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, *vp);
     return js_TryJSON(cx, Valueify(vp));
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -1075,19 +1075,19 @@ JSObject::makeDenseArraySlow(JSContext *
      * proto's empty shape.
      */
     clasp = &js_SlowArrayClass;
     return true;
 }
 
 /* Transfer ownership of buffer to returned string. */
 static inline JSBool
-BufferToString(JSContext *cx, JSCharBuffer &cb, Value *rval)
+BufferToString(JSContext *cx, StringBuffer &sb, Value *rval)
 {
-    JSString *str = js_NewStringFromCharBuffer(cx, cb);
+    JSString *str = sb.finishString();
     if (!str)
         return false;
     rval->setString(str);
     return true;
 }
 
 #if JS_HAS_TOSOURCE
 static JSBool
@@ -1112,38 +1112,38 @@ array_toSource(JSContext *cx, uintN argc
     /* After this point, all paths exit through the 'out' label. */
     MUST_FLOW_THROUGH("out");
     bool ok = false;
 
     /*
      * This object will take responsibility for the jschar buffer until the
      * buffer is transferred to the returned JSString.
      */
-    JSCharBuffer cb(cx);
+    StringBuffer sb(cx);
 
     /* Cycles/joins are indicated by sharp objects. */
 #if JS_HAS_SHARP_VARS
     if (IS_SHARP(he)) {
         JS_ASSERT(sharpchars != 0);
-        cb.replaceRawBuffer(sharpchars, js_strlen(sharpchars));
+        sb.replaceRawBuffer(sharpchars, js_strlen(sharpchars));
         goto make_string;
     } else if (sharpchars) {
         MAKE_SHARP(he);
-        cb.replaceRawBuffer(sharpchars, js_strlen(sharpchars));
+        sb.replaceRawBuffer(sharpchars, js_strlen(sharpchars));
     }
 #else
     if (IS_SHARP(he)) {
-        if (!js_AppendLiteral(cb, "[]"))
+        if (!sb.append("[]"))
             goto out;
         cx->free(sharpchars);
         goto make_string;
     }
 #endif
 
-    if (!cb.append('['))
+    if (!sb.append('['))
         goto out;
 
     jsuint length;
     if (!js_GetLengthProperty(cx, obj, &length))
         goto out;
 
     for (jsuint index = 0; index < length; index++) {
         /* Use vp to locally root each element value. */
@@ -1164,33 +1164,33 @@ array_toSource(JSContext *cx, uintN argc
         }
         vp->setString(str);
 
         const jschar *chars = str->getChars(cx);
         if (!chars)
             goto out;
 
         /* Append element to buffer. */
-        if (!cb.append(chars, chars + str->length()))
+        if (!sb.append(chars, chars + str->length()))
             goto out;
         if (index + 1 != length) {
-            if (!js_AppendLiteral(cb, ", "))
+            if (!sb.append(", "))
                 goto out;
         } else if (hole) {
-            if (!cb.append(','))
+            if (!sb.append(','))
                 goto out;
         }
     }
 
     /* Finalize the buffer. */
-    if (!cb.append(']'))
+    if (!sb.append(']'))
         goto out;
 
   make_string:
-    if (!BufferToString(cx, cb, vp))
+    if (!BufferToString(cx, sb, vp))
         goto out;
 
     ok = true;
 
   out:
     if (!initiallySharp)
         js_LeaveSharpObject(cx, NULL);
     return ok;
@@ -1240,17 +1240,17 @@ array_toString_sub(JSContext *cx, JSObje
     /* After this point, all paths exit through the 'out' label. */
     MUST_FLOW_THROUGH("out");
     bool ok = false;
 
     /*
      * This object will take responsibility for the jschar buffer until the
      * buffer is transferred to the returned JSString.
      */
-    JSCharBuffer cb(cx);
+    StringBuffer sb(cx);
 
     jsuint length;
     if (!js_GetLengthProperty(cx, obj, &length))
         goto out;
 
     for (jsuint index = 0; index < length; index++) {
         /* Use rval to locally root each element value. */
         JSBool hole;
@@ -1268,29 +1268,29 @@ array_toString_sub(JSContext *cx, JSObje
                 if (!js_ValueToObjectOrNull(cx, *rval, &robj))
                     goto out;
                 rval->setObjectOrNull(robj);
                 JSAtom *atom = cx->runtime->atomState.toLocaleStringAtom;
                 if (!js_TryMethod(cx, robj, atom, 0, NULL, rval))
                     goto out;
             }
 
-            if (!js_ValueToCharBuffer(cx, *rval, cb))
+            if (!ValueToStringBuffer(cx, *rval, sb))
                 goto out;
         }
 
         /* Append the separator. */
         if (index + 1 != length) {
-            if (!cb.append(sep, seplen))
+            if (!sb.append(sep, seplen))
                 goto out;
         }
     }
 
     /* Finalize the buffer. */
-    if (!BufferToString(cx, cb, rval))
+    if (!BufferToString(cx, sb, rval))
         goto out;
 
     ok = true;
 
   out:
     if (genBefore == cx->busyArrays.generation())
         cx->busyArrays.remove(hashp);
     else
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -584,16 +584,20 @@ js_Atomize(JSContext *cx, const char *by
 }
 
 JSAtom *
 js_AtomizeChars(JSContext *cx, const jschar *chars, size_t length, uintN flags)
 {
     JSString str;
 
     CHECK_REQUEST(cx);
+
+    if (!CheckStringLength(cx, length))
+        return NULL;
+
     str.initFlatNotTerminated((jschar *)chars, length);
     return js_AtomizeString(cx, &str, ATOM_TMPSTR | flags);
 }
 
 JSAtom *
 js_GetExistingStringAtom(JSContext *cx, const jschar *chars, size_t length)
 {
     JSString str, *str2;
--- a/js/src/jsbool.cpp
+++ b/js/src/jsbool.cpp
@@ -161,20 +161,20 @@ js_InitBooleanClass(JSContext *cx, JSObj
 
 JSString *
 js_BooleanToString(JSContext *cx, JSBool b)
 {
     return ATOM_TO_STRING(cx->runtime->atomState.booleanAtoms[b ? 1 : 0]);
 }
 
 /* This function implements E-262-3 section 9.8, toString. */
-JSBool
-js_BooleanToCharBuffer(JSContext *cx, JSBool b, JSCharBuffer &cb)
+bool
+js::BooleanToStringBuffer(JSContext *cx, JSBool b, StringBuffer &sb)
 {
-    return b ? js_AppendLiteral(cb, "true") : js_AppendLiteral(cb, "false");
+    return b ? sb.append("true") : sb.append("false");
 }
 
 JSBool
 js_ValueToBoolean(const Value &v)
 {
     if (v.isInt32())
         return v.toInt32() != 0;
     if (v.isString())
--- a/js/src/jsbool.h
+++ b/js/src/jsbool.h
@@ -40,30 +40,35 @@
 #ifndef jsbool_h___
 #define jsbool_h___
 /*
  * JS boolean interface.
  */
 
 #include "jsapi.h"
 #include "jsobj.h"
+#include "jsstr.h"
 
 extern js::Class js_BooleanClass;
 
 inline bool
 JSObject::isBoolean() const
 {
     return getClass() == &js_BooleanClass;
 }
 
 extern JSObject *
 js_InitBooleanClass(JSContext *cx, JSObject *obj);
 
 extern JSString *
 js_BooleanToString(JSContext *cx, JSBool b);
 
-extern JSBool
-js_BooleanToCharBuffer(JSContext *cx, JSBool b, JSCharBuffer &cb);
+namespace js {
+
+extern bool
+BooleanToStringBuffer(JSContext *cx, JSBool b, StringBuffer &sb);
+
+} /* namespace js */
 
 extern JSBool
 js_ValueToBoolean(const js::Value &v);
 
 #endif /* jsbool_h___ */
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -1208,26 +1208,28 @@ js_NumberToStringWithBase(JSContext *cx,
 }
 
 JSString * JS_FASTCALL
 js_NumberToString(JSContext *cx, jsdouble d)
 {
     return js_NumberToStringWithBase(cx, d, 10);
 }
 
+namespace js {
+
 JSFlatString *
-js::NumberToString(JSContext *cx, jsdouble d)
+NumberToString(JSContext *cx, jsdouble d)
 {
     if (JSString *str = js_NumberToStringWithBase(cx, d, 10))
         return str->assertIsFlat();
     return NULL;
 }
 
-JSBool JS_FASTCALL
-js_NumberValueToCharBuffer(JSContext *cx, const Value &v, JSCharBuffer &cb)
+bool JS_FASTCALL
+NumberValueToStringBuffer(JSContext *cx, const Value &v, StringBuffer &sb)
 {
     /* Convert to C-string. */
     ToCStringBuf cbuf;
     const char *cstr;
     if (v.isInt32()) {
         cstr = IntToCString(&cbuf, v.toInt32());
     } else {
         cstr = NumberToCString(cx, &cbuf, v.toDouble());
@@ -1238,31 +1240,19 @@ js_NumberValueToCharBuffer(JSContext *cx
     }
 
     /*
      * Inflate to jschar string.  The input C-string characters are < 127, so
      * even if jschars are UTF-8, all chars should map to one jschar.
      */
     size_t cstrlen = strlen(cstr);
     JS_ASSERT(!cbuf.dbuf && cstrlen < cbuf.sbufSize);
-    size_t sizeBefore = cb.length();
-    if (!cb.growByUninitialized(cstrlen))
-        return JS_FALSE;
-    jschar *appendBegin = cb.begin() + sizeBefore;
-#ifdef DEBUG
-    size_t oldcstrlen = cstrlen;
-    JSBool ok =
-#endif
-        js_InflateStringToBuffer(cx, cstr, cstrlen, appendBegin, &cstrlen);
-    JS_ASSERT(ok && cstrlen == oldcstrlen);
-    return JS_TRUE;
+    return sb.appendInflated(cstr, cstrlen);
 }
 
-namespace js {
-
 bool
 ValueToNumberSlow(JSContext *cx, Value v, double *out)
 {
     JS_ASSERT(!v.isNumber());
     goto skip_int_double;
     for (;;) {
         if (v.isNumber()) {
             *out = v.toNumber();
--- a/js/src/jsnum.h
+++ b/js/src/jsnum.h
@@ -192,24 +192,24 @@ js_IntToString(JSContext *cx, jsint i);
 /*
  * When base == 10, this function implements ToString() as specified by
  * ECMA-262-5 section 9.8.1; but note that it handles integers specially for
  * performance.  See also js::NumberToCString().
  */
 extern JSString * JS_FASTCALL
 js_NumberToString(JSContext *cx, jsdouble d);
 
+namespace js {
+
 /*
  * Convert an integer or double (contained in the given value) to a string and
  * append to the given buffer.
  */
-extern JSBool JS_FASTCALL
-js_NumberValueToCharBuffer(JSContext *cx, const js::Value &v, JSCharBuffer &cb);
-
-namespace js {
+extern bool JS_FASTCALL
+NumberValueToStringBuffer(JSContext *cx, const Value &v, StringBuffer &sb);
 
 /* Same as js_NumberToString, different signature. */
 extern JSFlatString *
 NumberToString(JSContext *cx, jsdouble d);
 
 /*
  * Usually a small amount of static storage is enough, but sometimes we need
  * to dynamically allocate much more.  This struct encapsulates that.
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -141,26 +141,26 @@ js_json_stringify(JSContext *cx, uintN a
     Value *argv = vp + 2;
     AutoValueRooter space(cx);
     AutoObjectRooter replacer(cx);
 
     // Must throw an Error if there isn't a first arg
     if (!JS_ConvertArguments(cx, argc, Jsvalify(argv), "v / o v", vp, replacer.addr(), space.addr()))
         return JS_FALSE;
 
-    JSCharBuffer cb(cx);
+    StringBuffer sb(cx);
 
-    if (!js_Stringify(cx, vp, replacer.object(), space.value(), cb))
+    if (!js_Stringify(cx, vp, replacer.object(), space.value(), sb))
         return JS_FALSE;
 
     // XXX This can never happen to nsJSON.cpp, but the JSON object
     // needs to support returning undefined. So this is a little awkward
     // for the API, because we want to support streaming writers.
-    if (!cb.empty()) {
-        JSString *str = js_NewStringFromCharBuffer(cx, cb);
+    if (!sb.empty()) {
+        JSString *str = sb.finishString();
         if (!str)
             return JS_FALSE;
         vp->setString(str);
     } else {
         vp->setUndefined();
     }
 
     return JS_TRUE;
@@ -181,73 +181,73 @@ js_TryJSON(JSContext *cx, Value *vp)
 }
 
 
 static const char quote = '\"';
 static const char backslash = '\\';
 static const char unicodeEscape[] = "\\u00";
 
 static JSBool
-write_string(JSContext *cx, JSCharBuffer &cb, const jschar *buf, uint32 len)
+write_string(JSContext *cx, StringBuffer &sb, const jschar *buf, uint32 len)
 {
-    if (!cb.append(quote))
+    if (!sb.append(quote))
         return JS_FALSE;
 
     uint32 mark = 0;
     uint32 i;
     for (i = 0; i < len; ++i) {
         if (buf[i] == quote || buf[i] == backslash) {
-            if (!cb.append(&buf[mark], i - mark) || !cb.append(backslash) ||
-                !cb.append(buf[i])) {
+            if (!sb.append(&buf[mark], i - mark) || !sb.append(backslash) ||
+                !sb.append(buf[i])) {
                 return JS_FALSE;
             }
             mark = i + 1;
         } else if (buf[i] <= 31 || buf[i] == 127) {
-            if (!cb.append(&buf[mark], i - mark) ||
-                !js_AppendLiteral(cb, unicodeEscape)) {
+            if (!sb.append(&buf[mark], i - mark) ||
+                !sb.append(unicodeEscape)) {
                 return JS_FALSE;
             }
             char ubuf[3];
             size_t len = JS_snprintf(ubuf, sizeof(ubuf), "%.2x", buf[i]);
             JS_ASSERT(len == 2);
             jschar wbuf[3];
             size_t wbufSize = JS_ARRAY_LENGTH(wbuf);
             if (!js_InflateStringToBuffer(cx, ubuf, len, wbuf, &wbufSize) ||
-                !cb.append(wbuf, wbufSize)) {
+                !sb.append(wbuf, wbufSize)) {
                 return JS_FALSE;
             }
             mark = i + 1;
         }
     }
 
-    if (mark < len && !cb.append(&buf[mark], len - mark))
+    if (mark < len && !sb.append(&buf[mark], len - mark))
         return JS_FALSE;
 
-    return cb.append(quote);
+    return sb.append(quote);
 }
 
 class StringifyContext
 {
 public:
-    StringifyContext(JSContext *cx, JSCharBuffer &cb, JSObject *replacer)
-    : cb(cb), gap(cx), replacer(replacer), depth(0), objectStack(cx)
+    StringifyContext(JSContext *cx, StringBuffer &sb, JSObject *replacer)
+    : sb(sb), gap(cx), replacer(replacer), depth(0), objectStack(cx)
     {}
 
     bool initializeGap(JSContext *cx, const Value &space) {
         AutoValueRooter gapValue(cx, space);
 
         if (space.isObject()) {
             JSObject &obj = space.toObject();
             Class *clasp = obj.getClass();
             if (clasp == &js_NumberClass || clasp == &js_StringClass)
                 *gapValue.addr() = obj.getPrimitiveThis();
         }
 
         if (gapValue.value().isString()) {
-            if (!js_ValueToCharBuffer(cx, gapValue.value(), gap))
+            if (!ValueToStringBuffer(cx, gapValue.value(), gap))
                 return false;
             if (gap.length() > 10)
                 gap.resize(10);
         } else if (gapValue.value().isNumber()) {
             jsdouble d = gapValue.value().isInt32()
                          ? gapValue.value().toInt32()
                          : js_DoubleToInteger(gapValue.value().toDouble());
             d = JS_MIN(10, d);
@@ -261,36 +261,36 @@ public:
     bool initializeStack() {
         return objectStack.init(16);
     }
 
 #ifdef DEBUG
     ~StringifyContext() { JS_ASSERT(objectStack.empty()); }
 #endif
 
-    JSCharBuffer &cb;
-    JSCharBuffer gap;
+    StringBuffer &sb;
+    StringBuffer gap;
     JSObject *replacer;
     uint32 depth;
     HashSet<JSObject *> objectStack;
 };
 
 static JSBool CallReplacerFunction(JSContext *cx, jsid id, JSObject *holder,
                                    StringifyContext *scx, Value *vp);
 static JSBool Str(JSContext *cx, jsid id, JSObject *holder,
                   StringifyContext *scx, Value *vp, bool callReplacer = true);
 
 static JSBool
 WriteIndent(JSContext *cx, StringifyContext *scx, uint32 limit)
 {
     if (!scx->gap.empty()) {
-        if (!scx->cb.append('\n'))
+        if (!scx->sb.append('\n'))
             return JS_FALSE;
         for (uint32 i = 0; i < limit; i++) {
-            if (!scx->cb.append(scx->gap.begin(), scx->gap.end()))
+            if (!scx->sb.append(scx->gap.begin(), scx->gap.end()))
                 return JS_FALSE;
         }
     }
 
     return JS_TRUE;
 }
 
 class CycleDetector
@@ -322,17 +322,17 @@ static JSBool
 JO(JSContext *cx, Value *vp, StringifyContext *scx)
 {
     JSObject *obj = &vp->toObject();
 
     CycleDetector detect(scx, obj);
     if (!detect.init(cx))
         return JS_FALSE;
 
-    if (!scx->cb.append('{'))
+    if (!scx->sb.append('{'))
         return JS_FALSE;
 
     Value vec[3] = { NullValue(), NullValue(), NullValue() };
     AutoArrayRooter tvr(cx, JS_ARRAY_LENGTH(vec), vec);
     Value& outputValue = vec[0];
     Value& whitelistElement = vec[1];
     AutoIdRooter idr(cx);
     jsid& id = *idr.addr();
@@ -389,57 +389,57 @@ JO(JSContext *cx, Value *vp, StringifyCo
 
         JSType type = JS_TypeOfValue(cx, Jsvalify(outputValue));
 
         // elide undefined values and functions and XML
         if (outputValue.isUndefined() || type == JSTYPE_FUNCTION || type == JSTYPE_XML)
             continue;
 
         // output a comma unless this is the first member to write
-        if (memberWritten && !scx->cb.append(','))
+        if (memberWritten && !scx->sb.append(','))
             return JS_FALSE;
         memberWritten = JS_TRUE;
 
         if (!WriteIndent(cx, scx, scx->depth))
             return JS_FALSE;
 
         // Be careful below, this string is weakly rooted
         JSString *s = js_ValueToString(cx, IdToValue(id));
         if (!s)
             return JS_FALSE;
 
         size_t length = s->length();
         const jschar *chars = s->getChars(cx);
         if (!chars)
             return JS_FALSE;
 
-        if (!write_string(cx, scx->cb, chars, length) ||
-            !scx->cb.append(':') ||
-            !(scx->gap.empty() || scx->cb.append(' ')) ||
+        if (!write_string(cx, scx->sb, chars, length) ||
+            !scx->sb.append(':') ||
+            !(scx->gap.empty() || scx->sb.append(' ')) ||
             !Str(cx, id, obj, scx, &outputValue, true)) {
             return JS_FALSE;
         }
     }
 
     if (memberWritten && !WriteIndent(cx, scx, scx->depth - 1))
         return JS_FALSE;
 
-    return scx->cb.append('}');
+    return scx->sb.append('}');
 }
 
 static JSBool
 JA(JSContext *cx, Value *vp, StringifyContext *scx)
 {
     JSObject *obj = &vp->toObject();
 
     CycleDetector detect(scx, obj);
     if (!detect.init(cx))
         return JS_FALSE;
 
-    if (!scx->cb.append('['))
+    if (!scx->sb.append('['))
         return JS_FALSE;
 
     jsuint length;
     if (!js_GetLengthProperty(cx, obj, &length))
         return JS_FALSE;
 
     if (length != 0 && !WriteIndent(cx, scx, scx->depth))
         return JS_FALSE;
@@ -453,32 +453,32 @@ JA(JSContext *cx, Value *vp, StringifyCo
 
         if (!obj->getProperty(cx, id, outputValue.addr()))
             return JS_FALSE;
 
         if (!Str(cx, id, obj, scx, outputValue.addr()))
             return JS_FALSE;
 
         if (outputValue.value().isUndefined()) {
-            if (!js_AppendLiteral(scx->cb, "null"))
+            if (!scx->sb.append("null"))
                 return JS_FALSE;
         }
 
         if (i < length - 1) {
-            if (!scx->cb.append(','))
+            if (!scx->sb.append(','))
                 return JS_FALSE;
             if (!WriteIndent(cx, scx, scx->depth))
                 return JS_FALSE;
         }
     }
 
     if (length != 0 && !WriteIndent(cx, scx, scx->depth - 1))
         return JS_FALSE;
 
-    return scx->cb.append(']');
+    return scx->sb.append(']');
 }
 
 static JSBool
 CallReplacerFunction(JSContext *cx, jsid id, JSObject *holder, StringifyContext *scx, Value *vp)
 {
     if (scx->replacer && scx->replacer->isCallable()) {
         Value vec[2] = { IdToValue(id), *vp};
         if (!JS_CallFunctionValue(cx, holder, OBJECT_TO_JSVAL(scx->replacer),
@@ -510,40 +510,37 @@ Str(JSContext *cx, jsid id, JSObject *ho
     }
 
     if (vp->isString()) {
         JSString *str = vp->toString();
         size_t length = str->length();
         const jschar *chars = str->getChars(cx);
         if (!chars)
             return JS_FALSE;
-        return write_string(cx, scx->cb, chars, length);
+        return write_string(cx, scx->sb, chars, length);
     }
 
-    if (vp->isNull()) {
-        return js_AppendLiteral(scx->cb, "null");
-    }
+    if (vp->isNull())
+        return scx->sb.append("null");
 
-    if (vp->isBoolean()) {
-        return vp->toBoolean() ? js_AppendLiteral(scx->cb, "true")
-                               : js_AppendLiteral(scx->cb, "false");
-    }
+    if (vp->isBoolean())
+        return vp->toBoolean() ? scx->sb.append("true") : scx->sb.append("false");
 
     if (vp->isNumber()) {
         if (vp->isDouble()) {
             jsdouble d = vp->toDouble();
             if (!JSDOUBLE_IS_FINITE(d))
-                return js_AppendLiteral(scx->cb, "null");
+                return scx->sb.append("null");
         }
 
-        JSCharBuffer cb(cx);
-        if (!js_NumberValueToCharBuffer(cx, *vp, cb))
+        StringBuffer sb(cx);
+        if (!NumberValueToStringBuffer(cx, *vp, sb))
             return JS_FALSE;
 
-        return scx->cb.append(cb.begin(), cb.length());
+        return scx->sb.append(sb.begin(), sb.length());
     }
 
     if (vp->isObject() && !IsFunctionObject(*vp) && !IsXML(*vp)) {
         JSBool ok;
 
         scx->depth++;
         ok = (JS_IsArrayObject(cx, &vp->toObject()) ? JA : JO)(cx, vp, scx);
         scx->depth--;
@@ -552,19 +549,19 @@ Str(JSContext *cx, jsid id, JSObject *ho
     }
 
     vp->setUndefined();
     return JS_TRUE;
 }
 
 JSBool
 js_Stringify(JSContext *cx, Value *vp, JSObject *replacer, const Value &space,
-             JSCharBuffer &cb)
+             StringBuffer &sb)
 {
-    StringifyContext scx(cx, cb, replacer);
+    StringifyContext scx(cx, sb, replacer);
     if (!scx.initializeGap(cx, space) || !scx.initializeStack())
         return JS_FALSE;
 
     JSObject *obj = NewBuiltinClassInstance(cx, &js_ObjectClass);
     if (!obj)
         return JS_FALSE;
 
     AutoObjectRooter tvr(cx, obj);
--- a/js/src/json.h
+++ b/js/src/json.h
@@ -48,17 +48,17 @@
 
 extern js::Class js_JSONClass;
 
 extern JSObject *
 js_InitJSONClass(JSContext *cx, JSObject *obj);
 
 extern JSBool
 js_Stringify(JSContext *cx, js::Value *vp, JSObject *replacer,
-             const js::Value &space, JSCharBuffer &cb);
+             const js::Value &space, js::StringBuffer &sb);
 
 extern JSBool js_TryJSON(JSContext *cx, js::Value *vp);
 
 /* JSON parsing states; most permit leading whitespace. */
 enum JSONParserState {
     /* Start of string. */
     JSON_PARSE_STATE_INIT,
 
--- a/js/src/jsprvtd.h
+++ b/js/src/jsprvtd.h
@@ -130,16 +130,17 @@ class ExecuteArgsGuard;
 class InvokeFrameGuard;
 class InvokeArgsGuard;
 class InvokeSessionGuard;
 class TraceRecorder;
 struct TraceMonitor;
 class StackSpace;
 class StackSegment;
 class FrameRegsIter;
+class StringBuffer;
 
 struct Compiler;
 struct Parser;
 class TokenStream;
 struct Token;
 struct TokenPos;
 struct TokenPtr;
 
@@ -168,19 +169,16 @@ class HashSet;
 class PropertyCache;
 struct PropertyCacheEntry;
 
 struct Shape;
 struct EmptyShape;
 
 } /* namespace js */
 
-/* Common instantiations. */
-typedef js::Vector<jschar, 32> JSCharBuffer;
-
 } /* export "C++" */
 #endif  /* __cplusplus */
 
 /* "Friend" types used by jscntxt.h and jsdbgapi.h. */
 typedef enum JSTrapStatus {
     JSTRAP_ERROR,
     JSTRAP_CONTINUE,
     JSTRAP_RETURN,
--- a/js/src/jsregexpinlines.h
+++ b/js/src/jsregexpinlines.h
@@ -503,24 +503,24 @@ RegExp::compile(JSContext *cx)
 
     /*
      * The sticky case we implement hackily by prepending a caret onto the front
      * and relying on |::execute| to pseudo-slice the string when it sees a sticky regexp.
      */
     static const jschar prefix[] = {'^', '(', '?', ':'};
     static const jschar postfix[] = {')'};
 
-    JSCharBuffer cb(cx);
-    if (!cb.reserve(JS_ARRAY_LENGTH(prefix) + source->length() + JS_ARRAY_LENGTH(postfix)))
+    StringBuffer sb(cx);
+    if (!sb.reserve(JS_ARRAY_LENGTH(prefix) + source->length() + JS_ARRAY_LENGTH(postfix)))
         return false;
-    JS_ALWAYS_TRUE(cb.append(prefix, JS_ARRAY_LENGTH(prefix)));
-    JS_ALWAYS_TRUE(cb.append(source->chars(), source->length()));
-    JS_ALWAYS_TRUE(cb.append(postfix, JS_ARRAY_LENGTH(postfix)));
+    JS_ALWAYS_TRUE(sb.append(prefix, JS_ARRAY_LENGTH(prefix)));
+    JS_ALWAYS_TRUE(sb.append(source->chars(), source->length()));
+    JS_ALWAYS_TRUE(sb.append(postfix, JS_ARRAY_LENGTH(postfix)));
 
-    JSLinearString *fakeySource = js_NewStringFromCharBuffer(cx, cb);
+    JSLinearString *fakeySource = sb.finishString();
     if (!fakeySource)
         return false;
     return compileHelper(cx, *fakeySource);
 }
 
 inline bool
 RegExp::isMetaChar(jschar c)
 {
--- a/js/src/jsscan.cpp
+++ b/js/src/jsscan.cpp
@@ -624,17 +624,17 @@ TokenStream::getXMLEntity()
 {
     ptrdiff_t offset, length, i;
     int c, d;
     JSBool ispair;
     jschar *bp, digit;
     char *bytes;
     JSErrNum msg;
 
-    JSCharBuffer &tb = tokenbuf;
+    CharBuffer &tb = tokenbuf;
 
     /* Put the entity, including the '&' already scanned, in tokenbuf. */
     offset = tb.length();
     if (!tb.append('&'))
         return JS_FALSE;
     while ((c = getChar()) != ';') {
         if (c == EOF || c == '\n') {
             ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, JSMSG_END_OF_XML_ENTITY);
@@ -796,18 +796,18 @@ static JS_ALWAYS_INLINE JSBool
 ScanAsSpace(jschar c)
 {
     /* Treat little- and big-endian BOMs as whitespace for compatibility. */
     if (JS_ISSPACE(c) || c == 0xfffe || c == 0xfeff)
         return JS_TRUE;
     return JS_FALSE;
 }
 
-static JS_ALWAYS_INLINE JSAtom *
-atomize(JSContext *cx, JSCharBuffer &cb)
+JS_ALWAYS_INLINE JSAtom *
+TokenStream::atomize(JSContext *cx, CharBuffer &cb)
 {
     return js_AtomizeChars(cx, cb.begin(), cb.length(), 0);
 }
 
 TokenKind
 TokenStream::getTokenInternal()
 {
     TokenKind tt;
@@ -1479,20 +1479,18 @@ TokenStream::getTokenInternal()
                     if (!tokenbuf.append(c))
                         goto error;
                 }
                 if (targetLength == 0)
                     goto bad_xml_markup;
                 if (contentIndex < 0) {
                     atom = cx->runtime->atomState.emptyAtom;
                 } else {
-                    atom = js_AtomizeChars(cx,
-                                           tokenbuf.begin() + contentIndex,
-                                           tokenbuf.length() - contentIndex,
-                                           0);
+                    atom = js_AtomizeChars(cx, tokenbuf.begin() + contentIndex,
+                                           tokenbuf.length() - contentIndex, 0);
                     if (!atom)
                         goto error;
                 }
                 tokenbuf.shrinkBy(tokenbuf.length() - targetLength);
                 tp->t_atom2 = atom;
                 tt = TOK_XMLPI;
 
         finish_xml_markup:
--- a/js/src/jsscan.h
+++ b/js/src/jsscan.h
@@ -295,16 +295,18 @@ enum TokenStreamFlags
 
 class TokenStream
 {
     static const size_t ntokens = 4;                /* 1 current + 2 lookahead, rounded
                                                        to power of 2 to avoid divmod by 3 */
     static const uintN ntokensMask = ntokens - 1;
 
   public:
+    typedef Vector<jschar, 32> CharBuffer;
+
     /*
      * To construct a TokenStream, first call the constructor, which is
      * infallible, then call |init|, which can fail. To destroy a TokenStream,
      * first call |close| then call the destructor. If |init| fails, do not call
      * |close|.
      *
      * This class uses JSContext.tempPool to allocate internal buffers. The
      * caller should JS_ARENA_MARK before calling |init| and JS_ARENA_RELEASE
@@ -320,17 +322,17 @@ class TokenStream
               const char *filename, uintN lineno);
     void close();
     ~TokenStream() {}
 
     /* Accessors. */
     JSContext *getContext() const { return cx; }
     bool onCurrentLine(const TokenPos &pos) const { return lineno == pos.end.lineno; }
     const Token &currentToken() const { return tokens[cursor]; }
-    const JSCharBuffer &getTokenbuf() const { return tokenbuf; }
+    const CharBuffer &getTokenbuf() const { return tokenbuf; }
     const char *getFilename() const { return filename; }
     uintN getLineno() const { return lineno; }
 
     /* Flag methods. */
     void setStrictMode(bool enabled = true) { setFlag(enabled, TSF_STRICT_MODE_CODE); }
     void setXMLTagMode(bool enabled = true) { setFlag(enabled, TSF_XMLTAGMODE); }
     void setXMLOnlyMode(bool enabled = true) { setFlag(enabled, TSF_XMLONLYMODE); }
     void setUnexpectedEOF(bool enabled = true) { setFlag(enabled, TSF_UNEXPECTED_EOF); }
@@ -349,16 +351,18 @@ class TokenStream
     void mungeCurrentToken(TokenKind newKind) { tokens[cursor].type = newKind; }
     void mungeCurrentToken(JSOp newOp) { tokens[cursor].t_op = newOp; }
     void mungeCurrentToken(TokenKind newKind, JSOp newOp) {
         mungeCurrentToken(newKind);
         mungeCurrentToken(newOp);
     }
 
   private:
+    static JSAtom *atomize(JSContext *cx, CharBuffer &cb);
+
     /*
      * Enables flags in the associated tokenstream for the object lifetime.
      * Useful for lexically-scoped flag toggles.
      */
     class Flagger {
         TokenStream * const parent;
         uintN       flags;
       public:
@@ -496,17 +500,17 @@ class TokenStream
     uintN               flags;          /* flags -- see above */
     jschar              *linebase;      /* start of current line;  points into userbuf */
     jschar              *prevLinebase;  /* start of previous line;  NULL if on the first line */
     TokenBuf            userbuf;        /* user input buffer */
     const char          *filename;      /* input filename or null */
     JSSourceHandler     listener;       /* callback for source; eg debugger */
     void                *listenerData;  /* listener 'this' data */
     void                *listenerTSData;/* listener data for this TokenStream */
-    JSCharBuffer        tokenbuf;       /* current token string buffer */
+    CharBuffer          tokenbuf;       /* current token string buffer */
     bool                maybeEOL[256];  /* probabilistic EOL lookup table */
     bool                maybeStrSpecial[256];/* speeds up string scanning */
     JSVersion           version;        /* cached version number for scan */
 };
 
 } /* namespace js */
 
 /* Unicode separators that are treated as line terminators, in addition to \n, \r */
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -1622,33 +1622,33 @@ class RegExpGuard
 
     /*
      * Upper bound on the number of characters we are willing to potentially
      * waste on searching for RegExp meta-characters.
      */
     static const size_t MAX_FLAT_PAT_LEN = 256;
 
     static JSString *flattenPattern(JSContext *cx, JSLinearString *patstr) {
-        JSCharBuffer cb(cx);
-        if (!cb.reserve(patstr->length()))
+        StringBuffer sb(cx);
+        if (!sb.reserve(patstr->length()))
             return NULL;
 
         static const jschar ESCAPE_CHAR = '\\';
         const jschar *chars = patstr->chars();
         size_t len = patstr->length();
         for (const jschar *it = chars; it != chars + len; ++it) {
             if (RegExp::isMetaChar(*it)) {
-                if (!cb.append(ESCAPE_CHAR) || !cb.append(*it))
+                if (!sb.append(ESCAPE_CHAR) || !sb.append(*it))
                     return NULL;
             } else {
-                if (!cb.append(*it))
+                if (!sb.append(*it))
                     return NULL;
             }
         }
-        return js_NewStringFromCharBuffer(cx, cb);
+        return sb.finishString();
     }
 
   public:
     explicit RegExpGuard(JSContext *cx) : cx(cx), rep(cx, NULL) {}
     ~RegExpGuard() {}
 
     /* init must succeed in order to call tryFlatMatch or normalizeRegExp. */
     bool
@@ -1908,33 +1908,32 @@ str_search(JSContext *cx, uintN argc, Va
     else
         vp->setInt32(-1);
     return true;
 }
 
 struct ReplaceData
 {
     ReplaceData(JSContext *cx)
-     : g(cx), cb(cx)
+     : g(cx), sb(cx)
     {}
 
     JSString           *str;           /* 'this' parameter object as a string */
     RegExpGuard        g;              /* regexp parameter object and private data */
     JSObject           *lambda;        /* replacement function object or null */
     JSObject           *elembase;      /* object for function(a){return b[a]} replace */
     JSLinearString     *repstr;        /* replacement string */
     const jschar       *dollar;        /* null or pointer to first $ in repstr */
     const jschar       *dollarEnd;     /* limit pointer for js_strchr_limit */
-    jsint              index;          /* index in result of next replacement */
     jsint              leftIndex;      /* left context index in str->chars */
     JSSubString        dollarStr;      /* for "$$" InterpretDollar result */
     bool               calledBack;     /* record whether callback has been called */
     InvokeSessionGuard session;        /* arguments for repeated lambda Invoke call */
     InvokeArgsGuard    singleShot;     /* arguments for single lambda Invoke call */
-    JSCharBuffer       cb;             /* buffer built during DoMatch */
+    StringBuffer       sb;             /* buffer built during DoMatch */
 };
 
 static bool
 InterpretDollar(JSContext *cx, RegExpStatics *res, const jschar *dp, const jschar *ep,
                 ReplaceData &rdata, JSSubString *out, size_t *skip)
 {
     JS_ASSERT(*dp == '$');
 
@@ -2117,44 +2116,47 @@ FindReplaceLength(JSContext *cx, RegExpS
         } else {
             dp++;
         }
     }
     *sizep = replen;
     return true;
 }
 
+/* 
+ * Precondition: |rdata.sb| already has necessary growth space reserved (as
+ * derived from FindReplaceLength).
+ */
 static void
-DoReplace(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, jschar *chars)
+DoReplace(JSContext *cx, RegExpStatics *res, ReplaceData &rdata)
 {
     JSLinearString *repstr = rdata.repstr;
     const jschar *cp;
     const jschar *bp = cp = repstr->chars();
 
     const jschar *dp = rdata.dollar;
     const jschar *ep = rdata.dollarEnd;
     for (; dp; dp = js_strchr_limit(dp, '$', ep)) {
+        /* Move one of the constant portions of the replacement value. */
         size_t len = dp - cp;
-        js_strncpy(chars, cp, len);
-        chars += len;
+        JS_ALWAYS_TRUE(rdata.sb.append(cp, len));
         cp = dp;
 
         JSSubString sub;
         size_t skip;
         if (InterpretDollar(cx, res, dp, ep, rdata, &sub, &skip)) {
             len = sub.length;
-            js_strncpy(chars, sub.chars, len);
-            chars += len;
+            JS_ALWAYS_TRUE(rdata.sb.append(sub.chars, len));
             cp += skip;
             dp += skip;
         } else {
             dp++;
         }
     }
-    js_strncpy(chars, cp, repstr->length() - (cp - bp));
+    JS_ALWAYS_TRUE(rdata.sb.append(cp, repstr->length() - (cp - bp)));
 }
 
 static bool
 ReplaceRegExpCallback(JSContext *cx, RegExpStatics *res, size_t count, void *p)
 {
     ReplaceData &rdata = *static_cast<ReplaceData *>(p);
 
     rdata.calledBack = true;
@@ -2164,24 +2166,20 @@ ReplaceRegExpCallback(JSContext *cx, Reg
     size_t leftlen = res->matchStart() - leftoff;
     rdata.leftIndex = res->matchLimit();
 
     size_t replen = 0;  /* silence 'unused' warning */
     if (!FindReplaceLength(cx, res, rdata, &replen))
         return false;
 
     size_t growth = leftlen + replen;
-    if (!rdata.cb.growByUninitialized(growth))
+    if (!rdata.sb.reserve(rdata.sb.length() + growth))
         return false;
-
-    jschar *chars = rdata.cb.begin() + rdata.index;
-    rdata.index += growth;
-    js_strncpy(chars, left, leftlen);
-    chars += leftlen;
-    DoReplace(cx, res, rdata, chars);
+    JS_ALWAYS_TRUE(rdata.sb.append(left, leftlen)); /* skipped-over portion of the search value */
+    DoReplace(cx, res, rdata);
     return true;
 }
 
 static bool
 BuildFlatReplacement(JSContext *cx, JSString *textstr, JSString *repstr,
                      const FlatMatch &fm, Value *vp)
 {
     RopeBuilder builder(cx);
@@ -2277,17 +2275,17 @@ BuildDollarReplacement(JSContext *cx, JS
 
     /*
      * Most probably:
      *
      *      len(newstr) >= len(orig) - len(match) + len(replacement)
      *
      * Note that dollar vars _could_ make the resulting text smaller than this.
      */
-    JSCharBuffer newReplaceChars(cx);
+    StringBuffer newReplaceChars(cx);
     if (!newReplaceChars.reserve(textstr->length() - fm.patternLength() + repstr->length()))
         return false;
 
     /* Move the pre-dollar chunk in bulk. */
     JS_ALWAYS_TRUE(newReplaceChars.append(repstr->chars(), firstDollar));
 
     /* Move the rest char-by-char, interpreting dollars as we encounter them. */
 #define ENSURE(__cond) if (!(__cond)) return false;
@@ -2318,17 +2316,17 @@ BuildDollarReplacement(JSContext *cx, JS
             continue;
         }
         ++it; /* We always eat an extra char in the above switch. */
     }
 
     JSString *leftSide = js_NewDependentString(cx, textstr, 0, matchStart);
     ENSURE(leftSide);
 
-    JSString *newReplace = js_NewStringFromCharBuffer(cx, newReplaceChars);
+    JSString *newReplace = newReplaceChars.finishString();
     ENSURE(newReplace);
 
     JS_ASSERT(textstr->length() >= matchLimit);
     JSString *rightSide = js_NewDependentString(cx, textstr, matchLimit,
                                                 textstr->length() - matchLimit);
     ENSURE(rightSide);
 
     RopeBuilder builder(cx);
@@ -2343,36 +2341,35 @@ BuildDollarReplacement(JSContext *cx, JS
 
 static inline bool
 str_replace_regexp(JSContext *cx, uintN argc, Value *vp, ReplaceData &rdata)
 {
     const RegExpPair *rep = rdata.g.normalizeRegExp(true, 2, argc, vp);
     if (!rep)
         return false;
 
-    rdata.index = 0;
     rdata.leftIndex = 0;
     rdata.calledBack = false;
 
     RegExpStatics *res = cx->regExpStatics();
     if (!DoMatch(cx, res, vp, rdata.str, *rep, ReplaceRegExpCallback, &rdata, REPLACE_ARGS))
         return false;
 
     if (!rdata.calledBack) {
         /* Didn't match, so the string is unmodified. */
         vp->setString(rdata.str);
         return true;
     }
 
     JSSubString sub;
     res->getRightContext(&sub);
-    if (!rdata.cb.append(sub.chars, sub.length))
+    if (!rdata.sb.append(sub.chars, sub.length))
         return false;
 
-    JSString *retstr = js_NewStringFromCharBuffer(cx, rdata.cb);
+    JSString *retstr = rdata.sb.finishString();
     if (!retstr)
         return false;
 
     vp->setString(retstr);
     return true;
 }
 
 static inline bool
@@ -3472,24 +3469,27 @@ NewShortString(JSContext *cx, const char
         *p = 0;
     }
     return str->header()->assertIsFlat();
 }
 
 static const size_t sMinWasteSize = 16;
 
 JSFlatString *
-js_NewStringFromCharBuffer(JSContext *cx, JSCharBuffer &cb)
+StringBuffer::finishString()
 {
+    JSContext *cx = context();
     if (cb.empty())
         return ATOM_TO_STRING(cx->runtime->atomState.emptyAtom);
 
     size_t length = cb.length();
-
-    JS_STATIC_ASSERT(JSShortString::MAX_SHORT_STRING_LENGTH < JSCharBuffer::InlineLength);
+    if (!checkLength(length))
+        return NULL;
+
+    JS_STATIC_ASSERT(JSShortString::MAX_SHORT_STRING_LENGTH < CharBuffer::InlineLength);
     if (JSShortString::fitsIntoShortString(length))
         return NewShortString(cx, cb.begin(), length);
 
     if (!cb.append('\0'))
         return NULL;
 
     size_t capacity = cb.capacity();
 
@@ -3675,46 +3675,40 @@ js_ValueToString(JSContext *cx, const Va
     } else if (v.isNull()) {
         str = ATOM_TO_STRING(cx->runtime->atomState.nullAtom);
     } else {
         str = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]);
     }
     return str;
 }
 
-static inline JSBool
-AppendAtom(JSAtom *atom, JSCharBuffer &cb)
-{
-    return cb.append(atom->chars(), atom->length());
-}
-
 /* This function implements E-262-3 section 9.8, toString. */
-JSBool
-js_ValueToCharBuffer(JSContext *cx, const Value &arg, JSCharBuffer &cb)
+bool
+js::ValueToStringBuffer(JSContext *cx, const Value &arg, StringBuffer &sb)
 {
     Value v = arg;
     if (v.isObject() && !DefaultValue(cx, &v.toObject(), JSTYPE_STRING, &v))
         return false;
 
     if (v.isString()) {
         JSString *str = v.toString();
         size_t length = str->length();
         const jschar *chars = str->getChars(cx);
         if (!chars)
             return false;
-        return cb.append(chars, length);
+        return sb.append(chars, length);
     }
     if (v.isNumber())
-        return js_NumberValueToCharBuffer(cx, v, cb);
+        return NumberValueToStringBuffer(cx, v, sb);
     if (v.isBoolean())
-        return js_BooleanToCharBuffer(cx, v.toBoolean(), cb);
+        return BooleanToStringBuffer(cx, v.toBoolean(), sb);
     if (v.isNull())
-        return AppendAtom(cx->runtime->atomState.nullAtom, cb);
+        return sb.append(cx->runtime->atomState.nullAtom);
     JS_ASSERT(v.isUndefined());
-    return AppendAtom(cx->runtime->atomState.typeAtoms[JSTYPE_VOID], cb);
+    return sb.append(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]);
 }
 
 JS_FRIEND_API(JSString *)
 js_ValueToSource(JSContext *cx, const Value &v)
 {
     if (v.isUndefined())
         return ATOM_TO_STRING(cx->runtime->atomState.void0Atom);
     if (v.isString())
@@ -5571,19 +5565,19 @@ const bool js_alnum[] = {
 /* 10 */ true,  true,  true,  true,  true,  true,  true,  true,  true,  true,
 /* 11 */ true,  true,  true,  true,  true,  true,  true,  true,  true,  true,
 /* 12 */ true,  true,  true,  false, false, false, false, false
 };
 
 #define URI_CHUNK 64U
 
 static inline bool
-TransferBufferToString(JSContext *cx, JSCharBuffer &cb, Value *rval)
+TransferBufferToString(JSContext *cx, StringBuffer &sb, Value *rval)
 {
-    JSString *str = js_NewStringFromCharBuffer(cx, cb);
+    JSString *str = sb.finishString();
     if (!str)
         return false;
     rval->setString(str);
     return true;;
 }
 
 /*
  * ECMA 3, 15.1.3 URI Handling Function Properties
@@ -5603,25 +5597,25 @@ Encode(JSContext *cx, JSString *str, con
     if (!chars)
         return JS_FALSE;
 
     if (length == 0) {
         rval->setString(cx->runtime->emptyString);
         return JS_TRUE;
     }
 
-    JSCharBuffer cb(cx);
+    StringBuffer sb(cx);
     jschar hexBuf[4];
     hexBuf[0] = '%';
     hexBuf[3] = 0;
     for (size_t k = 0; k < length; k++) {
         jschar c = chars[k];
         if (js_strchr(unescapedSet, c) ||
             (unescapedSet2 && js_strchr(unescapedSet2, c))) {
-            if (!cb.append(c))
+            if (!sb.append(c))
                 return JS_FALSE;
         } else {
             if ((c >= 0xDC00) && (c <= 0xDFFF)) {
                 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                                  JSMSG_BAD_URI, NULL);
                 return JS_FALSE;
             }
             uint32 v;
@@ -5642,39 +5636,39 @@ Encode(JSContext *cx, JSString *str, con
                 }
                 v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000;
             }
             uint8 utf8buf[4];
             size_t L = js_OneUcs4ToUtf8Char(utf8buf, v);
             for (size_t j = 0; j < L; j++) {
                 hexBuf[1] = HexDigits[utf8buf[j] >> 4];
                 hexBuf[2] = HexDigits[utf8buf[j] & 0xf];
-                if (!cb.append(hexBuf, 3))
+                if (!sb.append(hexBuf, 3))
                     return JS_FALSE;
             }
         }
     }
 
-    return TransferBufferToString(cx, cb, rval);
+    return TransferBufferToString(cx, sb, rval);
 }
 
 static JSBool
 Decode(JSContext *cx, JSString *str, const jschar *reservedSet, Value *rval)
 {
     size_t length = str->length();
     const jschar *chars = str->getChars(cx);
     if (!chars)
         return JS_FALSE;
 
     if (length == 0) {
         rval->setString(cx->runtime->emptyString);
         return JS_TRUE;
     }
 
-    JSCharBuffer cb(cx);
+    StringBuffer sb(cx);
     for (size_t k = 0; k < length; k++) {
         jschar c = chars[k];
         if (c == '%') {
             size_t start = k;
             if ((k + 2) >= length)
                 goto report_bad_uri;
             if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
                 goto report_bad_uri;
@@ -5706,36 +5700,36 @@ Decode(JSContext *cx, JSString *str, con
                 }
                 uint32 v = Utf8ToOneUcs4Char(octets, n);
                 if (v >= 0x10000) {
                     v -= 0x10000;
                     if (v > 0xFFFFF)
                         goto report_bad_uri;
                     c = (jschar)((v & 0x3FF) + 0xDC00);
                     jschar H = (jschar)((v >> 10) + 0xD800);
-                    if (!cb.append(H))
+                    if (!sb.append(H))
                         return JS_FALSE;
                 } else {
                     c = (jschar)v;
                 }
             }
             if (js_strchr(reservedSet, c)) {
-                if (!cb.append(chars + start, k - start + 1))
+                if (!sb.append(chars + start, k - start + 1))
                     return JS_FALSE;
             } else {
-                if (!cb.append(c))
+                if (!sb.append(c))
                     return JS_FALSE;
             }
         } else {
-            if (!cb.append(c))
+            if (!sb.append(c))
                 return JS_FALSE;
         }
     }
 
-    return TransferBufferToString(cx, cb, rval);
+    return TransferBufferToString(cx, sb, rval);
 
   report_bad_uri:
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_URI);
     /* FALL THROUGH */
 
     return JS_FALSE;
 }
 
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -571,16 +571,18 @@ class JSShortString : public js::gc::Cel
         JS_STATIC_ASSERT(offsetof(JSString, inlineStorage) +
                          sizeof(jschar) * (JSShortString::MAX_SHORT_STRING_LENGTH + 1) ==
                          sizeof(JSShortString));
     }
 };
 
 namespace js {
 
+class StringBuffer;
+
 /*
  * When an algorithm does not need a string represented as a single linear
  * array of characters, this range utility may be used to traverse the string a
  * sequence of linear arrays of characters. This avoids flattening ropes.
  *
  * Implemented in jsstrinlines.h.
  */
 class StringSegmentRange;
@@ -782,24 +784,16 @@ extern const char js_decodeURI_str[];
 extern const char js_encodeURI_str[];
 extern const char js_decodeURIComponent_str[];
 extern const char js_encodeURIComponent_str[];
 
 /* GC-allocate a string descriptor for the given malloc-allocated chars. */
 extern JSFlatString *
 js_NewString(JSContext *cx, jschar *chars, size_t length);
 
-/*
- * GC-allocate a string descriptor and steal the char buffer held by |cb|.
- * This function takes responsibility for adding the terminating '\0' required
- * by js_NewString.
- */
-extern JSFlatString *
-js_NewStringFromCharBuffer(JSContext *cx, JSCharBuffer &cb);
-
 extern JSLinearString *
 js_NewDependentString(JSContext *cx, JSString *base, size_t start,
                       size_t length);
 
 /* Copy a counted string and GC-allocate a descriptor for it. */
 extern JSFlatString *
 js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n);
 
@@ -837,25 +831,25 @@ namespace js {
 static JS_ALWAYS_INLINE JSString *
 ValueToString_TestForStringInline(JSContext *cx, const Value &v)
 {
     if (v.isString())
         return v.toString();
     return js_ValueToString(cx, v);
 }
 
-}
-
 /*
  * This function implements E-262-3 section 9.8, toString. Convert the given
  * value to a string of jschars appended to the given buffer. On error, the
  * passed buffer may have partial results appended.
  */
-extern JSBool
-js_ValueToCharBuffer(JSContext *cx, const js::Value &v, JSCharBuffer &cb);
+extern bool
+ValueToStringBuffer(JSContext *cx, const Value &v, StringBuffer &sb);
+
+} /* namespace js */
 
 /*
  * Convert a value to its source expression, returning null after reporting
  * an error, otherwise returning a new string reference.
  */
 extern JS_FRIEND_API(JSString *)
 js_ValueToSource(JSContext *cx, const js::Value &v);
 
--- a/js/src/jsstrinlines.h
+++ b/js/src/jsstrinlines.h
@@ -36,16 +36,186 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef jsstrinlines_h___
 #define jsstrinlines_h___
 
 #include "jsstr.h"
+#include "jscntxtinlines.h"
+
+namespace js {
+
+static inline bool
+CheckStringLength(JSContext *cx, size_t length)
+{
+    if (JS_UNLIKELY(length > JSString::MAX_LENGTH)) {
+        js_ReportAllocationOverflow(cx);
+        return false;
+    }
+
+    return true;
+}
+
+/*
+ * String builder that eagerly checks for over-allocation past the maximum
+ * string length.
+ *
+ * Note: over-allocation is not checked for when using the infallible
+ * |replaceRawBuffer|, so the implementation of |finishString| also must check
+ * for over-allocation.
+ */
+class StringBuffer
+{
+    typedef Vector<jschar, 32> CharBuffer;
+    CharBuffer cb;
+
+    static inline bool checkLength(JSContext *cx, size_t length);
+    inline bool checkLength(size_t length);
+    JSContext *context() const { return cb.allocPolicy().context(); }
+
+  public:
+    explicit inline StringBuffer(JSContext *cx);
+    bool reserve(size_t len);
+    bool resize(size_t len);
+    bool append(const jschar c);
+    bool append(const jschar *chars, size_t len);
+    bool append(const jschar *begin, const jschar *end);
+    bool append(JSString *str);
+    bool append(JSAtom *atom);
+    bool appendN(const jschar c, size_t n);
+    bool appendInflated(const char *cstr, size_t len);
+    JSAtom *atomize(uintN flags = 0);
+    static JSAtom *atomize(JSContext *cx, const CharBuffer &cb, uintN flags = 0);
+    static JSAtom *atomize(JSContext *cx, const jschar *begin, size_t length, uintN flags = 0);
+
+    void replaceRawBuffer(jschar *chars, size_t len) { cb.replaceRawBuffer(chars, len); }
+    jschar *begin() { return cb.begin(); }
+    jschar *end() { return cb.end(); }
+    const jschar *begin() const { return cb.begin(); }
+    const jschar *end() const { return cb.end(); }
+    bool empty() const { return cb.empty(); }
+    inline jsint length() const;
+
+    /*
+     * Produces a string, resetting the buffer to an empty state.
+     * This method takes responsibility for adding the terminating '\0'
+     * required by js_NewString.
+     */
+    JSFlatString *finishString();
+
+    template <size_t ArrayLength>
+    bool append(const char (&array)[ArrayLength]) {
+        return cb.append(array, array + ArrayLength - 1); /* No trailing '\0'. */
+    }
+};
+
+inline
+StringBuffer::StringBuffer(JSContext *cx)
+  : cb(cx)
+{}
+
+inline bool
+StringBuffer::reserve(size_t len)
+{
+    if (!checkLength(len))
+        return false;
+    return cb.reserve(len);
+}
+
+inline bool
+StringBuffer::resize(size_t len)
+{
+    if (!checkLength(len))
+        return false;
+    return cb.resize(len);
+}
+
+inline bool
+StringBuffer::append(const jschar c)
+{
+    if (!checkLength(cb.length() + 1))
+        return false;
+    return cb.append(c);
+}
+
+inline bool
+StringBuffer::append(const jschar *chars, size_t len)
+{
+    if (!checkLength(cb.length() + len))
+        return false;
+    return cb.append(chars, len);
+}
+
+inline bool
+StringBuffer::append(const jschar *begin, const jschar *end)
+{
+    if (!checkLength(cb.length() + (end - begin)))
+        return false;
+    return cb.append(begin, end);
+}
+
+inline bool
+StringBuffer::append(JSString *str)
+{
+    JSLinearString *linear = str->ensureLinear(context());
+    size_t strLen = linear->length();
+    if (!checkLength(cb.length() + strLen))
+        return false;
+    return cb.append(linear->chars(), strLen);
+}
+
+inline bool
+StringBuffer::append(JSAtom *atom)
+{
+    size_t strLen = atom->length();
+    if (!checkLength(cb.length() + strLen))
+        return false;
+    return cb.append(atom->chars(), strLen);
+}
+
+inline bool
+StringBuffer::appendN(const jschar c, size_t n)
+{
+    if (!checkLength(cb.length() + n))
+        return false;
+    return cb.appendN(c, n);
+}
+
+inline bool
+StringBuffer::appendInflated(const char *cstr, size_t cstrlen)
+{
+    size_t lengthBefore = length();
+    if (!cb.growByUninitialized(cstrlen))
+        return false;
+#if DEBUG
+    size_t oldcstrlen = cstrlen;
+    bool ok = 
+#endif
+    js_InflateStringToBuffer(context(), cstr, cstrlen, begin() + lengthBefore, &cstrlen);
+    JS_ASSERT(ok && oldcstrlen == cstrlen);
+    return true;
+}
+
+inline jsint
+StringBuffer::length() const
+{
+    JS_STATIC_ASSERT(jsint(JSString::MAX_LENGTH) == JSString::MAX_LENGTH);
+    JS_ASSERT(cb.length() <= JSString::MAX_LENGTH);
+    return jsint(cb.length());
+}
+
+inline bool
+StringBuffer::checkLength(size_t length)
+{
+    return CheckStringLength(context(), length);
+}
+
+} /* namespace js */
 
 inline JSFlatString *
 JSString::unitString(jschar c)
 {
     JS_ASSERT(c < UNIT_STRING_LIMIT);
     return const_cast<JSString *>(&unitStringTable[c])->assertIsFlat();
 }
 
--- a/js/src/jsvector.h
+++ b/js/src/jsvector.h
@@ -375,30 +375,16 @@ class Vector : AllocPolicy
 
     /*
      * Removes the element |t|, which must fall in the bounds [begin, end),
      * shifting existing elements from |t + 1| onward one position lower.
      */
     void erase(T *t);
 };
 
-/* Helper functions */
-
-/*
- * This helper function is specialized for appending the characters of a string
- * literal to a vector. This could not be done generically since one must take
- * care not to append the terminating '\0'.
- */
-template <class T, size_t N, class AP, size_t ArrayLength>
-JS_ALWAYS_INLINE bool
-js_AppendLiteral(Vector<T,N,AP> &v, const char (&array)[ArrayLength])
-{
-    return v.append(array, array + ArrayLength - 1);
-}
-
 /* This does the re-entrancy check plus several other sanity checks. */
 #define REENTRANCY_GUARD_ET_AL \
     ReentrancyGuard g(*this); \
     JS_ASSERT_IF(usingInlineStorage(), mCapacity == sInlineCapacity); \
     JS_ASSERT(mLength <= mCapacity)
 
 /* Vector Implementation */
 
--- a/js/src/jsxml.cpp
+++ b/js/src/jsxml.cpp
@@ -173,25 +173,16 @@ IsDeclared(const JSObject *obj)
 
 static JSBool
 xml_isXMLName(JSContext *cx, uintN argc, jsval *vp)
 {
     *vp = BOOLEAN_TO_JSVAL(js_IsXMLName(cx, argc ? vp[2] : JSVAL_VOID));
     return JS_TRUE;
 }
 
-static inline bool
-AppendString(JSCharBuffer &cb, JSString *str)
-{
-    const jschar *chars = str->getChars(cb.allocPolicy().context());
-    if (!chars)
-        return false;
-    return cb.append(chars, str->length());
-}
-
 /*
  * This wrapper is needed because NewBuiltinClassInstance doesn't
  * call the constructor, and we need a place to set the
  * HAS_EQUALITY bit.
  */
 static inline JSObject *
 NewBuiltinClassInstanceXML(JSContext *cx, Class *clasp)
 {
@@ -1996,186 +1987,185 @@ bad:
 }
 
 /*
  * ECMA-357 10.2.1 Steps 5-7 pulled out as common subroutines of XMLToXMLString
  * and their library-public js_* counterparts.  The guts of MakeXMLCDataString,
  * MakeXMLCommentString, and MakeXMLPIString are further factored into a common
  * MakeXMLSpecialString subroutine.
  *
- * These functions mutate cb, leaving it empty.
+ * These functions mutate sb, leaving it empty.
  */
-static JSString *
-MakeXMLSpecialString(JSContext *cx, JSCharBuffer &cb,
+static JSFlatString *
+MakeXMLSpecialString(JSContext *cx, StringBuffer &sb,
                      JSString *str, JSString *str2,
                      const jschar *prefix, size_t prefixlength,
                      const jschar *suffix, size_t suffixlength)
 {
-    if (!cb.append(prefix, prefixlength) || !AppendString(cb, str))
+    if (!sb.append(prefix, prefixlength) || !sb.append(str))
         return NULL;
     if (str2 && !str2->empty()) {
-        if (!cb.append(' ') || !AppendString(cb, str2))
+        if (!sb.append(' ') || !sb.append(str2))
             return NULL;
     }
-    if (!cb.append(suffix, suffixlength))
+    if (!sb.append(suffix, suffixlength))
         return NULL;
 
-    return js_NewStringFromCharBuffer(cx, cb);
-}
-
-static JSString *
-MakeXMLCDATAString(JSContext *cx, JSCharBuffer &cb, JSString *str)
+    return sb.finishString();
+}
+
+static JSFlatString *
+MakeXMLCDATAString(JSContext *cx, StringBuffer &sb, JSString *str)
 {
     static const jschar cdata_prefix_ucNstr[] = {'<', '!', '[',
                                                  'C', 'D', 'A', 'T', 'A',
                                                  '['};
     static const jschar cdata_suffix_ucNstr[] = {']', ']', '>'};
 
-    return MakeXMLSpecialString(cx, cb, str, NULL,
+    return MakeXMLSpecialString(cx, sb, str, NULL,
                                 cdata_prefix_ucNstr, 9,
                                 cdata_suffix_ucNstr, 3);
 }
 
-static JSString *
-MakeXMLCommentString(JSContext *cx, JSCharBuffer &cb, JSString *str)
+static JSFlatString *
+MakeXMLCommentString(JSContext *cx, StringBuffer &sb, JSString *str)
 {
     static const jschar comment_prefix_ucNstr[] = {'<', '!', '-', '-'};
     static const jschar comment_suffix_ucNstr[] = {'-', '-', '>'};
 
-    return MakeXMLSpecialString(cx, cb, str, NULL,
+    return MakeXMLSpecialString(cx, sb, str, NULL,
                                 comment_prefix_ucNstr, 4,
                                 comment_suffix_ucNstr, 3);
 }
 
-static JSString *
-MakeXMLPIString(JSContext *cx, JSCharBuffer &cb, JSString *name,
+static JSFlatString *
+MakeXMLPIString(JSContext *cx, StringBuffer &sb, JSString *name,
                 JSString *value)
 {
     static const jschar pi_prefix_ucNstr[] = {'<', '?'};
     static const jschar pi_suffix_ucNstr[] = {'?', '>'};
 
-    return MakeXMLSpecialString(cx, cb, name, value,
+    return MakeXMLSpecialString(cx, sb, name, value,
                                 pi_prefix_ucNstr, 2,
                                 pi_suffix_ucNstr, 2);
 }
 
 /*
  * ECMA-357 10.2.1 17(d-g) pulled out into a common subroutine that appends
  * equals, a double quote, an attribute value, and a closing double quote.
  */
 static bool
-AppendAttributeValue(JSContext *cx, JSCharBuffer &cb, JSString *valstr)
-{
-    if (!cb.append('='))
+AppendAttributeValue(JSContext *cx, StringBuffer &sb, JSString *valstr)
+{
+    if (!sb.append('='))
         return false;
     valstr = js_EscapeAttributeValue(cx, valstr, JS_TRUE);
-    return valstr && AppendString(cb, valstr);
+    return valstr && sb.append(valstr);
 }
 
 /*
  * ECMA-357 10.2.1.1 EscapeElementValue helper method.
 
- * These functions mutate cb, leaving it empty.
+ * These functions mutate sb, leaving it empty.
  */
-static JSString *
-EscapeElementValue(JSContext *cx, JSCharBuffer &cb, JSString *str, uint32 toSourceFlag)
+static JSFlatString *
+EscapeElementValue(JSContext *cx, StringBuffer &sb, JSString *str, uint32 toSourceFlag)
 {
     size_t length = str->length();
     const jschar *start = str->getChars(cx);
     if (!start)
         return NULL;
 
     for (const jschar *cp = start, *end = start + length; cp != end; ++cp) {
         jschar c = *cp;
         switch (*cp) {
           case '<':
-            if (!js_AppendLiteral(cb, js_lt_entity_str))
+            if (!sb.append(js_lt_entity_str))
                 return NULL;
             break;
           case '>':
-            if (!js_AppendLiteral(cb, js_gt_entity_str))
+            if (!sb.append(js_gt_entity_str))
                 return NULL;
             break;
           case '&':
-            if (!js_AppendLiteral(cb, js_amp_entity_str))
+            if (!sb.append(js_amp_entity_str))
                 return NULL;
             break;
           case '{':
             /*
              * If EscapeElementValue is called by toSource/uneval, we also need
              * to escape '{'. See bug 463360.
              */
             if (toSourceFlag) {
-                if (!js_AppendLiteral(cb, js_leftcurly_entity_str))
+                if (!sb.append(js_leftcurly_entity_str))
                     return NULL;
                 break;
             }
             /* FALL THROUGH */
           default:
-            if (!cb.append(c))
+            if (!sb.append(c))
                 return NULL;
         }
     }
-    return js_NewStringFromCharBuffer(cx, cb);
+    return sb.finishString();
 }
 
 /*
  * ECMA-357 10.2.1.2 EscapeAttributeValue helper method.
  *
- * These functions mutate cb, leaving it empty.
+ * These functions mutate sb, leaving it empty.
  */
-static JSLinearString *
-EscapeAttributeValue(JSContext *cx, JSCharBuffer &cb, JSString *str,
-                     JSBool quote)
+static JSFlatString *
+EscapeAttributeValue(JSContext *cx, StringBuffer &sb, JSString *str, JSBool quote)
 {
     size_t length = str->length();
     const jschar *start = str->getChars(cx);
     if (!start)
         return NULL;
 
-    if (quote && !cb.append('"'))
+    if (quote && !sb.append('"'))
         return NULL;
 
     for (const jschar *cp = start, *end = start + length; cp != end; ++cp) {
         jschar c = *cp;
         switch (c) {
           case '"':
-            if (!js_AppendLiteral(cb, js_quot_entity_str))
+            if (!sb.append(js_quot_entity_str))
                 return NULL;
             break;
           case '<':
-            if (!js_AppendLiteral(cb, js_lt_entity_str))
+            if (!sb.append(js_lt_entity_str))
                 return NULL;
             break;
           case '&':
-            if (!js_AppendLiteral(cb, js_amp_entity_str))
+            if (!sb.append(js_amp_entity_str))
                 return NULL;
             break;
           case '\n':
-            if (!js_AppendLiteral(cb, "&#xA;"))
+            if (!sb.append("&#xA;"))
                 return NULL;
             break;
           case '\r':
-            if (!js_AppendLiteral(cb, "&#xD;"))
+            if (!sb.append("&#xD;"))
                 return NULL;
             break;
           case '\t':
-            if (!js_AppendLiteral(cb, "&#x9;"))
+            if (!sb.append("&#x9;"))
                 return NULL;
             break;
           default:
-            if (!cb.append(c))
+            if (!sb.append(c))
                 return NULL;
         }
     }
 
-    if (quote && !cb.append('"'))
+    if (quote && !sb.append('"'))
         return NULL;
 
-    return js_NewStringFromCharBuffer(cx, cb);
+    return sb.finishString();
 }
 
 /* 13.3.5.4 [[GetNamespace]]([InScopeNamespaces]) */
 static JSObject *
 GetNamespace(JSContext *cx, JSObject *qn, const JSXMLArray *inScopeNSes)
 {
     JSLinearString *uri, *prefix, *nsprefix;
     JSObject *match, *ns;
@@ -2389,80 +2379,80 @@ namespace_match(const void *a, const voi
 /* ECMA-357 10.2.1 and 10.2.2 */
 #define TO_SOURCE_FLAG 0x80000000
 
 static JSString *
 XMLToXMLString(JSContext *cx, JSXML *xml, const JSXMLArray *ancestorNSes,
                uint32 indentLevel)
 {
     JSBool pretty, indentKids;
-    JSCharBuffer cb(cx);
+    StringBuffer sb(cx);
     JSString *str;
     JSLinearString *prefix, *nsuri;
     uint32 i, n, nextIndentLevel;
     JSObject *ns, *ns2;
     AutoNamespaceArray empty(cx), decls(cx), ancdecls(cx);
 
     if (!GetBooleanXMLSetting(cx, js_prettyPrinting_str, &pretty))
         return NULL;
 
     if (pretty) {
-        if (!cb.appendN(' ', indentLevel & ~TO_SOURCE_FLAG))
+        if (!sb.appendN(' ', indentLevel & ~TO_SOURCE_FLAG))
             return NULL;
     }
 
     str = NULL;
 
     switch (xml->xml_class) {
       case JSXML_CLASS_TEXT:
         /* Step 4. */
         if (pretty) {
             str = ChompXMLWhitespace(cx, xml->xml_value);
             if (!str)
                 return NULL;
         } else {
             str = xml->xml_value;
         }
-        return EscapeElementValue(cx, cb, str, indentLevel & TO_SOURCE_FLAG);
+        return EscapeElementValue(cx, sb, str, indentLevel & TO_SOURCE_FLAG);
 
       case JSXML_CLASS_ATTRIBUTE:
         /* Step 5. */
-        return EscapeAttributeValue(cx, cb, xml->xml_value,
+        return EscapeAttributeValue(cx, sb, xml->xml_value,
                                     (indentLevel & TO_SOURCE_FLAG) != 0);
 
       case JSXML_CLASS_COMMENT:
         /* Step 6. */
-        return MakeXMLCommentString(cx, cb, xml->xml_value);
+        return MakeXMLCommentString(cx, sb, xml->xml_value);
 
       case JSXML_CLASS_PROCESSING_INSTRUCTION:
         /* Step 7. */
-        return MakeXMLPIString(cx, cb, xml->name->getQNameLocalName(),
+        return MakeXMLPIString(cx, sb, xml->name->getQNameLocalName(),
                                xml->xml_value);
 
       case JSXML_CLASS_LIST:
         /* ECMA-357 10.2.2. */
         {
             JSXMLArrayCursor cursor(&xml->xml_kids);
             i = 0;
             while (JSXML *kid = (JSXML *) cursor.getNext()) {
                 if (pretty && i != 0) {
-                    if (!cb.append('\n'))
+                    if (!sb.append('\n'))
                         return NULL;
                 }
 
                 JSString *kidstr = XMLToXMLString(cx, kid, ancestorNSes, indentLevel);
-                if (!kidstr || !AppendString(cb, kidstr))
+                if (!kidstr || !sb.append(kidstr))
                     return NULL;
                 ++i;
             }
         }
 
-        if (cb.empty())
+        if (sb.empty())
             return cx->runtime->emptyString;
-        return js_NewStringFromCharBuffer(cx, cb);
+        return sb.finishString();
 
       default:;
     }
 
     /* After this point, control must flow through label out: to exit. */
     if (!js_EnterLocalRootScope(cx))
         return NULL;
 
@@ -2571,38 +2561,38 @@ XMLToXMLString(JSContext *cx, JSXML *xml
          * matters here: code at label out: releases strong refs in decls.
          */
         if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns) ||
             !XMLARRAY_APPEND(cx, &decls.array, ns)) {
             goto out;
         }
     }
 
-    /* Format the element or point-tag into cb. */
-    if (!cb.append('<'))
+    /* Format the element or point-tag into sb. */
+    if (!sb.append('<'))
         goto out;
 
     if (!prefix->empty()) {
-        if (!AppendString(cb, prefix) || !cb.append(':'))
+        if (!sb.append(prefix) || !sb.append(':'))
             goto out;
     }
-    if (!AppendString(cb, xml->name->getQNameLocalName()))
+    if (!sb.append(xml->name->getQNameLocalName()))
         goto out;
 
     /*
      * Step 16 makes a union to avoid writing two loops in step 17, to share
      * common attribute value appending spec-code.  We prefer two loops for
      * faster code and less data overhead.
      */
 
     /* Step 17(b): append attributes. */
     {
         JSXMLArrayCursor cursor(&xml->xml_attrs);
         while (JSXML *attr = (JSXML *) cursor.getNext()) {
-            if (!cb.append(' '))
+            if (!sb.append(' '))
                 goto out;
             ns2 = GetNamespace(cx, attr->name, &ancdecls.array);
             if (!ns2)
                 goto out;
 
             /* 17(b)(ii): NULL means *undefined* here. */
             prefix = ns2->getNamePrefix();
             if (!prefix) {
@@ -2625,68 +2615,68 @@ XMLToXMLString(JSContext *cx, JSXML *xml
                 if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns2) ||
                     !XMLARRAY_APPEND(cx, &decls.array, ns2)) {
                     goto out;
                 }
             }
 
             /* 17(b)(iii). */
             if (!prefix->empty()) {
-                if (!AppendString(cb, prefix) || !cb.append(':'))
+                if (!sb.append(prefix) || !sb.append(':'))
                     goto out;
             }
 
             /* 17(b)(iv). */
-            if (!AppendString(cb, attr->name->getQNameLocalName()))
+            if (!sb.append(attr->name->getQNameLocalName()))
                 goto out;
 
             /* 17(d-g). */
-            if (!AppendAttributeValue(cx, cb, attr->xml_value))
+            if (!AppendAttributeValue(cx, sb, attr->xml_value))
                 goto out;
         }
     }
 
     /* Step 17(c): append XML namespace declarations. */
     {
         JSXMLArrayCursor cursor(&decls.array);
         while (JSObject *ns3 = (JSObject *) cursor.getNext()) {
             JS_ASSERT(IsDeclared(ns3));
 
-            if (!js_AppendLiteral(cb, " xmlns"))
+            if (!sb.append(" xmlns"))
                 goto out;
 
             /* 17(c)(ii): NULL means *undefined* here. */
             prefix = ns3->getNamePrefix();
             if (!prefix) {
                 prefix = GeneratePrefix(cx, ns3->getNameURI(), &ancdecls.array);
                 if (!prefix)
                     goto out;
                 ns3->setNamePrefix(prefix);
             }
 
             /* 17(c)(iii). */
             if (!prefix->empty()) {
-                if (!cb.append(':') || !AppendString(cb, prefix))
+                if (!sb.append(':') || !sb.append(prefix))
                     goto out;
             }
 
             /* 17(d-g). */
-            if (!AppendAttributeValue(cx, cb, ns3->getNameURI()))
+            if (!AppendAttributeValue(cx, sb, ns3->getNameURI()))
                 goto out;
         }
     }
 
     /* Step 18: handle point tags. */
     n = xml->xml_kids.length;
     if (n == 0) {
-        if (!js_AppendLiteral(cb, "/>"))
+        if (!sb.append("/>"))
             goto out;
     } else {
         /* Steps 19 through 25: handle element content, and open the end-tag. */
-        if (!cb.append('>'))
+        if (!sb.append('>'))
             goto out;
         {
             JSXML *kid;
             indentKids = n > 1 ||
                          (n == 1 &&
                           (kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML)) &&
                           kid->xml_class != JSXML_CLASS_TEXT);
         }
@@ -2698,50 +2688,50 @@ XMLToXMLString(JSContext *cx, JSXML *xml
         } else {
             nextIndentLevel = indentLevel & TO_SOURCE_FLAG;
         }
 
         {
             JSXMLArrayCursor cursor(&xml->xml_kids);
             while (JSXML *kid = (JSXML *) cursor.getNext()) {
                 if (pretty && indentKids) {
-                    if (!cb.append('\n'))
+                    if (!sb.append('\n'))
                         goto out;
                 }
 
                 JSString *kidstr = XMLToXMLString(cx, kid, &ancdecls.array, nextIndentLevel);
                 if (!kidstr)
                     goto out;
 
-                if (!AppendString(cb, kidstr))
+                if (!sb.append(kidstr))
                     goto out;
             }
         }
 
         if (pretty && indentKids) {
-            if (!cb.append('\n') ||
-                !cb.appendN(' ', indentLevel & ~TO_SOURCE_FLAG))
+            if (!sb.append('\n') ||
+                !sb.appendN(' ', indentLevel & ~TO_SOURCE_FLAG))
                 goto out;
         }
-        if (!js_AppendLiteral(cb, "</"))
+        if (!sb.append("</"))
             goto out;
 
         /* Step 26. */
         prefix = ns->getNamePrefix();
         if (prefix && !prefix->empty()) {
-            if (!AppendString(cb, prefix) || !cb.append(':'))
+            if (!sb.append(prefix) || !sb.append(':'))
                 goto out;
         }
 
         /* Step 27. */
-        if (!AppendString(cb, xml->name->getQNameLocalName()) || !cb.append('>'))
+        if (!sb.append(xml->name->getQNameLocalName()) || !sb.append('>'))
             goto out;
     }
 
-    str = js_NewStringFromCharBuffer(cx, cb);
+    str = sb.finishString();
 out:
     js_LeaveLocalRootScopeWithResult(cx, str);
     return str;
 }
 
 /* ECMA-357 10.2 */
 static JSString *
 ToXMLString(JSContext *cx, jsval v, uint32 toSourceFlag)
@@ -2756,29 +2746,29 @@ ToXMLString(JSContext *cx, jsval v, uint
                              JSVAL_IS_NULL(v) ? js_null_str : js_undefined_str);
         return NULL;
     }
 
     if (JSVAL_IS_BOOLEAN(v) || JSVAL_IS_NUMBER(v))
         return js_ValueToString(cx, Valueify(v));
 
     if (JSVAL_IS_STRING(v)) {
-        JSCharBuffer cb(cx);
-        return EscapeElementValue(cx, cb, JSVAL_TO_STRING(v), toSourceFlag);
+        StringBuffer sb(cx);
+        return EscapeElementValue(cx, sb, JSVAL_TO_STRING(v), toSourceFlag);
     }
 
     obj = JSVAL_TO_OBJECT(v);
     if (!obj->isXML()) {
         if (!DefaultValue(cx, obj, JSTYPE_STRING, Valueify(&v)))
             return NULL;
         str = js_ValueToString(cx, Valueify(v));
         if (!str)
             return NULL;
-        JSCharBuffer cb(cx);
-        return EscapeElementValue(cx, cb, str, toSourceFlag);
+        StringBuffer sb(cx);
+        return EscapeElementValue(cx, sb, str, toSourceFlag);
     }
 
     /* Handle non-element cases in this switch, returning from each case. */
     xml = (JSXML *) obj->getPrivate();
     return XMLToXMLString(cx, xml, NULL, toSourceFlag | 0);
 }
 
 static JSObject *
@@ -7298,21 +7288,21 @@ js_ToAttributeName(JSContext *cx, Value 
 
     qn = ToAttributeName(cx, Jsvalify(*vp));
     if (!qn)
         return JS_FALSE;
     vp->setObject(*qn);
     return JS_TRUE;
 }
 
-JSLinearString *
+JSFlatString *
 js_EscapeAttributeValue(JSContext *cx, JSString *str, JSBool quote)
 {
-    JSCharBuffer cb(cx);
-    return EscapeAttributeValue(cx, cb, str, quote);
+    StringBuffer sb(cx);
+    return EscapeAttributeValue(cx, sb, str, quote);
 }
 
 JSString *
 js_AddAttributePart(JSContext *cx, JSBool isName, JSString *str, JSString *str2)
 {
     size_t len = str->length();
     const jschar *chars = str->getChars(cx);
     if (!chars)
@@ -7340,21 +7330,21 @@ js_AddAttributePart(JSContext *cx, JSBoo
         js_strncpy(newchars, chars2, len2);
         newchars += len2;
         *newchars++ = '"';
     }
     *newchars = 0;
     return js_NewString(cx, newchars - newlen, newlen);
 }
 
-JSString *
+JSFlatString *
 js_EscapeElementValue(JSContext *cx, JSString *str)
 {
-    JSCharBuffer cb(cx);
-    return EscapeElementValue(cx, cb, str, 0);
+    StringBuffer sb(cx);
+    return EscapeElementValue(cx, sb, str, 0);
 }
 
 JSString *
 js_ValueToXMLString(JSContext *cx, const Value &v)
 {
     return ToXMLString(cx, Jsvalify(v), 0);
 }
 
@@ -7742,27 +7732,27 @@ js_NewXMLSpecialObject(JSContext *cx, JS
     }
     xml->xml_value = value;
     return obj;
 }
 
 JSString *
 js_MakeXMLCDATAString(JSContext *cx, JSString *str)
 {
-    JSCharBuffer cb(cx);
-    return MakeXMLCDATAString(cx, cb, str);
+    StringBuffer sb(cx);
+    return MakeXMLCDATAString(cx, sb, str);
 }
 
 JSString *
 js_MakeXMLCommentString(JSContext *cx, JSString *str)
 {
-    JSCharBuffer cb(cx);
-    return MakeXMLCommentString(cx, cb, str);
+    StringBuffer sb(cx);
+    return MakeXMLCommentString(cx, sb, str);
 }
 
 JSString *
 js_MakeXMLPIString(JSContext *cx, JSString *name, JSString *str)
 {
-    JSCharBuffer cb(cx);
-    return MakeXMLPIString(cx, cb, name, str);
+    StringBuffer sb(cx);
+    return MakeXMLPIString(cx, sb, name, str);
 }
 
 #endif /* JS_HAS_XML_SUPPORT */
--- a/js/src/jsxml.h
+++ b/js/src/jsxml.h
@@ -301,24 +301,24 @@ js_SetDefaultXMLNamespace(JSContext *cx,
  * NB: This function is an infallible predicate, it hides exceptions.
  */
 extern JSBool
 js_IsXMLName(JSContext *cx, jsval v);
 
 extern JSBool
 js_ToAttributeName(JSContext *cx, js::Value *vp);
 
-extern JSLinearString *
+extern JSFlatString *
 js_EscapeAttributeValue(JSContext *cx, JSString *str, JSBool quote);
 
 extern JSString *
 js_AddAttributePart(JSContext *cx, JSBool isName, JSString *str,
                     JSString *str2);
 
-extern JSString *
+extern JSFlatString *
 js_EscapeElementValue(JSContext *cx, JSString *str);
 
 extern JSString *
 js_ValueToXMLString(JSContext *cx, const js::Value &v);
 
 extern JSObject *
 js_ConstructXMLQNameObject(JSContext *cx, const js::Value & nsval,
                            const js::Value & lnval);
--- a/js/src/tests/ecma_5/RegExp/jstests.list
+++ b/js/src/tests/ecma_5/RegExp/jstests.list
@@ -1,5 +1,6 @@
 url-prefix ../../jsreftest.html?test=ecma_5/RegExp/
 script 7.8.5-01.js
 script 15.10.5-01.js
 script 15.10.7.5-01.js
 script empty-lookahead.js
+script regress-617935.js
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/RegExp/regress-617935.js
@@ -0,0 +1,36 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * Author: Christian Holler <decoder@own-hero.net>
+ */
+
+/* Length of 32 */
+var foo = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+
+/* Make len(foo) 32768 */
+for (i = 0; i < 10; ++i) {
+    foo += foo;
+}
+
+/* Add one "a" to cause overflow later */
+foo += "a";
+
+var bar = "bbbbbbbbbbbbbbbb";
+
+/* Make len(bar) 65536 */
+for (i = 0; i < 12; ++i) {
+    bar += bar;
+}
+
+/* 
+ * Resulting string should be 
+ * len(foo)*len(bar) = (2^10 * 32 + 1) * 65536 = 2147549184 
+ * which will be negative as jsint
+ */
+try {
+    foo.replace(/[a]/g, bar);
+} catch (e) {
+    reportCompare(e instanceof InternalError, true, "Internal error due to overallocation is ok.");
+}
+reportCompare(true, true, "No crash occurred.");