Bug 964238 (part 1) - Remove JSStableString and StableTwoByteChars. r=terrence.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 30 Jan 2014 14:58:53 -0800
changeset 184919 f88ba0e5e3b15f2773a96d188696fbc4a4d45974
parent 184918 96f918d5006d5a79bfdb0317f127d331ddc6448b
child 184920 19b6dfafd05c071312ec71afd91246c8e0096dd4
push idunknown
push userunknown
push dateunknown
reviewersterrence
bugs964238
milestone30.0a1
Bug 964238 (part 1) - Remove JSStableString and StableTwoByteChars. r=terrence.
dom/src/json/nsJSON.cpp
js/public/CharacterEncoding.h
js/public/RootingAPI.h
js/src/NamespaceImports.h
js/src/builtin/Eval.cpp
js/src/ctypes/CTypes.cpp
js/src/frontend/Parser.cpp
js/src/jit/AsmJSLink.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsexn.cpp
js/src/jsfun.cpp
js/src/json.cpp
js/src/json.h
js/src/jsonparser.cpp
js/src/jsonparser.h
js/src/jspubtd.h
js/src/jsreflect.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsstr.cpp
js/src/jsstr.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/ErrorObject.cpp
js/src/vm/OldDebugAPI.cpp
js/src/vm/SelfHosting.cpp
js/src/vm/String-inl.h
js/src/vm/String.cpp
js/src/vm/String.h
js/src/vm/StringBuffer.h
js/src/vm/StructuredClone.cpp
--- a/dom/src/json/nsJSON.cpp
+++ b/dom/src/json/nsJSON.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 sw=2 et tw=79: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jsapi.h"
+#include "js/CharacterEncoding.h"
 #include "js/OldDebugAPI.h"
 #include "nsJSON.h"
 #include "nsIXPConnect.h"
 #include "nsIXPCScriptable.h"
 #include "nsStreamUtils.h"
 #include "nsIInputStream.h"
 #include "nsStringStream.h"
 #include "mozilla/dom/EncodingUtils.h"
@@ -519,18 +520,18 @@ nsJSONListener::OnStopRequest(nsIRequest
   if (!mSniffBuffer.IsEmpty()) {
     // Just consume mSniffBuffer
     rv = ProcessBytes(nullptr, 0);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   JS::Rooted<JS::Value> reviver(mCx, JS::NullValue()), value(mCx);
 
-  JS::StableCharPtr chars(reinterpret_cast<const jschar*>(mBufferedChars.Elements()),
-                          mBufferedChars.Length());
+  JS::ConstTwoByteChars chars(reinterpret_cast<const jschar*>(mBufferedChars.Elements()),
+                              mBufferedChars.Length());
   bool ok = JS_ParseJSONWithReviver(mCx, chars.get(),
                                       uint32_t(mBufferedChars.Length()),
                                       reviver, &value);
 
   *mRootVal = value;
   mBufferedChars.TruncateLength(0);
   return ok ? NS_OK : NS_ERROR_FAILURE;
 }
--- a/js/public/CharacterEncoding.h
+++ b/js/public/CharacterEncoding.h
@@ -119,31 +119,16 @@ class TwoByteChars : public mozilla::Ran
 
   public:
     TwoByteChars() : Base() {}
     TwoByteChars(jschar *aChars, size_t aLength) : Base(aChars, aLength) {}
     TwoByteChars(const jschar *aChars, size_t aLength) : Base(const_cast<jschar *>(aChars), aLength) {}
 };
 
 /*
- * A non-convertible variant of TwoByteChars that does not refer to characters
- * inlined inside a JSShortString or a JSInlineString. StableTwoByteChars are
- * thus safe to hold across a GC.
- */
-class StableTwoByteChars : public mozilla::Range<jschar>
-{
-    typedef mozilla::Range<jschar> Base;
-
-  public:
-    StableTwoByteChars() : Base() {}
-    StableTwoByteChars(jschar *aChars, size_t aLength) : Base(aChars, aLength) {}
-    StableTwoByteChars(const jschar *aChars, size_t aLength) : Base(const_cast<jschar *>(aChars), aLength) {}
-};
-
-/*
  * A TwoByteChars, but \0 terminated for compatibility with JSFlatString.
  */
 class TwoByteCharsZ : public mozilla::RangedPtr<jschar>
 {
     typedef mozilla::RangedPtr<jschar> Base;
 
   public:
     TwoByteCharsZ() : Base(nullptr, 0) {}
@@ -152,16 +137,35 @@ class TwoByteCharsZ : public mozilla::Ra
       : Base(chars, length)
     {
         JS_ASSERT(chars[length] == '\0');
     }
 
     using Base::operator=;
 };
 
+typedef mozilla::RangedPtr<const jschar> ConstCharPtr;
+
+/*
+ * Like TwoByteChars, but the chars are const.
+ */
+class ConstTwoByteChars : public mozilla::RangedPtr<const jschar>
+{
+  public:
+    ConstTwoByteChars(const ConstTwoByteChars &s) : ConstCharPtr(s) {}
+    ConstTwoByteChars(const mozilla::RangedPtr<const jschar> &s) : ConstCharPtr(s) {}
+    ConstTwoByteChars(const jschar *s, size_t len) : ConstCharPtr(s, len) {}
+    ConstTwoByteChars(const jschar *pos, const jschar *start, size_t len)
+      : ConstCharPtr(pos, start, len)
+    {}
+
+    using ConstCharPtr::operator=;
+};
+
+
 /*
  * Convert a 2-byte character sequence to "ISO-Latin-1". This works by
  * truncating each 2-byte pair in the sequence to a 1-byte pair. If the source
  * contains any UTF-16 extension characters, then this may give invalid Latin1
  * output. The returned string is zero terminated. The returned string or the
  * returned string's |start()| must be freed with JS_free or js_free,
  * respectively. If allocation fails, an OOM error will be set and the method
  * will return a nullptr chars (which can be tested for with the ! operator).
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -827,22 +827,16 @@ class MOZ_STACK_CLASS Rooted : public js
      */
     T ptr;
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
     Rooted(const Rooted &) MOZ_DELETE;
 };
 
