Encapsulate RegExpStatics more. (r=gal, b=610223)
authorChris Leary <cdleary@mozilla.com>
Wed, 10 Nov 2010 17:02:08 -0800
changeset 57789 cbd2053aa82547798292aa64664b969785fbc4a3
parent 57788 7ad090d53861db5e9e2f0d1fe3138ba495490926
child 57790 b6486db91e916bcac0a4ba5c7ac22fec933750e8
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersgal
bugs610223
milestone2.0b8pre
Encapsulate RegExpStatics more. (r=gal, b=610223)
js/src/jit-test/tests/basic/regexp-reset-input.js
js/src/jit-test/tests/basic/regexp-sticky-undef-capture.js
js/src/jsregexp.cpp
js/src/jsregexp.h
js/src/jsregexpinlines.h
js/src/jsstr.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/regexp-reset-input.js
@@ -0,0 +1,8 @@
+var re = /(pattern)/g;
+var input = "patternpatternpattern";
+re.exec(input)
+RegExp.input = "satturn";
+assertEq(RegExp.$1, "pattern");
+assertEq(RegExp.lastMatch, "pattern");
+assertEq(RegExp.lastParen, "pattern");
+assertEq(RegExp.rightContext, "patternpattern");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/regexp-sticky-undef-capture.js
@@ -0,0 +1,8 @@
+var re = /abc(WHOO!)?def/y;
+var input = 'abcdefabcdefabcdef';
+var count = 0;
+while ((match = re.exec(input)) !== null) {
+    print(count++);
+    assertEq(match[0], 'abcdef');
+    assertEq(match[1], undefined);
+}
--- a/js/src/jsregexp.cpp
+++ b/js/src/jsregexp.cpp
@@ -388,17 +388,17 @@ regexp_resolve(JSContext *cx, JSObject *
 #define DEFINE_STATIC_GETTER(name, code)                                        \
     static JSBool                                                               \
     name(JSContext *cx, JSObject *obj, jsid id, jsval *vp)                      \
     {                                                                           \
         RegExpStatics *res = cx->regExpStatics();                               \
         code;                                                                   \
     }
 
-DEFINE_STATIC_GETTER(static_input_getter,        return res->createInput(cx, Valueify(vp)))
+DEFINE_STATIC_GETTER(static_input_getter,        return res->createPendingInput(cx, Valueify(vp)))
 DEFINE_STATIC_GETTER(static_multiline_getter,    *vp = BOOLEAN_TO_JSVAL(res->multiline());
                                                  return true)
 DEFINE_STATIC_GETTER(static_lastMatch_getter,    return res->createLastMatch(cx, Valueify(vp)))
 DEFINE_STATIC_GETTER(static_lastParen_getter,    return res->createLastParen(cx, Valueify(vp)))
 DEFINE_STATIC_GETTER(static_leftContext_getter,  return res->createLeftContext(cx, Valueify(vp)))
 DEFINE_STATIC_GETTER(static_rightContext_getter, return res->createRightContext(cx, Valueify(vp)))
 
 DEFINE_STATIC_GETTER(static_paren1_getter,       return res->createParen(cx, 0, Valueify(vp)))
@@ -418,17 +418,17 @@ DEFINE_STATIC_GETTER(static_paren9_gette
         RegExpStatics *res = cx->regExpStatics();                               \
         code;                                                                   \
         return true;                                                            \
     }
 
 DEFINE_STATIC_SETTER(static_input_setter,
                      if (!JSVAL_IS_STRING(*vp) && !JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp))
                          return false;
-                     res->setInput(JSVAL_TO_STRING(*vp)))
+                     res->setPendingInput(JSVAL_TO_STRING(*vp)))
 DEFINE_STATIC_SETTER(static_multiline_setter,
                      if (!JSVAL_IS_BOOLEAN(*vp) && !JS_ConvertValue(cx, *vp, JSTYPE_BOOLEAN, vp))
                          return false;
                      res->setMultiline(!!JSVAL_TO_BOOLEAN(*vp)))
 
 const uint8 REGEXP_STATIC_PROP_ATTRS    = JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_ENUMERATE;
 const uint8 RO_REGEXP_STATIC_PROP_ATTRS = REGEXP_STATIC_PROP_ATTRS | JSPROP_READONLY;
 
