Bug 785551 - Do not allow inline JSString chars in the frontend; r=luke
authorTerrence Cole <terrence@mozilla.com>
Tue, 28 Aug 2012 10:28:19 -0700
changeset 111152 326ee1d5c9b0252cbde0bb99497aaa04e0c965bb
parent 111151 99a42cef003f2c8e7dc8dd9c625b8cceadf8d596
child 111153 cd63fc21ccfdad228952cfd34462814be4a2434b
push idunknown
push userunknown
push dateunknown
reviewersluke
bugs785551
milestone18.0a1
Bug 785551 - Do not allow inline JSString chars in the frontend; r=luke We need direct access to jschar*s in the frontend (and some other places) for speed, but if these pointers are inlined into JSStrings then a GC may invalidate them. This patch ensures that we uninline JSStrings before they enter any of these regions.
js/src/builtin/Eval.cpp
js/src/ctypes/CTypes.cpp
js/src/ion/CodeGenerator.cpp
js/src/ion/VMFunctions.cpp
js/src/ion/VMFunctions.h
js/src/jsapi-tests/testVersion.cpp
js/src/jsapi.cpp
js/src/jsatom.cpp
js/src/jsclone.cpp
js/src/jscntxt.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsexn.cpp
js/src/jsfun.cpp
js/src/jsnum.cpp
js/src/jsnum.h
js/src/json.cpp
js/src/jsonparser.h
js/src/jsprvtd.h
js/src/jsreflect.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsstr.cpp
js/src/jsstr.h
js/src/jsxml.cpp
js/src/vm/Debugger.cpp
js/src/vm/String-inl.h
js/src/vm/String.cpp
js/src/vm/String.h
js/src/vm/StringBuffer.cpp
js/src/vm/StringBuffer.h
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -198,23 +198,22 @@ EvalKernel(JSContext *cx, const CallArgs
 
         // Use the global as 'this', modulo outerization.
         JSObject *thisobj = JSObject::thisObject(cx, scopeobj);
         if (!thisobj)
             return false;
         thisv = ObjectValue(*thisobj);
     }
 
-    Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
-    if (!linearStr)
+    Rooted<JSStableString*> stableStr(cx, str->ensureStable(cx));
+    if (!stableStr)
         return false;
-    const jschar *chars = linearStr->chars();
-    size_t length = linearStr->length();
 
-    SkipRoot skip(cx, &chars);
+    const jschar *chars = stableStr->chars();
+    size_t length = stableStr->length();
 
     // If the eval string starts with '(' or '[' and ends with ')' or ']', it may be JSON.
     // Try the JSON parser first because it's much faster.  If the eval string
     // isn't JSON, JSON parsing will probably fail quickly, so little time
     // will be lost.
     //
     // Don't use the JSON parser if the caller is strict mode code, because in
     // strict mode object literals must not have repeated properties, and the
@@ -250,34 +249,34 @@ EvalKernel(JSContext *cx, const CallArgs
         }
     }
 
     EvalScriptGuard esg(cx);
 
     JSPrincipals *principals = PrincipalsForCompiledCode(args, cx);
 
     if (evalType == DIRECT_EVAL && caller->isNonEvalFunctionFrame())
-        esg.lookupInEvalCache(linearStr, caller->fun(), staticLevel);
+        esg.lookupInEvalCache(stableStr, caller->fun(), staticLevel);
 
     if (!esg.foundScript()) {
         unsigned lineno;
         const char *filename;
         JSPrincipals *originPrincipals;
         CurrentScriptFileLineOrigin(cx, &filename, &lineno, &originPrincipals,
                                     evalType == DIRECT_EVAL ? CALLED_FROM_JSOP_EVAL
                                                             : NOT_CALLED_FROM_JSOP_EVAL);
 
         CompileOptions options(cx);
         options.setFileAndLine(filename, lineno)
                .setCompileAndGo(true)
                .setNoScriptRval(false)
                .setPrincipals(principals)
                .setOriginPrincipals(originPrincipals);
         JSScript *compiled = frontend::CompileScript(cx, scopeobj, caller, options,
-                                                     chars, length, linearStr,
+                                                     chars, length, stableStr,
                                                      staticLevel);
         if (!compiled)
             return false;
 
         esg.setNewScript(compiled);
     }
 
     return ExecuteKernel(cx, esg.script(), *scopeobj, thisv, ExecuteType(evalType),
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -4548,17 +4548,20 @@ StructType::DefineInternal(JSContext* cx
     structAlign = 0;
 
     for (uint32_t i = 0; i < len; ++i) {
       js::AutoValueRooter item(cx);
       if (!JS_GetElement(cx, fieldsObj, i, item.jsval_addr()))
         return JS_FALSE;
 
       RootedObject fieldType(cx, NULL);
-      JSFlatString* name = ExtractStructField(cx, item.jsval_value(), fieldType.address());
+      JSFlatString* flat = ExtractStructField(cx, item.jsval_value(), fieldType.address());
+      if (!flat)
+        return JS_FALSE;
+      Rooted<JSStableString*> name(cx, flat->ensureStable(cx));
       if (!name)
         return JS_FALSE;
       fieldRootsArray[i] = OBJECT_TO_JSVAL(fieldType);
 
       // Make sure each field name is unique, and add it to the hash.
       FieldInfoHash::AddPtr entryPtr = fields->lookupForAdd(name);
       if (entryPtr) {
         JS_ReportError(cx, "struct fields must have unique names");
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -192,17 +192,17 @@ CodeGenerator::visitPolyInlineDispatch(L
         }
     }
     return true;
 }
 
 bool
 CodeGenerator::visitIntToString(LIntToString *lir)
 {
-    typedef JSFixedString *(*pf)(JSContext *, int);
+    typedef JSFlatString *(*pf)(JSContext *, int);
     static const VMFunction IntToStringInfo = FunctionInfo<pf>(Int32ToString);
 
     pushArg(ToRegister(lir->input()));
     return callVM(IntToStringInfo, lir);
 }
 
 bool
 CodeGenerator::visitRegExp(LRegExp *lir)
@@ -2042,17 +2042,17 @@ CodeGenerator::visitCharCodeAt(LCharCode
 }
 
 bool
 CodeGenerator::visitFromCharCode(LFromCharCode *lir)
 {
     Register code = ToRegister(lir->code());
     Register output = ToRegister(lir->output());
 
-    typedef JSFixedString *(*pf)(JSContext *, int32_t);
+    typedef JSFlatString *(*pf)(JSContext *, int32_t);
     static const VMFunction Info = FunctionInfo<pf>(ion::StringFromCharCode);
     OutOfLineCode *ool = oolCallVM(Info, lir, (ArgList(), code), StoreRegisterTo(output));
     if (!ool)
         return false;
 
     // OOL path if code >= UNIT_STATIC_LIMIT.
     masm.branch32(Assembler::AboveOrEqual, code, Imm32(StaticStrings::UNIT_STATIC_LIMIT),
                   ool->entry());
--- a/js/src/ion/VMFunctions.cpp
+++ b/js/src/ion/VMFunctions.cpp
@@ -325,17 +325,17 @@ ArrayShiftDense(JSContext *cx, HandleObj
     // If the result is |undefined|, the array was probably empty and we
     // have to monitor the return value.
     rval.set(argv[0]);
     if (rval.isUndefined())
         types::TypeScript::Monitor(cx, rval);
     return true;
 }
 
-JSFixedString *
+JSFlatString *
 StringFromCharCode(JSContext *cx, int32_t code)
 {
     jschar c = jschar(code);
 
     if (StaticStrings::hasUnit(c))
         return cx->runtime->staticStrings.getUnit(c);
 
     return js_NewStringCopyN(cx, &c, 1);
--- a/js/src/ion/VMFunctions.h
+++ b/js/src/ion/VMFunctions.h
@@ -169,17 +169,17 @@ struct VMFunction
         JS_ASSERT(returnType == Type_Bool || returnType == Type_Object);
     }
 };
 
 template <class> struct TypeToDataType { /* Unexpected return type for a VMFunction. */ };
 template <> struct TypeToDataType<bool> { static const DataType result = Type_Bool; };
 template <> struct TypeToDataType<JSObject *> { static const DataType result = Type_Object; };
 template <> struct TypeToDataType<JSString *> { static const DataType result = Type_Object; };
-template <> struct TypeToDataType<JSFixedString *> { static const DataType result = Type_Object; };
+template <> struct TypeToDataType<JSFlatString *> { static const DataType result = Type_Object; };
 template <> struct TypeToDataType<HandleObject> { static const DataType result = Type_Handle; };
 template <> struct TypeToDataType<HandleString> { static const DataType result = Type_Handle; };
 template <> struct TypeToDataType<HandlePropertyName> { static const DataType result = Type_Handle; };
 template <> struct TypeToDataType<HandleFunction> { static const DataType result = Type_Handle; };
 template <> struct TypeToDataType<HandleScript> { static const DataType result = Type_Handle; };
 template <> struct TypeToDataType<HandleValue> { static const DataType result = Type_Handle; };
 template <> struct TypeToDataType<MutableHandleValue> { static const DataType result = Type_Handle; };
 
@@ -425,17 +425,17 @@ bool IteratorMore(JSContext *cx, HandleO
 // Allocation functions for JSOP_NEWARRAY and JSOP_NEWOBJECT
 JSObject *NewInitArray(JSContext *cx, uint32_t count, types::TypeObject *type);
 JSObject *NewInitObject(JSContext *cx, HandleObject templateObject);
 
 bool ArrayPopDense(JSContext *cx, HandleObject obj, MutableHandleValue rval);
 bool ArrayPushDense(JSContext *cx, HandleObject obj, HandleValue v, uint32_t *length);
 bool ArrayShiftDense(JSContext *cx, HandleObject obj, MutableHandleValue rval);
 
-JSFixedString *StringFromCharCode(JSContext *cx, int32_t code);
+JSFlatString *StringFromCharCode(JSContext *cx, int32_t code);
 
 bool SetProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, HandleValue value,
                  bool strict, bool isSetName);
 
 bool InterruptCheck(JSContext *cx);
 
 HeapSlot *NewSlots(JSRuntime *rt, unsigned nslots);
 JSObject *NewCallObject(JSContext *cx, HandleShape shape, HandleTypeObject type, HeapSlot *slots);
--- a/js/src/jsapi-tests/testVersion.cpp
+++ b/js/src/jsapi-tests/testVersion.cpp
@@ -157,21 +157,19 @@ OverrideVersion15(JSContext *cx, unsigne
 }
 
 JSBool
 EvalScriptVersion16(JSContext *cx, unsigned argc, jsval *vp)
 {
     JS_ASSERT(argc == 1);
     jsval *argv = JS_ARGV(cx, vp);
     JS_ASSERT(JSVAL_IS_STRING(argv[0]));
-    JSString *str = JSVAL_TO_STRING(argv[0]);
-    const jschar *chars = str->getChars(cx);
-    JS_ASSERT(chars);
-    size_t len = str->length();
-    return callbackData->evalVersion(chars, len, JSVERSION_1_6);
+    JSStableString *str = JSVAL_TO_STRING(argv[0])->ensureStable(cx);
+    JS_ASSERT(str);
+    return callbackData->evalVersion(str->chars(), str->length(), JSVERSION_1_6);
 }
 
 JSBool
 CaptureVersion(JSContext *cx, unsigned argc, jsval *vp)
 {
     callbackData->captured = JS_GetVersion(cx);
     return true;
 }
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -341,20 +341,20 @@ JS_ConvertArgumentsVA(JSContext *cx, uns
             break;
           case 'S':
           case 'W':
             str = ToString(cx, *sp);
             if (!str)
                 return JS_FALSE;
             *sp = STRING_TO_JSVAL(str);
             if (c == 'W') {
-                JSFixedString *fixed = str->ensureFixed(cx);
-                if (!fixed)
+                JSStableString *stable = str->ensureStable(cx);
+                if (!stable)
                     return JS_FALSE;
-                *va_arg(ap, const jschar **) = fixed->chars();
+                *va_arg(ap, const jschar **) = stable->chars();
             } else {
                 *va_arg(ap, JSString **) = str;
             }
             break;
           case 'o':
             if (!js_ValueToObjectOrNull(cx, *sp, &obj))
                 return JS_FALSE;
             *sp = OBJECT_TO_JSVAL(obj);
@@ -4650,17 +4650,17 @@ JS_NextProperty(JSContext *cx, JSObject 
         STATIC_ASSUME(i <= ida->length);
         if (i == 0) {
             *idp = JSID_VOID;
         } else {
             *idp = ida->vector[--i];
             iterobj->setSlot(JSSLOT_ITER_INDEX, Int32Value(i));
         }
     }
-    return JS_TRUE;
+    return true;
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ArrayIterator(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     Rooted<Value> target(cx, args.thisv());
     AssertHeapIsIdle(cx);
@@ -6089,65 +6089,90 @@ JS_GetStringLength(JSString *str)
 }
 
 JS_PUBLIC_API(const jschar *)
 JS_GetStringCharsZ(JSContext *cx, JSString *str)
 {
     AssertHeapIsIdleOrStringIsFlat(cx, str);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, str);
-    return str->getCharsZ(cx);
+    JSStableString *stable = str->ensureStable(cx);
+    if (!stable)
+        return NULL;
+    return stable->chars();
 }
 
 JS_PUBLIC_API(const jschar *)
 JS_GetStringCharsZAndLength(JSContext *cx, JSString *str, size_t *plength)
 {
+    JS_ASSERT(plength);
     AssertHeapIsIdleOrStringIsFlat(cx, str);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, str);
-    *plength = str->length();
-    return str->getCharsZ(cx);
+    JSStableString *stable = str->ensureStable(cx);
+    if (!stable)
+        return NULL;
+    *plength = stable->length();
+    return stable->chars();
 }
 
 JS_PUBLIC_API(const jschar *)
 JS_GetStringCharsAndLength(JSContext *cx, JSString *str, size_t *plength)
 {
+    JS_ASSERT(plength);
     AssertHeapIsIdleOrStringIsFlat(cx, str);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, str);
-    *plength = str->length();
-    return str->getChars(cx);
+    JSStableString *stable = str->ensureStable(cx);
+    if (!stable)
+        return NULL;
+    *plength = stable->length();
+    return stable->chars();
 }
 
 JS_PUBLIC_API(const jschar *)
 JS_GetInternedStringChars(JSString *str)
 {
-    return str->asAtom().chars();
+    JS_ASSERT(str->isAtom());
+    JSStableString *stable = str->ensureStable(NULL);
+    if (!stable)
+        return NULL;
+    return stable->chars();
 }
 
 JS_PUBLIC_API(const jschar *)
 JS_GetInternedStringCharsAndLength(JSString *str, size_t *plength)
 {
-    JSAtom &atom = str->asAtom();
-    *plength = atom.length();
-    return atom.chars();
+    JS_ASSERT(str->isAtom());
+    JS_ASSERT(plength);
+    JSStableString *stable = str->ensureStable(NULL);
+    if (!stable)
+        return NULL;
+    *plength = stable->length();
+    return stable->chars();
 }
 
 extern JS_PUBLIC_API(JSFlatString *)
 JS_FlattenString(JSContext *cx, JSString *str)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, str);
