Bug 798624 - Specialize low-level character access to JSStableString; r=luke, rs=Waldo
☠☠ backed out by 14684be81166 ☠ ☠
authorTerrence Cole <terrence@mozilla.com>
Mon, 08 Oct 2012 15:04:36 -0700
changeset 109817 754a1efb5b37574eeec5d5841f34514ea37ea0a7
parent 109816 4de56a4bfa5f6033280fa71f94657f14f8b09df3
child 109818 3cfef9371c037d98dadff8cf762701839f15054e
push id84
push usernmatsakis@mozilla.com
push dateThu, 11 Oct 2012 23:26:24 +0000
reviewersluke, Waldo
bugs798624
milestone19.0a1
Bug 798624 - Specialize low-level character access to JSStableString; r=luke, rs=Waldo Implements JSStableString::chars() and pushes StableCharPtr into the interface at several key choke-points. This will solidly enforce the requirement that we have uninlined jschar*s in places that we may GC with a jschar* on the stack.
content/html/content/test/test_bug780993.html
js/src/builtin/Eval.cpp
js/src/builtin/RegExp.cpp
js/src/builtin/RegExp.h
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeCompiler.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/TokenStream.cpp
js/src/frontend/TokenStream.h
js/src/jsapi-tests/testVersion.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsatom.cpp
js/src/jsclone.cpp
js/src/jscompartment.cpp
js/src/jsdbgapi.cpp
js/src/jsexn.cpp
js/src/jsfun.cpp
js/src/json.cpp
js/src/json.h
js/src/jsonparser.h
js/src/jsreflect.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsstr.cpp
js/src/jsxml.cpp
js/src/shell/js.cpp
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/RegExpObject.cpp
js/src/vm/RegExpObject.h
js/src/vm/String.h
js/src/vm/StringBuffer.h
mfbt/RangedPtr.h
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -202,17 +202,17 @@ EvalKernel(JSContext *cx, const CallArgs
             return false;
         thisv = ObjectValue(*thisobj);
     }
 
     Rooted<JSStableString*> stableStr(cx, str->ensureStable(cx));
     if (!stableStr)
         return false;
 
-    const jschar *chars = stableStr->chars();
+    StableCharPtr 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
@@ -266,18 +266,17 @@ EvalKernel(JSContext *cx, const CallArgs
 
         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, stableStr,
-                                                     staticLevel);
+                                                     chars, length, stableStr, staticLevel);
         if (!compiled)
             return false;
 
         esg.setNewScript(compiled);
     }
 
     return ExecuteKernel(cx, esg.script(), *scopeobj, thisv, ExecuteType(evalType),
                          NULL /* evalInFrame */, args.rval().address());
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -44,17 +44,17 @@ class RegExpMatchBuilder
     bool setInput(JSString *str) {
         JS_ASSERT(str);
         RootedValue value(cx, StringValue(str));
         return setProperty(cx->names().input, value);
     }
 };
 
 static bool
