Bug 1253099. r=arai, r=efaust, a=ritu
authorJeff Walden <jwalden@mit.edu>
Sat, 05 Mar 2016 04:28:53 -0800
changeset 325550 b68ca8f1122c22f4dd58dfc902f43438b40ca848
parent 325549 094bebe3dafd9c7603ecabd181e0110375f64781
child 325551 ebe83472332c190ada8ed46e183ff91d41b1da69
push id1128
push userjlund@mozilla.com
push dateWed, 01 Jun 2016 01:31:59 +0000
treeherdermozilla-release@fe0d30de989d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai, efaust, ritu
bugs1253099
milestone47.0a2
Bug 1253099. r=arai, r=efaust, a=ritu
js/src/builtin/RegExp.cpp
js/src/jsstr.cpp
js/src/vm/RegExpObject.cpp
js/src/vm/RegExpObject.h
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -152,63 +152,62 @@ js::ExecuteRegExpLegacy(JSContext* cx, R
     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.
  */
 static bool
-RegExpInitialize(JSContext* cx, Handle<RegExpObject*> obj, HandleValue patternValue,
-                 HandleValue flagsValue, RegExpStaticsUse staticsUse)
+RegExpInitializeIgnoringLastIndex(JSContext* cx, Handle<RegExpObject*> obj,
+                                  HandleValue patternValue, HandleValue flagsValue,
+                                  RegExpStaticsUse staticsUse)
 {
     RootedAtom pattern(cx);
     if (patternValue.isUndefined()) {
         /* Step 1. */
-        pattern = cx->runtime()->emptyString;
+        pattern = cx->names().empty;
     } else {
         /* Steps 2-3. */
         pattern = ToAtom<CanGC>(cx, patternValue);
         if (!pattern)
             return false;
     }
 
     /* Step 4. */
     RegExpFlag flags = RegExpFlag(0);
     if (!flagsValue.isUndefined()) {
         /* Steps 5-6. */
         RootedString flagStr(cx, ToString<CanGC>(cx, flagsValue));
         if (!flagStr)
             return false;
+
         /* Step 7. */
         if (!ParseRegExpFlags(cx, flagStr, &flags))
             return false;
     }
 
-    /* Steps 9-10. */
+    /* Steps 8-10. */
     CompileOptions options(cx);
     frontend::TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr);
     if (!irregexp::ParsePatternSyntax(dummyTokenStream, cx->tempLifoAlloc(), pattern,
                                       flags & UnicodeFlag))
     {
         return false;
     }
 
     if (staticsUse == UseRegExpStatics) {
         RegExpStatics* res = cx->global()->getRegExpStatics(cx);
         if (!res)
             return false;
         flags = RegExpFlag(flags | res->getFlags());
     }
 
-    /* Steps 11-15. */
-    if (!RegExpObject::initFromAtom(cx, obj, pattern, flags))
-        return false;
-
-    /* Step 16. */
+    /* Steps 11-13. */
+    obj->initIgnoringLastIndex(pattern, flags);
     return true;
 }
 
 MOZ_ALWAYS_INLINE bool
 IsRegExpObject(HandleValue v)
 {
     return v.isObject() && v.toObject().is<RegExpObject>();
 }
@@ -277,31 +276,35 @@ regexp_compile_impl(JSContext* cx, const
             RegExpGuard g(cx);
             if (!RegExpToShared(cx, patternObj, &g))
                 return false;
 
             sourceAtom = g->getSource();
             flags = g->getFlags();
         }
 
-        // Step 5.
-        if (!RegExpObject::initFromAtom(cx, regexp, sourceAtom, flags))
+        // Step 5, minus lastIndex zeroing.
+        regexp->initIgnoringLastIndex(sourceAtom, flags);
+    } else {
+        // Step 4.
+        RootedValue P(cx, patternValue);
+        RootedValue F(cx, args.get(1));
+
+        // Step 5, minus lastIndex zeroing.
+        if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F, UseRegExpStatics))
             return false;
-
-        args.rval().setObject(*regexp);
-        return true;
     }
 