-    return str->getCharsZ(cx) ? (JSFlatString *)str : NULL;
+    JSFlatString *flat = str->ensureFlat(cx);
+    if (!flat)
+        return NULL;
+    return flat;
 }
 
 extern JS_PUBLIC_API(const jschar *)
 JS_GetFlatStringChars(JSFlatString *str)
 {
+    JSStableString *stable = str->ensureStable(NULL);
+    if (!stable)
+        return NULL;
     return str->chars();
 }
 
 JS_PUBLIC_API(JSBool)
 JS_CompareStrings(JSContext *cx, JSString *str1, JSString *str2, int32_t *result)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -258,28 +258,26 @@ AtomizeInline(JSContext *cx, const jscha
 
     AutoEnterAtomsCompartment ac(cx);
 
     SkipRoot skip(cx, &chars);
 
     /* Workaround for hash values in AddPtr being inadvertently poisoned. */
     SkipRoot skip2(cx, &p);
 
-    JSFixedString *key;
+    JSFlatString *key;
     if (ocb == TakeCharOwnership) {
         key = js_NewString(cx, const_cast<jschar *>(chars), length);
-        if (!key)
-            return NULL;
         *pchars = NULL; /* Called should not free *pchars. */
     } else {
         JS_ASSERT(ocb == CopyChars);
         key = js_NewStringCopyN(cx, chars, length);
-        if (!key)
-            return NULL;
     }
+    if (!key)
+        return NULL;
 
     /*
      * We have to relookup the key as the last ditch GC invoked from the
      * string allocation or OOM handling unlocks the atomsCompartment.
      *
      * N.B. this avoids recomputing the hash but still has a potential
      * (# collisions * # chars) comparison cost in the case of a hash
      * collision!
@@ -305,21 +303,22 @@ js::AtomizeString(JSContext *cx, JSStrin
         AtomSet::Ptr p = cx->runtime->atoms.lookup(AtomHasher::Lookup(&atom));
         JS_ASSERT(p); /* Non-static atom must exist in atom state set. */
         JS_ASSERT(p->asPtr() == &atom);
         JS_ASSERT(ib == InternAtom);
         p->setTagged(bool(ib));
         return &atom;
     }
 
-    size_t length = str->length();
-    const jschar *chars = str->getChars(cx);
-    if (!chars)
+    JSStableString *stable = str->ensureStable(cx);
+    if (!stable)
         return NULL;
 