-CreateRegExpMatchResult(JSContext *cx, JSString *input_, const jschar *chars, size_t length,
+CreateRegExpMatchResult(JSContext *cx, JSString *input_, StableCharPtr chars, size_t length,
                         MatchPairs *matchPairs, Value *rval)
 {
     RootedString input(cx, input_);
 
     /*
      * Create the (slow) result array for a match.
      *
      * Array contents:
@@ -63,17 +63,17 @@ CreateRegExpMatchResult(JSContext *cx, J
      *  input:          input string
      *  index:          start index for the match
      */
     RootedObject array(cx, NewSlowEmptyArray(cx));
     if (!array)
         return false;
 
     if (!input) {
-        input = js_NewStringCopyN(cx, chars, length);
+        input = js_NewStringCopyN(cx, chars.get(), length);
         if (!input)
             return false;
     }
 
     RegExpMatchBuilder builder(cx, array);
     RootedValue undefinedValue(cx, UndefinedValue());
 
     for (size_t i = 0; i < matchPairs->pairCount(); ++i) {
@@ -97,17 +97,17 @@ CreateRegExpMatchResult(JSContext *cx, J
 
     *rval = ObjectValue(*array);
     return true;
 }
 
 template <class T>
 bool
 ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, T &re, JSLinearString *input,
-                  const jschar *chars, size_t length,
+                  StableCharPtr chars, size_t length,
                   size_t *lastIndex, RegExpExecType type, Value *rval)
 {
     LifoAllocScope allocScope(&cx->tempLifoAlloc());
     MatchPairs *matchPairs = NULL;
     RegExpRunStatus status = re.execute(cx, chars, length, lastIndex, &matchPairs);
 
     switch (status) {
       case RegExpRunStatus_Error:
@@ -129,26 +129,26 @@ ExecuteRegExpImpl(JSContext *cx, RegExpS
         *rval = BooleanValue(true);
         return true;
     }
 
     return CreateRegExpMatchResult(cx, input, chars, length, matchPairs, rval);
 }
 
 bool
-js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpShared &shared, JSLinearString *input,
-                  const jschar *chars, size_t length,
+js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpShared &shared,
+                  Handle<JSStableString*> input, StableCharPtr chars, size_t length,
                   size_t *lastIndex, RegExpExecType type, Value *rval)
 {
     return ExecuteRegExpImpl(cx, res, shared, input, chars, length, lastIndex, type, rval);
 }
 
 bool
-js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject &reobj, JSLinearString *input,
-                  const jschar *chars, size_t length,
+js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject &reobj,
+                  Handle<JSStableString*> input, StableCharPtr chars, size_t length,
                   size_t *lastIndex, RegExpExecType type, Value *rval)
 {
     return ExecuteRegExpImpl(cx, res, reobj, input, chars, length, lastIndex, type, rval);
 }
 
 /* Note: returns the original if no escaping need be performed. */
 static JSAtom *
 EscapeNakedForwardSlashes(JSContext *cx, JSAtom *unescaped)
@@ -575,45 +575,45 @@ js::ExecuteRegExp(JSContext *cx, RegExpE
     } else {
         if (!reobj->getShared(cx, &re))
             return false;
     }
 
     RegExpStatics *res = cx->regExpStatics();
 
     /* Step 3. */
-    Rooted<JSLinearString*> linearInput(cx, string->ensureLinear(cx));
-    if (!linearInput)
+    Rooted<JSStableString*> stableInput(cx, string->ensureStable(cx));
+    if (!stableInput)
         return false;
 
     /* Step 4. */
     Value lastIndex = reobj->getLastIndex();
 
     /* Step 5. */
     double i;
     if (!ToInteger(cx, lastIndex, &i))
         return false;
 
     /* Steps 6-7 (with sticky extension). */
     if (!re->global() && !re->sticky())
         i = 0;
 
-    const jschar *chars = linearInput->chars();
-    size_t length = linearInput->length();
+    StableCharPtr chars = stableInput->chars();
+    size_t length = stableInput->length();
 
     /* Step 9a. */
     if (i < 0 || i > length) {
         reobj->zeroLastIndex();
         rval.setNull();
         return true;
     }
 
     /* Steps 8-21. */
     size_t lastIndexInt(i);
-    if (!ExecuteRegExp(cx, res, *re, linearInput.get(), chars, length, &lastIndexInt, execType,
+    if (!ExecuteRegExp(cx, res, *re, stableInput, chars, length, &lastIndexInt, execType,
                        rval.address())) {
         return false;
     }
 
     /* Step 11 (with sticky extension). */
     if (re->global() || (!rval.isNull() && re->sticky())) {
         if (rval.isNull())
             reobj->zeroLastIndex();
--- a/js/src/builtin/RegExp.h
+++ b/js/src/builtin/RegExp.h
@@ -22,22 +22,22 @@ namespace js {
 
 /*
  * |res| may be null if the |RegExpStatics| are not to be updated.
  * |input| may be null if there is no |JSString| corresponding to
  * |chars| and |length|.
  */
 bool
 ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject &reobj,
-              JSLinearString *input, const jschar *chars, size_t length,
+              Handle<JSStableString*> input, StableCharPtr chars, size_t length,
               size_t *lastIndex, RegExpExecType type, Value *rval);
 
 bool
 ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpShared &shared,
-              JSLinearString *input, const jschar *chars, size_t length,
+              Handle<JSStableString*> input, StableCharPtr chars, size_t length,
               size_t *lastIndex, RegExpExecType type, Value *rval);
 
 bool
 ExecuteRegExp(JSContext *cx, RegExpExecType execType, HandleObject regexp,
               HandleString string, MutableHandleValue rval);
 
 extern JSBool
 regexp_exec(JSContext *cx, unsigned argc, Value *vp);
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -44,17 +44,17 @@ SetSourceMap(JSContext *cx, TokenStream 
             return false;
     }
     return true;
 }
 
 JSScript *
 frontend::CompileScript(JSContext *cx, HandleObject scopeChain, StackFrame *callerFrame,
                         const CompileOptions &options,
-                        const jschar *chars, size_t length,
+                        StableCharPtr chars, size_t length,
                         JSString *source_ /* = NULL */,
                         unsigned staticLevel /* = 0 */)
 {
     RootedString source(cx, source_);
 
     class ProbesManager
     {
         const char* filename;
@@ -245,17 +245,17 @@ frontend::CompileScript(JSContext *cx, H
 
     return script;
 }
 
 // Compile a JS function body, which might appear as the value of an event
 // handler attribute in an HTML <INPUT> tag, or in a Function() constructor.
 bool
 frontend::CompileFunctionBody(JSContext *cx, HandleFunction fun, CompileOptions options,
-                              const AutoNameVector &formals, const jschar *chars, size_t length)
+                              const AutoNameVector &formals, StableCharPtr chars, size_t length)
 {
     if (!CheckLength(cx, length))
         return false;
     ScriptSource *ss = cx->new_<ScriptSource>();
     if (!ss)
         return false;
     ScriptSourceHolder ssh(cx->runtime, ss);
     SourceCompressionToken sct(cx);
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -10,19 +10,19 @@
 
 #include "frontend/Parser.h"
 
 namespace js {
 namespace frontend {
 
 JSScript *
 CompileScript(JSContext *cx, HandleObject scopeChain, StackFrame *callerFrame,
-              const CompileOptions &options, const jschar *chars, size_t length,
+              const CompileOptions &options, StableCharPtr chars, size_t length,
               JSString *source_ = NULL, unsigned staticLevel = 0);
 
 bool
 CompileFunctionBody(JSContext *cx, HandleFunction fun, CompileOptions options,
-                    const AutoNameVector &formals, const jschar *chars, size_t length);
+                    const AutoNameVector &formals, StableCharPtr chars, size_t length);
 
 } /* namespace frontend */
 } /* namespace js */
 
 #endif /* BytecodeCompiler_h__ */
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -318,17 +318,17 @@ ParseContext::generateFunctionBindings(J
     FunctionBox *funbox = sc->asFunbox();
     if (bindings->hasAnyAliasedBindings() || funbox->hasExtensibleScope())
         funbox->function()->flags |= JSFUN_HEAVYWEIGHT;
 
     return true;
 }
 
 Parser::Parser(JSContext *cx, const CompileOptions &options,
-               const jschar *chars, size_t length, bool foldConstants)
+               StableCharPtr chars, size_t length, bool foldConstants)
   : AutoGCRooter(cx, PARSER),
     context(cx),
     strictModeGetter(thisForCtor()),
     tokenStream(cx, options, chars, length, &strictModeGetter),
     tempPoolMark(NULL),
     allocator(cx),
     traceListHead(NULL),
     pc(NULL),
@@ -6936,18 +6936,18 @@ Parser::primaryExpr(TokenKind tt, bool a
         break;
 
       case TOK_REGEXP:
       {
         pn = NullaryNode::create(PNK_REGEXP, this);
         if (!pn)
             return NULL;
 
-        const jschar *chars = tokenStream.getTokenbuf().begin();
         size_t length = tokenStream.getTokenbuf().length();
+        const StableCharPtr chars(tokenStream.getTokenbuf().begin(), length);
         RegExpFlag flags = tokenStream.currentToken().regExpFlags();
         RegExpStatics *res = context->regExpStatics();
 
         Rooted<RegExpObject*> reobj(context);
         if (context->hasfp())
             reobj = RegExpObject::create(context, res, chars, length, flags, &tokenStream);
         else
             reobj = RegExpObject::createNoStatics(context, chars, length, flags, &tokenStream);
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -263,17 +263,17 @@ struct Parser : private AutoGCRooter
      * Additionally, the special syntax %_CallName(receiver, ...args, fun) is
      * supported, for which bytecode is emitted that invokes |fun| with
      * |receiver| as the this-object and ...args as the arguments..
      */
     const bool          selfHostingMode:1;
 
   public:
     Parser(JSContext *cx, const CompileOptions &options,
-           const jschar *chars, size_t length, bool foldConstants);
+           StableCharPtr chars, size_t length, bool foldConstants);
     ~Parser();
 
     friend void AutoGCRooter::trace(JSTracer *trc);
 
     /*
      * Initialize a parser. The compiler owns the arena pool "tops-of-stack"
      * space above the current JSContext.tempLifoAlloc mark. This means you
      * cannot allocate from tempLifoAlloc and save the pointer beyond the next
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -112,29 +112,28 @@ frontend::IsIdentifier(JSLinearString *s
 
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4351)
 #endif
 
 /* Initialize members that aren't initialized in |init|. */
 TokenStream::TokenStream(JSContext *cx, const CompileOptions &options,
-                         const jschar *base, size_t length, StrictModeGetter *smg)
+                         StableCharPtr base, size_t length, StrictModeGetter *smg)
   : tokens(),
     tokensRoot(cx, &tokens),
     cursor(),
     lookahead(),
     lineno(options.lineno),
     flags(),
-    linebase(base),
+    linebase(base.get()),
     prevLinebase(NULL),
     linebaseRoot(cx, &linebase),
     prevLinebaseRoot(cx, &prevLinebase),
-    userbuf(base, length),
-    userbufRoot(cx, &userbuf),
+    userbuf(base.get(), length),
     filename(options.filename),
     sourceMap(NULL),
     listenerTSData(),
     tokenbuf(cx),
     version(options.version),
     allowXML(VersionHasAllowXML(options.version)),
     moarXML(VersionHasMoarXML(options.version)),
     cx(cx),
@@ -144,17 +143,17 @@ TokenStream::TokenStream(JSContext *cx, 
 {
     if (originPrincipals)
         JS_HoldPrincipals(originPrincipals);
 
     JSSourceHandler listener = cx->runtime->debugHooks.sourceHandler;
     void *listenerData = cx->runtime->debugHooks.sourceHandlerData;
 
     if (listener)
-        listener(options.filename, options.lineno, base, length, &listenerTSData, listenerData);
+        listener(options.filename, options.lineno, base.get(), length, &listenerTSData, listenerData);
 
     /*
      * This table holds all the token kinds that satisfy these properties:
      * - A single char long.
      * - Cannot be a prefix of any longer token (eg. '+' is excluded because
      *   '+=' is a valid token).
      * - Doesn't need tp->t_op set (eg. this excludes '~').
      *
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -474,17 +474,17 @@ class TokenStream
     static const size_t ntokens = 4;                /* 1 current + 3 lookahead, rounded
                                                        to power of 2 to avoid divmod by 3 */
     static const unsigned ntokensMask = ntokens - 1;
 
   public:
     typedef Vector<jschar, 32> CharBuffer;
 
     TokenStream(JSContext *cx, const CompileOptions &options,
-                const jschar *base, size_t length, StrictModeGetter *smg);
+                StableCharPtr base, size_t length, StrictModeGetter *smg);
 
     ~TokenStream();
 
     /* Accessors. */
     JSContext *getContext() const { return cx; }
     bool onCurrentLine(const TokenPos &pos) const { return lineno == pos.end.lineno; }
     const Token &currentToken() const { return tokens[cursor]; }
     bool isCurrentTokenType(TokenKind type) const {
@@ -850,17 +850,16 @@ class TokenStream
     unsigned            lookahead;      /* count of lookahead tokens */
     unsigned            lineno;         /* current line number */
     unsigned            flags;          /* flags -- see above */
     const jschar        *linebase;      /* start of current line;  points into userbuf */
     const jschar        *prevLinebase;  /* start of previous line;  NULL if on the first line */
     js::SkipRoot        linebaseRoot;
     js::SkipRoot        prevLinebaseRoot;
     TokenBuf            userbuf;        /* user input buffer */
-    js::SkipRoot        userbufRoot;
     const char          *filename;      /* input filename or null */
     jschar              *sourceMap;     /* source map's filename or null */
     void                *listenerTSData;/* listener data for this TokenStream */
     CharBuffer          tokenbuf;       /* current token string buffer */
     int8_t              oneCharTokens[128];  /* table of one-char tokens */
     bool                maybeEOL[256];       /* probabilistic EOL lookup table */
     bool                maybeStrSpecial[256];/* speeds up string scanning */
     JSVersion           version;        /* (i.e. to identify keywords) */
--- a/js/src/jsapi-tests/testVersion.cpp
+++ b/js/src/jsapi-tests/testVersion.cpp
@@ -159,17 +159,17 @@ 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]));
     JSStableString *str = JSVAL_TO_STRING(argv[0])->ensureStable(cx);
     JS_ASSERT(str);
-    return callbackData->evalVersion(str->chars(), str->length(), JSVERSION_1_6);
+    return callbackData->evalVersion(str->chars().get(), 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
@@ -344,17 +344,17 @@ JS_ConvertArgumentsVA(JSContext *cx, uns
             str = ToString(cx, *sp);
             if (!str)
                 return JS_FALSE;
             *sp = STRING_TO_JSVAL(str);
             if (c == 'W') {
                 JSStableString *stable = str->ensureStable(cx);
                 if (!stable)
                     return JS_FALSE;
-                *va_arg(ap, const jschar **) = stable->chars();
+                *va_arg(ap, const jschar **) = stable->chars().get();
             } else {
                 *va_arg(ap, JSString **) = str;
             }
             break;
           case 'o':
             if (!js_ValueToObjectOrNull(cx, *sp, &obj))
                 return JS_FALSE;
             *sp = OBJECT_TO_JSVAL(obj);
@@ -5185,17 +5185,17 @@ JS::Compile(JSContext *cx, HandleObject 
 
     JS_THREADSAFE_ASSERT(cx->compartment != cx->runtime->atomsCompartment);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
     JS_ASSERT_IF(options.principals, cx->compartment->principals == options.principals);
     AutoLastFrameCheck lfc(cx);
 
-    return frontend::CompileScript(cx, obj, NULL, options, chars, length);
+    return frontend::CompileScript(cx, obj, NULL, options, StableCharPtr(chars, length), length);
 }
 
 JSScript *
 JS::Compile(JSContext *cx, HandleObject obj, CompileOptions options,
             const char *bytes, size_t length)
 {
     jschar *chars;
     if (options.utf8)
@@ -5354,17 +5354,17 @@ JS_BufferIsCompilableUnit(JSContext *cx,
      * Return true on any out-of-memory error, so our caller doesn't try to
      * collect more buffered source.
      */
     result = JS_TRUE;
     exnState = JS_SaveExceptionState(cx);
     {
         CompileOptions options(cx);
         options.setCompileAndGo(false);
-        Parser parser(cx, options, chars, length, /* foldConstants = */ true);
+        Parser parser(cx, options, StableCharPtr(chars, length), length, /* foldConstants = */ true);
         if (parser.init()) {
             older = JS_SetErrorReporter(cx, NULL);
             if (!parser.parse(obj) &&
                 parser.tokenStream.isUnexpectedEOF()) {
                 /*
                  * We ran into an error. If it was because we ran out of
                  * source, we return false so our caller knows to try to
                  * collect more buffered source.
@@ -5467,17 +5467,17 @@ JS::CompileFunction(JSContext *cx, Handl
         if (!argAtom || !formals.append(argAtom->asPropertyName()))
             return NULL;
     }
 
     RootedFunction fun(cx, js_NewFunction(cx, NullPtr(), NULL, 0, JSFUN_INTERPRETED, obj, funAtom));
     if (!fun)
         return NULL;
 
-    if (!frontend::CompileFunctionBody(cx, fun, options, formals, chars, length))
+    if (!frontend::CompileFunctionBody(cx, fun, options, formals, StableCharPtr(chars, length), length))
         return NULL;
 
     if (obj && funAtom) {
         Rooted<jsid> id(cx, AtomToId(funAtom));
         RootedValue value(cx, ObjectValue(*fun));
         if (!JSObject::defineGeneric(cx, obj, id, value, NULL, NULL, JSPROP_ENUMERATE))
             return NULL;
     }
@@ -5670,17 +5670,18 @@ JS::Evaluate(JSContext *cx, HandleObject
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
     JS_ASSERT_IF(options.principals, cx->compartment->principals == options.principals);
 
     AutoLastFrameCheck lfc(cx);
 
     options.setCompileAndGo(true);
     options.setNoScriptRval(!rval);
-    RootedScript script(cx, frontend::CompileScript(cx, obj, NULL, options, chars, length));
+    RootedScript script(cx, frontend::CompileScript(cx, obj, NULL, options,
+                                                    StableCharPtr(chars, length), length));
     if (!script)
         return false;
 
     JS_ASSERT(script->getVersion() == options.version);
 
     return Execute(cx, script, *obj, rval);
 }
 
@@ -6105,67 +6106,67 @@ JS_PUBLIC_API(const jschar *)
 JS_GetStringCharsZ(JSContext *cx, JSString *str)
 {
     AssertHeapIsIdleOrStringIsFlat(cx, str);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, str);
     JSStableString *stable = str->ensureStable(cx);
     if (!stable)
         return NULL;
-    return stable->chars();
+    return stable->chars().get();
 }
 
 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);
     JSStableString *stable = str->ensureStable(cx);
     if (!stable)
         return NULL;
     *plength = stable->length();
-    return stable->chars();
+    return stable->chars().get();
 }
 
 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);
     JSStableString *stable = str->ensureStable(cx);
     if (!stable)
         return NULL;
     *plength = stable->length();
-    return stable->chars();
+    return stable->chars().get();
 }
 
 JS_PUBLIC_API(const jschar *)
 JS_GetInternedStringChars(JSString *str)
 {
     JS_ASSERT(str->isAtom());
     JSStableString *stable = str->ensureStable(NULL);
     if (!stable)
         return NULL;
-    return stable->chars();
+    return stable->chars().get();
 }
 
 JS_PUBLIC_API(const jschar *)
 JS_GetInternedStringCharsAndLength(JSString *str, size_t *plength)
 {
     JS_ASSERT(str->isAtom());
     JS_ASSERT(plength);
     JSStableString *stable = str->ensureStable(NULL);
     if (!stable)
         return NULL;
     *plength = stable->length();
-    return stable->chars();
+    return stable->chars().get();
 }
 
 extern JS_PUBLIC_API(JSFlatString *)
 JS_FlattenString(JSContext *cx, JSString *str)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, str);
@@ -6176,17 +6177,17 @@ JS_FlattenString(JSContext *cx, JSString
 }
 
 extern JS_PUBLIC_API(const jschar *)
 JS_GetFlatStringChars(JSFlatString *str)
 {
     JSStableString *stable = str->ensureStable(NULL);
     if (!stable)
         return NULL;
-    return str->chars();
+    return stable->chars().get();
 }
 
 JS_PUBLIC_API(JSBool)
 JS_CompareStrings(JSContext *cx, JSString *str1, JSString *str2, int32_t *result)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
@@ -6386,31 +6387,31 @@ JS_Stringify(JSContext *cx, jsval *vp, J
 
 JS_PUBLIC_API(JSBool)
 JS_ParseJSON(JSContext *cx, const jschar *chars, uint32_t len, jsval *vp)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
     RootedValue reviver(cx, NullValue()), value(cx);
-    if (!ParseJSONWithReviver(cx, chars, len, reviver, &value))
+    if (!ParseJSONWithReviver(cx, StableCharPtr(chars, len), len, reviver, &value))
         return false;
 
     *vp = value;
     return true;
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ParseJSONWithReviver(JSContext *cx, const jschar *chars, uint32_t len, jsval reviverArg, jsval *vp)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
     RootedValue reviver(cx, reviverArg), value(cx);
-    if (!ParseJSONWithReviver(cx, chars, len, reviver, &value))
+    if (!ParseJSONWithReviver(cx, StableCharPtr(chars, len), len, reviver, &value))
         return false;
 
     *vp = value;
     return true;
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ReadStructuredClone(JSContext *cx, uint64_t *buf, size_t nbytes,
@@ -6818,29 +6819,31 @@ JS_NewRegExpObject(JSContext *cx, JSObje
     RootedObject obj(cx, objArg);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     jschar *chars = InflateString(cx, bytes, &length);
     if (!chars)
         return NULL;
 
     RegExpStatics *res = obj->asGlobal().getRegExpStatics();
-    RegExpObject *reobj = RegExpObject::create(cx, res, chars, length, RegExpFlag(flags), NULL);
+    RegExpObject *reobj = RegExpObject::create(cx, res, StableCharPtr(chars, length), length,
+                                               RegExpFlag(flags), NULL);
     js_free(chars);
     return reobj;
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_NewUCRegExpObject(JSContext *cx, JSObject *objArg, jschar *chars, size_t length, unsigned flags)
 {
     RootedObject obj(cx, objArg);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     RegExpStatics *res = obj->asGlobal().getRegExpStatics();
-    return RegExpObject::create(cx, res, chars, length, RegExpFlag(flags), NULL);
+    return RegExpObject::create(cx, res, StableCharPtr(chars, length), length,
+                                RegExpFlag(flags), NULL);
 }
 
 JS_PUBLIC_API(void)
 JS_SetRegExpInput(JSContext *cx, JSObject *objArg, JSString *input, JSBool multiline)
 {
     RootedObject obj(cx, objArg);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
@@ -6865,51 +6868,53 @@ JS_ExecuteRegExp(JSContext *cx, JSObject
                  size_t *indexp, JSBool test, jsval *rval)
 {
     RootedObject obj(cx, objArg);
     RootedObject reobj(cx, reobjArg);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
     RegExpStatics *res = obj->asGlobal().getRegExpStatics();
-    return ExecuteRegExp(cx, res, reobj->asRegExp(), NULL, chars, length,
-                         indexp, test ? RegExpTest : RegExpExec, rval);
+    return ExecuteRegExp(cx, res, reobj->asRegExp(), NullPtr(), StableCharPtr(chars, length),
+                         length, indexp, test ? RegExpTest : RegExpExec, rval);
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_NewRegExpObjectNoStatics(JSContext *cx, char *bytes, size_t length, unsigned flags)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     jschar *chars = InflateString(cx, bytes, &length);
     if (!chars)
         return NULL;
-    RegExpObject *reobj = RegExpObject::createNoStatics(cx, chars, length, RegExpFlag(flags), NULL);
+    RegExpObject *reobj = RegExpObject::createNoStatics(cx, StableCharPtr(chars, length), length,
+                                                        RegExpFlag(flags), NULL);
     js_free(chars);
     return reobj;
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_NewUCRegExpObjectNoStatics(JSContext *cx, jschar *chars, size_t length, unsigned flags)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
-    return RegExpObject::createNoStatics(cx, chars, length, RegExpFlag(flags), NULL);
+    return RegExpObject::createNoStatics(cx, StableCharPtr(chars, length), length,
+                                         RegExpFlag(flags), NULL);
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ExecuteRegExpNoStatics(JSContext *cx, JSObject *objArg, jschar *chars, size_t length,
                           size_t *indexp, JSBool test, jsval *rval)
 {
     RootedObject obj(cx, objArg);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
-    return ExecuteRegExp(cx, NULL, obj->asRegExp(), NULL, chars, length, indexp,
-                         test ? RegExpTest : RegExpExec, rval);
+    return ExecuteRegExp(cx, NULL, obj->asRegExp(), NullPtr(), StableCharPtr(chars, length),
+                         length, indexp, test ? RegExpTest : RegExpExec, rval);
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ObjectIsRegExp(JSContext *cx, JSObject *objArg)
 {
     RootedObject obj(cx, objArg);
     AssertHeapIsIdle(cx);
     JS_ASSERT(obj);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -10,16 +10,17 @@
 /*
  * JavaScript API.
  */
 
 #include "mozilla/Attributes.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/StandardInteger.h"
 #ifdef __cplusplus
+# include "mozilla/RangedPtr.h"
 # include "mozilla/ThreadLocal.h"
 #endif
 
 #include <stddef.h>
 #include <stdio.h>
 #include "js-config.h"
 #include "jspubtd.h"
 #include "jsutil.h"
@@ -42,16 +43,28 @@
 #define JSVAL_INT_MIN           ((int32_t)0x80000000)
 #define JSVAL_INT_MAX           ((int32_t)0x7fffffff)
 
 /************************************************************************/
 
 #ifdef __cplusplus
 namespace JS {
 
+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)
+    {}
+};
+
 /*
  * Protecting non-jsval, non-JSObject *, non-JSString * values from collection
  *
  * Most of the time, the garbage collector's conservative stack scanner works
  * behind the scenes, finding all live values and protecting them from being
  * collected. However, when JSAPI client code obtains a pointer to data the
  * scanner does not know about, owned by an object the scanner does know about,
  * Care Must Be Taken.
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -307,17 +307,17 @@ js::AtomizeString(JSContext *cx, JSStrin
         p->setTagged(bool(ib));
         return &atom;
     }
 
     JSStableString *stable = str->ensureStable(cx);
     if (!stable)
         return NULL;
 
-    const jschar *chars = stable->chars();
+    const jschar *chars = stable->chars().get();
     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)
 {
--- a/js/src/jsclone.cpp
+++ b/js/src/jsclone.cpp
@@ -999,17 +999,17 @@ JSStructuredCloneReader::startRead(Value
         JSString *str = readString(nchars);
         if (!str)
             return false;
         JSStableString *stable = str->ensureStable(context());
         if (!stable)
             return false;
 
         size_t length = stable->length();
-        const jschar *chars = stable->chars();
+        const StableCharPtr 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/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -284,17 +284,17 @@ JSCompartment::wrap(JSContext *cx, Value
         return true;
     }
 
     if (vp->isString()) {
         RootedValue orig(cx, *vp);
         JSStableString *str = vp->toString()->ensureStable(cx);
         if (!str)
             return false;
-        JSString *wrapped = js_NewStringCopyN(cx, str->chars(), str->length());
+        JSString *wrapped = js_NewStringCopyN(cx, str->chars().get(), str->length());
         if (!wrapped)
             return false;
         vp->setString(wrapped);
         return crossCompartmentWrappers.put(orig, *vp);
     }
 
     RootedObject obj(cx, &vp->toObject());
 
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -734,26 +734,25 @@ JS_PUBLIC_API(JSBool)
 JS_EvaluateUCInStackFrame(JSContext *cx, JSStackFrame *fpArg,
                           const jschar *chars, unsigned length,
                           const char *filename, unsigned lineno,
                           jsval *rval)
 {
     if (!CheckDebugMode(cx))
         return false;
 
-    SkipRoot skip(cx, &chars);
-
     Rooted<Env*> env(cx, JS_GetFrameScopeChain(cx, fpArg));
     if (!env)
         return false;
 
     StackFrame *fp = Valueify(fpArg);
 
     js::AutoCompartment ac(cx, env);
-    return EvaluateInEnv(cx, env, fp, chars, length, filename, lineno, rval);
+    return EvaluateInEnv(cx, env, fp, StableCharPtr(chars, length), length,
+                         filename, lineno, rval);
 }
 
 JS_PUBLIC_API(JSBool)
 JS_EvaluateInStackFrame(JSContext *cx, JSStackFrame *fp,
                         const char *bytes, unsigned length,
                         const char *filename, unsigned lineno,
                         jsval *rval)
 {
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -1109,17 +1109,17 @@ 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 (JSStableString *stable = str->ensureStable(cx))
-                report.ucmessage = stable->chars();
+                report.ucmessage = stable->chars().get();
         }
     }
 
     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
@@ -550,17 +550,17 @@ JS_FRIEND_DATA(Class) js::FunctionClass 
     fun_hasInstance,
     NULL,                    /* construct   */
     fun_trace
 };
 
 
 /* Find the body of a function (not including braces). */
 static bool
-FindBody(JSContext *cx, HandleFunction fun, const jschar *chars, size_t length,
+FindBody(JSContext *cx, HandleFunction fun, StableCharPtr 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->script()->getVersion());
     TokenStream ts(cx, options, chars, length, NULL);
     JS_ASSERT(chars[0] == '(');
@@ -586,26 +586,25 @@ FindBody(JSContext *cx, HandleFunction f
     TokenKind tt = ts.getToken();
     if (tt == TOK_ERROR)
         return false;
     bool braced = tt == TOK_LC;
     JS_ASSERT(!!(fun->flags & JSFUN_EXPR_CLOSURE) ^ braced);
     *bodyStart = ts.offsetOfToken(ts.currentToken());
     if (braced)
         *bodyStart += 1;
-    RangedPtr<const jschar> end(chars, length);
-    end = chars + length;
+    StableCharPtr end(chars.get() + length, chars.get(), length);
     if (end[-1] == '}') {
         end--;
     } else {
         JS_ASSERT(!braced);
         for (; unicode::IsSpaceOrBOM2(end[-1]); end--)
             ;
     }
-    *bodyEnd = end.get() - chars;
+    *bodyEnd = end - chars;
     JS_ASSERT(*bodyStart <= *bodyEnd);
     return true;
 }
 
 JSString *
 js::FunctionToString(JSContext *cx, HandleFunction fun, bool bodyOnly, bool lambdaParen)
 {
     StringBuffer out(cx);
@@ -641,17 +640,17 @@ js::FunctionToString(JSContext *cx, Hand
         RootedScript script(cx, fun->script());
         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->chars();
+        StableCharPtr 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);
@@ -1286,17 +1285,17 @@ Function(JSContext *cx, unsigned argc, V
          * 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;
         }
-        jschar *collected_args = cp;
+        StableCharPtr 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);
@@ -1365,32 +1364,30 @@ Function(JSContext *cx, unsigned argc, V
 #ifdef DEBUG
     for (unsigned i = 0; i < formals.length(); ++i) {
         RawString str = formals[i];
         JS_ASSERT(str->asAtom().asPropertyName() == formals[i]);
     }
 #endif
 
     JS::Anchor<JSString *> strAnchor(NULL);
-    const jschar *chars;
-    size_t length;
 
     RootedString str(cx);
     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();
+    StableCharPtr chars = stable->chars();
+    size_t 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.
      */
     RootedAtom anonymousAtom(cx, cx->names().anonymous);
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -858,17 +858,17 @@ Revive(JSContext *cx, HandleValue revive
 
     Rooted<jsid> id(cx, NameToId(cx->names().empty));
     return Walk(cx, obj, id, reviver, vp);
 }
 
 namespace js {
 
 JSBool
-ParseJSONWithReviver(JSContext *cx, const jschar *chars, size_t length, HandleValue reviver,
+ParseJSONWithReviver(JSContext *cx, StableCharPtr chars, size_t length, HandleValue reviver,
                      MutableHandleValue vp, DecodingMode decodingMode /* = STRICT */)
 {
     /* 15.12.2 steps 2-3. */
     JSONParser parser(cx, chars, length,
                       decodingMode == STRICT ? JSONParser::StrictJSON : JSONParser::LegacyJSON);
     if (!parser.parse(vp))
         return false;
 
--- a/js/src/json.h
+++ b/js/src/json.h
@@ -33,14 +33,14 @@ js_Stringify(JSContext *cx, js::MutableH
  * omitted.)  New users should use strict decoding rather than legacy decoding,
  * as legacy decoding might be removed at a future time.
  */
 enum DecodingMode { STRICT, LEGACY };
 
 namespace js {
 
 extern JS_FRIEND_API(JSBool)
-ParseJSONWithReviver(JSContext *cx, const jschar *chars, size_t length, HandleValue filter,
+ParseJSONWithReviver(JSContext *cx, JS::StableCharPtr chars, size_t length, HandleValue filter,
                      MutableHandleValue vp, DecodingMode decodingMode = STRICT);
 
 } /* namespace js */
 
 #endif /* json_h___ */
--- a/js/src/jsonparser.h
+++ b/js/src/jsonparser.h
@@ -22,18 +22,18 @@ class JSONParser
   public:
     enum ErrorHandling { RaiseError, NoError };
     enum ParsingMode { StrictJSON, LegacyJSON };
 
   private:
     /* Data members */
 
     JSContext * const cx;
-    mozilla::RangedPtr<const jschar> current;
-    const mozilla::RangedPtr<const jschar> end;
+    JS::StableCharPtr current;
+    const JS::StableCharPtr end;
 
     js::Value v;
 
     const ParsingMode parsingMode;
     const ErrorHandling errorHandling;
 
     enum Token { String, Number, True, False, Null,
                  ArrayOpen, ArrayClose,
@@ -50,22 +50,22 @@ class JSONParser
     /* Public API */
 
     /*
      * Create a parser for the provided JSON data.  The parser will accept
      * certain legacy, non-JSON syntax if decodingMode is LegacyJSON.
      * Description of this syntax is deliberately omitted: new code should only
      * use strict JSON parsing.
      */
-    JSONParser(JSContext *cx, const jschar *data, size_t length,
+    JSONParser(JSContext *cx, JS::StableCharPtr data, size_t length,
                ParsingMode parsingMode = StrictJSON,
                ErrorHandling errorHandling = RaiseError)
       : cx(cx),
-        current(data, length),
-        end(data + length, data, length),
+        current(data),
+        end((data + length).get(), data.get(), length),
         parsingMode(parsingMode),
         errorHandling(errorHandling)
 #ifdef DEBUG
       , lastToken(Error)
 #endif
     {
         JS_ASSERT(current <= end);
     }
--- a/js/src/jsreflect.cpp
+++ b/js/src/jsreflect.cpp
@@ -3479,17 +3479,17 @@ reflect_parse(JSContext *cx, uint32_t ar
     ASTSerializer serialize(cx, loc, filename, lineno);
     if (!serialize.init(builder))
         return JS_FALSE;
 
     JSStableString *stable = src->ensureStable(cx);
     if (!stable)
         return JS_FALSE;
 
-    const jschar *chars = stable->chars();
+    const StableCharPtr 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
@@ -1123,51 +1123,51 @@ 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->chars();
+        chars = cached->chars().get();
         JS_ASSERT(chars);
     } else {
         chars = data.source;
     }
 #else
     chars = data.source;
 #endif
     return js_NewStringCopyN(cx, chars + start, stop - start);
 }
 
 bool
-ScriptSource::setSourceCopy(JSContext *cx, const jschar *src, uint32_t length,
+ScriptSource::setSourceCopy(JSContext *cx, StableCharPtr src, uint32_t length,
                             bool argumentsNotIncluded, SourceCompressionToken *tok)
 {
     JS_ASSERT(!hasSourceData());
     const size_t nbytes = length * sizeof(jschar);
     data.compressed = static_cast<unsigned char *>(cx->malloc_(nbytes));
     if (!data.compressed)
         return false;
     length_ = length;
     argumentsNotIncluded_ = argumentsNotIncluded;
 
 #ifdef JS_THREADSAFE
     if (tok) {
 #ifdef DEBUG
         ready_ = false;
 #endif
         tok->ss = this;
-        tok->chars = src;
+        tok->chars = src.get();
         cx->runtime->sourceCompressorThread.compress(tok);
     } else
 #endif
     {
-        PodCopy(data.source, src, length_);
+        PodCopy(data.source, src.get(), length_);
     }
 
     return true;
 }
 
 void
 ScriptSource::setSource(const jschar *src, uint32_t length)
 {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -1023,17 +1023,17 @@ struct ScriptSource
     }
     void incref() { refs++; }
     void decref(JSRuntime *rt) {
         JS_ASSERT(refs != 0);
         if (--refs == 0)
             destroy(rt);
     }
     bool setSourceCopy(JSContext *cx,
-                       const jschar *src,
+                       StableCharPtr src,
                        uint32_t length,
                        bool argumentsNotIncluded,
                        SourceCompressionToken *tok);
     void setSource(const jschar *src, uint32_t length);
 #ifdef DEBUG
     bool ready() const { return ready_; }
 #endif
     void setSourceRetrievable() { sourceRetrievable_ = true; }
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -1668,46 +1668,46 @@ enum MatchControlFlags {
    REPLACE_ARGS  = TEST_GLOBAL_BIT | TEST_SINGLE_BIT | CALLBACK_ON_SINGLE_BIT
 };
 
 /* Factor out looping and matching logic. */
 static bool
 DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, RegExpShared &re,
         DoMatchCallback callback, void *data, MatchControlFlags flags, Value *rval)
 {
-    Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
-    if (!linearStr)
+    Rooted<JSStableString*> stableStr(cx, str->ensureStable(cx));
+    if (!stableStr)
         return false;
 
     if (re.global()) {
         RegExpExecType type = (flags & TEST_GLOBAL_BIT) ? RegExpTest : RegExpExec;
         for (size_t count = 0, i = 0, length = str->length(); i <= length; ++count) {
             if (!JS_CHECK_OPERATION_LIMIT(cx))
                 return false;
 
-            const jschar *chars = linearStr->chars();
-            size_t charsLen = linearStr->length();
-
-            if (!ExecuteRegExp(cx, res, re, linearStr, chars, charsLen, &i, type, rval))
+            StableCharPtr chars = stableStr->chars();
+            size_t charsLen = stableStr->length();
+
+            if (!ExecuteRegExp(cx, res, re, stableStr, chars, charsLen, &i, type, rval))
                 return false;
             if (!Matched(type, *rval))
                 break;
             if (!callback(cx, res, count, data))
                 return false;
             if (!res->matched())
                 ++i;
         }
     } else {
-        const jschar *chars = linearStr->chars();
-        size_t charsLen = linearStr->length();
+        StableCharPtr chars = stableStr->chars();
+        size_t charsLen = stableStr->length();
 
         RegExpExecType type = (flags & TEST_SINGLE_BIT) ? RegExpTest : RegExpExec;
         bool callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT);
         size_t i = 0;
-        if (!ExecuteRegExp(cx, res, re, linearStr, chars, charsLen, &i, type, rval))
+        if (!ExecuteRegExp(cx, res, re, stableStr, chars, charsLen, &i, type, rval))
             return false;
         if (callbackOnSingle && Matched(type, *rval) && !callback(cx, res, 0, data))
             return false;
     }
     return true;
 }
 
 static bool
@@ -1814,28 +1814,28 @@ js::str_search(JSContext *cx, unsigned a
     }
 
     if (cx->isExceptionPending())  /* from tryFlatMatch */
         return false;
 
     if (!g.normalizeRegExp(cx, false, 1, args))
         return false;
 
-    JSLinearString *linearStr = 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();
+    StableCharPtr chars = stableStr->chars();
+    size_t length = stableStr->length();
     RegExpStatics *res = cx->regExpStatics();
 
     /* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */
     size_t i = 0;
     Value result;
-    if (!ExecuteRegExp(cx, res, g.regExp(), linearStr, chars, length, &i, RegExpTest, &result))
+    if (!ExecuteRegExp(cx, res, g.regExp(), stableStr, chars, length, &i, RegExpTest, &result))
         return false;
 
     if (result.isTrue())
         args.rval().setInt32(res->matchStart());
     else
         args.rval().setInt32(-1);
     return true;
 }
@@ -2438,18 +2438,18 @@ js::str_replace(JSContext *cx, unsigned 
         rdata.repstr = ArgToRootedString(cx, args, 1);
         if (!rdata.repstr)
             return false;
 
         /* We're about to store pointers into the middle of our string. */
         JSStableString *stable = rdata.repstr->ensureStable(cx);
         if (!stable)
             return false;
-        rdata.dollarEnd = stable->chars() + stable->length();
-        rdata.dollar = js_strchr_limit(stable->chars(), '$', rdata.dollarEnd);
+        rdata.dollarEnd = stable->chars().get() + stable->length();
+        rdata.dollar = js_strchr_limit(stable->chars().get(), '$', rdata.dollarEnd);
     }
 
     rdata.fig.initFunction(ObjectOrNullValue(rdata.lambda));
 
     /*
      * Unlike its |String.prototype| brethren, |replace| doesn't convert
      * its input to a regular expression. (Even if it contains metachars.)
      *
@@ -2507,17 +2507,17 @@ class SplitMatchResult {
     void setResult(size_t length, size_t endIndex) {
         length_ = length;
         endIndex_ = endIndex;
     }
 };
 
 template<class Matcher>
 static JSObject *
-SplitHelper(JSContext *cx, Handle<JSLinearString*> str, uint32_t limit, const Matcher &splitMatch,
+SplitHelper(JSContext *cx, Handle<JSStableString*> str, uint32_t limit, const Matcher &splitMatch,
             Handle<TypeObject*> type)
 {
     size_t strLength = str->length();
     SplitMatchResult result;
 
     /* Step 11. */
     if (strLength == 0) {
         if (!splitMatch(cx, str, 0, &result))
@@ -2649,21 +2649,22 @@ class SplitRegExpMatcher
     RegExpShared &re;
     RegExpStatics *res;
 
   public:
     SplitRegExpMatcher(RegExpShared &re, RegExpStatics *res) : re(re), res(res) {}
 
     static const bool returnsCaptures = true;
 
-    bool operator()(JSContext *cx, JSLinearString *str, size_t index,
+    bool operator()(JSContext *cx, Handle<JSStableString*> str, size_t index,
                     SplitMatchResult *result) const
     {
+        AssertCanGC();
         Value rval = UndefinedValue();
-        const jschar *chars = str->chars();
+        StableCharPtr chars = str->chars();
         size_t length = str->length();
         if (!ExecuteRegExp(cx, res, re, str, chars, length, &index, RegExpTest, &rval))
             return false;
         if (!rval.isTrue()) {
             result->setFailure();
             return true;
         }
         JSSubString sep;
@@ -2682,16 +2683,17 @@ class SplitStringMatcher
     SplitStringMatcher(JSContext *cx, JSLinearString *sep)
       : sep(cx, sep)
     {}
 
     static const bool returnsCaptures = false;
 
     bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *res) const
     {
+        AutoAssertNoGC nogc;
         JS_ASSERT(index == 0 || index < str->length());
         const jschar *chars = str->chars();
         int match = StringMatch(chars + index, str->length() - index,
                                 sep->chars(), sep->length());
         if (match == -1)
             res->setFailure();
         else
             res->setResult(sep->length(), index + match + sep->length());
@@ -2756,28 +2758,28 @@ js::str_split(JSContext *cx, unsigned ar
         Value v = StringValue(str);
         JSObject *aobj = NewDenseCopiedArray(cx, 1, &v);
         if (!aobj)
             return false;
         aobj->setType(type);
         args.rval().setObject(*aobj);
         return true;
     }
-    Rooted<JSLinearString*> strlin(cx, str->ensureLinear(cx));
-    if (!strlin)
+    Rooted<JSStableString*> stableStr(cx, str->ensureStable(cx));
+    if (!stableStr)
         return false;
 
     /* Steps 11-15. */
     JSObject *aobj;
     if (!re.initialized()) {
         SplitStringMatcher matcher(cx, sepstr);
-        aobj = SplitHelper(cx, strlin, limit, matcher, type);
+        aobj = SplitHelper(cx, stableStr, limit, matcher, type);
     } else {
         SplitRegExpMatcher matcher(*re, cx->regExpStatics());
-        aobj = SplitHelper(cx, strlin, limit, matcher, type);
+        aobj = SplitHelper(cx, stableStr, limit, matcher, type);
     }
     if (!aobj)
         return false;
 
     /* Step 16. */
     aobj->setType(type);
     args.rval().setObject(*aobj);
     return true;
--- a/js/src/jsxml.cpp
+++ b/js/src/jsxml.cpp
@@ -1179,17 +1179,17 @@ ParseNodeToQName(Parser *parser, ParseNo
     JSObject *ns;
     JSLinearString *nsprefix;
 
     JS_ASSERT(pn->isArity(PN_NULLARY));
     JSAtom *atom = pn->pn_atom;
     JSStableString *str = atom->ensureStable(cx);
     if (!str)
         return NULL;
-    start = str->chars();
+    start = str->chars().get();
     length = str->length();
     JS_ASSERT(length != 0 && *start != '@');
     JS_ASSERT(length != 1 || *start != '*');
 
     JSAtom *localName;
 
     uri = cx->runtime->emptyString;
     limit = start + length;
@@ -1759,17 +1759,17 @@ ParseXMLSource(JSContext *cx, HandleStri
                     --lineno;
             }
         }
     }
 
     {
         CompileOptions options(cx);
         options.setFileAndLine(filename, lineno);
-        Parser parser(cx, options, chars, length, /* foldConstants = */ true);
+        Parser parser(cx, options, StableCharPtr(chars, length), length, /* foldConstants = */ true);
         if (parser.init()) {
             JSObject *scopeChain = GetCurrentScopeChain(cx);
             if (!scopeChain) {
                 js_free(chars);
                 return NULL;
             }
 
             ParseNode *pn = parser.parseXMLText(scopeChain, false);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3086,17 +3086,19 @@ Parse(JSContext *cx, unsigned argc, jsva
         return false;
     }
 
     JSString *scriptContents = JSVAL_TO_STRING(arg0);
     CompileOptions options(cx);
     options.setFileAndLine("<string>", 1)
            .setCompileAndGo(false);
     Parser parser(cx, options,
-                  JS_GetStringCharsZ(cx, scriptContents), JS_GetStringLength(scriptContents),
+                  JS::StableCharPtr(JS_GetStringCharsZ(cx, scriptContents),
+                                    JS_GetStringLength(scriptContents)),
+                  JS_GetStringLength(scriptContents),
                   /* foldConstants = */ true);
     if (!parser.init())
         return false;
 
     ParseNode *pn = parser.parse(NULL);
     if (!pn)
         return false;
 #ifdef DEBUG
@@ -3362,17 +3364,18 @@ ParseLegacyJSON(JSContext *cx, unsigned 
     JSString *str = JSVAL_TO_STRING(args[0]);
 
     size_t length;
     const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
     if (!chars)
         return false;
 
     RootedValue value(cx, NullValue());
-    return js::ParseJSONWithReviver(cx, chars, length, value, args.rval(), LEGACY);
+    return js::ParseJSONWithReviver(cx, StableCharPtr(chars, length), length,
+                                    value, args.rval(), LEGACY);
 }
 
 static JSBool
 EnableStackWalkingAssertion(JSContext *cx, unsigned argc, jsval *vp)
 {
     if (argc == 0 || !JSVAL_IS_BOOLEAN(JS_ARGV(cx, vp)[0])) {
         JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS,
                              "enableStackWalkingAssertion");
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -3386,23 +3386,22 @@ DebuggerFrame_setOnPop(JSContext *cx, un
     }
 
     thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER, args[0]);
     args.rval().setUndefined();
     return true;
 }
 
 JSBool
-js::EvaluateInEnv(JSContext *cx, Handle<Env*> env, StackFrame *fp, const jschar *chars,
+js::EvaluateInEnv(JSContext *cx, Handle<Env*> env, StackFrame *fp, StableCharPtr chars,
                   unsigned length, const char *filename, unsigned lineno, Value *rval)
 {
     assertSameCompartment(cx, env, fp);
 
-    JS_ASSERT(!IsPoisonedPtr(chars));
-    SkipRoot skip(cx, &chars);
+    JS_ASSERT(!IsPoisonedPtr(chars.get()));
 
     RootedValue thisv(cx);
     if (fp) {
         /* Execute assumes an already-computed 'this" value. */
         if (!ComputeThis(cx, fp))
             return false;
         thisv = fp->thisValue();
     } else {
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -518,14 +518,14 @@ Debugger::onNewScript(JSContext *cx, JSS
 {
     JS_ASSERT_IF(script->compileAndGo, compileAndGoGlobal);
     JS_ASSERT_IF(!script->compileAndGo, !compileAndGoGlobal);
     if (!script->compartment()->getDebuggees().empty())
         slowPathOnNewScript(cx, script, compileAndGoGlobal);
 }
 
 extern JSBool
-EvaluateInEnv(JSContext *cx, Handle<Env*> env, StackFrame *fp, const jschar *chars,
+EvaluateInEnv(JSContext *cx, Handle<Env*> env, StackFrame *fp, StableCharPtr chars,
               unsigned length, const char *filename, unsigned lineno, Value *rval);
 
 }
 
 #endif /* Debugger_h__ */
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -210,28 +210,28 @@ RegExpCode::compile(JSContext *cx, JSLin
 #if ENABLE_YARR_JIT
     codeBlock.setFallBack(true);
 #endif
     byteCode = byteCompile(yarrPattern, bumpAlloc).get();
     return true;
 }
 
 RegExpRunStatus
-RegExpCode::execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
+RegExpCode::execute(JSContext *cx, StableCharPtr chars, size_t length, size_t start,
                     int *output, size_t outputCount)
 {
     int result;
 #if ENABLE_YARR_JIT
     (void) cx; /* Unused. */
     if (codeBlock.isFallBack())
-        result = JSC::Yarr::interpret(byteCode, chars, start, length, output);
+        result = JSC::Yarr::interpret(byteCode, chars.get(), start, length, output);
     else
-        result = JSC::Yarr::execute(codeBlock, chars, start, length, output);
+        result = JSC::Yarr::execute(codeBlock, chars.get(), start, length, output);
 #else
-    result = JSC::Yarr::interpret(byteCode, chars, start, length, output);
+    result = JSC::Yarr::interpret(byteCode, chars.get(), start, length, output);
 #endif
 
     if (result == -1)
         return RegExpRunStatus_Success_NotFound;
 
     JS_ASSERT(result >= 0);
     return RegExpRunStatus_Success;
 }
@@ -270,28 +270,28 @@ Class js::RegExpClass = {
     regexp_trace
 };
 
 RegExpShared::RegExpShared(JSRuntime *rt, RegExpFlag flags)
   : parenCount(0), flags(flags), activeUseCount(0), gcNumberWhenUsed(rt->gcNumber)
 {}
 
 RegExpObject *
-RegExpObject::create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
+RegExpObject::create(JSContext *cx, RegExpStatics *res, StableCharPtr chars, size_t length,
                      RegExpFlag flags, TokenStream *tokenStream)
 {
     RegExpFlag staticsFlags = res->getFlags();
     return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream);
 }
 
 RegExpObject *
-RegExpObject::createNoStatics(JSContext *cx, const jschar *chars, size_t length, RegExpFlag flags,
+RegExpObject::createNoStatics(JSContext *cx, StableCharPtr chars, size_t length, RegExpFlag flags,
                               TokenStream *tokenStream)
 {
-    RootedAtom source(cx, AtomizeChars(cx, chars, length));
+    RootedAtom source(cx, AtomizeChars(cx, chars.get(), length));
     if (!source)
         return NULL;
 
     return createNoStatics(cx, source, flags, tokenStream);
 }
 
 RegExpObject *
 RegExpObject::createNoStatics(JSContext *cx, HandleAtom source, RegExpFlag flags,
@@ -398,17 +398,17 @@ RegExpObject::init(JSContext *cx, Handle
     self->setGlobal(flags & GlobalFlag);
     self->setIgnoreCase(flags & IgnoreCaseFlag);
     self->setMultiline(flags & MultilineFlag);
     self->setSticky(flags & StickyFlag);
     return true;
 }
 
 RegExpRunStatus
-RegExpObject::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
+RegExpObject::execute(JSContext *cx, StableCharPtr chars, size_t length, size_t *lastIndex,
                       MatchPairs **output)
 {
     RegExpGuard g;
     if (!getShared(cx, &g))
         return RegExpRunStatus_Error;
     return g->execute(cx, chars, length, lastIndex, output);
 }
 
@@ -464,17 +464,17 @@ RegExpShared::compile(JSContext *cx, JSA
 
     JSAtom *fakeySource = sb.finishAtom();
     if (!fakeySource)
         return false;
     return code.compile(cx, *fakeySource, &parenCount, getFlags());
 }
 
 RegExpRunStatus
-RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
+RegExpShared::execute(JSContext *cx, StableCharPtr chars, size_t length, size_t *lastIndex,
                       MatchPairs **output)
 {
     const size_t origLength = length;
     size_t backingPairCount = RegExpCode::getOutputSize(pairCount());
 
     LifoAlloc &alloc = cx->tempLifoAlloc();
     MatchPairs *matchPairs = MatchPairs::create(alloc, pairCount(), backingPairCount);
     if (!matchPairs)
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -127,17 +127,17 @@ class RegExpCode
     static size_t getOutputSize(size_t pairCount) {
         return pairCount * 2;
     }
 
     bool compile(JSContext *cx, JSLinearString &pattern, unsigned *parenCount, RegExpFlag flags);
 
 
     RegExpRunStatus
-    execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
+    execute(JSContext *cx, StableCharPtr chars, size_t length, size_t start,
             int *output, size_t outputCount);
 };
 
 }  /* namespace detail */
 
 /*
  * A RegExpShared is the compiled representation of a regexp. A RegExpShared is
  * pointed to by potentially multiple RegExpObjects. Additionally, C++ code may
@@ -178,17 +178,17 @@ class RegExpShared
     RegExpShared(JSRuntime *rt, RegExpFlag flags);
 
     /* Called when a RegExpShared is installed into a RegExpObject. */
     inline void prepareForUse(JSContext *cx);
 
     /* Primary interface: run this regular expression on the given string. */
 
     RegExpRunStatus
-    execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
+    execute(JSContext *cx, StableCharPtr chars, size_t length, size_t *lastIndex,
             MatchPairs **output);
 
     /* Accessors */
 
     size_t getParenCount() const        { return parenCount; }
     void incRef()                       { activeUseCount++; }
     void decRef()                       { JS_ASSERT(activeUseCount > 0); activeUseCount--; }
 
@@ -307,21 +307,21 @@ class RegExpObject : public JSObject
     static const unsigned RESERVED_SLOTS = 6;
 
     /*
      * Note: The regexp statics flags are OR'd into the provided flags,
      * so this function is really meant for object creation during code
      * execution, as opposed to during something like XDR.
      */
     static RegExpObject *
-    create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
+    create(JSContext *cx, RegExpStatics *res, StableCharPtr chars, size_t length,
            RegExpFlag flags, frontend::TokenStream *ts);
 
     static RegExpObject *
-    createNoStatics(JSContext *cx, const jschar *chars, size_t length, RegExpFlag flags,
+    createNoStatics(JSContext *cx, StableCharPtr chars, size_t length, RegExpFlag flags,
                     frontend::TokenStream *ts);
 
     static RegExpObject *
     createNoStatics(JSContext *cx, HandleAtom atom, RegExpFlag flags, frontend::TokenStream *ts);
 
     /*
      * Run the regular expression over the input text.
      *
@@ -329,17 +329,17 @@ class RegExpObject : public JSObject
      * |output[0]| and |output[1]| represent the text indices that make
      * up the "0" (whole match) pair. Capturing parens will result in
      * more output.
      *
      * N.B. it's the responsibility of the caller to hook the |output|
      * into the |RegExpStatics| appropriately, if necessary.
      */
     RegExpRunStatus
-    execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
+    execute(JSContext *cx, StableCharPtr chars, size_t length, size_t *lastIndex,
             MatchPairs **output);
 
     /* Accessors. */
 
     const Value &getLastIndex() const {
         return getSlot(LAST_INDEX_SLOT);
     }
     inline void setLastIndex(double d);
--- a/js/src/vm/String.h
+++ b/js/src/vm/String.h
@@ -541,16 +541,22 @@ class JSFlatString : public JSLinearStri
 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_ALWAYS_INLINE
+    JS::StableCharPtr chars() const {
+        JS_ASSERT(!JSString::isInline());
+        return JS::StableCharPtr(d.u1.chars, length());
+    }
 };
 
 JS_STATIC_ASSERT(sizeof(JSStableString) == sizeof(JSString));
 
 class JSExtensibleString : public JSFlatString
 {
     /* Vacuous and therefore unimplemented. */
     bool isExtensible() const MOZ_DELETE;
--- a/js/src/vm/StringBuffer.h
+++ b/js/src/vm/StringBuffer.h
@@ -41,16 +41,17 @@ class StringBuffer
 
   public:
     explicit StringBuffer(JSContext *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 CharPtr 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]) {
@@ -59,16 +60,19 @@ 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 CharPtr 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);
     }
 
     jschar *begin() { return cb.begin(); }
--- a/mfbt/RangedPtr.h
+++ b/mfbt/RangedPtr.h
@@ -123,23 +123,23 @@ class RangedPtr
       MOZ_ASSERT(rangeEnd == other.rangeEnd);
       ptr = other.ptr;
       checkSanity();
       return *this;
     }
 
     RangedPtr<T> operator+(size_t inc) {
       MOZ_ASSERT(inc <= size_t(-1) / sizeof(T));
-      MOZ_ASSERT(ptr + inc > ptr);
+      MOZ_ASSERT(ptr + inc >= ptr);
       return create(ptr + inc);
     }
 
     RangedPtr<T> operator-(size_t dec) {
       MOZ_ASSERT(dec <= size_t(-1) / sizeof(T));
-      MOZ_ASSERT(ptr - dec < ptr);
+      MOZ_ASSERT(ptr - dec <= ptr);
       return create(ptr - dec);
     }
 
     /*
      * You can assign a raw pointer into a RangedPtr if the raw pointer is
      * within the range specified at creation.
      */
     template <typename U>