diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -148,18 +148,29 @@ js::ExecuteRegExpLegacy(JSContext* cx, R
         rval.setBoolean(true);
         return true;
     }
 
     return CreateRegExpMatchResult(cx, input, matches, rval);
 }
 
 /*
- * ES6 21.2.3.2.2.  Because this function only ever returns |obj| in the spec,
- * provided by the user, we omit it and just return the usual success/failure.
+ * ES6 21.2.3.2.2, steps 1-13.
+ *
+ * Steps 14-15 set |obj|'s "lastIndex" property to zero.  Some of
+ * RegExpInitialize's callers have a fresh RegExp not yet exposed to script:
+ * in these cases zeroing "lastIndex" is infallible.  But others have a RegExp
+ * whose "lastIndex" property might have been made non-writable: here, zeroing
+ * "lastIndex" can fail.  We efficiently solve this problem by completely
+ * removing "lastIndex" zeroing from the provided function.
+ *
+ * CALLERS MUST HANDLE "lastIndex" ZEROING THEMSELVES!
+ *
+ * Because this function only ever returns a user-provided |obj| in the spec,
+ * we omit it and just return the usual success/failure.
  */
 static bool
 RegExpInitializeIgnoringLastIndex(JSContext* cx, Handle<RegExpObject*> obj,
                                   HandleValue patternValue, HandleValue flagsValue,
                                   RegExpStaticsUse staticsUse)
 {
     RootedAtom pattern(cx);
     if (patternValue.isUndefined()) {
@@ -288,16 +299,20 @@ regexp_compile_impl(JSContext* cx, const
         RootedValue P(cx, patternValue);
         RootedValue F(cx, args.get(1));
 
         // Step 5, minus lastIndex zeroing.
         if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F, UseRegExpStatics))
             return false;
     }
 
+    // The final niggling bit of step 5.
+    //
+    // |regexp| is user-exposed, but if its "lastIndex" property hasn't been
+    // made non-writable, we can still use a fast path to zero it.
     if (regexp->lookupPure(cx->names().lastIndex)->writable()) {
         regexp->zeroLastIndex(cx);
     } else {
         RootedValue zero(cx, Int32Value(0));
         if (!SetProperty(cx, regexp, cx->names().lastIndex, zero))
             return false;
     }
 
diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -443,16 +443,19 @@ class RegExpObject : public NativeObject
         MOZ_ASSERT(!maybeShared());
         NativeObject::setPrivate(&shared);
     }
 
     static void trace(JSTracer* trc, JSObject* obj);
 
     void initIgnoringLastIndex(HandleAtom source, RegExpFlag flags);
 
+    // NOTE: This method is *only* safe to call on RegExps that haven't been
+    //       exposed to script, because it requires that the "lastIndex"
+    //       property be writable.
     void initAndZeroLastIndex(HandleAtom source, RegExpFlag flags, ExclusiveContext* cx);
 
   private:
     /*
      * Precondition: the syntax for |source| has already been validated.
      * Side effect: sets the private field.
      */
     bool createShared(JSContext* cx, RegExpGuard* g);
