Back out de5db0c4c3ff and ff14e0b88c10 (bug 820124) because of intermittent xpcshell assertions
authorMatt Brubeck <mbrubeck@mozilla.com>
Thu, 27 Dec 2012 14:52:04 -0800
changeset 126214 f2a500997116e854815a068ea70c6a6b74759d1b
parent 126213 23549b4dffb1791a5509ce0b1d927720f08ebb46
child 126215 0ec205214f5f4d20cd178c92544e9e37b0ac2de0
child 126237 e065ba89f5c381d47feebb390f50f206fefb1b94
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs820124
milestone20.0a1
backs outde5db0c4c3ffb5a4e22e5ea4a9c60532e2e78b03
first release with
nightly linux32
f2a500997116 / 20.0a1 / 20121228030811 / files
nightly linux64
f2a500997116 / 20.0a1 / 20121228030811 / files
nightly mac
f2a500997116 / 20.0a1 / 20121228030811 / files
nightly win32
f2a500997116 / 20.0a1 / 20121228030811 / files
nightly win64
f2a500997116 / 20.0a1 / 20121228030811 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Back out de5db0c4c3ff and ff14e0b88c10 (bug 820124) because of intermittent xpcshell assertions
js/src/builtin/RegExp.cpp
js/src/gc/Marking.cpp
js/src/gc/Marking.h
js/src/gc/RootMarking.cpp
js/src/jsstr.cpp
js/src/vm/RegExpObject.cpp
js/src/vm/RegExpObject.h
js/src/vm/RegExpStatics-inl.h
js/src/vm/RegExpStatics.cpp
js/src/vm/RegExpStatics.h
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -109,29 +109,29 @@ js::CreateRegExpMatchResult(JSContext *c
 {
     Rooted<JSStableString*> input(cx, string->ensureStable(cx));
     if (!input)
         return false;
     return CreateRegExpMatchResult(cx, input, input->chars(), input->length(), matches, rval);
 }
 
 RegExpRunStatus
-ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, RegExpShared &re,
+ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, RegExpShared &re, RegExpObject &regexp,
                   JSLinearString *input, StableCharPtr chars, size_t length,
                   size_t *lastIndex, MatchConduit &matches)
 {
     RegExpRunStatus status;
 
     /* Switch between MatchOnly and IncludeSubpatterns modes. */
     if (matches.isPair) {
         size_t lastIndex_orig = *lastIndex;
         /* Only one MatchPair slot provided: execute short-circuiting regexp. */
         status = re.executeMatchOnly(cx, chars, length, lastIndex, *matches.u.pair);
         if (status == RegExpRunStatus_Success && res)
-            res->updateLazily(cx, input, &re, lastIndex_orig);
+            res->updateLazily(cx, input, &regexp, lastIndex_orig);
     } else {
         /* Vector of MatchPairs provided: execute full regexp. */
         status = re.execute(cx, chars, length, lastIndex, *matches.u.pairs);
         if (status == RegExpRunStatus_Success && res)
             res->updateFromMatchPairs(cx, input, *matches.u.pairs);
     }
 
     return status;
@@ -146,17 +146,17 @@ js::ExecuteRegExpLegacy(JSContext *cx, R
     RegExpGuard shared;
     if (!reobj.getShared(cx, &shared))
         return false;
 
     ScopedMatchPairs matches(&cx->tempLifoAlloc());
     MatchConduit conduit(&matches);
 
     RegExpRunStatus status =
-        ExecuteRegExpImpl(cx, res, *shared, input, chars, length, lastIndex, conduit);
+        ExecuteRegExpImpl(cx, res, *shared, reobj, input, chars, length, lastIndex, conduit);
 
     if (status == RegExpRunStatus_Error)
         return false;
 
     if (status == RegExpRunStatus_Success_NotFound) {
         /* ExecuteRegExp() previously returned an array or null. */
         rval->setNull();
         return true;
@@ -586,17 +586,17 @@ js::ExecuteRegExp(JSContext *cx, HandleO
     if (i < 0 || i > length) {
         reobj->zeroLastIndex();
         return RegExpRunStatus_Success_NotFound;
     }
 
     /* Steps 8-21. */
     size_t lastIndexInt(i);
     RegExpRunStatus status =
-        ExecuteRegExpImpl(cx, res, *re, stableInput, chars, length, &lastIndexInt, matches);
+        ExecuteRegExpImpl(cx, res, *re, *reobj, stableInput, chars, length, &lastIndexInt, matches);
 
     if (status == RegExpRunStatus_Error)
         return RegExpRunStatus_Error;
 
     /* Step 11 (with sticky extension). */
     if (re->global() || (status == RegExpRunStatus_Success && re->sticky())) {
         if (status == RegExpRunStatus_Success_NotFound)
             reobj->zeroLastIndex();
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -327,16 +327,17 @@ Is##base##AboutToBeFinalized(Encapsulate
 DeclMarkerImpl(BaseShape, BaseShape)
 DeclMarkerImpl(BaseShape, UnownedBaseShape)
 DeclMarkerImpl(IonCode, ion::IonCode)
 DeclMarkerImpl(Object, ArgumentsObject)
 DeclMarkerImpl(Object, DebugScopeObject)
 DeclMarkerImpl(Object, GlobalObject)
 DeclMarkerImpl(Object, JSObject)
 DeclMarkerImpl(Object, JSFunction)
+DeclMarkerImpl(Object, RegExpObject)
 DeclMarkerImpl(Object, ScopeObject)
 DeclMarkerImpl(Script, JSScript)
 DeclMarkerImpl(Shape, Shape)
 DeclMarkerImpl(String, JSAtom)
 DeclMarkerImpl(String, JSString)
 DeclMarkerImpl(String, JSFlatString)
 DeclMarkerImpl(String, JSLinearString)
 DeclMarkerImpl(String, PropertyName)
--- a/js/src/gc/Marking.h
+++ b/js/src/gc/Marking.h
@@ -92,16 +92,17 @@ bool Is##base##AboutToBeFinalized(Encaps
 DeclMarker(BaseShape, BaseShape)
 DeclMarker(BaseShape, UnownedBaseShape)
 DeclMarker(IonCode, ion::IonCode)
 DeclMarker(Object, ArgumentsObject)
 DeclMarker(Object, DebugScopeObject)
 DeclMarker(Object, GlobalObject)
 DeclMarker(Object, JSObject)
 DeclMarker(Object, JSFunction)
+DeclMarker(Object, RegExpObject)
 DeclMarker(Object, ScopeObject)
 DeclMarker(Script, JSScript)
 DeclMarker(Shape, Shape)
 DeclMarker(String, JSAtom)
 DeclMarker(String, JSString)
 DeclMarker(String, JSFlatString)
 DeclMarker(String, JSLinearString)
 DeclMarker(String, PropertyName)
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -669,16 +669,19 @@ Shape::Range::AutoRooter::trace(JSTracer
 {
     if (r->cursor)
         MarkShapeRoot(trc, const_cast<Shape**>(&r->cursor), "Shape::Range::AutoRooter");
 }
 
 void
 RegExpStatics::AutoRooter::trace(JSTracer *trc)
 {
+    if (statics->regexp)
+        MarkObjectRoot(trc, reinterpret_cast<JSObject**>(&statics->regexp),
+                       "RegExpStatics::AutoRooter regexp");
     if (statics->matchesInput)
         MarkStringRoot(trc, reinterpret_cast<JSString**>(&statics->matchesInput),
                        "RegExpStatics::AutoRooter matchesInput");
     if (statics->pendingInput)
         MarkStringRoot(trc, reinterpret_cast<JSString**>(&statics->pendingInput),
                        "RegExpStatics::AutoRooter pendingInput");
 }
 
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2303,142 +2303,28 @@ BuildDollarReplacement(JSContext *cx, JS
            builder.append(newReplace) &&
            builder.append(rightSide));
 #undef ENSURE
 
     args->rval().setString(builder.result());
     return true;
 }
 
-struct StringRange
-{
-    size_t start;
-    size_t length;
-
-    StringRange(size_t s, size_t l)
-      : start(s), length(l)
-    { }
-};
-
-static JSString *
-AppendSubstrings(JSContext *cx, Handle<JSStableString*> stableStr,
-                 const StringRange *ranges, size_t rangesLen)
-{
-    JS_ASSERT(rangesLen);
-
-    /* For single substrings, construct a dependent string. */
-    if (rangesLen == 1)
-        return js_NewDependentString(cx, stableStr, ranges[0].start, ranges[0].length);
-
-    /* Collect substrings into a rope. */
-    RopeBuilder rope(cx);
-    for (size_t i = 0; i < rangesLen; i++) {
-        const StringRange &sr = ranges[i];
-
-        RootedString substr(cx, js_NewDependentString(cx, stableStr, sr.start, sr.length));
-        if (!substr)
-            return NULL;
-
-        /* Appending to the rope permanently roots the substring. */
-        rope.append(substr);
-    }
-
-    return rope.result();
-}
-
-static bool
-str_replace_regexp_remove(JSContext *cx, CallArgs args, HandleString str, RegExpShared &re)
-{
-    Rooted<JSStableString*> stableStr(cx, str->ensureStable(cx));
-    if (!stableStr)
-        return false;
-
-    Vector<StringRange, 16, SystemAllocPolicy> ranges;
-
-    StableCharPtr chars = stableStr->chars();
-    size_t charsLen = stableStr->length();
-
-    MatchPair match;
-    size_t startIndex = 0; /* Index used for iterating through the string. */
-    size_t lastIndex = 0;  /* Index after last successful match. */
-
-    /* Accumulate StringRanges for unmatched substrings. */
-    while (startIndex <= charsLen) {
-        if (!JS_CHECK_OPERATION_LIMIT(cx))
-            return false;
-
-        RegExpRunStatus status = re.executeMatchOnly(cx, chars, charsLen, &startIndex, match);
-        if (status == RegExpRunStatus_Error)
-            return false;
-        if (status == RegExpRunStatus_Success_NotFound)
-            break;
-
-        /* Include the latest unmatched substring. */
-        if (size_t(match.start) > lastIndex) {
-            if (!ranges.append(StringRange(lastIndex, match.start - lastIndex)))
-                return false;
-        }
-
-        lastIndex = startIndex;
-
-        /* Non-global removal executes at most once. */
-        if (!re.global())
-            break;
-
-        if (match.isEmpty())
-            startIndex++;
-    }
-
-    /* If unmatched, return the input string. */
-    if (!lastIndex) {
-        args.rval().setString(str);
-        return true;
-    }
-
-    /* The last successful match updates the RegExpStatics. */
-    cx->regExpStatics()->updateLazily(cx, stableStr, &re, lastIndex);
-
-    /* Include any remaining part of the string. */
-    if (lastIndex < charsLen) {
-        if (!ranges.append(StringRange(lastIndex, charsLen - lastIndex)))
-            return false;
-    }
-
-    /* Handle the empty string before calling .begin(). */
-    if (ranges.empty()) {
-        args.rval().setString(cx->runtime->emptyString);
-        return true;
-    }
-
-    JSString *result = AppendSubstrings(cx, stableStr, ranges.begin(), ranges.length());
-    if (!result)
-        return false;
-
-    args.rval().setString(result);
-    return true;
-}
-
 static inline bool
 str_replace_regexp(JSContext *cx, CallArgs args, ReplaceData &rdata)
 {
     if (!rdata.g.normalizeRegExp(cx, true, 2, args))
         return false;
 
     rdata.leftIndex = 0;
     rdata.calledBack = false;
 
     RegExpStatics *res = cx->regExpStatics();
     RegExpShared &re = rdata.g.regExp();
 
-    /* Optimize removal. */
-    if (rdata.repstr && rdata.repstr->length() == 0 && !rdata.dollar) {
-        JS_ASSERT(!rdata.lambda && !rdata.elembase);
-        return str_replace_regexp_remove(cx, args, rdata.str, re);
-    }
-
     Value tmp;
     if (!DoMatch(cx, res, rdata.str, re, ReplaceRegExpCallback, &rdata, REPLACE_ARGS, &tmp))
         return false;
 
     if (!rdata.calledBack) {
         /* Didn't match, so the string is unmodified. */
         args.rval().setString(rdata.str);
         return true;
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -640,28 +640,16 @@ RegExpShared::executeMatchOnly(JSContext
 
 RegExpCompartment::RegExpCompartment(JSRuntime *rt)
   : map_(rt), inUse_(rt)
 {}
 
 RegExpCompartment::~RegExpCompartment()
 {
     JS_ASSERT(map_.empty());
-
-    /*
-     * RegExpStatics may have prevented a single RegExpShared from
-     * being collected during RegExpCompartment::sweep().
-     */
-    if (!inUse_.empty()) {
-        PendingSet::Enum e(inUse_);
-        RegExpShared *shared = e.front();
-        JS_ASSERT(shared->activeUseCount == 0);
-        js_delete(shared);
-        e.removeFront();
-    }
     JS_ASSERT(inUse_.empty());
 }
 
 bool
 RegExpCompartment::init(JSContext *cx)
 {
     if (!map_.init() || !inUse_.init()) {
         if (cx)
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -193,58 +193,46 @@ class RegExpShared
 
 /*
  * Extend the lifetime of a given RegExpShared to at least the lifetime of
  * the guard object. See Regular Expression comment at the top.
  */
 class RegExpGuard
 {
     RegExpShared *re_;
-
-  private:
     RegExpGuard(const RegExpGuard &) MOZ_DELETE;
     void operator=(const RegExpGuard &) MOZ_DELETE;
-
   public:
     RegExpGuard() : re_(NULL) {}
     RegExpGuard(RegExpShared &re) : re_(&re) {
         re_->incRef();
     }
-    ~RegExpGuard() { release(); }
-
-  public:
     void init(RegExpShared &re) {
-        JS_ASSERT(!initialized());
+        JS_ASSERT(!re_);
         re_ = &re;
         re_->incRef();
     }
-    void release() {
-        if (re_) {
+    ~RegExpGuard() {
+        if (re_)
             re_->decRef();
-            re_ = NULL;
-        }
     }
-
     bool initialized() const { return !!re_; }
     RegExpShared *re() const { JS_ASSERT(initialized()); return re_; }
     RegExpShared *operator->() { return re(); }
     RegExpShared &operator*() { return *re(); }
 };
 
 class RegExpCompartment
 {
     struct Key {
         JSAtom *atom;
         uint16_t flag;
-
         Key() {}
         Key(JSAtom *atom, RegExpFlag flag)
-          : atom(atom), flag(flag)
-        { }
-
+          : atom(atom), flag(flag) {}
         typedef Key Lookup;
         static HashNumber hash(const Lookup &l) {
             return DefaultHasher<JSAtom *>::hash(l.atom) ^ (l.flag << 1);
         }
         static bool match(Key l, Key r) {
             return l.atom == r.atom && l.flag == r.flag;
         }
     };
--- a/js/src/vm/RegExpStatics-inl.h
+++ b/js/src/vm/RegExpStatics-inl.h
@@ -22,16 +22,25 @@ js::GlobalObject::getRegExpStatics() con
 }
 
 inline size_t
 SizeOfRegExpStaticsData(const JSObject *obj, JSMallocSizeOfFun mallocSizeOf)
 {
     return mallocSizeOf(obj->getPrivate());
 }
 
+inline
+RegExpStatics::RegExpStatics()
+  : pendingLazyEvaluation(false),
+    bufferLink(NULL),
+    copied(false)
+{
+    clear();
+}
+
 inline bool
 RegExpStatics::createDependent(JSContext *cx, size_t start, size_t end, Value *out)
 {
     /* Private function: caller must perform lazy evaluation. */
     JS_ASSERT(!pendingLazyEvaluation);
 
     JS_ASSERT(start <= end);
     JS_ASSERT(end <= matchesInput->length());
@@ -217,30 +226,25 @@ RegExpStatics::getRightContext(JSSubStri
 
 inline void
 RegExpStatics::copyTo(RegExpStatics &dst)
 {
     /* Destination buffer has already been reserved by save(). */
     if (!pendingLazyEvaluation)
         dst.matches.initArrayFrom(matches);
 
-    if (regexpGuard.initialized())
-        dst.regexpGuard.init(*regexpGuard);
-    else
-        dst.regexpGuard.release();
-
     dst.matchesInput = matchesInput;
+    dst.regexp = regexp;
     dst.lastIndex = lastIndex;
     dst.pendingInput = pendingInput;
     dst.flags = flags;
     dst.pendingLazyEvaluation = pendingLazyEvaluation;
 
-    JS_ASSERT_IF(pendingLazyEvaluation, regexpGuard.initialized());
+    JS_ASSERT_IF(pendingLazyEvaluation, regexp);
     JS_ASSERT_IF(pendingLazyEvaluation, matchesInput);
-    JS_ASSERT(regexpGuard.initialized() == dst.regexpGuard.initialized());
 }
 
 inline void
 RegExpStatics::aboutToWrite()
 {
     if (bufferLink && !bufferLink->copied) {
         copyTo(*bufferLink);
         bufferLink->copied = true;
@@ -252,41 +256,38 @@ RegExpStatics::restore()
 {
     if (bufferLink->copied)
         bufferLink->copyTo(*this);
     bufferLink = bufferLink->bufferLink;
 }
 
 inline void
 RegExpStatics::updateLazily(JSContext *cx, JSLinearString *input,
-                            RegExpShared *shared, size_t lastIndex)
+                            RegExpObject *regexp, size_t lastIndex)
 {
-    JS_ASSERT(input && shared);
+    JS_ASSERT(input && regexp);
     aboutToWrite();
 
     BarrieredSetPair<JSString, JSLinearString>(cx->compartment,
                                                pendingInput, input,
                                                matchesInput, input);
-    if (regexpGuard.initialized())
-        regexpGuard.release();
-    regexpGuard.init(*shared);
-
+    pendingLazyEvaluation = true;
+    this->regexp = regexp;
     this->lastIndex = lastIndex;
-    pendingLazyEvaluation = true;
 }
 
 inline bool
 RegExpStatics::updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs &newPairs)
 {
     JS_ASSERT(input);
     aboutToWrite();
 
     /* Unset all lazy state. */
     pendingLazyEvaluation = false;
-    this->regexpGuard.release();
+    this->regexp = NULL;
     this->lastIndex = size_t(-1);
 
     BarrieredSetPair<JSString, JSLinearString>(cx->compartment,
                                                pendingInput, input,
                                                matchesInput, input);
 
     if (!matches.initArrayFrom(newPairs)) {
         js_ReportOutOfMemory(cx);
@@ -295,24 +296,21 @@ RegExpStatics::updateFromMatchPairs(JSCo
 
     return true;
 }
 
 inline void
 RegExpStatics::clear()
 {
     aboutToWrite();
-
-    matches.forgetArray();
+    flags = RegExpFlag(0);
+    pendingInput = NULL;
+    pendingLazyEvaluation = false;
     matchesInput = NULL;
-    regexpGuard.release();
-    lastIndex = size_t(-1);
-    pendingInput = NULL;
-    flags = RegExpFlag(0);
-    pendingLazyEvaluation = false;
+    matches.forgetArray();
 }
 
 inline void
 RegExpStatics::setPendingInput(JSString *newInput)
 {
     aboutToWrite();
     pendingInput = newInput;
 }
@@ -360,19 +358,18 @@ RegExpStatics::reset(JSContext *cx, JSSt
     checkInvariants();
 }
 
 inline void
 RegExpStatics::checkInvariants()
 {
 #ifdef DEBUG
     if (pendingLazyEvaluation) {
-        JS_ASSERT(regexpGuard.initialized());
+        JS_ASSERT(regexp);
         JS_ASSERT(pendingInput);
-        JS_ASSERT(lastIndex != size_t(-1));
         return;
     }
 
     if (matches.empty()) {
         JS_ASSERT(!matchesInput);
         return;
     }
 
--- a/js/src/vm/RegExpStatics.cpp
+++ b/js/src/vm/RegExpStatics.cpp
@@ -69,32 +69,35 @@ RegExpStatics::create(JSContext *cx, Glo
 }
 
 bool
 RegExpStatics::executeLazy(JSContext *cx)
 {
     if (!pendingLazyEvaluation)
         return true;
 
-    JS_ASSERT(regexpGuard.initialized());
+    JS_ASSERT(regexp);
     JS_ASSERT(matchesInput);
     JS_ASSERT(lastIndex != size_t(-1));
 
     /*
      * It is not necessary to call aboutToWrite(): evaluation of
      * implicit copies is safe.
      */
 
     size_t length = matchesInput->length();
     StableCharPtr chars(matchesInput->chars(), length);
 
     /* Execute the full regular expression. */
-    RegExpRunStatus status = regexpGuard->execute(cx, chars, length, &this->lastIndex, this->matches);
+    RegExpGuard shared;
+    if (!regexp->getShared(cx, &shared))
+        return false;
+
+    RegExpRunStatus status = shared->execute(cx, chars, length, &this->lastIndex, this->matches);
     if (status == RegExpRunStatus_Error)
         return false;
 
     /* Unset lazy state and remove rooted values that now have no use. */
     pendingLazyEvaluation = false;
-    regexpGuard.release();
-    lastIndex = size_t(-1);
+    regexp = NULL;
 
     return true;
 }
--- a/js/src/vm/RegExpStatics.h
+++ b/js/src/vm/RegExpStatics.h
@@ -10,48 +10,43 @@
 
 #include "jscntxt.h"
 
 #include "gc/Barrier.h"
 #include "gc/Marking.h"
 #include "js/Vector.h"
 
 #include "vm/MatchPairs.h"
-#include "vm/RegExpObject.h"
 
 namespace js {
 
 class RegExpStatics
 {
     /* The latest RegExp output, set after execution. */
     VectorMatchPairs        matches;
     HeapPtr<JSLinearString> matchesInput;
 
     /* The previous RegExp input, used to resolve lazy state. */
-    RegExpGuard             regexpGuard;  /* Strong reference to RegExpShared. */
+    HeapPtr<RegExpObject>   regexp;
     size_t                  lastIndex;
 
     /* The latest RegExp input, set before execution. */
     HeapPtr<JSString>       pendingInput;
     RegExpFlag              flags;
 
     /*
-     * If true, |matchesInput|, |regexpGuard|, and |lastIndex| may be used
+     * If true, |matchesInput|, |regexp|, and |lastIndex| may be used
      * to replay the last executed RegExp, and |matches| is invalid.
      */
     bool                    pendingLazyEvaluation;
 
     /* Linkage for preserving RegExpStatics during nested RegExp execution. */
     RegExpStatics           *bufferLink;
     bool                    copied;
 
-  public:
-    RegExpStatics() : bufferLink(NULL), copied(false) { clear(); }
-    static JSObject *create(JSContext *cx, GlobalObject *parent);
-
   private:
     bool executeLazy(JSContext *cx);
 
     inline void aboutToWrite();
     inline void copyTo(RegExpStatics &dst);
 
     inline void restore();
     bool save(JSContext *cx, RegExpStatics *buffer) {
@@ -78,19 +73,24 @@ class RegExpStatics
     void markFlagsSet(JSContext *cx);
 
     struct InitBuffer {};
     explicit RegExpStatics(InitBuffer) : bufferLink(NULL), copied(false) {}
 
     friend class PreserveRegExpStatics;
 
   public:
+    inline RegExpStatics();
+
+    static JSObject *create(JSContext *cx, GlobalObject *parent);
+
     /* Mutators. */
+
     inline void updateLazily(JSContext *cx, JSLinearString *input,
-                             RegExpShared *shared, size_t lastIndex);
+                             RegExpObject *regexp, size_t lastIndex);
     inline bool updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs &newPairs);
     inline void setMultiline(JSContext *cx, bool enabled);
 
     inline void clear();
 
     /* Corresponds to JSAPI functionality to set the pending RegExp input. */
     inline void reset(JSContext *cx, JSString *newInput, bool newMultiline);
 
@@ -113,16 +113,18 @@ class RegExpStatics
     bool matched() const {
         /* Safe: only used by String methods, which do not set lazy mode. */
         JS_ASSERT(!pendingLazyEvaluation);
         JS_ASSERT(matches.pairCount() > 0);
         return matches[0].limit - matches[0].start > 0;
     }
 
     void mark(JSTracer *trc) {
+        if (regexp)
+            gc::MarkObject(trc, &regexp, "res->regexp");
         if (pendingInput)
             MarkString(trc, &pendingInput, "res->pendingInput");
         if (matchesInput)
             MarkString(trc, &matchesInput, "res->matchesInput");
     }
 
     /* Value creators. */