Bug 1328386 - Part 3: Ensure PluralRules methods are always called with actual PluralRules instances. r=Waldo
authorAndré Bargull <andre.bargull@gmail.com>
Mon, 23 Jan 2017 08:33:34 -0800
changeset 331140 16d0d9e90e254865c501d1fcb924a103305dcd53
parent 331139 36ad679ec7dbdb6923ff0db88d0e9b720296527c
child 331141 e1415ba91dd2620247fab41c5472f42b33cbc299
push id31261
push usercbook@mozilla.com
push dateThu, 26 Jan 2017 11:32:02 +0000
treeherdermozilla-central@a338e596b1d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1328386
milestone54.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 1328386 - Part 3: Ensure PluralRules methods are always called with actual PluralRules instances. r=Waldo
js/src/builtin/Intl.cpp
js/src/builtin/Intl.js
js/src/jit/InlinableNatives.h
js/src/jit/MCallOptimize.cpp
js/src/tests/Intl/PluralRules/call.js
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -1688,17 +1688,17 @@ js::intl_numberingSystem(JSContext* cx, 
 /**
  * This creates new UNumberFormat with calculated digit formatting
  * properties for PluralRules.
  *
  * This is similar to NewUNumberFormat but doesn't allow for currency or
  * percent types.
  */
 static UNumberFormat*
-NewUNumberFormatForPluralRules(JSContext* cx, HandleObject pluralRules)
+NewUNumberFormatForPluralRules(JSContext* cx, Handle<PluralRulesObject*> pluralRules)
 {
     RootedObject internals(cx, GetInternals(cx, pluralRules));
     if (!internals)
        return nullptr;
 
     RootedValue value(cx);
 
     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
@@ -3750,17 +3750,17 @@ js::intl_PluralRules_availableLocales(JS
 }
 
 bool
 js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
 
-    RootedObject pluralRules(cx, &args[0].toObject());
+    Rooted<PluralRulesObject*> pluralRules(cx, &args[0].toObject().as<PluralRulesObject>());
 
     UNumberFormat* nf = NewUNumberFormatForPluralRules(cx, pluralRules);
     if (!nf)
         return false;
 
     ScopedICUObject<UNumberFormat, unum_close> closeNumberFormat(nf);
 
     RootedObject internals(cx, GetInternals(cx, pluralRules));
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -3018,20 +3018,22 @@ function resolvePluralRulesInternals(laz
         internalProps.minimumSignificantDigits = lazyPluralRulesData.minimumSignificantDigits;
         internalProps.maximumSignificantDigits = lazyPluralRulesData.maximumSignificantDigits;
     }
 
     return internalProps;
 }
 
 /**
- * Returns an object containing the PluralRules internal properties of |obj|,
- * or throws a TypeError if |obj| isn't PluralRules-initialized.
+ * Returns an object containing the PluralRules internal properties of |obj|.
  */
 function getPluralRulesInternals(obj, methodName) {
+    assert(IsObject(obj), "getPluralRulesInternals called with non-object");
+    assert(IsPluralRules(obj), "getPluralRulesInternals called with non-PluralRules");
+
     var internals = getIntlObjectInternals(obj, "PluralRules", methodName);
     assert(internals.type === "PluralRules", "bad type escaped getIntlObjectInternals");
 
     var internalProps = maybeInternalProperties(internals);
     if (internalProps)
         return internalProps;
 
     internalProps = resolvePluralRulesInternals(internals.lazyData);
@@ -3046,21 +3048,22 @@ function getPluralRulesInternals(obj, me
  * as a *lazy* concept.  Everything that must happen now, does -- but we defer
  * all the work we can until the object is actually used as a PluralRules.
  * This later work occurs in |resolvePluralRulesInternals|; steps not noted
  * here occur there.
  *
  * Spec: ECMAScript 402 API, PluralRules, 1.1.1.
  */
 function InitializePluralRules(pluralRules, locales, options) {
-    assert(IsObject(pluralRules), "InitializePluralRules");
-
-    // Step 1.
-    if (isInitializedIntlObject(pluralRules))
-        ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
+    assert(IsObject(pluralRules), "InitializePluralRules called with non-object");
+    assert(IsPluralRules(pluralRules), "InitializePluralRules called with non-PluralRules");
+
+    // Steps 1-2 (These steps are no longer required and should be removed
+    // from the spec; https://github.com/tc39/ecma402/issues/115).
+    assert(!isInitializedIntlObject(pluralRules), "pluralRules mustn't be initialized");
 
     let internals = initializeIntlObject(pluralRules);
 
     // Lazy PluralRules data has the following structure:
     //
     //   {
     //     requestedLocales: List of locales,
     //     type: "cardinal" / "ordinal",
@@ -3137,32 +3140,43 @@ function Intl_PluralRules_supportedLocal
  * the number passed as value according to the
  * effective locale and the formatting options of this PluralRules.
  *
  * Spec: ECMAScript 402 API, PluralRules, 1.4.3.
  */
 function Intl_PluralRules_select(value) {
     // Step 1.
     let pluralRules = this;
+
     // Step 2.
-    let internals = getPluralRulesInternals(pluralRules, "select");
+    if (!IsObject(pluralRules) || !IsPluralRules(pluralRules))
+        ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "PluralRules", "select", "PluralRules");
+
+    // Ensure the PluralRules internals are resolved.
+    getPluralRulesInternals(pluralRules, "select");
 
     // Steps 3-4.
     let n = ToNumber(value);
 
     // Step 5.
     return intl_SelectPluralRule(pluralRules, n);
 }
 
 /**
  * Returns the resolved options for a PluralRules object.
  *
  * Spec: ECMAScript 402 API, PluralRules, 1.4.4.
  */
 function Intl_PluralRules_resolvedOptions() {
+    // Check "this PluralRules object" per introduction of section 1.4.
+    if (!IsObject(this) || !IsPluralRules(this)) {
+        ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "PluralRules", "resolvedOptions",
+                       "PluralRules");
+    }
+
     var internals = getPluralRulesInternals(this, "resolvedOptions");
 
     var result = {
         locale: internals.locale,
         type: internals.type,
         pluralCategories: callFunction(std_Array_slice, internals.pluralCategories, 0),
         minimumIntegerDigits: internals.minimumIntegerDigits,
         minimumFractionDigits: internals.minimumFractionDigits,
--- a/js/src/jit/InlinableNatives.h
+++ b/js/src/jit/InlinableNatives.h
@@ -23,16 +23,18 @@
     _(AtomicsStore)                 \
     _(AtomicsAdd)                   \
     _(AtomicsSub)                   \
     _(AtomicsAnd)                   \
     _(AtomicsOr)                    \
     _(AtomicsXor)                   \
     _(AtomicsIsLockFree)            \
                                     \
+    _(IntlIsPluralRules)            \
+                                    \
     _(MathAbs)                      \
     _(MathFloor)                    \
     _(MathCeil)                     \
     _(MathRound)                    \
     _(MathClz32)                    \
     _(MathSqrt)                     \
     _(MathATan2)                    \
     _(MathHypot)                    \
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/Casting.h"
 
 #include "jsmath.h"
 #include "jsobj.h"
 #include "jsstr.h"
 
 #include "builtin/AtomicsObject.h"
+#include "builtin/Intl.h"
 #include "builtin/SIMD.h"
 #include "builtin/TestingFunctions.h"
 #include "builtin/TypedObject.h"
 #include "jit/BaselineInspector.h"
 #include "jit/InlinableNatives.h"
 #include "jit/IonBuilder.h"
 #include "jit/Lowering.h"
 #include "jit/MIR.h"
@@ -102,16 +103,20 @@ IonBuilder::inlineNativeCall(CallInfo& c
       case InlinableNative::AtomicsSub:
       case InlinableNative::AtomicsAnd:
       case InlinableNative::AtomicsOr:
       case InlinableNative::AtomicsXor:
         return inlineAtomicsBinop(callInfo, inlNative);
       case InlinableNative::AtomicsIsLockFree:
         return inlineAtomicsIsLockFree(callInfo);
 
+      // Intl natives.
+      case InlinableNative::IntlIsPluralRules:
+        return inlineHasClass(callInfo, &PluralRulesObject::class_);
+
       // Math natives.
       case InlinableNative::MathAbs:
         return inlineMathAbs(callInfo);
       case InlinableNative::MathFloor:
         return inlineMathFloor(callInfo);
       case InlinableNative::MathCeil:
         return inlineMathCeil(callInfo);
       case InlinableNative::MathRound:
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/PluralRules/call.js
@@ -0,0 +1,71 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty("addIntlExtras"))
+
+addIntlExtras(Intl);
+
+function IsConstructor(o) {
+  try {
+    new (new Proxy(o, {construct: () => ({})}));
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+function IsObject(o) {
+    return Object(o) === o;
+}
+
+function thisValues() {
+    const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsConstructor);
+
+    return [
+        // Primitive values.
+        ...[undefined, null, true, "abc", Symbol(), 123],
+
+        // Object values.
+        ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
+
+        // Intl objects.
+        ...[].concat(...intlConstructors.map(ctor => [
+            // Instance of an Intl constructor.
+            new ctor(),
+
+            // Instance of a subclassed Intl constructor.
+            new class extends ctor {},
+
+            // Object inheriting from an Intl constructor prototype.
+            Object.create(ctor.prototype),
+
+            // Intl object not inheriting from its default prototype.
+            Object.setPrototypeOf(new ctor(), Object.prototype),
+        ])),
+    ];
+}
+
+// Invoking [[Call]] for Intl.PluralRules always returns a new PluralRules instance.
+for (let thisValue of thisValues()) {
+    let obj = Intl.PluralRules.call(thisValue);
+    assertEq(Object.is(obj, thisValue), false);
+    assertEq(obj instanceof Intl.PluralRules, true);
+
+    // Ensure Intl.[[FallbackSymbol]] wasn't installed on |thisValue|.
+    if (IsObject(thisValue))
+        assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+}
+
+// Intl.PluralRules doesn't use the legacy Intl constructor compromise semantics.
+for (let thisValue of thisValues()) {
+    // Ensure instanceof operator isn't invoked for Intl.PluralRules.
+    Object.defineProperty(Intl.PluralRules, Symbol.hasInstance, {
+        get() {
+            assertEq(false, true, "@@hasInstance operator called");
+        }, configurable: true
+    });
+    let obj = Intl.PluralRules.call(thisValue);
+    delete Intl.PluralRules[Symbol.hasInstance];
+    assertEq(Object.is(obj, thisValue), false);
+    assertEq(obj instanceof Intl.PluralRules, true);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2477,16 +2477,20 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
     JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
     JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
     JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
     JS_FN("intl_PluralRules_availableLocales", intl_PluralRules_availableLocales, 0,0),
     JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0),
     JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0),
 
+    JS_INLINABLE_FN("IsPluralRules",
+                    intrinsic_IsInstanceOfBuiltin<PluralRulesObject>, 1,0,
+                    IntlIsPluralRules),
+
     JS_INLINABLE_FN("IsRegExpObject",
                     intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,
                     IsRegExpObject),
     JS_FN("CallRegExpMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<RegExpObject>>, 2,0),
     JS_INLINABLE_FN("RegExpMatcher", RegExpMatcher, 4,0,
                     RegExpMatcher),
     JS_INLINABLE_FN("RegExpSearcher", RegExpSearcher, 4,0,