-    // Step 4.
-    RootedValue P(cx, patternValue);
-    RootedValue F(cx, args.get(1));
-
-    // Step 5.
-    if (!RegExpInitialize(cx, regexp, P, F, UseRegExpStatics))
-        return false;
+    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;
+    }
 
     args.rval().setObject(*regexp);
     return true;
 }
 
 static bool
 regexp_compile(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -387,18 +390,17 @@ js::regexp_construct(JSContext* cx, unsi
             flags = RegExpFlag(0);
             RootedString flagStr(cx, ToString<CanGC>(cx, args[1]));
             if (!flagStr)
                 return false;
             if (!ParseRegExpFlags(cx, flagStr, &flags))
                 return false;
         }
 
-        if (!RegExpObject::initFromAtom(cx, regexp, sourceAtom, flags))
-            return false;
+        regexp->initAndZeroLastIndex(sourceAtom, flags, cx);
 
         args.rval().setObject(*regexp);
         return true;
     }
 
     RootedValue P(cx);
     RootedValue F(cx);
 
@@ -427,18 +429,19 @@ js::regexp_construct(JSContext* cx, unsi
     if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
         return false;
 
     Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, proto));
     if (!regexp)
         return false;
 
     // Step 10.
-    if (!RegExpInitialize(cx, regexp, P, F, UseRegExpStatics))
+    if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F, UseRegExpStatics))
         return false;
+    regexp->zeroLastIndex(cx);
 
     args.rval().setObject(*regexp);
     return true;
 }
 
 bool
 js::regexp_construct_no_statics(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -451,18 +454,19 @@ js::regexp_construct_no_statics(JSContex
 
     /* Steps 1-6 are not required since pattern is always string. */
 
     /* Steps 7-10. */
     Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx));
     if (!regexp)
         return false;
 
-    if (!RegExpInitialize(cx, regexp, args[0], args.get(1), DontUseRegExpStatics))
+    if (!RegExpInitializeIgnoringLastIndex(cx, regexp, args[0], args.get(1), DontUseRegExpStatics))
         return false;
+    regexp->zeroLastIndex(cx);
 
     args.rval().setObject(*regexp);
     return true;
 }
 
 /* ES6 draft rev32 21.2.5.4. */
 MOZ_ALWAYS_INLINE bool
 regexp_global_impl(JSContext* cx, const CallArgs& args)
