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 466646 16d0d9e90e254865c501d1fcb924a103305dcd53
parent 466645 36ad679ec7dbdb6923ff0db88d0e9b720296527c
child 466647 e1415ba91dd2620247fab41c5472f42b33cbc299
push id42948
push userbmo:gasolin@mozilla.com
push dateThu, 26 Jan 2017 07:49:21 +0000
reviewersWaldo
bugs1328386
milestone54.0a1
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,