Bug 1463163 - Make ArraySpeciesCreate realm check work with same-compartment realms. r=anba
authorJan de Mooij <jdemooij@mozilla.com>
Tue, 03 Jul 2018 10:08:36 +0200
changeset 479920 07b0a9838f2d2c06b7f82e74b4f75a12fedfb2c6
parent 479919 82a5ce0860f640d6d4e9709611ae4ebc02c5df0a
child 479921 232d577631d1380d171989820e6f730f2fa54d15
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersanba
bugs1463163
milestone63.0a1
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
Bug 1463163 - Make ArraySpeciesCreate realm check work with same-compartment realms. r=anba
js/src/builtin/Array.cpp
js/src/builtin/Array.h
js/src/builtin/Array.js
js/src/jit-test/tests/realms/array-species-create.js
js/src/jit/InlinableNatives.h
js/src/jit/IonBuilder.h
js/src/jit/MCallOptimize.cpp
js/src/vm/SelfHosting.cpp
js/src/vm/TypeInference.cpp
js/src/vm/TypeInference.h
--- a/js/src/builtin/Array.cpp
+++ b/js/src/builtin/Array.cpp
@@ -1018,48 +1018,45 @@ AddLengthProperty(JSContext* cx, HandleA
     return NativeObject::addAccessorProperty(cx, obj, lengthId,
                                              array_length_getter, array_length_setter,
                                              JSPROP_PERMANENT);
 }
 
 static bool
 IsArrayConstructor(const JSObject* obj)
 {
-    // This must only return true if v is *the* Array constructor for the
-    // current compartment; we rely on the fact that any other Array
-    // constructor would be represented as a wrapper.
-    return obj->is<JSFunction>() &&
-           obj->as<JSFunction>().isNative() &&
-           obj->as<JSFunction>().native() == ArrayConstructor;
+    // Note: this also returns true for cross-realm Array constructors in the
+    // same compartment.
+    return IsNativeFunction(obj, ArrayConstructor);
 }
 
 static bool
 IsArrayConstructor(const Value& v)
 {
     return v.isObject() && IsArrayConstructor(&v.toObject());
 }
 
 bool