@@ -766,19 +770,22 @@ js::CreateRegExpPrototype(JSContext* cx,
 {
     MOZ_ASSERT(key == JSProto_RegExp);
 
     Rooted<RegExpObject*> proto(cx, cx->global()->createBlankPrototype<RegExpObject>(cx));
     if (!proto)
         return nullptr;
     proto->NativeObject::setPrivate(nullptr);
 
+    if (!RegExpObject::assignInitialShape(cx, proto))
+        return nullptr;
+
     RootedAtom source(cx, cx->names().empty);
-    if (!RegExpObject::initFromAtom(cx, proto, source, RegExpFlag(0)))
-        return nullptr;
+    proto->initAndZeroLastIndex(source, RegExpFlag(0), cx);
+
     return proto;
 }
 
 template <typename CharT>
 static bool
 IsTrailSurrogateWithLeadSurrogateImpl(JSContext* cx, HandleLinearString input, size_t index)
 {
     JS::AutoCheckCannotGC nogc;
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2230,17 +2230,17 @@ class MOZ_STACK_CLASS StringRegExpGuard
         if (!regExpIsObject())
             return true;
 
         // Use a fast path for same-global RegExp objects with writable
         // lastIndex.
         if (obj_->is<RegExpObject>()) {
             RegExpObject* nobj = &obj_->as<RegExpObject>();
             if (nobj->lookup(cx, cx->names().lastIndex)->writable()) {
-                nobj->zeroLastIndex();
+                nobj->zeroLastIndex(cx);
                 return true;
             }
         }
 
         // Handle everything else generically (including throwing if .lastIndex is non-writable).
         RootedValue zero(cx, Int32Value(0));
         return SetProperty(cx, obj_, cx->names().lastIndex, zero);
     }
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -41,20 +41,30 @@ JS_STATIC_ASSERT(MultilineFlag == JSREG_
 JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
 JS_STATIC_ASSERT(UnicodeFlag == JSREG_UNICODE);
 
 RegExpObject*
 js::RegExpAlloc(ExclusiveContext* cx, HandleObject proto /* = nullptr */)
 {
     // Note: RegExp objects are always allocated in the tenured heap. This is
     // not strictly required, but simplifies embedding them in jitcode.
-    RegExpObject* regexp = NewObjectWithClassProto<RegExpObject>(cx, proto, TenuredObject);
+    Rooted<RegExpObject*> regexp(cx);
+
+    regexp = NewObjectWithClassProto<RegExpObject>(cx, proto, TenuredObject);
     if (!regexp)
         return nullptr;
+
     regexp->initPrivate(nullptr);
+
+    if (!EmptyShape::ensureInitialCustomShape<RegExpObject>(cx, regexp))
+        return nullptr;
+
+    MOZ_ASSERT(regexp->lookupPure(cx->names().lastIndex)->slot() ==
+               RegExpObject::lastIndexSlot());
+
     return regexp;
 }
 
 /* MatchPairs */
 
 bool
 MatchPairs::initArrayFrom(MatchPairs& copyFrom)
 {
@@ -170,23 +180,16 @@ RegExpObject::trace(JSTracer* trc, JSObj
         !obj->asTenured().zone()->isPreservingCode())
     {
         obj->as<RegExpObject>().NativeObject::setPrivate(nullptr);
     } else {
         shared->trace(trc);
     }
 }
 
-/* static */ bool
-RegExpObject::initFromAtom(ExclusiveContext* cx, Handle<RegExpObject*> regexp, HandleAtom source,
-                           RegExpFlag flags)
-{
-    return regexp->init(cx, source, flags);
-}
-
 const Class RegExpObject::class_ = {
     js_RegExp_str,
     JSCLASS_HAS_PRIVATE |
     JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
@@ -246,18 +249,17 @@ RegExpObject::createNoStatics(ExclusiveC
 
     if (!irregexp::ParsePatternSyntax(*tokenStream, alloc, source, flags & UnicodeFlag))
         return nullptr;
 
     Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx));
     if (!regexp)
         return nullptr;
 
-    if (!RegExpObject::initFromAtom(cx, regexp, source, flags))
-        return nullptr;
+    regexp->initAndZeroLastIndex(source, flags, cx);
 
     return regexp;
 }
 
 bool
 RegExpObject::createShared(JSContext* cx, RegExpGuard* g)
 {
     Rooted<RegExpObject*> self(cx, this);
@@ -276,37 +278,32 @@ RegExpObject::assignInitialShape(Exclusi
     MOZ_ASSERT(self->empty());
 
     JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0);
 
     /* The lastIndex property alone is writable but non-configurable. */
     return self->addDataProperty(cx, cx->names().lastIndex, LAST_INDEX_SLOT, JSPROP_PERMANENT);
 }
 
-bool
-RegExpObject::init(ExclusiveContext* cx, HandleAtom source, RegExpFlag flags)
+void
+RegExpObject::initIgnoringLastIndex(HandleAtom source, RegExpFlag flags)
 {
-    Rooted<RegExpObject*> self(cx, this);
-
-    if (!EmptyShape::ensureInitialCustomShape<RegExpObject>(cx, self))
-        return false;
-
-    MOZ_ASSERT(self->lookup(cx, NameToId(cx->names().lastIndex))->slot() ==
-               LAST_INDEX_SLOT);
+    // If this is a re-initialization with an existing RegExpShared, 'flags'
+    // may not match getShared()->flags, so forget the RegExpShared.
+    NativeObject::setPrivate(nullptr);
 
-    /*
-     * If this is a re-initialization with an existing RegExpShared, 'flags'
-     * may not match getShared()->flags, so forget the RegExpShared.
-     */
-    self->NativeObject::setPrivate(nullptr);
+    setSource(source);
+    setFlags(flags);
+}
 
-    self->zeroLastIndex();
-    self->setSource(source);
-    self->setFlags(flags);
-    return true;
+void
+RegExpObject::initAndZeroLastIndex(HandleAtom source, RegExpFlag flags, ExclusiveContext* cx)
+{
+    initIgnoringLastIndex(source, flags);
+    zeroLastIndex(cx);
 }
 
 static MOZ_ALWAYS_INLINE bool
 IsLineTerminator(const JS::Latin1Char c)
 {
     return c == '\n' || c == '\r';
 }
 
@@ -936,28 +933,25 @@ js::CloneRegExpObject(JSContext* cx, JSO
     if (!currentStatics)
         return nullptr;
 
     RegExpFlag origFlags = regex->getFlags();
     RegExpFlag staticsFlags = currentStatics->getFlags();
     if ((origFlags & staticsFlags) != staticsFlags) {
         // If |currentStatics| provides additional flags, we'll have to use a
         // new |RegExpShared|.
-        if (!RegExpObject::initFromAtom(cx, clone, source, RegExpFlag(origFlags | staticsFlags)))
-            return nullptr;
+        clone->initAndZeroLastIndex(source, RegExpFlag(origFlags | staticsFlags), cx);
     } else {
         // Otherwise we can use |regexp|'s RegExpShared.  Initialize using its
         // flags and associate it with the clone.
         RegExpGuard g(cx);
         if (!regex->getShared(cx, &g))
             return nullptr;
 
-        if (!RegExpObject::initFromAtom(cx, clone, source, g->getFlags()))
-            return nullptr;
-
+        clone->initAndZeroLastIndex(source, g->getFlags(), cx);
         clone->setShared(*g.re());
     }
 
     return clone;
 }
 
 static bool
 HandleRegExpFlag(RegExpFlag flag, RegExpFlag* flags)
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -398,17 +398,20 @@ class RegExpObject : public NativeObject
     static unsigned lastIndexSlot() { return LAST_INDEX_SLOT; }
 
     const Value& getLastIndex() const { return getSlot(LAST_INDEX_SLOT); }
 
     void setLastIndex(double d) {
         setSlot(LAST_INDEX_SLOT, NumberValue(d));
     }
 
-    void zeroLastIndex() {
+    void zeroLastIndex(ExclusiveContext* cx) {
+        MOZ_ASSERT(lookupPure(cx->names().lastIndex)->writable(),
+                   "can't infallibly zero a non-writable lastIndex on a "
+                   "RegExp that's been exposed to script");
         setSlot(LAST_INDEX_SLOT, Int32Value(0));
     }
 
     JSFlatString* toString(JSContext* cx) const;
 
     JSAtom* getSource() const { return &getSlot(SOURCE_SLOT).toString()->asAtom(); }
 
     void setSource(JSAtom* source) {
@@ -438,22 +441,21 @@ class RegExpObject : public NativeObject
 
     void setShared(RegExpShared& shared) {
         MOZ_ASSERT(!maybeShared());
         NativeObject::setPrivate(&shared);
     }
 
     static void trace(JSTracer* trc, JSObject* obj);
 
-    static bool initFromAtom(ExclusiveContext* cx, Handle<RegExpObject*> regexp, HandleAtom source,
-                             RegExpFlag flags);
+    void initIgnoringLastIndex(HandleAtom source, RegExpFlag flags);
+
+    void initAndZeroLastIndex(HandleAtom source, RegExpFlag flags, ExclusiveContext* cx);
 
   private:
-    bool init(ExclusiveContext* cx, HandleAtom source, RegExpFlag flags);
-
     /*
      * Precondition: the syntax for |source| has already been validated.
      * Side effect: sets the private field.
      */
     bool createShared(JSContext* cx, RegExpGuard* g);
     RegExpShared* maybeShared() const {
         return static_cast<RegExpShared*>(NativeObject::getPrivate(PRIVATE_SLOT));
     }