Encapsulate RegExpStatics more. (r=gal, b=610223)
authorChris Leary <cdleary@mozilla.com>
Wed, 10 Nov 2010 17:02:08 -0800
changeset 57584 f0458767cf4b2d8f37eabb8feb9475fbccf43f18
parent 57583 860d55aec420a96d0535d39c9a68b50099fb697c
child 57585 4a224009eab510b719fa68ec5a7f56e6f3b55220
push id16994
push userjwalden@mit.edu
push dateTue, 16 Nov 2010 18:14:53 +0000
treeherdermozilla-central@4a224009eab5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgal
bugs610223
milestone2.0b8pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
@@ -1950,17 +1950,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)
@@ -2040,36 +2040,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
@@ -2150,17 +2130,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)
@@ -2217,18 +2197,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;