-#if !(defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING))
-// Defined in vm/String.h.
-template <>
-class Rooted<JSStableString *>;
-#endif
-
 } /* namespace JS */
 
 namespace js {
 
 /*
  * Mark a stack location as a root for the rooting analysis, without actually
  * rooting it in release builds. This should only be used for stack locations
  * of GC things that cannot be relocated by a garbage collection, and that
--- a/js/src/NamespaceImports.h
+++ b/js/src/NamespaceImports.h
@@ -16,17 +16,17 @@
 #include "js/TypeDecls.h"
 #include "js/Value.h"
 
 // ... but we do forward declarations of the structs and classes not pulled in
 // by the headers included above.
 namespace JS {
 
 class Latin1CharsZ;
-class StableCharPtr;
+class ConstTwoByteChars;
 class TwoByteChars;
 
 class AutoFunctionVector;
 class AutoIdVector;
 class AutoObjectVector;
 class AutoScriptVector;
 class AutoValueVector;
 
@@ -57,17 +57,17 @@ using JS::ObjectValue;
 using JS::PrivateUint32Value;
 using JS::PrivateValue;
 using JS::StringValue;
 using JS::UndefinedValue;
 
 using JS::IsPoisonedPtr;
 
 using JS::Latin1CharsZ;
-using JS::StableCharPtr;
+using JS::ConstTwoByteChars;
 using JS::TwoByteChars;
 
 using JS::AutoFunctionVector;
 using JS::AutoIdVector;
 using JS::AutoObjectVector;
 using JS::AutoScriptVector;
 using JS::AutoValueVector;
 
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -143,17 +143,17 @@ class EvalScriptGuard
 enum EvalJSONResult {
     EvalJSON_Failure,
     EvalJSON_Success,
     EvalJSON_NotJSON
 };
 
 static EvalJSONResult
 TryEvalJSON(JSContext *cx, JSScript *callerScript,
-            StableCharPtr chars, size_t length, MutableHandleValue rval)
+            ConstTwoByteChars chars, size_t length, MutableHandleValue rval)
 {
     // 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
@@ -275,34 +275,34 @@ 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<JSStableString*> stableStr(cx, str->ensureStable(cx));
-    if (!stableStr)
+    Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
+    if (!flatStr)
         return false;
 
-    StableCharPtr chars = stableStr->chars();
-    size_t length = stableStr->length();
+    size_t length = flatStr->length();
+    ConstTwoByteChars chars(flatStr->chars(), length);
 
     JSPrincipals *principals = PrincipalsForCompiledCode(args, cx);
 
     RootedScript callerScript(cx, caller ? caller.script() : nullptr);
     EvalJSONResult ejr = TryEvalJSON(cx, callerScript, chars, length, args.rval());
     if (ejr != EvalJSON_NotJSON)
         return ejr == EvalJSON_Success;
 
     EvalScriptGuard esg(cx);
 
     if (evalType == DIRECT_EVAL && caller.isNonEvalFunctionFrame())
-        esg.lookupInEvalCache(stableStr, callerScript, pc);
+        esg.lookupInEvalCache(flatStr, callerScript, pc);
 
     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);
@@ -311,17 +311,17 @@ EvalKernel(JSContext *cx, const CallArgs
         options.setFileAndLine(filename, lineno)
                .setCompileAndGo(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setPrincipals(principals)
                .setOriginPrincipals(originPrincipals);
         JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
                                                      scopeobj, callerScript, options,
-                                                     chars.get(), length, stableStr, staticLevel);
+                                                     chars.get(), length, flatStr, staticLevel);
         if (!compiled)
             return false;
 
         MarkFunctionsWithinEvalScript(compiled);
 
         esg.setNewScript(compiled);
     }
 
@@ -342,33 +342,33 @@ js::DirectEvalStringFromIon(JSContext *c
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
         return false;
     }
 
     // ES5 15.1.2.1 steps 2-8.
 
     unsigned staticLevel = callerScript->staticLevel() + 1;
 
-    Rooted<JSStableString*> stableStr(cx, str->ensureStable(cx));
-    if (!stableStr)
+    Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
+    if (!flatStr)
         return false;
 
-    StableCharPtr chars = stableStr->chars();
-    size_t length = stableStr->length();
+    size_t length = flatStr->length();
+    ConstTwoByteChars chars(flatStr->chars(), length);
 
     EvalJSONResult ejr = TryEvalJSON(cx, callerScript, chars, length, vp);
     if (ejr != EvalJSON_NotJSON)
         return ejr == EvalJSON_Success;
 
     EvalScriptGuard esg(cx);
 
     // Ion will not perform cross compartment direct eval calls.
     JSPrincipals *principals = cx->compartment()->principals;
 
-    esg.lookupInEvalCache(stableStr, callerScript, pc);
+    esg.lookupInEvalCache(flatStr, callerScript, pc);
 
     if (!esg.foundScript()) {
         unsigned lineno;
         const char *filename;
         JSPrincipals *originPrincipals;
         CurrentScriptFileLineOrigin(cx, &filename, &lineno, &originPrincipals,
                                     CALLED_FROM_JSOP_EVAL);
 
@@ -376,17 +376,17 @@ js::DirectEvalStringFromIon(JSContext *c
         options.setFileAndLine(filename, lineno)
                .setCompileAndGo(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setPrincipals(principals)
                .setOriginPrincipals(originPrincipals);
         JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
                                                      scopeobj, callerScript, options,
-                                                     chars.get(), length, stableStr, staticLevel);
+                                                     chars.get(), length, flatStr, staticLevel);
         if (!compiled)
             return false;
 
         MarkFunctionsWithinEvalScript(compiled);
 
         esg.setNewScript(compiled);
     }
 
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -4815,34 +4815,31 @@ StructType::DefineInternal(JSContext* cx
     structAlign = 0;
 
     for (uint32_t i = 0; i < len; ++i) {
       RootedValue item(cx);
       if (!JS_GetElement(cx, fieldsObj, i, &item))
         return false;
 
       RootedObject fieldType(cx, nullptr);
-      JSFlatString* flat = ExtractStructField(cx, item, fieldType.address());
-      if (!flat)
-        return false;
-      Rooted<JSStableString*> name(cx, flat->ensureStable(cx));
+      Rooted<JSFlatString*> name(cx, ExtractStructField(cx, item, fieldType.address()));
       if (!name)
         return false;
       fieldRoots[i] = JS::ObjectValue(*fieldType);
 
       // Make sure each field name is unique
       FieldInfoHash::AddPtr entryPtr = fields->lookupForAdd(name);
       if (entryPtr) {
         JS_ReportError(cx, "struct fields must have unique names");
         return false;
       }
 
       // Add the field to the StructType's 'prototype' property.
       if (!JS_DefineUCProperty(cx, prototype,
-             name->chars().get(), name->length(), JSVAL_VOID,
+             name->chars(), name->length(), JSVAL_VOID,
              StructType::FieldGetter, StructType::FieldSetter,
              JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_PERMANENT))
         return false;
 
       size_t fieldSize = CType::GetSize(fieldType);
       size_t fieldAlign = CType::GetAlignment(fieldType);
       size_t fieldOffset = Align(structSize, fieldAlign);
       // Check for overflow. Since we hold invariant that fieldSize % fieldAlign
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -6630,25 +6630,25 @@ Parser<ParseHandler>::stringLiteral()
     return handler.newStringLiteral(atom, pos());
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::newRegExp()
 {
     // Create the regexp even when doing a syntax parse, to check the regexp's syntax.
+    const jschar *chars = tokenStream.getTokenbuf().begin();
     size_t length = tokenStream.getTokenbuf().length();
-    const StableCharPtr chars(tokenStream.getTokenbuf().begin(), length);
     RegExpFlag flags = tokenStream.currentToken().regExpFlags();
 
     Rooted<RegExpObject*> reobj(context);
     if (RegExpStatics *res = context->global()->getRegExpStatics())
-        reobj = RegExpObject::create(context, res, chars.get(), length, flags, &tokenStream);
+        reobj = RegExpObject::create(context, res, chars, length, flags, &tokenStream);
     else
-        reobj = RegExpObject::createNoStatics(context, chars.get(), length, flags, &tokenStream);
+        reobj = RegExpObject::createNoStatics(context, chars, length, flags, &tokenStream);
 
     if (!reobj)
         return null();
 
     return handler.newRegExp(reobj, pos(), *this);
 }
 
 template <typename ParseHandler>
--- a/js/src/jit/AsmJSLink.cpp
+++ b/js/src/jit/AsmJSLink.cpp
@@ -437,17 +437,17 @@ NewExportedFunction(JSContext *cx, const
 static bool
 HandleDynamicLinkFailure(JSContext *cx, CallArgs args, AsmJSModule &module, HandlePropertyName name)
 {
     if (cx->isExceptionPending())
         return false;
 
     uint32_t begin = module.charsBegin();
     uint32_t end = module.charsEnd();
-    Rooted<JSStableString*> src(cx, module.scriptSource()->substring(cx, begin, end));
+    Rooted<JSFlatString*> src(cx, module.scriptSource()->substring(cx, begin, end));
     if (!src)
         return false;
 
     RootedFunction fun(cx, NewFunction(cx, NullPtr(), nullptr, 0, JSFunction::INTERPRETED,
                                        cx->global(), name, JSFunction::FinalizeKind,
                                        TenuredObject));
     if (!fun)
         return false;
@@ -462,17 +462,17 @@ HandleDynamicLinkFailure(JSContext *cx, 
         formals.infallibleAppend(module.bufferArgumentName());
 
     CompileOptions options(cx);
     options.setPrincipals(cx->compartment()->principals)
            .setOriginPrincipals(module.scriptSource()->originPrincipals())
            .setCompileAndGo(false)
            .setNoScriptRval(false);
 
-    if (!frontend::CompileFunctionBody(cx, &fun, options, formals, src->chars().get(), end - begin))
+    if (!frontend::CompileFunctionBody(cx, &fun, options, formals, src->chars(), end - begin))
         return false;
 
     // Call the function we just recompiled.
 
     unsigned argc = args.length();
 
     InvokeArgs args2(cx);
     if (!args2.init(argc))
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -274,20 +274,20 @@ JS_ConvertArgumentsVA(JSContext *cx, uns
           case 'S':
           case 'W':
             val = *sp;
             str = ToString<CanGC>(cx, val);
             if (!str)
                 return false;
             *sp = STRING_TO_JSVAL(str);
             if (c == 'W') {
-                JSStableString *stable = str->ensureStable(cx);
-                if (!stable)
+                JSFlatString *flat = str->ensureFlat(cx);
+                if (!flat)
                     return false;
-                *va_arg(ap, const jschar **) = stable->chars().get();
+                *va_arg(ap, const jschar **) = flat->chars();
             } else {
                 *va_arg(ap, JSString **) = str;
             }
             break;
           case 'o':
             if (sp->isNullOrUndefined()) {
                 obj = nullptr;
             } else {
@@ -5480,25 +5480,25 @@ JS_Stringify(JSContext *cx, MutableHandl
 
 JS_PUBLIC_API(bool)
 JS_ParseJSON(JSContext *cx, const jschar *chars, uint32_t len, JS::MutableHandleValue vp)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
     RootedValue reviver(cx, NullValue());
-    return ParseJSONWithReviver(cx, JS::StableCharPtr(chars, len), len, reviver, vp);
+    return ParseJSONWithReviver(cx, ConstTwoByteChars(chars, len), len, reviver, vp);
 }
 
 JS_PUBLIC_API(bool)
 JS_ParseJSONWithReviver(JSContext *cx, const jschar *chars, uint32_t len, HandleValue reviver, MutableHandleValue vp)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
-    return ParseJSONWithReviver(cx, StableCharPtr(chars, len), len, reviver, vp);
+    return ParseJSONWithReviver(cx, ConstTwoByteChars(chars, len), len, reviver, vp);
 }
 
 /************************************************************************/
 
 JS_PUBLIC_API(void)
 JS_ReportError(JSContext *cx, const char *format, ...)
 {
     va_list ap;
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -35,30 +35,16 @@
 
 struct JSTracer;
 
 namespace JS {
 
 class Latin1CharsZ;
 class TwoByteChars;
 
-typedef mozilla::RangedPtr<const jschar> CharPtr;
-
-class StableCharPtr : public CharPtr {
-  public:
-    StableCharPtr(const StableCharPtr &s) : CharPtr(s) {}
-    StableCharPtr(const mozilla::RangedPtr<const jschar> &s) : CharPtr(s) {}
-    StableCharPtr(const jschar *s, size_t len) : CharPtr(s, len) {}
-    StableCharPtr(const jschar *pos, const jschar *start, size_t len)
-      : CharPtr(pos, start, len)
-    {}
-
-    using CharPtr::operator=;
-};
-
 #if defined JS_THREADSAFE && defined JS_DEBUG
 
 class JS_PUBLIC_API(AutoCheckRequestDepth)
 {
     JSContext *cx;
   public:
     AutoCheckRequestDepth(JSContext *cx);
     AutoCheckRequestDepth(js::ContextFriendFields *cx);
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -842,18 +842,18 @@ js_ReportUncaughtException(JSContext *cx
         if (str) {
             // Note that using |str| for |ucmessage| here is kind of wrong,
             // because |str| is supposed to be of the format
             // |ErrorName: ErrorMessage|, and |ucmessage| is supposed to
             // correspond to |ErrorMessage|. But this is what we've historically
             // done for duck-typed error objects.
             //
             // If only this stuff could get specced one day...
-            if (JSStableString *stable = str->ensureStable(cx))
-                report.ucmessage = stable->chars().get();
+            if (JSFlatString *flat = str->ensureFlat(cx))
+                report.ucmessage = flat->chars();
         }
     }
 
     JSAutoByteString bytesStorage;
     const char *bytes = nullptr;
     if (str)
         bytes = bytesStorage.encodeLatin1(cx, str);
     if (!bytes)
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -577,17 +577,17 @@ const Class JSFunction::class_ = {
     nullptr,                 /* construct   */
     fun_trace
 };
 
 const Class* const js::FunctionClassPtr = &JSFunction::class_;
 
 /* Find the body of a function (not including braces). */
 static bool
-FindBody(JSContext *cx, HandleFunction fun, StableCharPtr chars, size_t length,
+FindBody(JSContext *cx, HandleFunction fun, ConstTwoByteChars chars, size_t length,
          size_t *bodyStart, size_t *bodyEnd)
 {
     // We don't need principals, since those are only used for error reporting.
     CompileOptions options(cx);
     options.setFileAndLine("internal-findBody", 0)
            .setVersion(fun->nonLazyScript()->getVersion());
     AutoKeepAtoms keepAtoms(cx->perThreadData);
     TokenStream ts(cx, options, chars.get(), length, nullptr);
@@ -620,17 +620,17 @@ FindBody(JSContext *cx, HandleFunction f
         tt = ts.getToken();
     if (tt == TOK_ERROR)
         return false;
     bool braced = tt == TOK_LC;
     JS_ASSERT_IF(fun->isExprClosure(), !braced);
     *bodyStart = ts.currentToken().pos.begin;
     if (braced)
         *bodyStart += 1;
-    StableCharPtr end(chars.get() + length, chars.get(), length);
+    ConstTwoByteChars end(chars.get() + length, chars.get(), length);
     if (end[-1] == '}') {
         end--;
     } else {
         JS_ASSERT(!braced);
         for (; unicode::IsSpaceOrBOM2(end[-1]); end--)
             ;
     }
     *bodyEnd = end - chars;
@@ -688,21 +688,21 @@ js::FunctionToString(JSContext *cx, Hand
         !JSScript::loadSource(cx, script->scriptSource(), &haveSource))
     {
         return nullptr;
     }
     if (haveSource) {
         RootedString srcStr(cx, script->sourceData(cx));
         if (!srcStr)
             return nullptr;
-        Rooted<JSStableString *> src(cx, srcStr->ensureStable(cx));
+        Rooted<JSFlatString *> src(cx, srcStr->ensureFlat(cx));
         if (!src)
             return nullptr;
 
-        StableCharPtr chars = src->chars();
+        ConstTwoByteChars chars(src->chars(), src->length());
         bool exprBody = fun->isExprClosure();
 
         // The source data for functions created by calling the Function
         // constructor is only the function's body.  This depends on the fact,
         // asserted below, that in Function("function f() {}"), the inner
         // function's sourceStart points to the '(', not the 'f'.
         bool funCon = !fun->isArrow() &&
                       script->sourceStart() == 0 &&
@@ -1538,17 +1538,17 @@ FunctionConstructor(JSContext *cx, unsig
          * free collected_args and its tokenstream in one swoop.
          */
         LifoAllocScope las(&cx->tempLifoAlloc());
         jschar *cp = cx->tempLifoAlloc().newArray<jschar>(args_length + 1);
         if (!cp) {
             js_ReportOutOfMemory(cx);
             return false;
         }
-        StableCharPtr collected_args(cp, args_length + 1);
+        ConstTwoByteChars collected_args(cp, args_length + 1);
 
         /*
          * Concatenate the arguments into the new string, separated by commas.
          */
         for (unsigned i = 0; i < n; i++) {
             arg = args[i].toString();
             size_t arg_length = arg->length();
             const jschar *arg_chars = arg->getChars(cx);
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -769,18 +769,18 @@ Revive(JSContext *cx, HandleValue revive
     if (!JSObject::defineProperty(cx, obj, cx->names().empty, vp))
         return false;
 
     Rooted<jsid> id(cx, NameToId(cx->names().empty));
     return Walk(cx, obj, id, reviver, vp);
 }
 
 bool
-js::ParseJSONWithReviver(JSContext *cx, StableCharPtr chars, size_t length, HandleValue reviver,
-                         MutableHandleValue vp)
+js::ParseJSONWithReviver(JSContext *cx, ConstTwoByteChars chars, size_t length,
+                         HandleValue reviver, MutableHandleValue vp)
 {
     /* 15.12.2 steps 2-3. */
     JSONParser parser(cx, chars, length);
     if (!parser.parse(vp))
         return false;
 
     /* 15.12.2 steps 4-5. */
     if (js_IsCallable(reviver))
@@ -805,26 +805,27 @@ json_parse(JSContext *cx, unsigned argc,
 
     /* Step 1. */
     JSString *str = (args.length() >= 1)
                     ? ToString<CanGC>(cx, args[0])
                     : cx->names().undefined;
     if (!str)
         return false;
 
-    JSStableString *stable = str->ensureStable(cx);
-    if (!stable)
+    Rooted<JSFlatString*> flat(cx, str->ensureFlat(cx));
+    if (!flat)
         return false;
 
-    JS::Anchor<JSString *> anchor(stable);
+    JS::Anchor<JSString *> anchor(flat);
 
     RootedValue reviver(cx, (argc >= 2) ? args[1] : UndefinedValue());
 
     /* Steps 2-5. */
-    return ParseJSONWithReviver(cx, stable->chars(), stable->length(), reviver, args.rval());
+    return ParseJSONWithReviver(cx, ConstTwoByteChars(flat->chars(), flat->length()),
+                                flat->length(), reviver, args.rval());
 }
 
 /* ES5 15.12.3. */
 bool
 json_stringify(JSContext *cx, unsigned argc, Value *vp)
 {
     RootedObject replacer(cx, (argc >= 2 && vp[3].isObject())
                               ? &vp[3].toObject()
--- a/js/src/json.h
+++ b/js/src/json.h
@@ -20,14 +20,14 @@ js_InitJSONClass(JSContext *cx, js::Hand
 
 extern bool
 js_Stringify(JSContext *cx, js::MutableHandleValue vp, JSObject *replacer,
              js::Value space, js::StringBuffer &sb);
 
 namespace js {
 
 extern bool
-ParseJSONWithReviver(JSContext *cx, JS::StableCharPtr chars, size_t length, HandleValue reviver,
-                     MutableHandleValue vp);
+ParseJSONWithReviver(JSContext *cx, JS::ConstTwoByteChars chars, size_t length,
+                     HandleValue reviver, MutableHandleValue vp);
 
 } // namespace js
 
 #endif /* json_h */
--- a/js/src/jsonparser.cpp
+++ b/js/src/jsonparser.cpp
@@ -55,17 +55,17 @@ JSONParser::trace(JSTracer *trc)
             }
         }
     }
 }
 
 void
 JSONParser::getTextPosition(uint32_t *column, uint32_t *line)
 {
-    StableCharPtr ptr = begin;
+    ConstTwoByteChars ptr = begin;
     uint32_t col = 1;
     uint32_t row = 1;
     for (; ptr < current; ptr++) {
         if (*ptr == '\n' || *ptr == '\r') {
             ++row;
             col = 1;
             // \r\n is treated as a single newline.
             if (ptr + 1 < current && *ptr == '\r' && *(ptr + 1) == '\n')
--- a/js/src/jsonparser.h
+++ b/js/src/jsonparser.h
@@ -18,18 +18,18 @@ class MOZ_STACK_CLASS JSONParser : priva
 {
   public:
     enum ErrorHandling { RaiseError, NoError };
 
   private:
     /* Data members */
 
     JSContext * const cx;
-    StableCharPtr current;
-    const StableCharPtr begin, end;
+    JS::ConstTwoByteChars current;
+    const JS::ConstTwoByteChars begin, end;
 
     Value v;
 
     const ErrorHandling errorHandling;
 
     enum Token { String, Number, True, False, Null,
                  ArrayOpen, ArrayClose,
                  ObjectOpen, ObjectClose,
@@ -102,17 +102,17 @@ class MOZ_STACK_CLASS JSONParser : priva
 #ifdef DEBUG
     Token lastToken;
 #endif
 
   public:
     /* Public API */
 
     /* Create a parser for the provided JSON data. */
-    JSONParser(JSContext *cx, JS::StableCharPtr data, size_t length,
+    JSONParser(JSContext *cx, JS::ConstTwoByteChars data, size_t length,
                ErrorHandling errorHandling = RaiseError)
       : AutoGCRooter(cx, JSONPARSER),
         cx(cx),
         current(data),
         begin(data),
         end((data + length).get(), data.get(), length),
         errorHandling(errorHandling),
         stack(cx),
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -133,17 +133,16 @@ typedef struct JSPropertySpec           
 typedef struct JSRuntime                    JSRuntime;
 typedef struct JSSecurityCallbacks          JSSecurityCallbacks;
 typedef struct JSStructuredCloneCallbacks   JSStructuredCloneCallbacks;
 typedef struct JSStructuredCloneReader      JSStructuredCloneReader;
 typedef struct JSStructuredCloneWriter      JSStructuredCloneWriter;
 typedef struct JSTracer                     JSTracer;
 
 class                                       JSFlatString;
-class                                       JSStableString;  // long story
 
 #ifdef JS_THREADSAFE
 typedef struct PRCallOnceType   JSCallOnceType;
 #else
 typedef bool                    JSCallOnceType;
 #endif
 typedef bool                    (*JSInitCallback)(void);
 
--- a/js/src/jsreflect.cpp
+++ b/js/src/jsreflect.cpp
@@ -3266,27 +3266,25 @@ 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 false;
 
-    JSStableString *stable = src->ensureStable(cx);
-    if (!stable)
+    JSFlatString *flat = src->ensureFlat(cx);
+    if (!flat)
         return false;
 
-    const StableCharPtr chars = stable->chars();
-    size_t length = stable->length();
     CompileOptions options(cx);
     options.setFileAndLine(filename, lineno);
     options.setCanLazilyParse(false);
-    Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), options, chars.get(), length,
-                                    /* foldConstants = */ false, nullptr, nullptr);
+    Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), options, flat->chars(),
+                                    flat->length(), /* foldConstants = */ false, nullptr, nullptr);
 
     serialize.setParser(&parser);
 
     ParseNode *pn = parser.parse(nullptr);
     if (!pn)
         return false;
 
     RootedValue val(cx);
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1255,28 +1255,25 @@ ScriptSource::chars(JSContext *cx, const
         }
 
         return decompressed;
     }
 #endif
     return data.source;
 }
 
-JSStableString *
+JSFlatString *
 ScriptSource::substring(JSContext *cx, uint32_t start, uint32_t stop)
 {
     JS_ASSERT(start <= stop);
     SourceDataCache::AutoSuppressPurge asp(cx);
     const jschar *chars = this->chars(cx, asp);
     if (!chars)
         return nullptr;
-    JSFlatString *flatStr = js_NewStringCopyN<CanGC>(cx, chars + start, stop - start);
-    if (!flatStr)
-        return nullptr;
-    return flatStr->ensureStable(cx);
+    return js_NewStringCopyN<CanGC>(cx, chars + start, stop - start);
 }
 
 bool
 ScriptSource::setSourceCopy(ExclusiveContext *cx, const jschar *src, uint32_t length,
                             bool argumentsNotIncluded, SourceCompressionTask *task)
 {
     JS_ASSERT(!hasSourceData());
     length_ = length;
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -425,17 +425,17 @@ class ScriptSource
         JS_ASSERT(hasSourceData());
         return length_;
     }
     bool argumentsNotIncluded() const {
         JS_ASSERT(hasSourceData());
         return argumentsNotIncluded_;
     }
     const jschar *chars(JSContext *cx, const SourceDataCache::AutoSuppressPurge &asp);
-    JSStableString *substring(JSContext *cx, uint32_t start, uint32_t stop);
+    JSFlatString *substring(JSContext *cx, uint32_t start, uint32_t stop);
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
     // XDR handling
     template <XDRMode mode>
     bool performXDR(XDRState<mode> *xdr);
 
     bool setFilename(ExclusiveContext *cx, const char *filename);
     const char *filename() const {
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2644,26 +2644,26 @@ FlattenSubstrings(JSContext *cx, const j
     }
     JS_ASSERT(pos == outputLen);
 
     buf[outputLen] = 0;
     return str;
 }
 
 static JSString *
-AppendSubstrings(JSContext *cx, Handle<JSStableString*> stableStr,
+AppendSubstrings(JSContext *cx, Handle<JSFlatString*> flatStr,
                  const StringRange *ranges, size_t rangesLen)
 {
     JS_ASSERT(rangesLen);
 
     /* For single substrings, construct a dependent string. */
     if (rangesLen == 1)
-        return js_NewDependentString(cx, stableStr, ranges[0].start, ranges[0].length);
-
-    const jschar *chars = stableStr->getChars(cx);
+        return js_NewDependentString(cx, flatStr, ranges[0].start, ranges[0].length);
+
+    const jschar *chars = flatStr->getChars(cx);
     if (!chars)
         return nullptr;
 
     /* Collect substrings into a rope */
     size_t i = 0;
     RopeBuilder rope(cx);
     RootedString part(cx, nullptr);
     while (i < rangesLen) {
@@ -2675,17 +2675,17 @@ AppendSubstrings(JSContext *cx, Handle<J
             if (substrLen + ranges[end].length > JSShortString::MAX_SHORT_LENGTH)
                 break;
             substrLen += ranges[end].length;
         }
 
         if (i == end) {
             /* Not even one range fits JSShortString, use DependentString */
             const StringRange &sr = ranges[i++];
-            part = js_NewDependentString(cx, stableStr, sr.start, sr.length);
+            part = js_NewDependentString(cx, flatStr, sr.start, sr.length);
         } else {
             /* Copy the ranges (linearly) into a JSShortString */
             part = FlattenSubstrings(cx, chars, ranges + i, end - i, substrLen);
             i = end;
         }
 
         if (!part)
             return nullptr;
@@ -2696,36 +2696,36 @@ AppendSubstrings(JSContext *cx, Handle<J
     }
 
     return rope.result();
 }
 
 static bool
 StrReplaceRegexpRemove(JSContext *cx, HandleString str, RegExpShared &re, MutableHandleValue rval)
 {
-    Rooted<JSStableString*> stableStr(cx, str->ensureStable(cx));
-    if (!stableStr)
+    Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
+    if (!flatStr)
         return false;
 
     Vector<StringRange, 16, SystemAllocPolicy> ranges;
 
-    StableCharPtr chars = stableStr->chars();
-    size_t charsLen = stableStr->length();
+    size_t charsLen = flatStr->length();
 
     MatchPair match;
     size_t startIndex = 0; /* Index used for iterating through the string. */
     size_t lastIndex = 0;  /* Index after last successful match. */
     size_t lazyIndex = 0;  /* Index before last successful match. */
 
     /* Accumulate StringRanges for unmatched substrings. */
     while (startIndex <= charsLen) {
         if (!JS_CHECK_OPERATION_LIMIT(cx))
             return false;
 
-        RegExpRunStatus status = re.executeMatchOnly(cx, chars.get(), charsLen, &startIndex, match);
+        RegExpRunStatus status =
+            re.executeMatchOnly(cx, flatStr->chars(), charsLen, &startIndex, match);
         if (status == RegExpRunStatus_Error)
             return false;
         if (status == RegExpRunStatus_Success_NotFound)
             break;
 
         /* Include the latest unmatched substring. */
         if (size_t(match.start) > lastIndex) {
             if (!ranges.append(StringRange(lastIndex, match.start - lastIndex)))
@@ -2741,37 +2741,37 @@ StrReplaceRegexpRemove(JSContext *cx, Ha
         /* Non-global removal executes at most once. */
         if (!re.global())
             break;
     }
 
     /* If unmatched, return the input string. */
     if (!lastIndex) {
         if (startIndex > 0)
-            cx->global()->getRegExpStatics()->updateLazily(cx, stableStr, &re, lazyIndex);
+            cx->global()->getRegExpStatics()->updateLazily(cx, flatStr, &re, lazyIndex);
         rval.setString(str);
         return true;
     }
 
     /* The last successful match updates the RegExpStatics. */
-    cx->global()->getRegExpStatics()->updateLazily(cx, stableStr, &re, lazyIndex);
+    cx->global()->getRegExpStatics()->updateLazily(cx, flatStr, &re, lazyIndex);
 
     /* Include any remaining part of the string. */
     if (lastIndex < charsLen) {
         if (!ranges.append(StringRange(lastIndex, charsLen - lastIndex)))
             return false;
     }
 
     /* Handle the empty string before calling .begin(). */
     if (ranges.empty()) {
         rval.setString(cx->runtime()->emptyString);
         return true;
     }
 
-    JSString *result = AppendSubstrings(cx, stableStr, ranges.begin(), ranges.length());
+    JSString *result = AppendSubstrings(cx, flatStr, ranges.begin(), ranges.length());
     if (!result)
         return false;
 
     rval.setString(result);
     return true;
 }
 
 static inline bool
@@ -3948,26 +3948,26 @@ js_InitStringClass(JSContext *cx, Handle
      */
     if (!JS_DefineFunctions(cx, global, string_functions))
         return nullptr;
 
     return proto;
 }
 
 template <AllowGC allowGC>
-JSStableString *
+JSFlatString *
 js_NewString(ThreadSafeContext *cx, jschar *chars, size_t length)
 {
-    return JSStableString::new_<allowGC>(cx, chars, length);
+    return JSFlatString::new_<allowGC>(cx, chars, length);
 }
 
-template JSStableString *
+template JSFlatString *
 js_NewString<CanGC>(ThreadSafeContext *cx, jschar *chars, size_t length);
 
-template JSStableString *
+template JSFlatString *
 js_NewString<NoGC>(ThreadSafeContext *cx, jschar *chars, size_t length);
 
 JSLinearString *
 js_NewDependentString(JSContext *cx, JSString *baseArg, size_t start, size_t length)
 {
     if (length == 0)
         return cx->emptyString();
 
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -15,17 +15,16 @@
 
 #include "gc/Rooting.h"
 #include "js/RootingAPI.h"
 #include "vm/Unicode.h"
 
 class JSAutoByteString;
 class JSFlatString;
 class JSLinearString;
-class JSStableString;
 
 namespace js {
 
 class StringBuffer;
 
 class MutatingRopeSegmentRange;
 
 template <AllowGC allowGC>
@@ -95,17 +94,17 @@ 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. */
 template <js::AllowGC allowGC>
-extern JSStableString *
+extern JSFlatString *
 js_NewString(js::ThreadSafeContext *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. */
 template <js::AllowGC allowGC>
 extern JSFlatString *
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -4381,17 +4381,17 @@ DebuggerFrame_setOnPop(JSContext *cx, un
  *
  * If |frame| is non-nullptr, evaluate as for a direct eval in that frame; |env|
  * must be either |frame|'s DebugScopeObject, or some extension of that
  * environment; either way, |frame|'s scope is where newly declared variables
  * go. In this case, |frame| must have a computed 'this' value, equal to |thisv|.
  */
 bool
 js::EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFramePtr frame,
-                  StableCharPtr chars, unsigned length, const char *filename, unsigned lineno,
+                  ConstTwoByteChars chars, unsigned length, const char *filename, unsigned lineno,
                   MutableHandleValue rval)
 {
     assertSameCompartment(cx, env, frame);
     JS_ASSERT_IF(frame, thisv.get() == frame.thisValue());
 
     JS_ASSERT(!IsPoisonedPtr(chars.get()));
 
     /*
@@ -4432,18 +4432,18 @@ DebuggerGenericEval(JSContext *cx, const
     JS_ASSERT_IF(!iter, scope && scope->is<GlobalObject>());
 
     /* Check the first argument, the eval code string. */
     if (!code.isString()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
                              fullMethodName, "string", InformalValueTypeName(code));
         return false;
     }
-    Rooted<JSStableString *> stable(cx, code.toString()->ensureStable(cx));
-    if (!stable)
+    Rooted<JSFlatString *> flat(cx, code.toString()->ensureFlat(cx));
+    if (!flat)
         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);
@@ -4537,20 +4537,21 @@ DebuggerGenericEval(JSContext *cx, const
             {
                 return false;
             }
         }
     }
 
     /* Run the code and produce the completion value. */
     RootedValue rval(cx);
-    JS::Anchor<JSString *> anchor(stable);
+    JS::Anchor<JSString *> anchor(flat);
     AbstractFramePtr frame = iter ? iter->abstractFramePtr() : NullFramePtr();
-    bool ok = EvaluateInEnv(cx, env, thisv, frame, stable->chars(), stable->length(),
-                            url ? url : "debugger eval code", lineNumber, &rval);
+    bool ok = EvaluateInEnv(cx, env, thisv, frame,
+                            ConstTwoByteChars(flat->chars(), flat->length()),
+                            flat->length(), url ? url : "debugger eval code", lineNumber, &rval);
     if (url)
         JS_free(cx, url);
     return dbg->receiveCompletionValue(ac, ok, rval, vp);
 }
 
 static bool
 DebuggerFrame_eval(JSContext *cx, unsigned argc, Value *vp)
 {
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -726,14 +726,14 @@ Debugger::onNewGlobalObject(JSContext *c
     global->compartment()->firedOnNewGlobalObject = true;
 #endif
     if (!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers))
         Debugger::slowPathOnNewGlobalObject(cx, global);
 }
 
 extern bool
 EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFramePtr frame,
-              StableCharPtr chars, unsigned length, const char *filename, unsigned lineno,
+              ConstTwoByteChars chars, unsigned length, const char *filename, unsigned lineno,
               MutableHandleValue rval);
 
 }
 
 #endif /* vm_Debugger_h */
--- a/js/src/vm/ErrorObject.cpp
+++ b/js/src/vm/ErrorObject.cpp
@@ -133,19 +133,19 @@ js::ErrorObject::getOrCreateErrorReport(
     report.lineno = lineNumber();
     report.column = columnNumber();
 
     // Message. Note that |new Error()| will result in an undefined |message|
     // slot, so we need to explicitly substitute the empty string in that case.
     RootedString message(cx, getMessage());
     if (!message)
         message = cx->runtime()->emptyString;
-    if (!message->ensureStable(cx))
+    if (!message->ensureFlat(cx))
         return nullptr;
-    report.ucmessage = message->asStable().chars().get();
+    report.ucmessage = message->asFlat().chars();
 
     // Cache and return.
     JSErrorReport *copy = CopyErrorReport(cx, &report);
     if (!copy)
         return nullptr;
     setReservedSlot(ERROR_REPORT_SLOT, PrivateValue(copy));
     return copy;
 }
--- a/js/src/vm/OldDebugAPI.cpp
+++ b/js/src/vm/OldDebugAPI.cpp
@@ -1296,17 +1296,17 @@ JSAbstractFramePtr::evaluateUCInStackFra
         return false;
 
     AbstractFramePtr frame(*this);
     if (!ComputeThis(cx, frame))
         return false;
     RootedValue thisv(cx, frame.thisValue());
 
     js::AutoCompartment ac(cx, env);
-    return EvaluateInEnv(cx, env, thisv, frame, StableCharPtr(chars, length), length,
+    return EvaluateInEnv(cx, env, thisv, frame, ConstTwoByteChars(chars, length), length,
                          filename, lineno, rval);
 }
 
 JSBrokenFrameIterator::JSBrokenFrameIterator(JSContext *cx)
 {
     // Show all frames on the stack whose principal is subsumed by the current principal.
     NonBuiltinScriptFrameIter iter(cx,
                                    ScriptFrameIter::ALL_CONTEXTS,
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -904,20 +904,20 @@ CloneObject(JSContext *cx, HandleObject 
         clone = RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr);
     } else if (srcObj->is<DateObject>()) {
         clone = JS_NewDateObjectMsec(cx, srcObj->as<DateObject>().UTCTime().toNumber());
     } else if (srcObj->is<BooleanObject>()) {
         clone = BooleanObject::create(cx, srcObj->as<BooleanObject>().unbox());
     } else if (srcObj->is<NumberObject>()) {
         clone = NumberObject::create(cx, srcObj->as<NumberObject>().unbox());
     } else if (srcObj->is<StringObject>()) {
-        Rooted<JSStableString*> str(cx, srcObj->as<StringObject>().unbox()->ensureStable(cx));
+        Rooted<JSFlatString*> str(cx, srcObj->as<StringObject>().unbox()->ensureFlat(cx));
         if (!str)
             return nullptr;
-        str = js_NewStringCopyN<CanGC>(cx, str->chars().get(), str->length())->ensureStable(cx);
+        str = js_NewStringCopyN<CanGC>(cx, str->chars(), str->length());
         if (!str)
             return nullptr;
         clone = StringObject::create(cx, str);
     } else if (srcObj->is<ArrayObject>()) {
         clone = NewDenseEmptyArray(cx, nullptr, TenuredObject);
     } else {
         JS_ASSERT(srcObj->isNative());
         clone = NewObjectWithGivenProto(cx, srcObj->getClass(), nullptr, cx->global(),
@@ -942,20 +942,20 @@ CloneValue(JSContext *cx, MutableHandleV
         RootedObject obj(cx, &vp.toObject());
         RootedObject clone(cx, CloneObject(cx, obj, clonedObjects));
         if (!clone)
             return false;
         vp.setObject(*clone);
     } else if (vp.isBoolean() || vp.isNumber() || vp.isNullOrUndefined()) {
         // Nothing to do here: these are represented inline in the value
     } else if (vp.isString()) {
-        Rooted<JSStableString*> str(cx, vp.toString()->ensureStable(cx));
+        Rooted<JSFlatString*> str(cx, vp.toString()->ensureFlat(cx));
         if (!str)
             return false;
-        RootedString clone(cx, js_NewStringCopyN<CanGC>(cx, str->chars().get(), str->length()));
+        RootedString clone(cx, js_NewStringCopyN<CanGC>(cx, str->chars(), str->length()));
         if (!clone)
             return false;
         vp.setString(clone);
     } else {
         MOZ_ASSUME_UNREACHABLE("Self-hosting CloneValue can't clone given value.");
     }
     return true;
 }
--- a/js/src/vm/String-inl.h
+++ b/js/src/vm/String-inl.h
@@ -35,17 +35,17 @@ NewShortString(ThreadSafeContext *cx, JS
     for (size_t i = 0; i < len; ++i)
         p[i] = static_cast<jschar>(chars[i]);
     p[len] = '\0';
     return str;
 }
 
 template <AllowGC allowGC>
 static MOZ_ALWAYS_INLINE JSInlineString *
-NewShortString(ExclusiveContext *cx, JS::StableTwoByteChars chars)
+NewShortString(ExclusiveContext *cx, JS::TwoByteChars chars)
 {
     size_t len = chars.length();
 
     /*
      * Don't bother trying to find a static atom; measurement shows that not
      * many get here (for one, Atomize is catching them).
      */
     JS_ASSERT(JSShortString::lengthFits(len));
@@ -56,44 +56,16 @@ NewShortString(ExclusiveContext *cx, JS:
         return nullptr;
 
     jschar *storage = str->init(len);
     mozilla::PodCopy(storage, chars.start().get(), len);
     storage[len] = 0;
     return str;
 }
 
-template <AllowGC allowGC>
-static MOZ_ALWAYS_INLINE JSInlineString *
-NewShortString(ExclusiveContext *cx, JS::TwoByteChars chars)
-{
-    size_t len = chars.length();
-
-    /*
-     * Don't bother trying to find a static atom; measurement shows that not
-     * many get here (for one, Atomize is catching them).
-     */
-    JS_ASSERT(JSShortString::lengthFits(len));
-    JSInlineString *str = JSInlineString::lengthFits(len)
-                          ? JSInlineString::new_<NoGC>(cx)
-                          : JSShortString::new_<NoGC>(cx);
-    if (!str) {
-        if (!allowGC)
-            return nullptr;
-        jschar tmp[JSShortString::MAX_SHORT_LENGTH];
-        mozilla::PodCopy(tmp, chars.start().get(), len);
-        return NewShortString<CanGC>(cx, JS::StableTwoByteChars(tmp, len));
-    }
-
-    jschar *storage = str->init(len);
-    mozilla::PodCopy(storage, chars.start().get(), len);
-    storage[len] = 0;
-    return str;
-}
-
 static inline void
 StringWriteBarrierPost(js::ThreadSafeContext *maybecx, JSString **strp)
 {
 }
 
 static inline void
 StringWriteBarrierPostRemove(js::ThreadSafeContext *maybecx, JSString **strp)
 {
@@ -205,53 +177,53 @@ JSDependentString::new_(js::ExclusiveCon
 
 inline void
 JSString::markBase(JSTracer *trc)
 {
     JS_ASSERT(hasBase());
     js::gc::MarkStringUnbarriered(trc, &d.s.u2.base, "base");
 }
 
+MOZ_ALWAYS_INLINE void
+JSFlatString::init(const jschar *chars, size_t length)
+{
+    d.lengthAndFlags = buildLengthAndFlags(length, FIXED_FLAGS);
+    d.u1.chars = chars;
+}
+
+template <js::AllowGC allowGC>
+MOZ_ALWAYS_INLINE JSFlatString *
+JSFlatString::new_(js::ThreadSafeContext *cx, const jschar *chars, size_t length)
+{
+    JS_ASSERT(chars[length] == jschar(0));
+
+    if (!validateLength(cx, length))
+        return nullptr;
+    JSFlatString *str = (JSFlatString *)js_NewGCString<allowGC>(cx);
+    if (!str)
+        return nullptr;
+    str->init(chars, length);
+    return str;
+}
+
 inline js::PropertyName *
 JSFlatString::toPropertyName(JSContext *cx)
 {
 #ifdef DEBUG
     uint32_t dummy;
     JS_ASSERT(!isIndex(&dummy));
 #endif
     if (isAtom())
         return asAtom().asPropertyName();
     JSAtom *atom = js::AtomizeString(cx, this);
     if (!atom)
         return nullptr;
     return atom->asPropertyName();
 }
 
-MOZ_ALWAYS_INLINE void
-JSStableString::init(const jschar *chars, size_t length)
-{
-    d.lengthAndFlags = buildLengthAndFlags(length, FIXED_FLAGS);
-    d.u1.chars = chars;
-}
-
-template <js::AllowGC allowGC>
-MOZ_ALWAYS_INLINE JSStableString *
-JSStableString::new_(js::ThreadSafeContext *cx, const jschar *chars, size_t length)
-{
-    JS_ASSERT(chars[length] == jschar(0));
-
-    if (!validateLength(cx, length))
-        return nullptr;
-    JSStableString *str = (JSStableString *)js_NewGCString<allowGC>(cx);
-    if (!str)
-        return nullptr;
-    str->init(chars, length);
-    return str;
-}
-
 template <js::AllowGC allowGC>
 MOZ_ALWAYS_INLINE JSInlineString *
 JSInlineString::new_(js::ThreadSafeContext *cx)
 {
     return (JSInlineString *)js_NewGCString<allowGC>(cx);
 }
 
 MOZ_ALWAYS_INLINE jschar *
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -65,19 +65,19 @@ JSString::sizeOfExcludingThis(mozilla::M
     // JSExternalString: don't count, the chars could be stored anywhere.
     if (isExternal())
         return 0;
 
     // JSInlineString, JSShortString [JSInlineAtom, JSShortAtom]: the chars are inline.
     if (isInline())
         return 0;
 
-    // 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.
+    // JSAtom, 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.
     JSFlatString &flat = asFlat();
     return mallocSizeOf(flat.chars());
 }
 
 #ifdef DEBUG
 
 void
 JSString::dumpChars(const jschar *s, size_t n)
@@ -465,31 +465,16 @@ JSDependentString::undepend(ExclusiveCon
      * 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->asFlat();
 }
 
-JSStableString *
-JSInlineString::uninline(ExclusiveContext *maybecx)
-{
-    JS_ASSERT(isInline());
-    size_t n = length();
-    jschar *news = maybecx ? maybecx->pod_malloc<jschar>(n + 1) : js_pod_malloc<jschar>(n + 1);
-    if (!news)
-        return nullptr;
-    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;
 
     if (!JS7_ISDEC(ch))
         return false;
@@ -596,25 +581,25 @@ const StaticStrings::SmallChar StaticStr
 bool
 StaticStrings::init(JSContext *cx)
 {
     AutoLockForExclusiveAccess lock(cx);
     AutoCompartment ac(cx, cx->runtime()->atomsCompartment());
 
     for (uint32_t i = 0; i < UNIT_STATIC_LIMIT; i++) {
         jschar buffer[] = { jschar(i), '\0' };
-        JSFlatString *s = js_NewStringCopyN<CanGC>(cx, buffer, 1);
+        JSFlatString *s = js_NewStringCopyN<NoGC>(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' };
-        JSFlatString *s = js_NewStringCopyN<CanGC>(cx, buffer, 2);
+        JSFlatString *s = js_NewStringCopyN<NoGC>(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'];
@@ -622,17 +607,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' };
-            JSFlatString *s = js_NewStringCopyN<CanGC>(cx, buffer, 3);
+            JSFlatString *s = js_NewStringCopyN<NoGC>(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
@@ -20,17 +20,16 @@
 #include "gc/Rooting.h"
 #include "js/CharacterEncoding.h"
 #include "js/RootingAPI.h"
 
 class JSDependentString;
 class JSExtensibleString;
 class JSExternalString;
 class JSInlineString;
-class JSStableString;
 class JSRope;
 
 namespace js {
 
 class StaticStrings;
 class PropertyName;
 
 /* The buffer length required to contain any unsigned 32-bit integer. */
@@ -91,18 +90,16 @@ static const size_t UINT32_CHAR_BUFFER_L
  *  | JSRope                    leftChild, rightChild / -
  *  |
  * JSLinearString (abstract)    chars / might be null-terminated
  *  | \
  *  | JSDependentString         base / -
  *  |
  * 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
  *  |         \
@@ -116,17 +113,16 @@ static const size_t UINT32_CHAR_BUFFER_L
  * 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).
  */
 
@@ -183,17 +179,16 @@ class JSString : public js::gc::Barriere
      *   Rope         0000       0000
      *   Linear       -         !0000
      *   HasBase      -          xxx1
      *   Dependent    0001       0001
      *   Flat         -          isLinear && !isDependent
      *   Undepended   0011       0011
      *   Extensible   0010       0010
      *   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)
      *
@@ -286,17 +281,16 @@ class JSString : public js::gc::Barriere
                                  js::ScopedJSFreePtr<jschar> &out) const;
     inline bool copyNonPureCharsZ(js::ThreadSafeContext *cx,
                                   js::ScopedJSFreePtr<jschar> &out) const;
 
     /* Fallible conversions to more-derived string types. */
 
     inline JSLinearString *ensureLinear(js::ExclusiveContext *cx);
     inline JSFlatString *ensureFlat(js::ExclusiveContext *cx);
-    inline JSStableString *ensureStable(js::ExclusiveContext *cx);
 
     static bool ensureLinear(js::ExclusiveContext *cx, JSString *str) {
         return str->ensureLinear(cx) != nullptr;
     }
 
     /* Type query and debug-checked casts */
 
     MOZ_ALWAYS_INLINE
@@ -362,22 +356,16 @@ class JSString : public js::gc::Barriere
     MOZ_ALWAYS_INLINE
     JSInlineString &asInline() const {
         JS_ASSERT(isInline());
         return *(JSInlineString *)this;
     }
 
     bool isShort() const;
 
-    MOZ_ALWAYS_INLINE
-    JSStableString &asStable() const {
-        JS_ASSERT(!isInline());
-        return *(JSStableString *)this;
-    }
-
     /* For hot code, prefer other type queries. */
     bool isExternal() const;
 
     MOZ_ALWAYS_INLINE
     JSExternalString &asExternal() const {
         JS_ASSERT(isExternal());
         return *(JSExternalString *)this;
     }
@@ -539,17 +527,23 @@ class JSFlatString : public JSLinearStri
 {
     /* Vacuous and therefore unimplemented. */
     JSFlatString *ensureFlat(JSContext *cx) MOZ_DELETE;
     bool isFlat() const MOZ_DELETE;
     JSFlatString &asFlat() const MOZ_DELETE;
 
     bool isIndexSlow(uint32_t *indexp) const;
 
+    void init(const jschar *chars, size_t length);
+
   public:
+    template <js::AllowGC allowGC>
+    static inline JSFlatString *new_(js::ThreadSafeContext *cx,
+                                     const jschar *chars, size_t length);
+
     MOZ_ALWAYS_INLINE
     const jschar *charsZ() const {
         JS_ASSERT(JSString::isFlat());
         return chars();
     }
 
     /*
      * Returns true if this string's characters store an unsigned 32-bit
@@ -578,98 +572,16 @@ class JSFlatString : public JSLinearStri
         return &asAtom();
     }
 
     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:
-    template <js::AllowGC allowGC>
-    static inline JSStableString *new_(js::ThreadSafeContext *cx,
-                                       const jschar *chars, size_t length);
-
-    MOZ_ALWAYS_INLINE
-    JS::StableCharPtr chars() const {
-        JS_ASSERT(!JSString::isInline());
-        return JS::StableCharPtr(d.u1.chars, length());
-    }
-
-    MOZ_ALWAYS_INLINE
-    JS::StableTwoByteChars range() const {
-        JS_ASSERT(!JSString::isInline());
-        return JS::StableTwoByteChars(d.u1.chars, length());
-    }
-};
-
-JS_STATIC_ASSERT(sizeof(JSStableString) == sizeof(JSString));
-
-#if !(defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING))
-namespace JS {
-/*
- * Specialization of Rooted<T> to explicitly root the string rather than
- * relying on conservative stack scanning.
- *
- * In exact-gc builds, Rooted<T> already keeps the T reachable, so this hack is
- * ifdef'd out. In non-exact-gc builds, conservative scanning would ordinarily
- * pick up the slack. However in the case where the Rooted pointer is no longer
- * used, but some subobject or malloc'd memory with the same lifetime may be
- * used, conservative scanning can fail. JSStableString's chars() method makes
- * it particularly attractive to use that way, so we explicitly keep the
- * JSString gc-reachable for the full lifetime of the Rooted<JSStableString *>.
- *
- * It would suffice simply to force the pointer to remain on the stack, a la
- * JS::Anchor<T>, but for some reason using that voodoo here seems to cause
- * some compilers (clang, VC++ with PGO) to generate incorrect code.
- */
-template <>
-class Rooted<JSStableString *>
-{
-  public:
-    Rooted(JSContext *cx, JSStableString *initial = nullptr
-           MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-      : rooter(cx, initial)
-    {
-        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    }
-
-    operator JSStableString *() const { return get(); }
-    JSStableString * operator ->() const { return get(); }
-    JSStableString ** address() { return reinterpret_cast<JSStableString **>(rooter.addr()); }
-    JSStableString * const * address() const {
-        return reinterpret_cast<JSStableString * const *>(rooter.addr());
-    }
-    JSStableString * get() const { return static_cast<JSStableString *>(rooter.string()); }
-
-    Rooted & operator =(JSStableString *value)
-    {
-        JS_ASSERT(!js::GCMethods<JSStableString *>::poisoned(value));
-        rooter.setString(value);
-        return *this;
-    }
-
-    Rooted & operator =(const Rooted &value)
-    {
-        rooter.setString(value.get());
-        return *this;
-    }
-
-  private:
-    JS::AutoStringRooter rooter;
-    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-    Rooted(const Rooted &) MOZ_DELETE;
-};
-}
-#endif
-
 class JSExtensibleString : public JSFlatString
 {
     /* Vacuous and therefore unimplemented. */
     bool isExtensible() const MOZ_DELETE;
     JSExtensibleString &asExtensible() const MOZ_DELETE;
 
   public:
     MOZ_ALWAYS_INLINE
@@ -686,18 +598,16 @@ class JSInlineString : public JSFlatStri
     static const size_t MAX_INLINE_LENGTH = NUM_INLINE_CHARS - 1;
 
   public:
     template <js::AllowGC allowGC>
     static inline JSInlineString *new_(js::ThreadSafeContext *cx);
 
     inline jschar *init(size_t length);
 
-    JSStableString *uninline(js::ExclusiveContext *cx);
-
     inline void resetLength(size_t length);
 
     static bool lengthFits(size_t length) {
         return length <= MAX_INLINE_LENGTH;
     }
 
     static size_t offsetOfInlineStorage() {
         return offsetof(JSInlineString, d.inlineStorage);
@@ -1089,41 +999,16 @@ JSString::ensureFlat(js::ExclusiveContex
 {
     return isFlat()
            ? &asFlat()
            : isDependent()
              ? asDependent().undepend(cx)
              : asRope().flatten(cx);
 }
 
-MOZ_ALWAYS_INLINE JSStableString *
-JSString::ensureStable(js::ExclusiveContext *maybecx)
-{
-    if (isRope()) {
-        JSFlatString *flat = asRope().flatten(maybecx);
-        if (!flat)
-            return nullptr;
-        JS_ASSERT(!flat->isInline());
-        return &flat->asStable();
-    }
-
-    if (isDependent()) {
-        JSFlatString *flat = asDependent().undepend(maybecx);
-        if (!flat)
-            return nullptr;
-        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.h
+++ b/js/src/vm/StringBuffer.h
@@ -42,17 +42,17 @@ class StringBuffer
 
   public:
     explicit StringBuffer(ExclusiveContext *cx) : cb(cx) { }
 
     inline bool reserve(size_t len) { return cb.reserve(len); }
     inline bool resize(size_t len) { return cb.resize(len); }
     inline bool append(const jschar c) { return cb.append(c); }
     inline bool append(const jschar *chars, size_t len) { return cb.append(chars, len); }
-    inline bool append(const JS::CharPtr chars, size_t len) { return cb.append(chars.get(), len); }
+    inline bool append(const JS::ConstCharPtr chars, size_t len) { return cb.append(chars.get(), len); }
     inline bool append(const jschar *begin, const jschar *end) { return cb.append(begin, end); }
     inline bool append(JSString *str);
     inline bool append(JSLinearString *str);
     inline bool appendN(const jschar c, size_t n) { return cb.appendN(c, n); }
     inline bool appendInflated(const char *cstr, size_t len);
 
     template <size_t ArrayLength>
     bool append(const char (&array)[ArrayLength]) {
@@ -61,17 +61,17 @@ class StringBuffer
 
     /* Infallible variants usable when the corresponding space is reserved. */
     void infallibleAppend(const jschar c) {
         cb.infallibleAppend(c);
     }
     void infallibleAppend(const jschar *chars, size_t len) {
         cb.infallibleAppend(chars, len);
     }
-    void infallibleAppend(const JS::CharPtr chars, size_t len) {
+    void infallibleAppend(const JS::ConstCharPtr chars, size_t len) {
         cb.infallibleAppend(chars.get(), len);
     }
     void infallibleAppend(const jschar *begin, const jschar *end) {
         cb.infallibleAppend(begin, end);
     }
     void infallibleAppendN(const jschar c, size_t n) {
         cb.infallibleAppendN(c, n);
     }
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -1334,24 +1334,22 @@ JSStructuredCloneReader::startRead(Value
         if (tag2 != SCTAG_STRING) {
             JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr,
                                  JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
             return false;
         }
         JSString *str = readString(nchars);
         if (!str)
             return false;
-        JSStableString *stable = str->ensureStable(context());
-        if (!stable)
+        JSFlatString *flat = str->ensureFlat(context());
+        if (!flat)
             return false;
 
-        size_t length = stable->length();
-        const StableCharPtr chars = stable->chars();
-        RegExpObject *reobj = RegExpObject::createNoStatics(context(), chars.get(), length,
-                                                            flags, nullptr);
+        RegExpObject *reobj = RegExpObject::createNoStatics(context(), flat->chars(),
+                                                            flat->length(), flags, nullptr);
         if (!reobj)
             return false;
         vp->setObject(*reobj);
         break;
       }
 
       case SCTAG_ARRAY_OBJECT:
       case SCTAG_OBJECT_OBJECT: {