@@ -778,17 +778,17 @@ regexp_exec_sub(JSContext *cx, JSObject 
         str = js_ValueToString(cx, argv[0]);
         if (!str) {
             ok = JS_FALSE;
             goto out;
         }
         argv[0] = StringValue(str);
     } else {
         /* Need to grab input from statics. */
-        str = res->getInput();
+        str = res->getPendingInput();
         if (!str) {
             const char *sourceBytes = js_GetStringBytes(cx, re->getSource());
             if (sourceBytes) {
                 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_INPUT, sourceBytes,
                                      re->global() ? "g" : "",
                                      re->ignoreCase() ? "i" : "",
                                      re->multiline() ? "m" : "",
                                      re->sticky() ? "y" : "");
--- a/js/src/jsregexp.h
+++ b/js/src/jsregexp.h
@@ -54,63 +54,48 @@
 
 extern js::Class js_RegExpClass;
 
 namespace js {
 
 class RegExpStatics
 {
     typedef Vector<int, 20, SystemAllocPolicy> MatchPairs;
-    MatchPairs    matchPairs;
-    JSString      *input;
-    uintN         flags;
-    RegExpStatics *bufferLink;
-    bool          copied;
+    MatchPairs      matchPairs;
+    /* The input that was used to produce matchPairs. */
+    JSString        *matchPairsInput;
+    /* The input last set on the statics. */
+    JSString        *pendingInput;
+    uintN           flags;
+    RegExpStatics   *bufferLink;
+    bool            copied;
 
     bool createDependent(JSContext *cx, size_t start, size_t end, Value *out) const;
 
     size_t pairCount() const {
         JS_ASSERT(matchPairs.length() % 2 == 0);
         return matchPairs.length() / 2;
     }
 
     void copyTo(RegExpStatics &dst) {
         dst.matchPairs.clear();
         /* 'save' has already reserved space in matchPairs */
         JS_ALWAYS_TRUE(dst.matchPairs.append(matchPairs));
-        dst.input = input;
+        dst.matchPairsInput = matchPairsInput;
+        dst.pendingInput = pendingInput;
         dst.flags = flags;
     }
 
     void aboutToWrite() {
         if (bufferLink && !bufferLink->copied) {
             copyTo(*bufferLink);
             bufferLink->copied = true;
         }
     }
 
-    /*
-     * Check whether the index at |checkValidIndex| is valid (>= 0).
-     * If so, construct a string for it and place it in |*out|.
-     * If not, place undefined in |*out|.
-     */
-    bool makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum, Value *out) const;
-    static const uintN allFlags = JSREG_FOLD | JSREG_GLOB | JSREG_STICKY | JSREG_MULTILINE;
-    friend class RegExp;
-
-  public:
-    RegExpStatics() : bufferLink(NULL), copied(false) { clear(); }
-
-    struct InitBuffer {};
-    explicit RegExpStatics(InitBuffer) : bufferLink(NULL), copied(false) {}
-
-    static RegExpStatics *extractFrom(JSObject *global);
-
-    /* Mutators. */
-
     bool save(JSContext *cx, RegExpStatics *buffer) {
         JS_ASSERT(!buffer->copied && !buffer->bufferLink);
         buffer->bufferLink = bufferLink;
         bufferLink = buffer;
         if (!buffer->matchPairs.reserve(matchPairs.length())) {
             js_ReportOutOfMemory(cx);
             return false;
         }
@@ -118,79 +103,162 @@ class RegExpStatics
     }
 
     void restore() {
         if (bufferLink->copied)
             bufferLink->copyTo(*this);
         bufferLink = bufferLink->bufferLink;
     }
 
+    void checkInvariants() {
+#if DEBUG
+        if (pairCount() == 0) {
+            JS_ASSERT(!matchPairsInput);
+            return;
+        }
+
+        /* Pair count is non-zero, so there must be match pairs input. */
+        JS_ASSERT(matchPairsInput);
+        size_t mpiLen = matchPairsInput->length();
+
+        JS_ASSERT(pairIsPresent(0));
+
+        /* Present pairs must be valid. */
+        for (size_t i = 0; i < pairCount(); ++i) {
+            if (!pairIsPresent(i))
+                continue;
+            int start = get(i, 0);
+            int limit = get(i, 1);
+            JS_ASSERT(mpiLen >= size_t(limit) && limit >= start && start >= 0);
+        }
+#endif
+    }
+
+    int get(size_t pairNum, bool which) const {
+        JS_ASSERT(pairNum < pairCount());
+        return matchPairs[2 * pairNum + which];
+    }
+
+    /*
+     * Check whether the index at |checkValidIndex| is valid (>= 0).
+     * If so, construct a string for it and place it in |*out|.
+     * If not, place undefined in |*out|.
+     */
+    bool makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum, Value *out) const;
+
+    static const uintN allFlags = JSREG_FOLD | JSREG_GLOB | JSREG_STICKY | JSREG_MULTILINE;
+
+    struct InitBuffer {};
+    explicit RegExpStatics(InitBuffer) : bufferLink(NULL), copied(false) {}
+
+    friend class PreserveRegExpStatics;
+
+  public:
+    RegExpStatics() : bufferLink(NULL), copied(false) { clear(); }
+
+    static RegExpStatics *extractFrom(JSObject *global);
+
+    /* Mutators. */
+
+    /* 
+     * The inputOffset parameter is added to the present (i.e. non-negative) match items to emulate
+     * sticky mode.
+     */
+    bool updateFromMatch(JSContext *cx, JSString *input, int *buf, size_t matchItemCount) {
+        aboutToWrite();
+        pendingInput = input;
+
+        if (!matchPairs.resizeUninitialized(matchItemCount)) {
+            js_ReportOutOfMemory(cx);
+            return false;
+        }
+
+        for (size_t i = 0; i < matchItemCount; ++i)
+            matchPairs[i] = buf[i];
+
+        matchPairsInput = input;
+        return true;
+    }
+
     void setMultiline(bool enabled) {
         aboutToWrite();
         if (enabled)
             flags = flags | JSREG_MULTILINE;
         else
             flags = flags & ~JSREG_MULTILINE;
     }
 
     void clear() {
         aboutToWrite();
-        input = 0;
         flags = 0;
+        pendingInput = NULL;
+        matchPairsInput = NULL;
         matchPairs.clear();
     }
 
-    void checkInvariants() {
-        if (pairCount() > 0) {
-            JS_ASSERT(input);
-            JS_ASSERT(get(0, 0) <= get(0, 1));
-            JS_ASSERT(get(0, 1) <= int(input->length()));
-        }
-    }
+    bool pairIsPresent(size_t pairNum) { return get(0, 0) != -1; }
 
+    /* Corresponds to JSAPI functionality to set the pending RegExp input. */
     void reset(JSString *newInput, bool newMultiline) {
         aboutToWrite();
         clear();
-        input = newInput;
+        pendingInput = newInput;
         setMultiline(newMultiline);
         checkInvariants();
     }
 
-    void setInput(JSString *newInput) {
+    void setPendingInput(JSString *newInput) {
         aboutToWrite();
-        input = newInput;
+        pendingInput = newInput;
     }
 
     /* Accessors. */
 
-    JSString *getInput() const { return input; }
+    JSString *getPendingInput() const { return pendingInput; }
     uintN getFlags() const { return flags; }
     bool multiline() const { return flags & JSREG_MULTILINE; }
-    bool matched() const { JS_ASSERT(pairCount() > 0); return get(0, 1) - get(0, 0) > 0; }
-    size_t getParenCount() const { JS_ASSERT(pairCount() > 0); return pairCount() - 1; }
+
+    size_t matchStart() const {
+        int start = get(0, 0);
+        JS_ASSERT(start >= 0);
+        return size_t(start);
+    }
+
+    size_t matchLimit() const {
+        int limit = get(0, 1);
+        JS_ASSERT(size_t(limit) >= matchStart() && limit >= 0);
+        return size_t(limit);
+    }
+
+    bool matched() const {
+        JS_ASSERT(pairCount() > 0);
+        return get(0, 1) - get(0, 0) > 0;
+    }
+
+    size_t getParenCount() const {
+        JS_ASSERT(pairCount() > 0);
+        return pairCount() - 1;
+    }
 
     void mark(JSTracer *trc) const {
-        if (input)
-            JS_CALL_STRING_TRACER(trc, input, "res->input");
+        if (pendingInput)
+            JS_CALL_STRING_TRACER(trc, pendingInput, "res->pendingInput");
+        if (matchPairsInput)
+            JS_CALL_STRING_TRACER(trc, matchPairsInput, "res->matchPairsInput");
     }
 
     size_t getParenLength(size_t parenNum) const {
         if (pairCount() <= parenNum + 1)
             return 0;
         return get(parenNum + 1, 1) - get(parenNum + 1, 0);
     }
 
-    int get(size_t pairNum, bool which) const {
-        JS_ASSERT(pairNum < pairCount());
-        return matchPairs[2 * pairNum + which];
-    }
-
     /* Value creators. */
 
-    bool createInput(JSContext *cx, Value *out) const;
+    bool createPendingInput(JSContext *cx, Value *out) const;
     bool createLastMatch(JSContext *cx, Value *out) const { return makeMatch(cx, 0, 0, out); }
     bool createLastParen(JSContext *cx, Value *out) const;
     bool createLeftContext(JSContext *cx, Value *out) const;
     bool createRightContext(JSContext *cx, Value *out) const;
 
     bool createParen(JSContext *cx, size_t parenNum, Value *out) const {
         return makeMatch(cx, (parenNum + 1) * 2, parenNum + 1, out);
     }
@@ -199,16 +267,36 @@ class RegExpStatics
 
     void getParen(size_t num, JSSubString *out) const;
     void getLastMatch(JSSubString *out) const;
     void getLastParen(JSSubString *out) const;
     void getLeftContext(JSSubString *out) const;
     void getRightContext(JSSubString *out) const;
 };
 
+class PreserveRegExpStatics
+{
+    RegExpStatics *const original;
+    RegExpStatics buffer;
+
+  public:
+    explicit PreserveRegExpStatics(RegExpStatics *original)
+     : original(original),
+       buffer(RegExpStatics::InitBuffer())
+    {}
+
+    bool init(JSContext *cx) {
+        return original->save(cx, &buffer);
+    }
+
+    ~PreserveRegExpStatics() {
+        original->restore();
+    }
+};
+
 }
 
 static inline bool
 VALUE_IS_REGEXP(JSContext *cx, js::Value v)
 {
     return !v.isPrimitive() && v.toObject().isRegExp();
 }
 
--- a/js/src/jsregexpinlines.h
+++ b/js/src/jsregexpinlines.h
@@ -88,19 +88,18 @@ class RegExp
     RegExp(JSString *source, uint32 flags)
       : refCount(1), source(source), compiled(), parenCount(), flags(flags) {}
     bool compileHelper(JSContext *cx, UString &pattern);
     bool compile(JSContext *cx);
     static const uint32 allFlags = JSREG_FOLD | JSREG_GLOB | JSREG_MULTILINE | JSREG_STICKY;
     void handlePCREError(JSContext *cx, int error);
     void handleYarrError(JSContext *cx, int error);
     static inline bool initArena(JSContext *cx);
-    static inline void checkMatchPairs(int *buf, size_t matchItemCount);
-    JSObject *createResult(JSContext *cx, JSString *input, int *buf, size_t matchItemCount,
-                           size_t inputOffset);
+    static inline void checkMatchPairs(JSString *input, int *buf, size_t matchItemCount);
+    static JSObject *createResult(JSContext *cx, JSString *input, int *buf, size_t matchItemCount);
     inline bool executeInternal(JSContext *cx, RegExpStatics *res, JSString *input,
                                 size_t *lastIndex, bool test, Value *rval);
 
   public:
     ~RegExp() {
 #if !ENABLE_YARR_JIT
         if (compiled)
             jsRegExpFree(compiled);
@@ -213,65 +212,74 @@ RegExp::initArena(JSContext *cx)
     JS_ARENA_ALLOCATE_CAST(timestamp, int64 *, &cx->regExpPool, sizeof *timestamp);
     if (!timestamp)
         return false;
     *timestamp = JS_Now();
     return true;
 }
 
 inline void
-RegExp::checkMatchPairs(int *buf, size_t matchItemCount)
+RegExp::checkMatchPairs(JSString *input, int *buf, size_t matchItemCount)
 {
 #if DEBUG
-    for (size_t i = 0; i < matchItemCount; i += 2)
-        JS_ASSERT(buf[i + 1] >= buf[i]); /* Limit index must be larger than the start index. */
+    size_t inputLength = input->length();
+    int largestStartSeen = 0;
+    for (size_t i = 0; i < matchItemCount; i += 2) {
+        int start = buf[i];
+        int limit = buf[i + 1];
+        JS_ASSERT(limit >= start); /* Limit index must be larger than the start index. */
+        if (start == -1)
+            continue;
+        JS_ASSERT(start >= 0);
+        JS_ASSERT(size_t(limit) <= inputLength);
+        /* Test the monotonically increasing nature of left parens. */
+        JS_ASSERT(start >= largestStartSeen);
+        largestStartSeen = start;
+    }
 #endif
 }
 
 inline JSObject *
-RegExp::createResult(JSContext *cx, JSString *input, int *buf, size_t matchItemCount,
-                     size_t inputOffset)
+RegExp::createResult(JSContext *cx, JSString *input, int *buf, size_t matchItemCount)
 {
-#define MATCH_VALUE(__index) (buf[(__index)] + inputOffset)
     /*
      * Create the result array for a match. Array contents:
      *  0:              matched string
      *  1..parenCount:  paren matches
      */
     JSObject *array = js_NewSlowArrayObject(cx);
     if (!array)
         return NULL;
 
     RegExpMatchBuilder builder(cx, array);
     for (size_t i = 0; i < matchItemCount; i += 2) {
-        int start = MATCH_VALUE(i);
-        int end = MATCH_VALUE(i + 1);
+        int start = buf[i];
+        int end = buf[i + 1];
 
         JSString *captured;
         if (start >= 0) {
             JS_ASSERT(start <= end);
-            JS_ASSERT((unsigned) end <= input->length());
+            JS_ASSERT(unsigned(end) <= input->length());
             captured = js_NewDependentString(cx, input, start, end - start);
             if (!(captured && builder.append(i / 2, captured)))
                 return NULL;
         } else {
             /* Missing parenthesized match. */
             JS_ASSERT(i != 0); /* Since we had a match, first pair must be present. */
             JS_ASSERT(start == end && end == -1);
             if (!builder.append(INT_TO_JSID(i / 2), UndefinedValue()))
                 return NULL;
         }
     }
 
-    if (!builder.appendIndex(MATCH_VALUE(0)) ||
+    if (!builder.appendIndex(buf[0]) ||
         !builder.appendInput(input))
         return NULL;
 
     return array;
-#undef MATCH_VALUE
 }
 
 inline bool
 RegExp::executeInternal(JSContext *cx, RegExpStatics *res, JSString *input,
                         size_t *lastIndex, bool test, Value *rval)
 {
 #if !ENABLE_YARR_JIT
     JS_ASSERT(compiled);
@@ -292,16 +300,21 @@ RegExp::executeInternal(JSContext *cx, R
      * The JIT regexp procedure doesn't always initialize matchPair values.
      * Maybe we can make this faster by ensuring it does?
      */
     for (int *it = buf; it != buf + matchItemCount; ++it)
         *it = -1;
 
     const jschar *chars = input->chars();
     size_t len = input->length();
+
+    /* 
+     * inputOffset emulates sticky mode by matching from this offset into the char buf and
+     * subtracting the delta off at the end.
+     */
     size_t inputOffset = 0;
 
     if (sticky()) {
         /* Sticky matches at the last index for the regexp object. */
         chars += *lastIndex;
         len -= *lastIndex;
         inputOffset = *lastIndex;
     }
@@ -313,37 +326,39 @@ RegExp::executeInternal(JSContext *cx, R
     int result = jsRegExpExecute(cx, compiled, chars, len, *lastIndex - inputOffset, buf, 
                                  bufCount) < 0 ? -1 : buf[0];
 #endif
     if (result == -1) {
         *rval = NullValue();
         return true;
     }
 
-    checkMatchPairs(buf, matchItemCount);
-
-    if (res) {
-        res->aboutToWrite();
-        res->input = input;
-        if (!res->matchPairs.resizeUninitialized(matchItemCount)) {
-            js_ReportOutOfMemory(cx);
-            return false;
-        }
+    /* 
+     * Adjust buf for the inputOffset. Use of sticky is rare and the matchItemCount is small, so
+     * just do another pass.
+     */
+    if (JS_UNLIKELY(inputOffset)) {
         for (size_t i = 0; i < matchItemCount; ++i)
-            res->matchPairs[i] = buf[i] + inputOffset;
+            buf[i] = buf[i] < 0 ? -1 : buf[i] + inputOffset;
     }
 
-    *lastIndex = buf[1] + inputOffset;
+    /* Make sure the populated contents of |buf| are sane values against |input|. */
+    checkMatchPairs(input, buf, matchItemCount);
+
+    if (res)
+        res->updateFromMatch(cx, input, buf, matchItemCount);
+
+    *lastIndex = buf[1];
 
     if (test) {
         *rval = BooleanValue(true);
         return true;
     }
 
-    JSObject *array = createResult(cx, input, buf, matchItemCount, inputOffset);
+    JSObject *array = createResult(cx, input, buf, matchItemCount);
     if (!array)
         return false;
 
     *rval = ObjectValue(*array);
     return true;
 }
 
 inline RegExp *
@@ -525,28 +540,28 @@ RegExpStatics::extractFrom(JSObject *glo
     RegExpStatics *res = static_cast<RegExpStatics *>(resVal.toObject().getPrivate());
     return res;
 }
 
 inline bool
 RegExpStatics::createDependent(JSContext *cx, size_t start, size_t end, Value *out) const 
 {
     JS_ASSERT(start <= end);
-    JS_ASSERT(end <= input->length());
-    JSString *str = js_NewDependentString(cx, input, start, end - start);
+    JS_ASSERT(end <= matchPairsInput->length());
+    JSString *str = js_NewDependentString(cx, matchPairsInput, start, end - start);
     if (!str)
         return false;
     *out = StringValue(str);
     return true;
 }
 
 inline bool
-RegExpStatics::createInput(JSContext *cx, Value *out) const
+RegExpStatics::createPendingInput(JSContext *cx, Value *out) const
 {
-    out->setString(input ? input : cx->runtime->emptyString);
+    out->setString(pendingInput ? pendingInput : cx->runtime->emptyString);
     return true;
 }
 
 inline bool
 RegExpStatics::makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum, Value *out) const
 {
     if (checkValidIndex / 2 >= pairCount() || matchPairs[checkValidIndex] < 0) {
         out->setString(cx->runtime->emptyString);
@@ -594,70 +609,70 @@ RegExpStatics::createRightContext(JSCont
     if (!pairCount()) {
         out->setString(cx->runtime->emptyString);
         return true;
     }
     if (matchPairs[1] < 0) {
         *out = UndefinedValue();
         return true;
     }
-    return createDependent(cx, matchPairs[1], input->length(), out);
+    return createDependent(cx, matchPairs[1], matchPairsInput->length(), out);
 }
 
 inline void
 RegExpStatics::getParen(size_t num, JSSubString *out) const
 {
-    out->chars = input->chars() + get(num + 1, 0);
+    out->chars = matchPairsInput->chars() + get(num + 1, 0);
     out->length = getParenLength(num);
 }
 
 inline void
 RegExpStatics::getLastMatch(JSSubString *out) const
 {
     if (!pairCount()) {
         *out = js_EmptySubString;
         return;
     }
-    JS_ASSERT(input);
-    out->chars = input->chars() + get(0, 0);
+    JS_ASSERT(matchPairsInput);
+    out->chars = matchPairsInput->chars() + get(0, 0);
     JS_ASSERT(get(0, 1) >= get(0, 0));
     out->length = get(0, 1) - get(0, 0);
 }
 
 inline void
 RegExpStatics::getLastParen(JSSubString *out) const
 {
     if (!pairCount()) {
         *out = js_EmptySubString;
         return;
     }
     size_t num = pairCount() - 1;
-    out->chars = input->chars() + get(num, 0);
+    out->chars = matchPairsInput->chars() + get(num, 0);
     JS_ASSERT(get(num, 1) >= get(num, 0));
     out->length = get(num, 1) - get(num, 0);
 }
 
 inline void
 RegExpStatics::getLeftContext(JSSubString *out) const
 {
     if (!pairCount()) {
         *out = js_EmptySubString;
         return;
     }
-    out->chars = input->chars();
+    out->chars = matchPairsInput->chars();
     out->length = get(0, 0);
 }
 
 inline void
 RegExpStatics::getRightContext(JSSubString *out) const
 {
     if (!pairCount()) {
         *out = js_EmptySubString;
         return;
     }
-    out->chars = input->chars() + get(0, 1);
-    JS_ASSERT(get(0, 1) <= int(input->length()));
-    out->length = input->length() - get(0, 1);
+    out->chars = matchPairsInput->chars() + get(0, 1);
+    JS_ASSERT(get(0, 1) <= int(matchPairsInput->length()));
+    out->length = matchPairsInput->length() - get(0, 1);
 }
 
 }
 
 #endif /* jsregexpinlines_h___ */
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -1955,17 +1955,17 @@ str_search(JSContext *cx, uintN argc, Va
         return false;
 
     RegExpStatics *res = cx->regExpStatics();
     size_t i = 0;
     if (!rep->re().execute(cx, res, str, &i, true, vp))
         return false;
 
     if (vp->isTrue())
-        vp->setInt32(res->get(0, 0));
+        vp->setInt32(res->matchStart());
     else
         vp->setInt32(-1);
     return true;
 }
 
 struct ReplaceData
 {
     ReplaceData(JSContext *cx)
@@ -2045,36 +2045,16 @@ InterpretDollar(JSContext *cx, RegExpSta
         return true;
       case '\'':
         res->getRightContext(out);
         return true;
     }
     return false;
 }
 
-class PreserveRegExpStatics
-{
-    js::RegExpStatics *const original;
-    js::RegExpStatics buffer;
-
-  public:
-    explicit PreserveRegExpStatics(RegExpStatics *original)
-     : original(original),
-       buffer(RegExpStatics::InitBuffer())
-    {}
-
-    bool init(JSContext *cx) {
-        return original->save(cx, &buffer);
-    }
-
-    ~PreserveRegExpStatics() {
-        original->restore();
-    }
-};
-
 static bool
 FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t *sizep)
 {
     JSObject *base = rdata.elembase;
     if (base) {
         /*
          * The base object is used when replace was passed a lambda which looks like
          * 'function(a) { return b[a]; }' for the base object b.  b will not change
@@ -2155,17 +2135,17 @@ FindReplaceLength(JSContext *cx, RegExpS
             return false;
 
         for (size_t i = 0; i < res->getParenCount(); ++i) {
             if (!res->createParen(cx, i, &session[argi++]))
                 return false;
         }
 
         /* Push match index and input string. */
-        session[argi++].setInt32(res->get(0, 0));
+        session[argi++].setInt32(res->matchStart());
         session[argi].setString(rdata.str);
 
         if (!session.invoke(cx))
             return false;
 
         /* root repstr: rdata is on the stack, so scanned by conservative gc. */
         rdata.repstr = ValueToString_TestForStringInline(cx, session.rval());
         if (!rdata.repstr)
@@ -2222,18 +2202,18 @@ static bool
 ReplaceCallback(JSContext *cx, RegExpStatics *res, size_t count, void *p)
 {
     ReplaceData &rdata = *static_cast<ReplaceData *>(p);
 
     rdata.calledBack = true;
     JSString *str = rdata.str;
     size_t leftoff = rdata.leftIndex;
     const jschar *left = str->chars() + leftoff;
-    size_t leftlen = res->get(0, 0) - leftoff;
-    rdata.leftIndex = res->get(0, 1);
+    size_t leftlen = res->matchStart() - leftoff;
+    rdata.leftIndex = res->matchLimit();
 
     size_t replen = 0;  /* silence 'unused' warning */
     if (!FindReplaceLength(cx, res, rdata, &replen))
         return false;
 
     size_t growth = leftlen + replen;
     if (!rdata.cb.growByUninitialized(growth))
         return false;