-js::IsWrappedArrayConstructor(JSContext* cx, const Value& v, bool* result)
+js::IsCrossRealmArrayConstructor(JSContext* cx, const Value& v, bool* result)
 {
     if (!v.isObject()) {
         *result = false;
         return true;
     }
-    if (v.toObject().is<WrapperObject>()) {
-        JSObject* obj = CheckedUnwrap(&v.toObject());
+
+    JSObject* obj = &v.toObject();
+    if (obj->is<WrapperObject>()) {
+        obj = CheckedUnwrap(obj);
         if (!obj) {
             ReportAccessDenied(cx);
             return false;
         }
-
-        *result = IsArrayConstructor(obj);
-    } else {
-        *result = false;
     }
+
+    *result = IsArrayConstructor(obj) && obj->as<JSFunction>().realm() != cx->realm();
     return true;
 }
 
 static MOZ_ALWAYS_INLINE bool
 IsArraySpecies(JSContext* cx, HandleObject origArray)
 {
     if (MOZ_UNLIKELY(origArray->is<ProxyObject>())) {
         if (origArray->getClass()->isDOMClass()) {
@@ -1083,16 +1080,21 @@ IsArraySpecies(JSContext* cx, HandleObje
 
     Value ctor;
     if (!GetPropertyPure(cx, origArray, NameToId(cx->names().constructor), &ctor))
         return false;
 
     if (!IsArrayConstructor(ctor))
         return ctor.isUndefined();
 
+    // 9.4.2.3 Step 6.c. Use the current realm's constructor if |ctor| is a
+    // cross-realm Array constructor.
+    if (cx->realm() != ctor.toObject().as<JSFunction>().realm())
+        return true;
+
     jsid speciesId = SYMBOL_TO_JSID(cx->wellKnownSymbols().species);
     JSFunction* getter;
     if (!GetGetterPure(cx, &ctor.toObject(), speciesId, &getter))
         return false;
 
     if (!getter)
         return false;
 
--- a/js/src/builtin/Array.h
+++ b/js/src/builtin/Array.h
@@ -187,17 +187,17 @@ ArrayInfo(JSContext* cx, unsigned argc, 
 extern bool
 ArrayConstructor(JSContext* cx, unsigned argc, Value* vp);
 
 // Like Array constructor, but doesn't perform GetPrototypeFromConstructor.
 extern bool
 array_construct(JSContext* cx, unsigned argc, Value* vp);
 
 extern bool
-IsWrappedArrayConstructor(JSContext* cx, const Value& v, bool* result);
+IsCrossRealmArrayConstructor(JSContext* cx, const Value& v, bool* result);
 
 class MOZ_NON_TEMPORARY_CLASS ArraySpeciesLookup final
 {
     /*
      * An ArraySpeciesLookup holds the following:
      *
      *  Array.prototype (arrayProto_)
      *      To ensure that the incoming array has the standard proto.
--- a/js/src/builtin/Array.js
+++ b/js/src/builtin/Array.js
@@ -977,17 +977,17 @@ function ArraySpeciesCreate(originalArra
     // Step 4, 6.
     if (!IsArray(originalArray))
         return std_Array(length);
 
     // Step 5.a.
     var C = originalArray.constructor;
 
     // Step 5.b.
-    if (IsConstructor(C) && IsWrappedArrayConstructor(C))
+    if (IsConstructor(C) && IsCrossRealmArrayConstructor(C))
         return std_Array(length);
 
     // Step 5.c.
     if (IsObject(C)) {
         // Step 5.c.i.
         C = C[std_species];
 
         // Optimized path for an ordinary Array.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/realms/array-species-create.js
@@ -0,0 +1,31 @@
+function testSelfHosted() {
+    var g = newGlobal({sameCompartmentAs: this});
+    var arr = g.evaluate("[1, 2]");
+    for (var i = 0; i < 20; i++) {
+        var mapped = Array.prototype.map.call(arr, x => x + 1);
+        assertEq(mapped.constructor, Array);
+    }
+}
+testSelfHosted();
+
+function testNative() {
+    var g = newGlobal({sameCompartmentAs: this});
+    var arr = g.evaluate("[1, 2, 3, 4]");
+    for (var i = 0; i < 20; i++) {
+        var slice = Array.prototype.slice.call(arr, 0, 3);
+        assertEq(slice.constructor, Array);
+    }
+}
+testNative();
+
+function testIntrinsic() {
+    var g1 = newGlobal({sameCompartmentAs: this});
+    var g2 = newGlobal();
+    var IsCrossRealmArrayConstructor = getSelfHostedValue("IsCrossRealmArrayConstructor");
+    for (var i = 0; i < 20; i++) {
+        assertEq(IsCrossRealmArrayConstructor(Array), false);
+        assertEq(IsCrossRealmArrayConstructor(g1.Array), true);
+        assertEq(IsCrossRealmArrayConstructor(g2.Array), true);
+    }
+}
+testIntrinsic();
--- a/js/src/jit/InlinableNatives.h
+++ b/js/src/jit/InlinableNatives.h
@@ -118,17 +118,17 @@
     _(IntrinsicUnsafeGetInt32FromReservedSlot) \
     _(IntrinsicUnsafeGetStringFromReservedSlot) \
     _(IntrinsicUnsafeGetBooleanFromReservedSlot) \
                                     \
     _(IntrinsicIsCallable)          \
     _(IntrinsicIsConstructor)       \
     _(IntrinsicToObject)            \
     _(IntrinsicIsObject)            \
-    _(IntrinsicIsWrappedArrayConstructor) \
+    _(IntrinsicIsCrossRealmArrayConstructor) \
     _(IntrinsicToInteger)           \
     _(IntrinsicToString)            \
     _(IntrinsicIsConstructing)      \
     _(IntrinsicSubstringKernel)     \
     _(IntrinsicObjectHasPrototype)  \
     _(IntrinsicFinishBoundFunctionInit) \
     _(IntrinsicIsPackedArray)       \
                                     \
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -775,17 +775,17 @@ class IonBuilder
     InliningResult inlineSimdAnyAllTrue(CallInfo& callInfo, bool IsAllTrue, JSNative native,
                                         SimdType type);
 
     // Utility intrinsics.
     InliningResult inlineIsCallable(CallInfo& callInfo);
     InliningResult inlineIsConstructor(CallInfo& callInfo);
     InliningResult inlineIsObject(CallInfo& callInfo);
     InliningResult inlineToObject(CallInfo& callInfo);
-    InliningResult inlineIsWrappedArrayConstructor(CallInfo& callInfo);
+    InliningResult inlineIsCrossRealmArrayConstructor(CallInfo& callInfo);
     InliningResult inlineToInteger(CallInfo& callInfo);
     InliningResult inlineToString(CallInfo& callInfo);
     InliningResult inlineDump(CallInfo& callInfo);
     InliningResult inlineHasClass(CallInfo& callInfo, const Class* clasp,
                                   const Class* clasp2 = nullptr,
                                   const Class* clasp3 = nullptr,
                                   const Class* clasp4 = nullptr);
     InliningResult inlineGuardToClass(CallInfo& callInfo, const Class* clasp);
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -311,18 +311,18 @@ IonBuilder::inlineNativeCall(CallInfo& c
       case InlinableNative::IntrinsicIsCallable:
         return inlineIsCallable(callInfo);
       case InlinableNative::IntrinsicIsConstructor:
         return inlineIsConstructor(callInfo);
       case InlinableNative::IntrinsicToObject:
         return inlineToObject(callInfo);
       case InlinableNative::IntrinsicIsObject:
         return inlineIsObject(callInfo);
-      case InlinableNative::IntrinsicIsWrappedArrayConstructor:
-        return inlineIsWrappedArrayConstructor(callInfo);
+      case InlinableNative::IntrinsicIsCrossRealmArrayConstructor:
+        return inlineIsCrossRealmArrayConstructor(callInfo);
       case InlinableNative::IntrinsicToInteger:
         return inlineToInteger(callInfo);
       case InlinableNative::IntrinsicToString:
         return inlineToString(callInfo);
       case InlinableNative::IntrinsicIsConstructing:
         return inlineIsConstructing(callInfo);
       case InlinableNative::IntrinsicSubstringKernel:
         return inlineSubstringKernel(callInfo);
@@ -3294,40 +3294,36 @@ IonBuilder::inlineToObject(CallInfo& cal
 
         MOZ_TRY(pushTypeBarrier(ins, getInlineReturnTypeSet(), BarrierKind::TypeSet));
     }
 
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningResult
-IonBuilder::inlineIsWrappedArrayConstructor(CallInfo& callInfo)
+IonBuilder::inlineIsCrossRealmArrayConstructor(CallInfo& callInfo)
 {
     MOZ_ASSERT(!callInfo.constructing());
     MOZ_ASSERT(callInfo.argc() == 1);
 
     if (getInlineReturnType() != MIRType::Boolean)
         return InliningStatus_NotInlined;
     MDefinition* arg = callInfo.getArg(0);
     if (arg->type() != MIRType::Object)
         return InliningStatus_NotInlined;
 
     TemporaryTypeSet* types = arg->resultTypeSet();
-    switch (types->forAllClasses(constraints(), IsProxyClass)) {
-      case TemporaryTypeSet::ForAllResult::ALL_FALSE:
-        break;
-      case TemporaryTypeSet::ForAllResult::EMPTY:
-      case TemporaryTypeSet::ForAllResult::ALL_TRUE:
-      case TemporaryTypeSet::ForAllResult::MIXED:
-        return InliningStatus_NotInlined;
-    }
+    Realm* realm = types->getKnownRealm(constraints());
+    if (!realm || realm != script()->realm())
+        return InliningStatus_NotInlined;
 
     callInfo.setImplicitlyUsedUnchecked();
 
-    // Inline only if argument is absolutely *not* a Proxy.
+    // Inline only if argument is absolutely *not* a wrapper or a cross-realm
+    // object.
     pushConstant(BooleanValue(false));
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningResult
 IonBuilder::inlineToInteger(CallInfo& callInfo)
 {
     MOZ_ASSERT(!callInfo.constructing());
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -118,21 +118,21 @@ intrinsic_IsArray(JSContext* cx, unsigne
         args.rval().setBoolean(isArray);
     } else {
         args.rval().setBoolean(false);
     }
     return true;
 }
 
 static bool
-intrinsic_IsWrappedArrayConstructor(JSContext* cx, unsigned argc, Value* vp)
+intrinsic_IsCrossRealmArrayConstructor(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     bool result = false;
-    if (!IsWrappedArrayConstructor(cx, args[0], &result))
+    if (!IsCrossRealmArrayConstructor(cx, args[0], &result))
         return false;
     args.rval().setBoolean(result);
     return true;
 }
 
 static bool
 intrinsic_ToInteger(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -2415,18 +2415,18 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("std_SIMD_Bool16x8_extractLane",       simd_bool16x8_extractLane,    2,0),
     JS_FN("std_SIMD_Bool32x4_extractLane",       simd_bool32x4_extractLane,    2,0),
     JS_FN("std_SIMD_Bool64x2_extractLane",       simd_bool64x2_extractLane,    2,0),
 
     // Helper funtions after this point.
     JS_INLINABLE_FN("ToObject",      intrinsic_ToObject,                1,0, IntrinsicToObject),
     JS_INLINABLE_FN("IsObject",      intrinsic_IsObject,                1,0, IntrinsicIsObject),
     JS_INLINABLE_FN("IsArray",       intrinsic_IsArray,                 1,0, ArrayIsArray),
-    JS_INLINABLE_FN("IsWrappedArrayConstructor", intrinsic_IsWrappedArrayConstructor, 1,0,
-                    IntrinsicIsWrappedArrayConstructor),
+    JS_INLINABLE_FN("IsCrossRealmArrayConstructor", intrinsic_IsCrossRealmArrayConstructor, 1,0,
+                    IntrinsicIsCrossRealmArrayConstructor),
     JS_INLINABLE_FN("ToInteger",     intrinsic_ToInteger,               1,0, IntrinsicToInteger),
     JS_INLINABLE_FN("ToString",      intrinsic_ToString,                1,0, IntrinsicToString),
     JS_FN("ToSource",                intrinsic_ToSource,                1,0),
     JS_FN("ToPropertyKey",           intrinsic_ToPropertyKey,           1,0),
     JS_INLINABLE_FN("IsCallable",    intrinsic_IsCallable,              1,0, IntrinsicIsCallable),
     JS_INLINABLE_FN("IsConstructor", intrinsic_IsConstructor,           1,0,
                     IntrinsicIsConstructor),
     JS_FN("GetBuiltinConstructorImpl", intrinsic_GetBuiltinConstructor, 1,0),
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -2342,16 +2342,60 @@ TemporaryTypeSet::getKnownClass(Compiler
             if (key && !key->hasStableClassAndProto(constraints))
                 return nullptr;
         }
     }
 
     return clasp;
 }
 
+Realm*
+TemporaryTypeSet::getKnownRealm(CompilerConstraintList* constraints)
+{
+    if (unknownObject())
+        return nullptr;
+
+    Realm* realm = nullptr;
+    unsigned count = getObjectCount();
+
+    for (unsigned i = 0; i < count; i++) {
+        const Class* clasp = getObjectClass(i);
+        if (!clasp)
+            continue;
+
+        // If clasp->isProxy(), this might be a cross-compartment wrapper and
+        // CCWs don't have a (single) realm, so we give up. If the object has
+        // unknownProperties(), hasStableClassAndProto (called below) will
+        // return |false| so fail now before attaching any constraints.
+        if (clasp->isProxy() || getObject(i)->unknownProperties())
+            return nullptr;
+
+        MOZ_ASSERT(hasSingleton(i) || hasGroup(i));
+
+        Realm* nrealm = hasSingleton(i) ? getSingleton(i)->nonCCWRealm() : getGroup(i)->realm();
+        MOZ_ASSERT(nrealm);
+        if (!realm) {
+            realm = nrealm;
+            continue;
+        }
+        if (realm != nrealm)
+            return nullptr;
+    }
+
+    if (realm) {
+        for (unsigned i = 0; i < count; i++) {
+            ObjectKey* key = getObject(i);
+            if (key && !key->hasStableClassAndProto(constraints))
+                return nullptr;
+        }
+    }
+
+    return realm;
+}
+
 void
 TemporaryTypeSet::getTypedArraySharedness(CompilerConstraintList* constraints,
                                           TypedArraySharedness* sharedness)
 {
     // In the future this will inspect the object set.
     *sharedness = UnknownSharedness;
 }
 
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -873,16 +873,23 @@ class TemporaryTypeSet : public TypeSet
     }
 
     /* Whether the type set contains objects with any of a set of flags. */
     bool hasObjectFlags(CompilerConstraintList* constraints, ObjectGroupFlags flags);
 
     /* Get the class shared by all objects in this set, or nullptr. */
     const Class* getKnownClass(CompilerConstraintList* constraints);
 
+    /*
+     * Get the realm shared by all objects in this set, or nullptr. Returns
+     * nullptr if the set contains proxies (because cross-compartment wrappers
+     * don't have a single realm associated with them).
+     */
+    Realm* getKnownRealm(CompilerConstraintList* constraints);
+
     /* Result returned from forAllClasses */
     enum ForAllResult {
         EMPTY=1,                // Set empty
         ALL_TRUE,               // Set not empty and predicate returned true for all classes
         ALL_FALSE,              // Set not empty and predicate returned false for all classes
         MIXED,                  // Set not empty and predicate returned false for some classes
                                 // and true for others, or set contains an unknown or non-object
                                 // type