+    const jschar *chars = stable->chars();
+    size_t length = stable->length();
     JS_ASSERT(length <= JSString::MAX_LENGTH);
     return AtomizeInline(cx, &chars, length, ib);
 }
 
 JSAtom *
 js::Atomize(JSContext *cx, const char *bytes, size_t length, InternBehavior ib, FlationCoding fc)
 {
     CHECK_REQUEST(cx);
--- a/js/src/jsclone.cpp
+++ b/js/src/jsclone.cpp
@@ -798,21 +798,22 @@ JSStructuredCloneReader::startRead(Value
         if (tag2 != SCTAG_STRING) {
             JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
                                  "regexp");
             return false;
         }
         JSString *str = readString(nchars);
         if (!str)
             return false;
-        size_t length = str->length();
-        const jschar *chars = str->getChars(context());
-        if (!chars)
+        JSStableString *stable = str->ensureStable(context());
+        if (!stable)
             return false;
 
+        size_t length = stable->length();
+        const jschar *chars = stable->chars();
         RegExpObject *reobj = RegExpObject::createNoStatics(context(), chars, length, flags, NULL);
         if (!reobj)
             return false;
         vp->setObject(*reobj);
         break;
       }
 
       case SCTAG_ARRAY_OBJECT:
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -173,25 +173,26 @@ struct ConservativeGCData
     bool hasStackToScan() const {
         return !!nativeStackTop;
     }
 };
 
 class SourceDataCache
 {
     typedef HashMap<ScriptSource *,
-                    JSFixedString *,
+                    JSStableString *,
                     DefaultHasher<ScriptSource *>,
                     SystemAllocPolicy> Map;
-     Map *map_;
-   public:
+    Map *map_;
+
+  public:
     SourceDataCache() : map_(NULL) {}
-    JSFixedString *lookup(ScriptSource *ss);
-    void put(ScriptSource *ss, JSFixedString *);
-     void purge();
+    JSStableString *lookup(ScriptSource *ss);
+    void put(ScriptSource *ss, JSStableString *);
+    void purge();
 };
 
 struct EvalCacheLookup
 {
     JSLinearString *str;
     JSFunction *caller;
     unsigned staticLevel;
     JSVersion version;
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -279,21 +279,20 @@ JSCompartment::wrap(JSContext *cx, Value
                 } while (obj && obj->isCrossCompartmentWrapper());
             }
         }
         return true;
     }
 
     if (vp->isString()) {
         RootedValue orig(cx, *vp);
-        JSString *str = vp->toString();
-        const jschar *chars = str->getChars(cx);
-        if (!chars)
+        JSStableString *str = vp->toString()->ensureStable(cx);
+        if (!str)
             return false;
-        JSString *wrapped = js_NewStringCopyN(cx, chars, str->length());
+        JSString *wrapped = js_NewStringCopyN(cx, str->chars(), str->length());
         if (!wrapped)
             return false;
         vp->setString(wrapped);
         return crossCompartmentWrappers.put(orig, *vp);
     }
 
     RootedObject obj(cx, &vp->toObject());
 
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -31,31 +31,31 @@ namespace ion {
  * helps date-format-xparb.js.  It also avoids skewing the results for
  * v8-splay.js when measured by the SunSpider harness, where the splay tree
  * initialization (which includes many repeated double-to-string conversions)
  * is erroneously included in the measurement; see bug 562553.
  */
 class DtoaCache {
     double        d;
     int         base;
-    JSFixedString *s;      // if s==NULL, d and base are not valid
+    JSFlatString *s;      // if s==NULL, d and base are not valid
+
   public:
     DtoaCache() : s(NULL) {}
     void purge() { s = NULL; }
 
-    JSFixedString *lookup(int base, double d) {
+    JSFlatString *lookup(int base, double d) {
         return this->s && base == this->base && d == this->d ? this->s : NULL;
     }
 
-    void cache(int base, double d, JSFixedString *s) {
+    void cache(int base, double d, JSFlatString *s) {
         this->base = base;
         this->d = d;
         this->s = s;
     }
-
 };
 
 /* If HashNumber grows, need to change WrapperHasher. */
 JS_STATIC_ASSERT(sizeof(HashNumber) == 4);
 
 struct CrossCompartmentKey
 {
     enum Kind {
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -1130,18 +1130,18 @@ js_ReportUncaughtException(JSContext *cx
 
         reportp = &report;
         PodZero(&report);
         report.filename = filename.ptr();
         report.lineno = (unsigned) lineno;
         report.exnType = int16_t(JSEXN_NONE);
         report.column = (unsigned) column;
         if (str) {
-            if (JSFixedString *fixed = str->ensureFixed(cx))
-                report.ucmessage = fixed->chars();
+            if (JSStableString *stable = str->ensureStable(cx))
+                report.ucmessage = stable->chars();
         }
     }
 
     JSAutoByteString bytesStorage;
     const char *bytes = NULL;
     if (str)
         bytes = bytesStorage.encode(cx, str);
     if (!bytes)
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -630,24 +630,25 @@ js::FunctionToString(JSContext *cx, Hand
     bool haveSource = fun->isInterpreted() && !fun->isSelfHostedBuiltin();
     if (haveSource && !fun->script()->scriptSource()->hasSourceData() &&
         !fun->script()->loadSource(cx, &haveSource))
     {
         return NULL;
     }
     if (haveSource) {
         RootedScript script(cx, fun->script());
-        RootedString src(cx, fun->script()->sourceData(cx));
+        RootedString srcStr(cx, fun->script()->sourceData(cx));
+        if (!srcStr)
+            return NULL;
+        Rooted<JSStableString *> src(cx, srcStr->ensureStable(cx));
         if (!src)
             return NULL;
-        const jschar *chars = src->getChars(cx);
-        if (!chars)
-            return NULL;
+
+        const jschar *chars = src->chars();
         bool exprBody = fun->flags & JSFUN_EXPR_CLOSURE;
-
         // The source data for functions created by calling the Function
         // constructor is only the function's body.
         bool funCon = script->sourceStart == 0 && script->scriptSource()->argumentsNotIncluded();
 
         // Functions created with the constructor should not be using the
         // expression body extension.
         JS_ASSERT_IF(funCon, !exprBody);
         JS_ASSERT_IF(!funCon, src->length() > 0 && chars[0] == '(');
@@ -1362,29 +1363,29 @@ Function(JSContext *cx, unsigned argc, V
         JS_ASSERT(str->asAtom().asPropertyName() == formals[i]);
     }
 #endif
 
     JS::Anchor<JSString *> strAnchor(NULL);
     const jschar *chars;
     size_t length;
 
-    SkipRoot skip(cx, &chars);
-
-    if (args.length()) {
-        JSString *str = ToString(cx, args[args.length() - 1]);
-        if (!str)
-            return false;
-        strAnchor.set(str);
-        chars = str->getChars(cx);
-        length = str->length();
-    } else {
-        chars = cx->runtime->emptyString->chars();
-        length = 0;
-    }
+    JSString *str;
+    if (!args.length())
+        str = cx->runtime->emptyString;
+    else
+        str = ToString(cx, args[args.length() - 1]);
+    if (!str)
+        return false;
+    JSStableString *stable = str->ensureStable(cx);
+    if (!stable)
+        return false;
+    strAnchor.set(str);
+    chars = stable->chars();
+    length = stable->length();
 
     /*
      * NB: (new Function) is not lexically closed by its caller, it's just an
      * anonymous function in the top-level scope that its constructor inhabits.
      * Thus 'var x = 42; f = new Function("return x"); print(f())' prints 42,
      * and so would a call to f from another top-level's script or function.
      */
     RootedFunction fun(cx, js_NewFunction(cx, NULL, NULL, 0, JSFUN_LAMBDA | JSFUN_INTERPRETED,
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -512,31 +512,31 @@ ToCStringBuf::ToCStringBuf() :dbuf(NULL)
 }
 
 ToCStringBuf::~ToCStringBuf()
 {
     if (dbuf)
         js_free(dbuf);
 }
 
-JSFixedString *
+JSFlatString *
 js::Int32ToString(JSContext *cx, int32_t si)
 {
     uint32_t ui;
     if (si >= 0) {
         if (StaticStrings::hasInt(si))
             return cx->runtime->staticStrings.getInt(si);
         ui = si;
     } else {
         ui = uint32_t(-si);
         JS_ASSERT_IF(si == INT32_MIN, ui == uint32_t(INT32_MAX) + 1);
     }
 
     JSCompartment *c = cx->compartment;
-    if (JSFixedString *str = c->dtoaCache.lookup(10, si))
+    if (JSFlatString *str = c->dtoaCache.lookup(10, si))
         return str;
 
     JSShortString *str = js_NewGCShortString(cx);
     if (!str)
         return NULL;
 
     jschar buffer[JSShortString::MAX_SHORT_LENGTH + 1];
     RangedPtr<jschar> end(buffer + JSShortString::MAX_SHORT_LENGTH,
@@ -1263,45 +1263,45 @@ js_NumberToStringWithBase(JSContext *cx,
             return NULL;
         }
         JS_ASSERT_IF(base == 10,
                      !cbuf.dbuf && numStr >= cbuf.sbuf && numStr < cbuf.sbuf + cbuf.sbufSize);
         JS_ASSERT_IF(base != 10,
                      cbuf.dbuf && cbuf.dbuf == numStr);
     }
 
-    JSFixedString *s = js_NewStringCopyZ(cx, numStr);
+    JSFlatString *s = js_NewStringCopyZ(cx, numStr);
     c->dtoaCache.cache(base, d, s);
     return s;
 }
 
 JSString *
 js_NumberToString(JSContext *cx, double d)
 {
     return js_NumberToStringWithBase(cx, d, 10);
 }
 
 namespace js {
 
-JSFixedString *
+JSFlatString *
 NumberToString(JSContext *cx, double d)
 {
     if (JSString *str = js_NumberToStringWithBase(cx, d, 10))
-        return &str->asFixed();
+        return &str->asFlat();
     return NULL;
 }
 
-JSFixedString *
+JSFlatString *
 IndexToString(JSContext *cx, uint32_t index)
 {
     if (StaticStrings::hasUint(index))
         return cx->runtime->staticStrings.getUint(index);
 
     JSCompartment *c = cx->compartment;
-    if (JSFixedString *str = c->dtoaCache.lookup(10, index))
+    if (JSFlatString *str = c->dtoaCache.lookup(10, index))
         return str;
 
     JSShortString *str = js_NewGCShortString(cx);
     if (!str)
         return NULL;
 
     jschar buffer[JSShortString::MAX_SHORT_LENGTH + 1];
     RangedPtr<jschar> end(buffer + JSShortString::MAX_SHORT_LENGTH,
--- a/js/src/jsnum.h
+++ b/js/src/jsnum.h
@@ -37,43 +37,42 @@ js_InitNumberClass(JSContext *cx, JSObje
  * String constants for global function names, used in jsapi.c and jsnum.c.
  */
 extern const char js_isNaN_str[];
 extern const char js_isFinite_str[];
 extern const char js_parseFloat_str[];
 extern const char js_parseInt_str[];
 
 class JSString;
-class JSFixedString;
 
 /*
  * 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_NumberToString(JSContext *cx, double d);
 
 namespace js {
 
-extern JSFixedString *
+extern JSFlatString *
 Int32ToString(JSContext *cx, int32_t i);
 
 /*
  * Convert an integer or double (contained in the given value) to a string and
  * append to the given buffer.
  */
 extern bool JS_FASTCALL
 NumberValueToStringBuffer(JSContext *cx, const Value &v, StringBuffer &sb);
 
 /* Same as js_NumberToString, different signature. */
-extern JSFixedString *
+extern JSFlatString *
 NumberToString(JSContext *cx, double d);
 
-extern JSFixedString *
+extern JSFlatString *
 IndexToString(JSContext *cx, uint32_t index);
 
 /*
  * Usually a small amount of static storage is enough, but sometimes we need
  * to dynamically allocate much more.  This struct encapsulates that.
  * Dynamically allocated memory will be freed when the object is destroyed.
  */
 struct ToCStringBuf
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -54,33 +54,30 @@ Class js::JSONClass = {
 
 /* ES5 15.12.2. */
 JSBool
 js_json_parse(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     /* Step 1. */
-    JSLinearString *linear;
-    if (argc >= 1) {
-        JSString *str = ToString(cx, args[0]);
-        if (!str)
-            return false;
-        linear = str->ensureLinear(cx);
-        if (!linear)
-            return false;
-    } else {
-        linear = cx->names().undefined;
-    }
-    JS::Anchor<JSString *> anchor(linear);
+    JSString *str = (argc >= 1) ? ToString(cx, args[0]) : cx->names().undefined;
+    if (!str)
+        return false;
+
+    JSStableString *stable = str->ensureStable(cx);
+    if (!stable)
+        return false;
+
+    JS::Anchor<JSString *> anchor(stable);
 
     RootedValue reviver(cx, (argc >= 2) ? args[1] : UndefinedValue());
 
     /* Steps 2-5. */
-    return ParseJSONWithReviver(cx, linear->chars(), linear->length(), reviver, args.rval());
+    return ParseJSONWithReviver(cx, stable->chars(), stable->length(), reviver, args.rval());
 }
 
 /* ES5 15.12.3. */
 JSBool
 js_json_stringify(JSContext *cx, unsigned argc, Value *vp)
 {
     RootedObject replacer(cx, (argc >= 2 && vp[3].isObject())
                               ? &vp[3].toObject()
--- a/js/src/jsonparser.h
+++ b/js/src/jsonparser.h
@@ -25,19 +25,16 @@ class JSONParser
 
   private:
     /* Data members */
 
     JSContext * const cx;
     mozilla::RangedPtr<const jschar> current;
     const mozilla::RangedPtr<const jschar> end;
 
-    /* For current/end as cursors into a string. */
-    js::SkipRoot root;
-
     js::Value v;
 
     const ParsingMode parsingMode;
     const ErrorHandling errorHandling;
 
     enum Token { String, Number, True, False, Null,
                  ArrayOpen, ArrayClose,
                  ObjectOpen, ObjectClose,
@@ -59,17 +56,16 @@ class JSONParser
      * use strict JSON parsing.
      */
     JSONParser(JSContext *cx, const jschar *data, size_t length,
                ParsingMode parsingMode = StrictJSON,
                ErrorHandling errorHandling = RaiseError)
       : cx(cx),
         current(data, length),
         end(data + length, data, length),
-        root(cx, thisDuringConstruction()),
         parsingMode(parsingMode),
         errorHandling(errorHandling)
 #ifdef DEBUG
       , lastToken(Error)
 #endif
     {
         JS_ASSERT(current <= end);
     }
--- a/js/src/jsprvtd.h
+++ b/js/src/jsprvtd.h
@@ -74,17 +74,16 @@ typedef struct JSXML                JSXM
 #ifdef __cplusplus
 
 extern "C++" {
 
 class JSDependentString;
 class JSExtensibleString;
 class JSExternalString;
 class JSLinearString;
-class JSFixedString;
 class JSRope;
 class JSAtom;
 class JSWrapper;
 
 namespace js {
 
 struct ArgumentsData;
 struct Class;
--- a/js/src/jsreflect.cpp
+++ b/js/src/jsreflect.cpp
@@ -3473,21 +3473,22 @@ reflect_parse(JSContext *cx, uint32_t ar
         }
     }
 
     /* Extract the builder methods first to report errors before parsing. */
     ASTSerializer serialize(cx, loc, filename, lineno);
     if (!serialize.init(builder))
         return JS_FALSE;
 
-    size_t length = src->length();
-    const jschar *chars = src->getChars(cx);
-    if (!chars)
+    JSStableString *stable = src->ensureStable(cx);
+    if (!stable)
         return JS_FALSE;
 
+    const jschar *chars = stable->chars();
+    size_t length = stable->length();
     CompileOptions options(cx);
     options.setFileAndLine(filename, lineno);
     Parser parser(cx, options, chars, length, /* foldConstants = */ false);
     if (!parser.init())
         return JS_FALSE;
 
     serialize.setParser(&parser);
 
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1042,35 +1042,35 @@ JSScript::loadSource(JSContext *cx, bool
     if (!src)
         return true;
     ScriptSource *ss = scriptSource();
     ss->setSource(src, length);
     *worked = true;
     return true;
 }
 
-JSFixedString *
+JSFlatString *
 JSScript::sourceData(JSContext *cx)
 {
     JS_ASSERT(scriptSource_->hasSourceData());
     return scriptSource_->substring(cx, sourceStart, sourceEnd);
 }
 
-JSFixedString *
+JSStableString *
 SourceDataCache::lookup(ScriptSource *ss)
 {
     if (!map_)
         return NULL;
     if (Map::Ptr p = map_->lookup(ss))
         return p->value;
     return NULL;
 }
 
 void
-SourceDataCache::put(ScriptSource *ss, JSFixedString *str)
+SourceDataCache::put(ScriptSource *ss, JSStableString *str)
 {
     if (!map_) {
         map_ = js_new<Map>();
         if (!map_)
             return;
         if (!map_->init()) {
             purge();
             return;
@@ -1082,23 +1082,23 @@ SourceDataCache::put(ScriptSource *ss, J
 
 void
 SourceDataCache::purge()
 {
     js_delete(map_);
     map_ = NULL;
 }
 
-JSFixedString *
+JSFlatString *
 ScriptSource::substring(JSContext *cx, uint32_t start, uint32_t stop)
 {
     JS_ASSERT(ready());
     const jschar *chars;
 #if USE_ZLIB
-    Rooted<JSFixedString *> cached(cx, NULL);
+    Rooted<JSStableString *> cached(cx, NULL);
     if (compressed()) {
         cached = cx->runtime->sourceDataCache.lookup(this);
         if (!cached) {
             const size_t nbytes = sizeof(jschar) * (length_ + 1);
             jschar *decompressed = static_cast<jschar *>(cx->malloc_(nbytes));
             if (!decompressed)
                 return NULL;
             if (!DecompressString(data.compressed, compressedLength_,
@@ -1110,17 +1110,17 @@ ScriptSource::substring(JSContext *cx, u
             decompressed[length_] = 0;
             cached = js_NewString(cx, decompressed, length_);
             if (!cached) {
                 js_free(decompressed);
                 return NULL;
             }
             cx->runtime->sourceDataCache.put(this, cached);
         }
-        chars = cached->getChars(cx);
+        chars = cached->chars();
         JS_ASSERT(chars);
     } else {
         chars = data.source;
     }
 #else
     chars = data.source;
 #endif
     return js_NewStringCopyN(cx, chars + start, stop - start);
@@ -1136,17 +1136,17 @@ ScriptSource::setSourceCopy(JSContext *c
     if (!data.compressed)
         return false;
     length_ = length;
     argumentsNotIncluded_ = argumentsNotIncluded;
 
 #ifdef JS_THREADSAFE
     if (tok) {
 #ifdef DEBUG
-        ready_ = false;  
+        ready_ = false;
 #endif
         tok->ss = this;
         tok->chars = src;
         cx->runtime->sourceCompressorThread.compress(tok);
     } else
 #endif
     {
         PodCopy(data.source, src, length_);
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -554,17 +554,17 @@ struct JSScript : public js::gc::Cell
 
     /*
      * Original compiled function for the script, if it has a function.
      * NULL for global and eval scripts.
      */
     JSFunction *function() const { return function_; }
     void setFunction(JSFunction *fun);
 
-    JSFixedString *sourceData(JSContext *cx);
+    JSFlatString *sourceData(JSContext *cx);
 
     bool loadSource(JSContext *cx, bool *worked);
 
     js::ScriptSource *scriptSource() {
         return scriptSource_;
     }
 
     void setScriptSource(JSContext *cx, js::ScriptSource *ss);
@@ -1028,17 +1028,17 @@ struct ScriptSource
     uint32_t length() const {
         JS_ASSERT(hasSourceData());
         return length_;
     }
     bool argumentsNotIncluded() const {
         JS_ASSERT(hasSourceData());
         return argumentsNotIncluded_;
     }
-    JSFixedString *substring(JSContext *cx, uint32_t start, uint32_t stop);
+    JSFlatString *substring(JSContext *cx, uint32_t start, uint32_t stop);
     size_t sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf);
 
     // XDR handling
     template <XDRMode mode>
     bool performXDR(XDRState<mode> *xdr);
 
     // Source maps
     bool setSourceMap(JSContext *cx, jschar *sourceMapURL, const char *filename);
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2433,21 +2433,21 @@ js::str_replace(JSContext *cx, unsigned 
     } else {
         rdata.lambda = NULL;
         rdata.elembase = NULL;
         rdata.repstr = ArgToRootedString(cx, args, 1);
         if (!rdata.repstr)
             return false;
 
         /* We're about to store pointers into the middle of our string. */
-        JSFixedString *fixed = rdata.repstr->ensureFixed(cx);
-        if (!fixed)
+        JSStableString *stable = rdata.repstr->ensureStable(cx);
+        if (!stable)
             return false;
-        rdata.dollarEnd = fixed->chars() + fixed->length();
-        rdata.dollar = js_strchr_limit(fixed->chars(), '$', rdata.dollarEnd);
+        rdata.dollarEnd = stable->chars() + stable->length();
+        rdata.dollar = js_strchr_limit(stable->chars(), '$', rdata.dollarEnd);
     }
 
     /*
      * Unlike its |String.prototype| brethren, |replace| doesn't convert
      * its input to a regular expression. (Even if it contains metachars.)
      *
      * However, if the user invokes our (non-standard) |flags| argument
      * extension then we revert to creating a regular expression. Note that
@@ -2986,17 +2986,17 @@ tagify(JSContext *cx, const char *begin,
 
     sb.infallibleAppend('<');
     sb.infallibleAppend('/');
 
     MOZ_ALWAYS_TRUE(sb.appendInflated(end, endlen));
 
     sb.infallibleAppend('>');
 
-    JSFixedString *retstr = sb.finishString();
+    JSFlatString *retstr = sb.finishString();
     if (!retstr)
         return false;
 
     call.rval().setString(retstr);
     return true;
 }
 
 static JSBool
@@ -3265,20 +3265,20 @@ js_InitStringClass(JSContext *cx, JSObje
      * uneval on the global object.
      */
     if (!JS_DefineFunctions(cx, global, string_functions))
         return NULL;
 
     return proto;
 }
 
-JSFixedString *
+JSStableString *
 js_NewString(JSContext *cx, jschar *chars, size_t length)
 {
-    JSFixedString *s = JSFixedString::new_(cx, chars, length);
+    JSStableString *s = JSStableString::new_(cx, chars, length);
     if (s)
         Probes::createString(cx, s, length);
     return s;
 }
 
 static JSInlineString *
 NewShortString(JSContext *cx, const char *chars, size_t length)
 {
@@ -3328,67 +3328,67 @@ js_NewDependentString(JSContext *cx, JSS
     if (JSLinearString *staticStr = cx->runtime->staticStrings.lookup(chars, length))
         return staticStr;
 
     JSLinearString *s = JSDependentString::new_(cx, base, chars, length);
     Probes::createString(cx, s, length);
     return s;
 }
 
-JSFixedString *
+JSFlatString *
 js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n)
 {
     if (JSShortString::lengthFits(n))
         return NewShortString(cx, s, n);
 
     jschar *news = cx->pod_malloc<jschar>(n + 1);
     if (!news)
         return NULL;
     js_strncpy(news, s, n);
     news[n] = 0;
-    JSFixedString *str = js_NewString(cx, news, n);
+    JSFlatString *str = js_NewString(cx, news, n);
     if (!str)
         js_free(news);
     return str;
 }
 
-JSFixedString *
+JSFlatString *
 js_NewStringCopyN(JSContext *cx, const char *s, size_t n)
 {
     if (JSShortString::lengthFits(n))
         return NewShortString(cx, s, n);
 
     jschar *chars = InflateString(cx, s, &n);
     if (!chars)
         return NULL;
-    JSFixedString *str = js_NewString(cx, chars, n);
+    JSFlatString *str = js_NewString(cx, chars, n);
     if (!str)
         js_free(chars);
     return str;
 }
 
-JSFixedString *
+JSFlatString *
 js_NewStringCopyZ(JSContext *cx, const jschar *s)
 {
     size_t n = js_strlen(s);
     if (JSShortString::lengthFits(n))
         return NewShortString(cx, s, n);
 
     size_t m = (n + 1) * sizeof(jschar);
     jschar *news = (jschar *) cx->malloc_(m);
     if (!news)
         return NULL;
     js_memcpy(news, s, m);
-    JSFixedString *str = js_NewString(cx, news, n);
+    JSFlatString *str = js_NewString(cx, news, n);
     if (!str)
         js_free(news);
     return str;
 }
 
-JSFixedString *
+JSFlatString *
 js_NewStringCopyZ(JSContext *cx, const char *s)
 {
     return js_NewStringCopyN(cx, s, strlen(s));
 }
 
 const char *
 js_ValueToPrintable(JSContext *cx, const Value &v, JSAutoByteString *bytes, bool asSource)
 {
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -5,23 +5,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jsstr_h___
 #define jsstr_h___
 
 #include <ctype.h>
 #include "jsapi.h"
 #include "jsatom.h"
-#include "jsprvtd.h"
 #include "jslock.h"
 #include "jsutil.h"
 
 #include "js/HashTable.h"
 #include "vm/Unicode.h"
 
+class JSFlatString;
+class JSStableString;
+
 namespace js {
 
 /* Implemented in jsstrinlines.h */
 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
@@ -74,34 +76,34 @@ extern const char js_escape_str[];
 extern const char js_unescape_str[];
 extern const char js_uneval_str[];
 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 JSFixedString *
+extern JSStableString *
 js_NewString(JSContext *cx, jschar *chars, size_t length);
 
 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 JSFixedString *
+extern JSFlatString *
 js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n);
 
-extern JSFixedString *
+extern JSFlatString *
 js_NewStringCopyN(JSContext *cx, const char *s, size_t n);
 
 /* Copy a C string and GC-allocate a descriptor for it. */
-extern JSFixedString *
+extern JSFlatString *
 js_NewStringCopyZ(JSContext *cx, const jschar *s);
 
-extern JSFixedString *
+extern JSFlatString *
 js_NewStringCopyZ(JSContext *cx, const char *s);
 
 /*
  * Convert a value to a printable C string.
  */
 extern const char *
 js_ValueToPrintable(JSContext *cx, const js::Value &,
                     JSAutoByteString *bytes, bool asSource = false);
--- a/js/src/jsxml.cpp
+++ b/js/src/jsxml.cpp
@@ -1175,17 +1175,20 @@ ParseNodeToQName(Parser *parser, ParseNo
     JSLinearString *uri, *prefix;
     size_t length, offset;
     const jschar *start, *limit, *colon;
     uint32_t n;
     JSObject *ns;
     JSLinearString *nsprefix;
 
     JS_ASSERT(pn->isArity(PN_NULLARY));
-    JSAtom *str = pn->pn_atom;
+    JSAtom *atom = pn->pn_atom;
+    JSStableString *str = atom->ensureStable(cx);
+    if (!str)
+        return NULL;
     start = str->chars();
     length = str->length();
     JS_ASSERT(length != 0 && *start != '@');
     JS_ASSERT(length != 1 || *start != '*');
 
     JSAtom *localName;
 
     uri = cx->runtime->emptyString;
@@ -1253,17 +1256,17 @@ ParseNodeToQName(Parser *parser, ParseNo
                 nsprefix = ns->getNamePrefix();
                 if (!nsprefix || nsprefix->empty()) {
                     uri = ns->getNameURI();
                     break;
                 }
             }
             prefix = uri->empty() ? parser->context->runtime->emptyString : NULL;
         }
-        localName = str;
+        localName = atom;
     }
 
     return NewXMLQName(parser->context, uri, prefix, localName);
 }
 
 static JSString *
 ChompXMLWhitespace(JSContext *cx, JSString *str)
 {
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -3439,18 +3439,18 @@ DebuggerGenericEval(JSContext *cx, const
     JS_ASSERT_IF(!fp, scope && scope->isGlobal());
 
     /* Check the first argument, the eval code string. */
     if (!code.isString()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_EXPECTED_TYPE,
                              fullMethodName, "string", InformalValueTypeName(code));
         return false;
     }
-    Rooted<JSLinearString*> linearStr(cx, code.toString()->ensureLinear(cx));
-    if (!linearStr)
+    Rooted<JSStableString *> stable(cx, code.toString()->ensureStable(cx));
+    if (!stable)
         return false;
 
     /*
      * Gather keys and values of bindings, if any. This must be done in the
      * debugger compartment, since that is where any exceptions must be
      * thrown.
      */
     AutoIdVector keys(cx);
@@ -3469,17 +3469,16 @@ DebuggerGenericEval(JSContext *cx, const
             if (!JSObject::getGeneric(cx, bindingsobj, bindingsobj, keyp, valp) ||
                 !dbg->unwrapDebuggeeValue(cx, valp.address()))
             {
                 return false;
             }
         }
     }
 
-
     Maybe<AutoCompartment> ac;
     if (fp)
         ac.construct(cx, fp->scopeChain());
     else
         ac.construct(cx, scope);
 
     Rooted<Env *> env(cx, fp ? GetDebugScopeForFrame(cx, fp) : scope);
     if (!env)
@@ -3500,18 +3499,18 @@ DebuggerGenericEval(JSContext *cx, const
             {
                 return false;
             }
         }
     }
 
     /* Run the code and produce the completion value. */
     Value rval;
-    JS::Anchor<JSString *> anchor(linearStr);
-    bool ok = EvaluateInEnv(cx, env, fp, linearStr->chars(), linearStr->length(),
+    JS::Anchor<JSString *> anchor(stable);
+    bool ok = EvaluateInEnv(cx, env, fp, stable->chars(), stable->length(),
                             "debugger eval code", 1, &rval);
     return dbg->receiveCompletionValue(ac, ok, rval, vp);
 }
 
 static JSBool
 DebuggerFrame_eval(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_FRAME(cx, argc, vp, "eval", args, thisobj, fp);
--- a/js/src/vm/String-inl.h
+++ b/js/src/vm/String-inl.h
@@ -16,17 +16,17 @@
 
 #include "jsgcinlines.h"
 #include "jsobjinlines.h"
 #include "gc/Barrier-inl.h"
 #include "gc/StoreBuffer.h"
 
 namespace js {
 
-static JS_ALWAYS_INLINE JSFixedString *
+static JS_ALWAYS_INLINE JSInlineString *
 NewShortString(JSContext *cx, const jschar *chars, size_t length)
 {
     SkipRoot skip(cx, &chars);
 
     /*
      * Don't bother trying to find a static atom; measurement shows that not
      * many get here (for one, Atomize is catching them).
      */
@@ -206,44 +206,44 @@ JSFlatString::toPropertyName(JSContext *
     if (isAtom())
         return asAtom().asPropertyName();
     JSAtom *atom = js::AtomizeString(cx, this);
     if (!atom)
         return NULL;
     return atom->asPropertyName();
 }
 
+JS_ALWAYS_INLINE JSAtom *
+JSFlatString::morphAtomizedStringIntoAtom()
+{
+    d.lengthAndFlags = buildLengthAndFlags(length(), ATOM_BIT);
+    return &asAtom();
+}
+
 JS_ALWAYS_INLINE void
-JSFixedString::init(const jschar *chars, size_t length)
+JSStableString::init(const jschar *chars, size_t length)
 {
     d.lengthAndFlags = buildLengthAndFlags(length, FIXED_FLAGS);
     d.u1.chars = chars;
 }
 
-JS_ALWAYS_INLINE JSFixedString *
-JSFixedString::new_(JSContext *cx, const jschar *chars, size_t length)
+JS_ALWAYS_INLINE JSStableString *
+JSStableString::new_(JSContext *cx, const jschar *chars, size_t length)
 {
     JS_ASSERT(chars[length] == jschar(0));
 
     if (!validateLength(cx, length))
         return NULL;
-    JSFixedString *str = (JSFixedString *)js_NewGCString(cx);
+    JSStableString *str = (JSStableString *)js_NewGCString(cx);
     if (!str)
         return NULL;
     str->init(chars, length);
     return str;
 }
 
-JS_ALWAYS_INLINE JSAtom *
-JSFixedString::morphAtomizedStringIntoAtom()
-{
-    d.lengthAndFlags = buildLengthAndFlags(length(), ATOM_BIT);
-    return &asAtom();
-}
-
 JS_ALWAYS_INLINE JSInlineString *
 JSInlineString::new_(JSContext *cx)
 {
     return (JSInlineString *)js_NewGCString(cx);
 }
 
 JS_ALWAYS_INLINE jschar *
 JSInlineString::init(size_t length)
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -17,26 +17,26 @@
 using namespace mozilla;
 using namespace js;
 
 #ifdef DEBUG
 bool
 JSString::isShort() const
 {
     bool is_short = (getAllocKind() == gc::FINALIZE_SHORT_STRING);
-    JS_ASSERT_IF(is_short, isFixed());
+    JS_ASSERT_IF(is_short, isFlat());
     return is_short;
 }
 #endif
 
 bool
 JSString::isExternal() const
 {
     bool is_external = (getAllocKind() == gc::FINALIZE_EXTERNAL_STRING);
-    JS_ASSERT_IF(is_external, isFixed());
+    JS_ASSERT_IF(is_external, isFlat());
     return is_external;
 }
 
 size_t
 JSString::sizeOfExcludingThis(JSMallocSizeOfFun mallocSizeOf)
 {
     // JSRope: do nothing, we'll count all children chars when we hit the leaf strings.
     if (isRope())
@@ -51,31 +51,29 @@ JSString::sizeOfExcludingThis(JSMallocSi
     JS_ASSERT(isFlat());
 
     // JSExtensibleString: count the full capacity, not just the used space.
     if (isExtensible()) {
         JSExtensibleString &extensible = asExtensible();
         return mallocSizeOf(extensible.chars());
     }
 
-    JS_ASSERT(isFixed());
-
     // JSExternalString: don't count, the chars could be stored anywhere.
     if (isExternal())
         return 0;
 
-    // JSInlineString, JSShortString, JSInlineAtom, JSShortAtom: the chars are inline.
+    // JSInlineString, JSShortString [JSInlineAtom, JSShortAtom]: the chars are inline.
     if (isInline())
         return 0;
 
-    // JSAtom, JSFixedString, JSUndependedString: measure the space for the
+    // JSAtom, JSStableString, JSUndependedString: measure the space for the
     // chars.  For JSUndependedString, there is no need to count the base
     // string, for the same reason as JSDependentString above.
-    JSFixedString &fixed = asFixed();
-    return mallocSizeOf(fixed.chars());
+    JSFlatString &flat = asFlat();
+    return mallocSizeOf(flat.chars());
 }
 
 #ifdef DEBUG
 
 void
 JSString::dumpChars(const jschar *s, size_t n)
 {
     if (n == SIZE_MAX) {
@@ -330,17 +328,17 @@ js_ConcatStrings(JSContext *cx, HandleSt
         PodCopy(buf + leftLen, rightChars, rightLen);
         buf[wholeLength] = 0;
         return str;
     }
 
     return JSRope::new_(cx, left, right, wholeLength);
 }
 
-JSFixedString *
+JSFlatString *
 JSDependentString::undepend(JSContext *cx)
 {
     JS_ASSERT(JSString::isDependent());
 
     /*
      * We destroy the base() pointer in undepend, so we need a pre-barrier. We
      * don't need a post-barrier because there aren't any outgoing pointers
      * afterwards.
@@ -358,17 +356,33 @@ JSDependentString::undepend(JSContext *c
     d.u1.chars = s;
 
     /*
      * Transform *this into an undepended string so 'base' will remain rooted
      * for the benefit of any other dependent string that depends on *this.
      */
     d.lengthAndFlags = buildLengthAndFlags(n, UNDEPENDED_FLAGS);
 
-    return &this->asFixed();
+    return &this->asFlat();
+}
+
+JSStableString *
+JSInlineString::uninline(JSContext *maybecx)
+{
+    JS_ASSERT(isInline());
+    size_t n = length();
+    size_t nbytes = (n + 1) * sizeof(jschar);
+    jschar *news = maybecx ? maybecx->pod_malloc<jschar>(n + 1) : js_pod_malloc<jschar>(n + 1);
+    if (!news)
+        return NULL;
+    js_strncpy(news, d.inlineStorage, n);
+    news[n] = 0;
+    d.u1.chars = news;
+    JS_ASSERT(!isInline());
+    return &asStable();
 }
 
 bool
 JSFlatString::isIndexSlow(uint32_t *indexp) const
 {
     const jschar *s = charsZ();
     jschar ch = *s;
 
@@ -451,25 +465,25 @@ const StaticStrings::SmallChar StaticStr
 
 bool
 StaticStrings::init(JSContext *cx)
 {
     AutoEnterAtomsCompartment ac(cx);
 
     for (uint32_t i = 0; i < UNIT_STATIC_LIMIT; i++) {
         jschar buffer[] = { jschar(i), '\0' };
-        JSFixedString *s = js_NewStringCopyN(cx, buffer, 1);
+        JSFlatString *s = js_NewStringCopyN(cx, buffer, 1);
         if (!s)
             return false;
         unitStaticTable[i] = s->morphAtomizedStringIntoAtom();
     }
 
     for (uint32_t i = 0; i < NUM_SMALL_CHARS * NUM_SMALL_CHARS; i++) {
         jschar buffer[] = { FROM_SMALL_CHAR(i >> 6), FROM_SMALL_CHAR(i & 0x3F), '\0' };
-        JSFixedString *s = js_NewStringCopyN(cx, buffer, 2);
+        JSFlatString *s = js_NewStringCopyN(cx, buffer, 2);
         if (!s)
             return false;
         length2StaticTable[i] = s->morphAtomizedStringIntoAtom();
     }
 
     for (uint32_t i = 0; i < INT_STATIC_LIMIT; i++) {
         if (i < 10) {
             intStaticTable[i] = unitStaticTable[i + '0'];
@@ -477,17 +491,17 @@ StaticStrings::init(JSContext *cx)
             size_t index = ((size_t)TO_SMALL_CHAR((i / 10) + '0') << 6) +
                 TO_SMALL_CHAR((i % 10) + '0');
             intStaticTable[i] = length2StaticTable[index];
         } else {
             jschar buffer[] = { jschar('0' + (i / 100)),
                                 jschar('0' + ((i / 10) % 10)),
                                 jschar('0' + (i % 10)),
                                 '\0' };
-            JSFixedString *s = js_NewStringCopyN(cx, buffer, 3);
+            JSFlatString *s = js_NewStringCopyN(cx, buffer, 3);
             if (!s)
                 return false;
             intStaticTable[i] = s->morphAtomizedStringIntoAtom();
         }
     }
 
     return true;
 }
--- a/js/src/vm/String.h
+++ b/js/src/vm/String.h
@@ -19,17 +19,18 @@
 #include "gc/Heap.h"
 
 class JSString;
 class JSDependentString;
 class JSUndependedString;
 class JSExtensibleString;
 class JSExternalString;
 class JSLinearString;
-class JSFixedString;
+class JSStableString;
+class JSInlineString;
 class JSRope;
 class JSAtom;
 
 namespace js {
 
 class StaticStrings;
 class PropertyName;
 
@@ -79,53 +80,55 @@ static const size_t UINT32_CHAR_BUFFER_L
  *  - To avoid copying all strings created through the JSAPI, an "external"
  *    string (JSExternalString) can be created whose chars are managed by the
  *    JSAPI client.
  *
  * Although all strings share the same basic memory layout, we can conceptually
  * arrange them into a hierarchy of operations/invariants and represent this
  * hierarchy in C++ with classes:
  *
- * C++ type                      operations+fields / invariants+properties
- *
- * JSString (abstract)           getCharsZ, getChars, length / -
+ * C++ type                     operations+fields / invariants+properties
+ * ==========================   =========================================
+ * JSString (abstract)          getCharsZ, getChars, length / -
  *  | \
- *  | JSRope                     leftChild, rightChild / -
+ *  | JSRope                    leftChild, rightChild / -
  *  |
- * JSLinearString (abstract)     chars / might be null-terminated
+ * JSLinearString (abstract)    chars / might be null-terminated
  *  | \
- *  | JSDependentString          base / -
- *  |
- * JSFlatString (abstract)       - / null-terminated
- *  | \
- *  | JSExtensibleString         capacity / no external pointers into char array
+ *  | JSDependentString         base / -
  *  |
- * JSFixedString                 - / may have external pointers into char array
- *  | \  \  \
- *  |  \  \ JSUndependedString   original dependent base / -
- *  |   \  \
- *  |    \ JSExternalString      - / char array memory managed by embedding
- *  |     \
- *  |     JSInlineString         - / chars stored in header
- *  |      | \
- *  |      | JSShortString       - / header is fat
- *  |      |        |
- * JSAtom  |        |            - / string equality === pointer equality
- *  | \    |        |
- *  | JSInlineAtom  |            - / atomized JSInlineString
- *  |      \        |
- *  |      JSShortAtom           - / atomized JSShortString
+ * JSFlatString                 - / null terminated
+ *  |  |
+ *  |  +-- JSStableString       - / may have external pointers into char array
+ *  |  |
+ *  |  +-- JSExternalString     - / char array memory managed by embedding
+ *  |  |
+ *  |  +-- JSExtensibleString   capacity / no external pointers into char array
+ *  |  |
+ *  |  +-- JSUndependedString   original dependent base / -
+ *  |  |
+ *  |  +-- JSInlineString       - / chars stored in header
+ *  |         \
+ *  |         JSShortString     - / header is fat
  *  |
- * js::PropertyName              - / chars don't contain an index (uint32_t)
+ * JSAtom                       - / string equality === pointer equality
+ *  |
+ * js::PropertyName             - / chars don't contain an index (uint32_t)
  *
  * Classes marked with (abstract) above are not literally C++ Abstract Base
  * Classes (since there are no virtual functions, pure or not, in this
  * hierarchy), but have the same meaning: there are no strings with this type as
  * its most-derived type.
  *
+ * Technically, there are three additional most-derived types that satisfy the
+ * invariants of more than one of the abovementioned most-derived types:
+ *  - InlineAtom = JSInlineString + JSAtom (atom with inline chars)
+ *  - ShortAtom  = JSShortString  + JSAtom (atom with (more) inline chars)
+ *  - StableAtom = JSStableString + JSAtom (atom with out-of-line chars)
+ *
  * Derived string types can be queried from ancestor types via isX() and
  * retrieved with asX() debug-only-checked casts.
  *
  * The ensureX() operations mutate 'this' in place to effectively the type to be
  * at least X (e.g., ensureLinear will change a JSRope to be a JSFlatString).
  */
 
 class JSString : public js::gc::Cell
@@ -180,18 +183,18 @@ class JSString : public js::gc::Cell
      *
      *   Rope         0000       0000
      *   Linear       -         !0000
      *   HasBase      -          xxx1
      *   Dependent    0001       0001
      *   Flat         -          isLinear && !isDependent
      *   Undepended   0011       0011
      *   Extensible   0010       0010
-     *   Fixed        0100       isFlat && !isExtensible
-     *   Inline       0100       isFixed && (u1.chars == inlineStorage) || isInt32)
+     *   Inline       0100       isFlat && !isExtensible && (u1.chars == inlineStorage) || isInt32)
+     *   Stable       0100       isFlat && !isExtensible && (u1.chars != inlineStorage)
      *   Short        0100       header in FINALIZE_SHORT_STRING arena
      *   External     0100       header in FINALIZE_EXTERNAL_STRING arena
      *   Int32        0110       x110 (NYI, Bug 654190)
      *   Atom         1000       1xxx
      *   InlineAtom   1000       1000 && is Inline
      *   ShortAtom    1000       1000 && is Short
      *   Int32Atom    1110       1110 (NYI, Bug 654190)
      *
@@ -266,17 +269,17 @@ class JSString : public js::gc::Cell
 
     inline const jschar *getChars(JSContext *cx);
     inline const jschar *getCharsZ(JSContext *cx);
 
     /* Fallible conversions to more-derived string types. */
 
     inline JSLinearString *ensureLinear(JSContext *cx);
     inline JSFlatString *ensureFlat(JSContext *cx);
-    inline JSFixedString *ensureFixed(JSContext *cx);
+    inline JSStableString *ensureStable(JSContext *cx);
 
     static bool ensureLinear(JSContext *cx, JSString *str) {
         return str->ensureLinear(cx) != NULL;
     }
 
     /* Type query and debug-checked casts */
 
     JS_ALWAYS_INLINE
@@ -330,29 +333,30 @@ class JSString : public js::gc::Cell
 
     JS_ALWAYS_INLINE
     JSExtensibleString &asExtensible() const {
         JS_ASSERT(isExtensible());
         return *(JSExtensibleString *)this;
     }
 
     JS_ALWAYS_INLINE
-    bool isFixed() const {
-        return isFlat() && !isExtensible();
+    bool isInline() const {
+        return isFlat() && !isExtensible() && (d.u1.chars == d.inlineStorage);
     }
 
     JS_ALWAYS_INLINE
-    JSFixedString &asFixed() const {
-        JS_ASSERT(isFixed());
-        return *(JSFixedString *)this;
+    JSInlineString &asInline() const {
+        JS_ASSERT(isInline());
+        return *(JSInlineString *)this;
     }
 
     JS_ALWAYS_INLINE
-    bool isInline() const {
-        return isFixed() && (d.u1.chars == d.inlineStorage);
+    JSStableString &asStable() const {
+        JS_ASSERT(!isInline());
+        return *(JSStableString *)this;
     }
 
     /* For hot code, prefer other type queries. */
     bool isExternal() const;
 
     JS_ALWAYS_INLINE
     JSExternalString &asExternal() const {
         JS_ASSERT(isExternal());
@@ -471,17 +475,17 @@ class JSLinearString : public JSString
     }
 };
 
 JS_STATIC_ASSERT(sizeof(JSLinearString) == sizeof(JSString));
 
 class JSDependentString : public JSLinearString
 {
     friend class JSString;
-    JSFixedString *undepend(JSContext *cx);
+    JSFlatString *undepend(JSContext *cx);
 
     void init(JSLinearString *base, const jschar *chars, size_t length);
 
     /* Vacuous and therefore unimplemented. */
     bool isDependent() const MOZ_DELETE;
     JSDependentString &asDependent() const MOZ_DELETE;
 
   public:
@@ -520,74 +524,69 @@ class JSFlatString : public JSLinearStri
 
     /*
      * Returns a property name represented by this string, or null on failure.
      * You must verify that this is not an index per isIndex before calling
      * this method.
      */
     inline js::PropertyName *toPropertyName(JSContext *cx);
 
+    /*
+     * Once a JSFlatString sub-class has been added to the atom state, this
+     * operation changes the string to the JSAtom type, in place.
+     */
+    inline JSAtom *morphAtomizedStringIntoAtom();
+
     inline void finalize(js::FreeOp *fop);
 };
 
 JS_STATIC_ASSERT(sizeof(JSFlatString) == sizeof(JSString));
 
+class JSStableString : public JSFlatString
+{
+    void init(const jschar *chars, size_t length);
+
+  public:
+    static inline JSStableString *new_(JSContext *cx, const jschar *chars, size_t length);
+};
+
+JS_STATIC_ASSERT(sizeof(JSStableString) == sizeof(JSString));
+
 class JSExtensibleString : public JSFlatString
 {
     /* Vacuous and therefore unimplemented. */
     bool isExtensible() const MOZ_DELETE;
     JSExtensibleString &asExtensible() const MOZ_DELETE;
 
   public:
     JS_ALWAYS_INLINE
     size_t capacity() const {
         JS_ASSERT(JSString::isExtensible());
         return d.s.u2.capacity;
     }
 };
 
 JS_STATIC_ASSERT(sizeof(JSExtensibleString) == sizeof(JSString));
 
-class JSFixedString : public JSFlatString
-{
-    void init(const jschar *chars, size_t length);
-
-    /* Vacuous and therefore unimplemented. */
-    JSFlatString *ensureFixed(JSContext *cx) MOZ_DELETE;
-    bool isFixed() const MOZ_DELETE;
-    JSFixedString &asFixed() const MOZ_DELETE;
-
-  public:
-    static inline JSFixedString *new_(JSContext *cx, const jschar *chars, size_t length);
-
-    /*
-     * Once a JSFixedString has been added to the atom state, this operation
-     * changes the type (in place, as reflected by the flag bits) of the
-     * JSFixedString into a JSAtom.
-     */
-    inline JSAtom *morphAtomizedStringIntoAtom();
-};
-
-JS_STATIC_ASSERT(sizeof(JSFixedString) == sizeof(JSString));
-
-class JSInlineString : public JSFixedString
+class JSInlineString : public JSFlatString
 {
     static const size_t MAX_INLINE_LENGTH = NUM_INLINE_CHARS - 1;
 
   public:
     static inline JSInlineString *new_(JSContext *cx);
 
     inline jschar *init(size_t length);
 
+    JSStableString *uninline(JSContext *cx);
+
     inline void resetLength(size_t length);
 
     static bool lengthFits(size_t length) {
         return length <= MAX_INLINE_LENGTH;
     }
-
 };
 
 JS_STATIC_ASSERT(sizeof(JSInlineString) == sizeof(JSString));
 
 class JSShortString : public JSInlineString
 {
     /* This can be any value that is a multiple of Cell::CellSize. */
     static const size_t INLINE_EXTENSION_CHARS = sizeof(JSString::Data) / sizeof(jschar);
@@ -615,17 +614,17 @@ class JSShortString : public JSInlineStr
 
     /* Only called by the GC for strings with the FINALIZE_SHORT_STRING kind. */
 
     JS_ALWAYS_INLINE void finalize(js::FreeOp *fop);
 };
 
 JS_STATIC_ASSERT(sizeof(JSShortString) == 2 * sizeof(JSString));
 
-class JSExternalString : public JSFixedString
+class JSExternalString : public JSFlatString
 {
     void init(const jschar *chars, size_t length, const JSStringFinalizer *fin);
 
     /* Vacuous and therefore unimplemented. */
     bool isExternal() const MOZ_DELETE;
     JSExternalString &asExternal() const MOZ_DELETE;
 
   public:
@@ -639,28 +638,28 @@ class JSExternalString : public JSFixedS
 
     /* Only called by the GC for strings with the FINALIZE_EXTERNAL_STRING kind. */
 
     inline void finalize(js::FreeOp *fop);
 };
 
 JS_STATIC_ASSERT(sizeof(JSExternalString) == sizeof(JSString));
 
-class JSUndependedString : public JSFixedString
+class JSUndependedString : public JSFlatString
 {
     /*
      * JSUndependedString is not explicitly used and is only present for
      * consistency. See JSDependentString::undepend for how a JSDependentString
      * gets morphed into a JSUndependedString.
      */
 };
 
 JS_STATIC_ASSERT(sizeof(JSUndependedString) == sizeof(JSString));
 
-class JSAtom : public JSFixedString
+class JSAtom : public JSFlatString
 {
     /* Vacuous and therefore unimplemented. */
     bool isAtom() const MOZ_DELETE;
     JSAtom &asAtom() const MOZ_DELETE;
 
   public:
     /* Returns the PropertyName for this.  isIndex() must be false. */
     inline js::PropertyName *asPropertyName();
@@ -669,36 +668,16 @@ class JSAtom : public JSFixedString
 
 #ifdef DEBUG
     void dump();
 #endif
 };
 
 JS_STATIC_ASSERT(sizeof(JSAtom) == sizeof(JSString));
 
-class JSInlineAtom : public JSInlineString /*, JSAtom */
-{
-    /*
-     * JSInlineAtom is not explicitly used and is only present for consistency.
-     * See Atomize() for how JSInlineStrings get morphed into JSInlineAtoms.
-     */
-};
-
-JS_STATIC_ASSERT(sizeof(JSInlineAtom) == sizeof(JSInlineString));
-
-class JSShortAtom : public JSShortString /*, JSInlineAtom */
-{
-    /*
-     * JSShortAtom is not explicitly used and is only present for consistency.
-     * See Atomize() for how JSShortStrings get morphed into JSShortAtoms.
-     */
-};
-
-JS_STATIC_ASSERT(sizeof(JSShortAtom) == sizeof(JSShortString));
-
 namespace js {
 
 class StaticStrings
 {
   private:
     /* Bigger chars cannot be in a length-2 string. */
     static const size_t SMALL_CHAR_LIMIT    = 128U;
     static const size_t NUM_SMALL_CHARS     = 64U;
@@ -830,24 +809,39 @@ JSString::ensureFlat(JSContext *cx)
 {
     return isFlat()
            ? &asFlat()
            : isDependent()
              ? asDependent().undepend(cx)
              : asRope().flatten(cx);
 }
 
-JS_ALWAYS_INLINE JSFixedString *
-JSString::ensureFixed(JSContext *cx)
+JS_ALWAYS_INLINE JSStableString *
+JSString::ensureStable(JSContext *maybecx)
 {
-    if (!ensureFlat(cx))
-        return NULL;
-    if (isExtensible())
-        d.lengthAndFlags = buildLengthAndFlags(length(), FIXED_FLAGS);
-    return &asFixed();
+    if (isRope()) {
+        JSFlatString *flat = asRope().flatten(maybecx);
+        if (!flat)
+            return NULL;
+        JS_ASSERT(!flat->isInline());
+        return &flat->asStable();
+    }
+
+    if (isDependent()) {
+        JSFlatString *flat = asDependent().undepend(maybecx);
+        if (!flat)
+            return NULL;
+        return &flat->asStable();
+    }
+
+    if (!isInline())
+        return &asStable();
+
+    JS_ASSERT(isInline());
+    return asInline().uninline(maybecx);
 }
 
 inline JSLinearString *
 JSString::base() const
 {
     JS_ASSERT(hasBase());
     JS_ASSERT(!d.s.u2.base->isInline());
     return d.s.u2.base;
--- a/js/src/vm/StringBuffer.cpp
+++ b/js/src/vm/StringBuffer.cpp
@@ -33,17 +33,17 @@ StringBuffer::extractWellSized()
             return NULL;
         }
         buf = tmp;
     }
 
     return buf;
 }
 
-JSFixedString *
+JSFlatString *
 StringBuffer::finishString()
 {
     JSContext *cx = context();
     if (cb.empty())
         return cx->names().empty;
 
     size_t length = cb.length();
     if (!JSString::validateLength(cx, length))
@@ -55,17 +55,17 @@ StringBuffer::finishString()
 
     if (!cb.append('\0'))
         return NULL;
 
     jschar *buf = extractWellSized();
     if (!buf)
         return NULL;
 
-    JSFixedString *str = js_NewString(cx, buf, length);
+    JSFlatString *str = js_NewString(cx, buf, length);
     if (!str)
         js_free(buf);
     return str;
 }
 
 JSAtom *
 StringBuffer::finishAtom()
 {
--- a/js/src/vm/StringBuffer.h
+++ b/js/src/vm/StringBuffer.h
@@ -77,17 +77,17 @@ class StringBuffer
     const jschar *end() const { return cb.end(); }
     bool empty() const { return cb.empty(); }
     size_t length() const { return cb.length(); }
 
     /*
      * Creates a string from the characters in this buffer, then (regardless
      * whether string creation succeeded or failed) empties the buffer.
      */
-    JSFixedString *finishString();
+    JSFlatString *finishString();
 
     /* Identical to finishString() except that an atom is created. */
     JSAtom *finishAtom();
 
     /*
      * Creates a raw string from the characters in this buffer.  The string is
      * exactly the characters in this buffer: it is *not* null-terminated
      * unless the last appended character was |(jschar)0|.