From 30157344fee44f294abbfb5cda9b2d9b4e70057f Mon Sep 17 00:00:00 2001 From: Martok Date: Thu, 29 Jun 2023 23:02:11 +0200 Subject: [PATCH] Issue #2259 - Update Intl.* Object-ness to ECMA-402, 4th edition - turn each Intl object into a NativeObject subclass - create them as PlainObject - ensure correct type is passed in self-hosted code - implement legacy constructor semantics for DateTime&NumberFormat - store internals on object slot instead of JS WeakMap Based-on: m-c 1328386, 1332604 --- js/public/Class.h | 2 +- js/src/builtin/Date.js | 6 +- js/src/builtin/SymbolObject.cpp | 48 +++-- js/src/builtin/SymbolObject.h | 5 +- js/src/builtin/Utilities.js | 2 + js/src/builtin/intl/Collator.cpp | 146 ++++---------- js/src/builtin/intl/Collator.js | 35 ++-- js/src/builtin/intl/CommonFunctions.cpp | 54 +++--- js/src/builtin/intl/CommonFunctions.h | 15 +- js/src/builtin/intl/CommonFunctions.js | 181 +++++++----------- js/src/builtin/intl/DateTimeFormat.cpp | 152 +++++---------- js/src/builtin/intl/DateTimeFormat.h | 4 +- js/src/builtin/intl/DateTimeFormat.js | 99 +++++++--- js/src/builtin/intl/IntlObject.cpp | 8 +- js/src/builtin/intl/IntlObject.js | 105 ++++++---- js/src/builtin/intl/NumberFormat.cpp | 157 ++++----------- js/src/builtin/intl/NumberFormat.h | 5 +- js/src/builtin/intl/NumberFormat.js | 88 ++++++--- js/src/builtin/intl/PluralRules.cpp | 31 +-- js/src/builtin/intl/PluralRules.js | 37 ++-- js/src/builtin/intl/RelativeTimeFormat.cpp | 27 +-- js/src/builtin/intl/RelativeTimeFormat.js | 31 +-- .../jit-test/tests/auto-regress/bug1263558.js | 2 +- js/src/jit-test/tests/basic/bug920484.js | 9 +- js/src/jit-test/tests/ion/bug913749.js | 3 +- js/src/jit/InlinableNatives.h | 6 + js/src/jit/MCallOptimize.cpp | 17 ++ js/src/js.msg | 1 - js/src/vm/GlobalObject.cpp | 1 + js/src/vm/GlobalObject.h | 14 ++ js/src/vm/SelfHosting.cpp | 39 ++++ 31 files changed, 650 insertions(+), 680 deletions(-) diff --git a/js/public/Class.h b/js/public/Class.h index d7e2ab40db..1c785646e3 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -913,7 +913,7 @@ struct JSClass { // application. #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5 #define JSCLASS_GLOBAL_SLOT_COUNT \ - (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 47) + (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 49) #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \ (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n))) #define JSCLASS_GLOBAL_FLAGS \ diff --git a/js/src/builtin/Date.js b/js/src/builtin/Date.js index 6983d26859..534deb0f6f 100644 --- a/js/src/builtin/Date.js +++ b/js/src/builtin/Date.js @@ -104,7 +104,7 @@ function Date_toLocaleString() { } // Step 7. - return intl_FormatDateTime(dateTimeFormat, x, false); + return intl_FormatDateTime(dateTimeFormat, x, /* formatToParts = */ false); } @@ -137,7 +137,7 @@ function Date_toLocaleDateString() { } // Step 7. - return intl_FormatDateTime(dateTimeFormat, x, false); + return intl_FormatDateTime(dateTimeFormat, x, /* formatToParts = */ false); } @@ -170,5 +170,5 @@ function Date_toLocaleTimeString() { } // Step 7. - return intl_FormatDateTime(dateTimeFormat, x, false); + return intl_FormatDateTime(dateTimeFormat, x, /* formatToParts = */ false); } diff --git a/js/src/builtin/SymbolObject.cpp b/js/src/builtin/SymbolObject.cpp index effcf4f954..0f1e8164d5 100644 --- a/js/src/builtin/SymbolObject.cpp +++ b/js/src/builtin/SymbolObject.cpp @@ -50,7 +50,7 @@ const JSFunctionSpec SymbolObject::staticMethods[] = { }; JSObject* -SymbolObject::initClass(JSContext* cx, HandleObject obj) +SymbolObject::initClass(JSContext* cx, HandleObject obj, bool defineMembers) { Handle global = obj.as(); @@ -66,25 +66,33 @@ SymbolObject::initClass(JSContext* cx, HandleObject obj) if (!ctor) return nullptr; - // Define the well-known symbol properties, such as Symbol.iterator. - ImmutablePropertyNamePtr* names = cx->names().wellKnownSymbolNames(); - RootedValue value(cx); - unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT; - WellKnownSymbols* wks = cx->runtime()->wellKnownSymbols; - for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) { - value.setSymbol(wks->get(i)); - if (!NativeDefineProperty(cx, ctor, names[i], value, nullptr, nullptr, attrs)) - return nullptr; + if (defineMembers) { + // Define the well-known symbol properties, such as Symbol.iterator. + ImmutablePropertyNamePtr* names = cx->names().wellKnownSymbolNames(); + RootedValue value(cx); + unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT; + WellKnownSymbols* wks = cx->runtime()->wellKnownSymbols; + for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) { + value.setSymbol(wks->get(i)); + if (!NativeDefineProperty(cx, ctor, names[i], value, nullptr, nullptr, attrs)) + return nullptr; + } } - if (!LinkConstructorAndPrototype(cx, ctor, proto) || - !DefinePropertiesAndFunctions(cx, proto, properties, methods) || - !DefineToStringTag(cx, proto, cx->names().Symbol) || - !DefinePropertiesAndFunctions(cx, ctor, nullptr, staticMethods) || - !GlobalObject::initBuiltinConstructor(cx, global, JSProto_Symbol, ctor, proto)) - { + if (!LinkConstructorAndPrototype(cx, ctor, proto)) return nullptr; + + if (defineMembers) { + if (!DefinePropertiesAndFunctions(cx, proto, properties, methods) || + !DefineToStringTag(cx, proto, cx->names().Symbol) || + !DefinePropertiesAndFunctions(cx, ctor, nullptr, staticMethods)) + { + return nullptr; + } } + + if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Symbol, ctor, proto)) + return nullptr; return proto; } @@ -258,5 +266,11 @@ SymbolObject::descriptionGetter(JSContext* cx, unsigned argc, Value* vp) JSObject* js::InitSymbolClass(JSContext* cx, HandleObject obj) { - return SymbolObject::initClass(cx, obj); + return SymbolObject::initClass(cx, obj, true); +} + +JSObject* +js::InitBareSymbolCtor(JSContext* cx, HandleObject obj) +{ + return SymbolObject::initClass(cx, obj, false); } diff --git a/js/src/builtin/SymbolObject.h b/js/src/builtin/SymbolObject.h index 01099100b1..54d187fb59 100644 --- a/js/src/builtin/SymbolObject.h +++ b/js/src/builtin/SymbolObject.h @@ -21,7 +21,7 @@ class SymbolObject : public NativeObject static const Class class_; - static JSObject* initClass(JSContext* cx, js::HandleObject obj); + static JSObject* initClass(JSContext* cx, js::HandleObject obj, bool defineMembers); /* * Creates a new Symbol object boxing the given primitive Symbol. The @@ -63,6 +63,9 @@ class SymbolObject : public NativeObject extern JSObject* InitSymbolClass(JSContext* cx, HandleObject obj); +extern JSObject* +InitBareSymbolCtor(JSContext* cx, HandleObject obj); + } /* namespace js */ #endif /* builtin_SymbolObject_h */ diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index 259fef7ee0..09c15957c6 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -50,6 +50,8 @@ // Do not create an alias to a self-hosted builtin, otherwise it will be cloned // twice. // +// Symbol is a bare constructor without properties or methods. +var std_Symbol = Symbol; // WeakMap is a bare constructor without properties or methods. var std_WeakMap = WeakMap; // StopIteration is a bare constructor without properties or methods. diff --git a/js/src/builtin/intl/Collator.cpp b/js/src/builtin/intl/Collator.cpp index 080974b066..c9e24c27e9 100644 --- a/js/src/builtin/intl/Collator.cpp +++ b/js/src/builtin/intl/Collator.cpp @@ -79,64 +79,36 @@ static const JSFunctionSpec collator_methods[] = { * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b */ static bool -Collator(JSContext* cx, const CallArgs& args, bool construct) +Collator(JSContext* cx, const CallArgs& args) { - RootedObject obj(cx); + // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code). - // We're following ECMA-402 1st Edition when Collator is called because of - // backward compatibility issues. - // See https://github.com/tc39/ecma402/issues/57 - if (!construct) { - // ES Intl 1st ed., 10.1.2.1 step 3 - JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global()); - if (!intl) - return false; - RootedValue self(cx, args.thisv()); - if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { - // ES Intl 1st ed., 10.1.2.1 step 4 - obj = ToObject(cx, self); - if (!obj) - return false; - - // ES Intl 1st ed., 10.1.2.1 step 5 - bool extensible; - if (!IsExtensible(cx, obj, &extensible)) - return false; - if (!extensible) - return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); - } else { - // ES Intl 1st ed., 10.1.2.1 step 3.a - construct = true; - } - } - if (construct) { - // Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor). - RootedObject proto(cx); - if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) - return false; - - if (!proto) { - proto = GlobalObject::getOrCreateCollatorPrototype(cx, cx->global()); - if (!proto) - return false; - } - - obj = NewObjectWithGivenProto(cx, proto); - if (!obj) - return false; - - obj->as().setReservedSlot(CollatorObject::INTERNALS_SLOT, NullValue()); - obj->as().setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(nullptr)); - } - - RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); - RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); - - // Step 6. - if (!intl::InitializeObject(cx, obj, cx->names().InitializeCollator, locales, options)) + // Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; - args.rval().setObject(*obj); + if (!proto) { + proto = GlobalObject::getOrCreateCollatorPrototype(cx, cx->global()); + if (!proto) + return false; + } + + Rooted collator(cx, NewObjectWithGivenProto(cx, proto)); + if (!collator) + return false; + + collator->setReservedSlot(CollatorObject::INTERNALS_SLOT, NullValue()); + collator->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(nullptr)); + + RootedValue locales(cx, args.get(0)); + RootedValue options(cx, args.get(1)); + + // Step 6. + if (!intl::InitializeObject(cx, collator, cx->names().InitializeCollator, locales, options)) + return false; + + args.rval().setObject(*collator); return true; } @@ -144,7 +116,7 @@ static bool Collator(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - return Collator(cx, args, args.isConstructing()); + return Collator(cx, args); } bool @@ -153,9 +125,8 @@ js::intl_Collator(JSContext* cx, unsigned argc, Value* vp) CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); MOZ_ASSERT(!args.isConstructing()); - // intl_Collator is an intrinsic for self-hosted JavaScript, so it cannot - // be used with "new", but it still has to be treated as a constructor. - return Collator(cx, args, true); + + return Collator(cx, args); } void @@ -163,15 +134,9 @@ js::CollatorObject::finalize(FreeOp* fop, JSObject* obj) { MOZ_ASSERT(fop->onMainThread()); - // This is-undefined check shouldn't be necessary, but for internal - // brokenness in object allocation code. For the moment, hack around it by - // explicitly guarding against the possibility of the reserved slot not - // containing a private. See bug 949220. - const Value& slot = obj->as().getReservedSlot(CollatorObject::UCOLLATOR_SLOT); - if (!slot.isUndefined()) { - if (UCollator* coll = static_cast(slot.toPrivate())) - ucol_close(coll); - } + const Value& slot = obj->as().getReservedSlot(CollatorObject::UCOLLATOR_SLOT); + if (UCollator* coll = static_cast(slot.toPrivate())) + ucol_close(coll); } JSObject* @@ -182,10 +147,9 @@ js::CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handle(cx, global)); if (!proto) return nullptr; - proto->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(nullptr)); if (!LinkConstructorAndPrototype(cx, ctor, proto)) return nullptr; @@ -213,14 +177,6 @@ js::CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handlenames().InitializeCollator, UndefinedHandleValue, options)) - return nullptr; - // 8.1 RootedValue ctorValue(cx, ObjectValue(*ctor)); if (!DefineProperty(cx, Intl, cx->names().Collator, ctorValue, nullptr, nullptr, 0)) @@ -313,7 +269,7 @@ js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp) * of the given Collator. */ static UCollator* -NewUCollator(JSContext* cx, HandleObject collator) +NewUCollator(JSContext* cx, Handle collator) { RootedValue value(cx); @@ -490,40 +446,20 @@ js::intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp) MOZ_ASSERT(args[2].isString()); Rooted collator(cx, &args[0].toObject().as()); - - // Obtain a UCollator object, cached if possible. + + // Obtain a cached UCollator object. // XXX Does this handle Collator instances from other globals correctly? - bool isCollatorInstance = collator->getClass() == &CollatorObject::class_; - UCollator* coll; - if (isCollatorInstance) { - void* priv = collator->getReservedSlot(CollatorObject::UCOLLATOR_SLOT).toPrivate(); - coll = static_cast(priv); - if (!coll) { - coll = NewUCollator(cx, collator); - if (!coll) - return false; - collator->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(coll)); - } - } else { - // There's no good place to cache the ICU collator for an object - // that has been initialized as a Collator but is not a Collator - // instance. One possibility might be to add a Collator instance as an - // internal property to each such object. + void* priv = collator->getReservedSlot(CollatorObject::UCOLLATOR_SLOT).toPrivate(); + UCollator* coll = static_cast(priv); + if (!coll) { coll = NewUCollator(cx, collator); if (!coll) return false; + collator->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(coll)); } // Use the UCollator to actually compare the strings. RootedString str1(cx, args[1].toString()); RootedString str2(cx, args[2].toString()); - RootedValue result(cx); - bool success = intl_CompareStrings(cx, coll, str1, str2, &result); - - if (!isCollatorInstance) - ucol_close(coll); - if (!success) - return false; - args.rval().set(result); - return true; + return intl_CompareStrings(cx, coll, str1, str2, args.rval()); } diff --git a/js/src/builtin/intl/Collator.js b/js/src/builtin/intl/Collator.js index 5fb85f8e6a..aa7ab38dad 100644 --- a/js/src/builtin/intl/Collator.js +++ b/js/src/builtin/intl/Collator.js @@ -107,11 +107,13 @@ function resolveCollatorInternals(lazyCollatorData) /** - * Returns an object containing the Collator internal properties of |obj|, or - * throws a TypeError if |obj| isn't Collator-initialized. + * Returns an object containing the Collator internal properties of |obj|. */ -function getCollatorInternals(obj, methodName) { - var internals = getIntlObjectInternals(obj, "Collator", methodName); +function getCollatorInternals(obj) { + assert(IsObject(obj), "getCollatorInternals called with non-object"); + assert(IsCollator(obj), "getCollatorInternals called with non-Collator"); + + var internals = getIntlObjectInternals(obj); assert(internals.type === "Collator", "bad type escaped getIntlObjectInternals"); // If internal properties have already been computed, use them. @@ -138,14 +140,11 @@ function getCollatorInternals(obj, methodName) { * Spec: ECMAScript Internationalization API Specification, 10.1.1. */ function InitializeCollator(collator, locales, options) { - assert(IsObject(collator), "InitializeCollator"); + assert(IsObject(collator), "InitializeCollator called with non-object"); + assert(IsCollator(collator), "InitializeCollator called with non-Collator"); - // Step 1. - if (isInitializedIntlObject(collator)) - ThrowTypeError(JSMSG_INTL_OBJECT_REINITED); - - // Step 2. - var internals = initializeIntlObject(collator); + // Steps 1-2 (These steps are no longer required and should be removed + // from the spec; https://github.com/tc39/ecma402/issues/115).; // Lazy Collator data has the following structure: // @@ -219,7 +218,7 @@ function InitializeCollator(collator, locales, options) { // // We've done everything that must be done now: mark the lazy data as fully // computed and install it. - setLazyData(internals, "Collator", lazyCollatorData); + initializeIntlObject(collator, "Collator", lazyCollatorData); } @@ -311,14 +310,17 @@ function collatorCompareToBind(x, y) { */ function Intl_Collator_compare_get() { // Check "this Collator object" per introduction of section 10.3. - var internals = getCollatorInternals(this, "compare"); + if (!IsObject(this) || !IsCollator(this)) + ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "Collator", "compare", "Collator"); + + var internals = getCollatorInternals(this); // Step 1. if (internals.boundCompare === undefined) { // Step 1.a. var F = collatorCompareToBind; - // Step 1.b-d. + // Steps 1.b-d. var bc = callFunction(FunctionBind, F, this); internals.boundCompare = bc; } @@ -335,7 +337,10 @@ function Intl_Collator_compare_get() { */ function Intl_Collator_resolvedOptions() { // Check "this Collator object" per introduction of section 10.3. - var internals = getCollatorInternals(this, "resolvedOptions"); + if (!IsObject(this) || !IsCollator(this)) + ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "Collator", "resolvedOptions", "Collator"); + + var internals = getCollatorInternals(this); var result = { locale: internals.locale, diff --git a/js/src/builtin/intl/CommonFunctions.cpp b/js/src/builtin/intl/CommonFunctions.cpp index b1e88851ab..4b607b60cd 100644 --- a/js/src/builtin/intl/CommonFunctions.cpp +++ b/js/src/builtin/intl/CommonFunctions.cpp @@ -19,26 +19,10 @@ #include "jsobjinlines.h" -bool -js::intl::CreateDefaultOptions(JSContext* cx, MutableHandleValue defaultOptions) -{ - RootedObject options(cx, NewObjectWithGivenProto(cx, nullptr)); - if (!options) - return false; - defaultOptions.setObject(*options); - return true; -} - bool js::intl::InitializeObject(JSContext* cx, HandleObject obj, Handle initializer, HandleValue locales, HandleValue options) { - RootedValue initializerValue(cx); - if (!GlobalObject::getIntrinsicValue(cx, cx->global(), initializer, &initializerValue)) - return false; - MOZ_ASSERT(initializerValue.isObject()); - MOZ_ASSERT(initializerValue.toObject().is()); - FixedInvokeArgs<3> args(cx); args[0].setObject(*obj); @@ -47,7 +31,32 @@ js::intl::InitializeObject(JSContext* cx, HandleObject obj, Handle initializer, + HandleValue thisValue, HandleValue locales, HandleValue options, + MutableHandleValue result) +{ + FixedInvokeArgs<4> args(cx); + + args[0].setObject(*obj); + args[1].set(thisValue); + args[2].set(locales); + args[3].set(options); + + RootedValue thisv(cx, NullValue()); + if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, result)) + return false; + + MOZ_ASSERT(result.isObject(), "Legacy Intl object initializer must return an object"); + return true; } /** @@ -56,21 +65,12 @@ js::intl::InitializeObject(JSContext* cx, HandleObject obj, Handleglobal(), cx->names().getInternals, - &getInternalsValue)) - { - return nullptr; - } - MOZ_ASSERT(getInternalsValue.isObject()); - MOZ_ASSERT(getInternalsValue.toObject().is()); - FixedInvokeArgs<1> args(cx); args[0].setObject(*obj); RootedValue v(cx, NullValue()); - if (!js::Call(cx, getInternalsValue, v, args, &v)) + if (!js::CallSelfHostedFunction(cx, cx->names().getInternals, v, args, &v)) return nullptr; return &v.toObject(); diff --git a/js/src/builtin/intl/CommonFunctions.h b/js/src/builtin/intl/CommonFunctions.h index 575597c0a2..7d11c4a8f0 100644 --- a/js/src/builtin/intl/CommonFunctions.h +++ b/js/src/builtin/intl/CommonFunctions.h @@ -26,12 +26,6 @@ namespace js { namespace intl { -/** - * Setup the |options| argument of |IntlInitialize| - */ -extern bool -CreateDefaultOptions(JSContext* cx, MutableHandleValue defaultOptions); - /** * Initialize a new Intl.* object using the named self-hosted function. */ @@ -39,6 +33,15 @@ extern bool InitializeObject(JSContext* cx, HandleObject obj, Handle initializer, HandleValue locales, HandleValue options); +/** + * Initialize an existing object as an Intl.* object using the named + * self-hosted function. This is only for a few old Intl.* constructors, for + * legacy reasons -- new ones should use the function above instead. + */ +extern bool +LegacyIntlInitialize(JSContext* cx, HandleObject obj, Handle initializer, + HandleValue thisValue, HandleValue locales, HandleValue options, + MutableHandleValue result); /** * Returns the object holding the internal properties for obj. */ diff --git a/js/src/builtin/intl/CommonFunctions.js b/js/src/builtin/intl/CommonFunctions.js index 5a963edfbf..10e02a5ac6 100644 --- a/js/src/builtin/intl/CommonFunctions.js +++ b/js/src/builtin/intl/CommonFunctions.js @@ -1057,26 +1057,38 @@ function GetNumberOption(options, property, minimum, maximum, fallback) { } +// Symbols in the self-hosting compartment can't be cloned, use a separate +// object to hold the actual symbol value. +// TODO: Can we add support to clone symbols? +var intlFallbackSymbolHolder = { value: undefined }; + /** - * Weak map used to track the initialize-as-Intl status (and, if an object has - * been so initialized, the Intl-specific internal properties) of all objects. - * Presence of an object as a key within this map indicates that the object has - * its [[initializedIntlObject]] internal property set to true. The associated - * value is an object whose structure is documented in |initializeIntlObject| - * below. + * The [[FallbackSymbol]] symbol of the %Intl% intrinsic object. * - * Ideally we'd be using private symbols for internal properties, but - * SpiderMonkey doesn't have those yet. + * This symbol is used to implement the legacy constructor semantics for + * Intl.DateTimeFormat and Intl.NumberFormat. */ -var internalsMap = new WeakMap(); +function intlFallbackSymbol() { + var fallbackSymbol = intlFallbackSymbolHolder.value; + if (!fallbackSymbol) + intlFallbackSymbolHolder.value = fallbackSymbol = std_Symbol(); + return fallbackSymbol; +} /** - * Set the [[initializedIntlObject]] internal property of |obj| to true. + * Initializes the INTL_INTERNALS_OBJECT_SLOT of the given object. */ -function initializeIntlObject(obj) { +function initializeIntlObject(obj, type, lazyData) { assert(IsObject(obj), "Non-object passed to initializeIntlObject"); + assert((type === "Collator" && IsCollator(obj)) || + (type === "DateTimeFormat" && IsDateTimeFormat(obj)) || + (type === "NumberFormat" && IsNumberFormat(obj)) || + (type === "PluralRules" && IsPluralRules(obj)) || + (type === "RelativeTimeFormat" && IsRelativeTimeFormat(obj)), + "type must match the object's class"); + assert(IsObject(lazyData), "non-object lazy data"); // Intl-initialized objects are weird. They have [[initializedIntlObject]] // set on them, but they don't *necessarily* have any other properties. @@ -1084,51 +1096,26 @@ function initializeIntlObject(obj) { // The meaning of an internals object for an object |obj| is as follows. // - // If the .type is "partial", |obj| has [[initializedIntlObject]] set but - // nothing else. No other property of |internals| can be used. (This - // occurs when InitializeCollator or similar marks an object as - // [[initializedIntlObject]] but fails before marking it as the appropriate - // more-specific type ["Collator", "DateTimeFormat", "NumberFormat"].) + // The .type property indicates the type of Intl object that |obj| is: + // "Collator", "DateTimeFormat", "NumberFormat", or "PluralRules" (likely + // with more coming in future Intl specs). // - // Otherwise, the .type indicates the type of Intl object that |obj| is: - // "Collator", "DateTimeFormat", or "NumberFormat" (likely with more coming - // in future Intl specs). In these cases |obj| *conceptually* also has - // [[initializedCollator]] or similar set, and all the other properties - // implied by that. - // - // If |internals| doesn't have a "partial" .type, two additional properties - // have meaning. The .lazyData property stores information needed to - // compute -- without observable side effects -- the actual internal Intl - // properties of |obj|. If it is non-null, then the actual internal - // properties haven't been computed, and .lazyData must be processed by + // The .lazyData property stores information needed to compute -- without + // observable side effects -- the actual internal Intl properties of + // |obj|. If it is non-null, then the actual internal properties haven't + // been computed, and .lazyData must be processed by // |setInternalProperties| before internal Intl property values are // available. If it is null, then the .internalProps property contains an // object whose properties are the internal Intl properties of |obj|. - internals.type = "partial"; - internals.lazyData = null; + var internals = std_Object_create(null); + internals.type = type; + internals.lazyData = lazyData; internals.internalProps = null; - callFunction(std_WeakMap_set, internalsMap, obj, internals); - return internals; -} - - -/** - * Mark |internals| as having the given type and lazy data. - */ -function setLazyData(internals, type, lazyData) -{ - assert(internals.type === "partial", "can't set lazy data for anything but a newborn"); - assert(type === "Collator" || type === "DateTimeFormat" || - type === "NumberFormat" || type === "PluralRules" || - type === "RelativeTimeFormat", - "bad type"); - assert(IsObject(lazyData), "non-object lazy data"); - - // Set in reverse order so that the .type change is a barrier. - internals.lazyData = lazyData; - internals.type = type; + assert(UnsafeGetReservedSlot(obj, INTL_INTERNALS_OBJECT_SLOT) === null, + "Internal slot already initialized?"); + UnsafeSetReservedSlot(obj, INTL_INTERNALS_OBJECT_SLOT, internals); } @@ -1136,9 +1123,7 @@ function setLazyData(internals, type, lazyData) * Set the internal properties object for an |internals| object previously * associated with lazy data. */ -function setInternalProperties(internals, internalProps) -{ - assert(internals.type !== "partial", "newborn internals can't have computed internals"); +function setInternalProperties(internals, internalProps) { assert(IsObject(internals.lazyData), "lazy data must exist already"); assert(IsObject(internalProps), "internalProps argument should be an object"); @@ -1152,10 +1137,8 @@ function setInternalProperties(internals, internalProps) * Get the existing internal properties out of a non-newborn |internals|, or * null if none have been computed. */ -function maybeInternalProperties(internals) -{ +function maybeInternalProperties(internals) { assert(IsObject(internals), "non-object passed to maybeInternalProperties"); - assert(internals.type !== "partial", "maybeInternalProperties must only be used on completely-initialized internals objects"); var lazyData = internals.lazyData; if (lazyData) return null; @@ -1165,48 +1148,33 @@ function maybeInternalProperties(internals) /** - * Return whether |obj| has an[[initializedIntlObject]] property set to true. - */ -function isInitializedIntlObject(obj) { -#ifdef DEBUG - var internals = callFunction(std_WeakMap_get, internalsMap, obj); - if (IsObject(internals)) { - assert(hasOwn("type", internals), "missing type"); - var type = internals.type; - assert(type === "partial" || type === "Collator" || - type === "DateTimeFormat" || type === "NumberFormat" || - type === "PluralRules" || type === "RelativeTimeFormat", - "unexpected type"); - assert(hasOwn("lazyData", internals), "missing lazyData"); - assert(hasOwn("internalProps", internals), "missing internalProps"); - } else { - assert(internals === undefined, "bad mapping for |obj|"); - } -#endif - return callFunction(std_WeakMap_has, internalsMap, obj); -} - - -/** - * Check that |obj| meets the requirements for "this Collator object", "this - * NumberFormat object", or "this DateTimeFormat object" as used in the method - * with the given name. Throw a TypeError if |obj| doesn't meet these - * requirements. But if it does, return |obj|'s internals object (*not* the - * object holding its internal properties!), associated with it by - * |internalsMap|, with structure specified above. + * Return |obj|'s internals object (*not* the object holding its internal + * properties!), with structure specified above. * * Spec: ECMAScript Internationalization API Specification, 10.3. * Spec: ECMAScript Internationalization API Specification, 11.3. * Spec: ECMAScript Internationalization API Specification, 12.3. */ -function getIntlObjectInternals(obj, className, methodName) { - assert(typeof className === "string", "bad className for getIntlObjectInternals"); +function getIntlObjectInternals(obj) { + assert(IsObject(obj), "getIntlObjectInternals called with non-Object"); + assert(IsCollator(obj) || IsDateTimeFormat(obj) || IsNumberFormat(obj) || + IsPluralRules(obj) || IsRelativeTimeFormat(obj), + "getIntlObjectInternals called with non-Intl object"); - var internals = callFunction(std_WeakMap_get, internalsMap, obj); - assert(internals === undefined || isInitializedIntlObject(obj), "bad mapping in internalsMap"); + var internals = UnsafeGetReservedSlot(obj, INTL_INTERNALS_OBJECT_SLOT); - if (internals === undefined || internals.type !== className) - ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, className, methodName, className); + assert(IsObject(internals), "internals not an object"); + assert(hasOwn("type", internals), "missing type"); + assert((internals.type === "Collator" && IsCollator(obj)) || + (internals.type === "DateTimeFormat" && IsDateTimeFormat(obj)) || + (internals.type === "NumberFormat" && IsNumberFormat(obj)) || + (internals.type === "PluralRules" && IsPluralRules(obj)) || + (internals.type === "RelativeTimeFormat" && IsRelativeTimeFormat(obj)), + "type must match the object's class"); + assert(hasOwn("lazyData", internals), + "missing lazyData"); + assert(hasOwn("internalProps", internals), + "missing internalProps"); return internals; } @@ -1216,35 +1184,32 @@ function getIntlObjectInternals(obj, className, methodName) { * Get the internal properties of known-Intl object |obj|. For use only by * C++ code that knows what it's doing! */ -function getInternals(obj) -{ - assert(isInitializedIntlObject(obj), "for use only on guaranteed Intl objects"); +function getInternals(obj) { + var internals = getIntlObjectInternals(obj); - var internals = callFunction(std_WeakMap_get, internalsMap, obj); + // If internal properties have already been computed, use them. + var internalProps = maybeInternalProperties(internals); + if (internalProps) + return internalProps; - assert(internals.type !== "partial", "must have been successfully initialized"); - var lazyData = internals.lazyData; - if (!lazyData) - return internals.internalProps; - - var internalProps; + // Otherwise it's time to fully create them. var type = internals.type; - + switch (type) { case "Collator": - internalProps = resolveCollatorInternals(lazyData); + internalProps = resolveCollatorInternals(internals.lazyData); break; case "DateTimeFormat": - internalProps = resolveDateTimeFormatInternals(lazyData); + internalProps = resolveDateTimeFormatInternals(internals.lazyData); break; case "PluralRules": - internalProps = resolvePluralRulesInternals(lazyData); + internalProps = resolvePluralRulesInternals(internals.lazyData); break; - case "RelativeTimeFormat": - internalProps = resolveRelativeTimeFormatInternals(lazyData); + case "NumberFormat": + internalProps = resolveNumberFormatInternals(internals.lazyData); break; - default: // type === "NumberFormat" - internalProps = resolveNumberFormatInternals(lazyData); + default: // type === "RelativeTimeFormat" + internalProps = resolveRelativeTimeFormatInternals(internals.lazyData); break; } setInternalProperties(internals, internalProps); diff --git a/js/src/builtin/intl/DateTimeFormat.cpp b/js/src/builtin/intl/DateTimeFormat.cpp index 55ef4d826f..6a5b6584a5 100644 --- a/js/src/builtin/intl/DateTimeFormat.cpp +++ b/js/src/builtin/intl/DateTimeFormat.cpp @@ -91,63 +91,35 @@ static const JSFunctionSpec dateTimeFormat_methods[] = { static bool DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct) { - RootedObject obj(cx); + // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code). - // We're following ECMA-402 1st Edition when DateTimeFormat is called - // because of backward compatibility issues. - // See https://github.com/tc39/ecma402/issues/57 - if (!construct) { - // ES Intl 1st ed., 12.1.2.1 step 3 - JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global()); - if (!intl) - return false; - RootedValue self(cx, args.thisv()); - if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { - // ES Intl 1st ed., 12.1.2.1 step 4 - obj = ToObject(cx, self); - if (!obj) - return false; - - // ES Intl 1st ed., 12.1.2.1 step 5 - bool extensible; - if (!IsExtensible(cx, obj, &extensible)) - return false; - if (!extensible) - return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); - } else { - // ES Intl 1st ed., 12.1.2.1 step 3.a - construct = true; - } - } - if (construct) { - // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). - RootedObject proto(cx); - if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) - return false; - - if (!proto) { - proto = GlobalObject::getOrCreateDateTimeFormatPrototype(cx, cx->global()); - if (!proto) - return false; - } - - obj = NewObjectWithGivenProto(cx, proto); - if (!obj) - return false; - - obj->as().setReservedSlot(DateTimeFormatObject::INTERNALS_SLOT, NullValue()); - obj->as().setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(nullptr)); - } - - RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); - RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); - - // Step 3. - if (!intl::InitializeObject(cx, obj, cx->names().InitializeDateTimeFormat, locales, options)) + // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; - args.rval().setObject(*obj); - return true; + if (!proto) { + proto = GlobalObject::getOrCreateDateTimeFormatPrototype(cx, cx->global()); + if (!proto) + return false; + } + + Rooted dateTimeFormat(cx); + dateTimeFormat = NewObjectWithGivenProto(cx, proto); + if (!dateTimeFormat) + return false; + + dateTimeFormat->setReservedSlot(DateTimeFormatObject::INTERNALS_SLOT, NullValue()); + dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, + PrivateValue(nullptr)); + + RootedValue thisValue(cx, construct ? ObjectValue(*dateTimeFormat) : args.thisv()); + RootedValue locales(cx, args.get(0)); + RootedValue options(cx, args.get(1)); + + // Step 3. + return intl::LegacyIntlInitialize(cx, dateTimeFormat, cx->names().InitializeDateTimeFormat, + thisValue, locales, options, args.rval()); } static bool @@ -174,30 +146,23 @@ js::DateTimeFormatObject::finalize(FreeOp* fop, JSObject* obj) { MOZ_ASSERT(fop->onMainThread()); - // This is-undefined check shouldn't be necessary, but for internal - // brokenness in object allocation code. For the moment, hack around it by - // explicitly guarding against the possibility of the reserved slot not - // containing a private. See bug 949220. const Value& slot = obj->as().getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT); - if (!slot.isUndefined()) { - if (UDateFormat* df = static_cast(slot.toPrivate())) - udat_close(df); - } + if (UDateFormat* df = static_cast(slot.toPrivate())) + udat_close(df); } JSObject* -js::CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle global) +js::CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle global, + MutableHandleObject constructor) { RootedFunction ctor(cx); ctor = GlobalObject::createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0); if (!ctor) return nullptr; - RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, - &DateTimeFormatObject::class_)); + RootedObject proto(cx, GlobalObject::createBlankPrototype(cx, global)); if (!proto) return nullptr; - proto->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(nullptr)); if (!LinkConstructorAndPrototype(cx, ctor, proto)) return nullptr; @@ -226,22 +191,12 @@ js::CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handlenames().InitializeDateTimeFormat, UndefinedHandleValue, - options)) - { - return nullptr; - } - // 8.1 RootedValue ctorValue(cx, ObjectValue(*ctor)); if (!DefineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, nullptr, nullptr, 0)) return nullptr; + constructor.set(ctor); return proto; } @@ -495,7 +450,7 @@ js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp) * of the given DateTimeFormat. */ static UDateFormat* -NewUDateFormat(JSContext* cx, HandleObject dateTimeFormat) +NewUDateFormat(JSContext* cx, Handle dateTimeFormat) { RootedValue value(cx); @@ -779,42 +734,23 @@ js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp) MOZ_ASSERT(args[1].isNumber()); MOZ_ASSERT(args[2].isBoolean()); - RootedObject dateTimeFormat(cx, &args[0].toObject()); + Rooted dateTimeFormat(cx); + dateTimeFormat = &args[0].toObject().as(); - // Obtain a UDateFormat object, cached if possible. - bool isDateTimeFormatInstance = dateTimeFormat->getClass() == &DateTimeFormatObject::class_; - UDateFormat* df; - if (isDateTimeFormatInstance) { - void* priv = - dateTimeFormat->as().getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT).toPrivate(); - df = static_cast(priv); - if (!df) { - df = NewUDateFormat(cx, dateTimeFormat); - if (!df) - return false; - dateTimeFormat->as().setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(df)); - } - } else { - // There's no good place to cache the ICU date-time format for an object - // that has been initialized as a DateTimeFormat but is not a - // DateTimeFormat instance. One possibility might be to add a - // DateTimeFormat instance as an internal property to each such object. + // Obtain a cached UDateFormat object. + void* priv = + dateTimeFormat->getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT).toPrivate(); + UDateFormat* df = static_cast(priv); + if (!df) { df = NewUDateFormat(cx, dateTimeFormat); if (!df) return false; + dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(df)); } // Use the UDateFormat to actually format the time stamp. - RootedValue result(cx); - bool success = args[2].toBoolean() - ? intl_FormatToPartsDateTime(cx, df, args[1].toNumber(), &result) - : intl_FormatDateTime(cx, df, args[1].toNumber(), &result); - - if (!isDateTimeFormatInstance) - udat_close(df); - if (!success) - return false; - args.rval().set(result); - return true; + return args[2].toBoolean() + ? intl_FormatToPartsDateTime(cx, df, args[1].toNumber(), args.rval()) + : intl_FormatDateTime(cx, df, args[1].toNumber(), args.rval()); } diff --git a/js/src/builtin/intl/DateTimeFormat.h b/js/src/builtin/intl/DateTimeFormat.h index e9617c0e06..26d46b77b6 100644 --- a/js/src/builtin/intl/DateTimeFormat.h +++ b/js/src/builtin/intl/DateTimeFormat.h @@ -41,7 +41,7 @@ class DateTimeFormatObject : public NativeObject extern JSObject* CreateDateTimeFormatPrototype(JSContext* cx, JS::Handle Intl, - JS::Handle global); + JS::Handle global, MutableHandleObject constructor); /** * Returns a new instance of the standard built-in DateTimeFormat constructor. @@ -131,7 +131,7 @@ intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp); * * Spec: ECMAScript Internationalization API Specification, 12.3.2. * - * Usage: formatted = intl_FormatDateTime(dateTimeFormat, x) + * Usage: formatted = intl_FormatDateTime(dateTimeFormat, x, formatToParts) */ extern MOZ_MUST_USE bool intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp); diff --git a/js/src/builtin/intl/DateTimeFormat.js b/js/src/builtin/intl/DateTimeFormat.js index 353c24fd7d..388b6a1415 100644 --- a/js/src/builtin/intl/DateTimeFormat.js +++ b/js/src/builtin/intl/DateTimeFormat.js @@ -90,11 +90,13 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) { /** - * Returns an object containing the DateTimeFormat internal properties of |obj|, - * or throws a TypeError if |obj| isn't DateTimeFormat-initialized. + * Returns an object containing the DateTimeFormat internal properties of |obj|. */ -function getDateTimeFormatInternals(obj, methodName) { - var internals = getIntlObjectInternals(obj, "DateTimeFormat", methodName); +function getDateTimeFormatInternals(obj) { + assert(IsObject(obj), "getDateTimeFormatInternals called with non-object"); + assert(IsDateTimeFormat(obj), "getDateTimeFormatInternals called with non-DateTimeFormat"); + + var internals = getIntlObjectInternals(obj); assert(internals.type === "DateTimeFormat", "bad type escaped getIntlObjectInternals"); // If internal properties have already been computed, use them. @@ -214,6 +216,29 @@ function DefaultTimeZone() { return defaultTimeZone; } + +/** + * UnwrapDateTimeFormat(dtf) + */ +function UnwrapDateTimeFormat(dtf, methodName) { + // Step 1. + if ((!IsObject(dtf) || !IsDateTimeFormat(dtf)) && + dtf instanceof GetDateTimeFormatConstructor()) + { + dtf = dtf[intlFallbackSymbol()]; + } + + // Step 2. + if (!IsObject(dtf) || !IsDateTimeFormat(dtf)) { + ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "DateTimeFormat", methodName, + "DateTimeFormat"); + } + + // Step 3. + return dtf; +} + + /** * Initializes an object as a DateTimeFormat. * @@ -225,15 +250,13 @@ function DefaultTimeZone() { * * Spec: ECMAScript Internationalization API Specification, 12.1.1. */ -function InitializeDateTimeFormat(dateTimeFormat, locales, options) { - assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat"); +function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options) { + assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat called with non-Object"); + assert(IsDateTimeFormat(dateTimeFormat), + "InitializeDateTimeFormat called with non-DateTimeFormat"); - // Step 1. - if (isInitializedIntlObject(dateTimeFormat)) - ThrowTypeError(JSMSG_INTL_OBJECT_REINITED); - - // Step 2. - var internals = initializeIntlObject(dateTimeFormat); + // Steps 1-2 (These steps are no longer required and should be removed + // from the spec; https://github.com/tc39/ecma402/issues/115). // Lazy DateTimeFormat data has the following structure: // @@ -334,7 +357,19 @@ function InitializeDateTimeFormat(dateTimeFormat, locales, options) { // // We've done everything that must be done now: mark the lazy data as fully // computed and install it. - setLazyData(internals, "DateTimeFormat", lazyDateTimeFormatData); + initializeIntlObject(dateTimeFormat, "DateTimeFormat", lazyDateTimeFormatData); + + if (dateTimeFormat !== thisValue && thisValue instanceof GetDateTimeFormatConstructor()) { + if (!IsObject(thisValue)) + ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof thisValue); + + _DefineDataProperty(thisValue, intlFallbackSymbol(), dateTimeFormat, + ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE); + + return thisValue; + } + + return dateTimeFormat; } @@ -734,7 +769,7 @@ function dateTimeFormatFormatToBind() { var x = (date === undefined) ? std_Date_now() : ToNumber(date); // Step 1.a.iii. - return intl_FormatDateTime(this, x, false); + return intl_FormatDateTime(this, x, /* formatToParts = */ false); } /** @@ -745,34 +780,40 @@ function dateTimeFormatFormatToBind() { * Spec: ECMAScript Internationalization API Specification, 12.3.2. */ function Intl_DateTimeFormat_format_get() { - // Check "this DateTimeFormat object" per introduction of section 12.3. - var internals = getDateTimeFormatInternals(this, "format"); + // Steps 1-3. + var dtf = UnwrapDateTimeFormat(this, "format"); - // Step 1. + var internals = getDateTimeFormatInternals(dtf); + + // Step 4. if (internals.boundFormat === undefined) { - // Step 1.a. + // Step 4.a. var F = dateTimeFormatFormatToBind; - // Step 1.b-d. - var bf = callFunction(FunctionBind, F, this); + // Steps 4.b-d. + var bf = callFunction(FunctionBind, F, dtf); internals.boundFormat = bf; } - // Step 2. + // Step 5. return internals.boundFormat; } +_SetCanonicalName(Intl_DateTimeFormat_format_get, "get format"); function Intl_DateTimeFormat_formatToParts() { - // Check "this DateTimeFormat object" per introduction of section 12.3. - getDateTimeFormatInternals(this, "formatToParts"); + // Steps 1-3. + var dtf = UnwrapDateTimeFormat(this, "formatToParts"); - // Steps 1.a.i-ii + // Ensure the DateTimeFormat internals are resolved. + getDateTimeFormatInternals(dtf); + + // Steps 4-5. var date = arguments.length > 0 ? arguments[0] : undefined; var x = (date === undefined) ? std_Date_now() : ToNumber(date); - // Step 1.a.iii. - return intl_FormatDateTime(this, x, true); + // Step 6. + return intl_FormatDateTime(dtf, x, /* formatToParts = */ true); } @@ -782,8 +823,10 @@ function Intl_DateTimeFormat_formatToParts() { * Spec: ECMAScript Internationalization API Specification, 12.3.3 and 12.4. */ function Intl_DateTimeFormat_resolvedOptions() { - // Check "this DateTimeFormat object" per introduction of section 12.3. - var internals = getDateTimeFormatInternals(this, "resolvedOptions"); + // Invoke |UnwrapDateTimeFormat| per introduction of section 12.3. + var dtf = UnwrapDateTimeFormat(this, "resolvedOptions"); + + var internals = getDateTimeFormatInternals(dtf); var result = { locale: internals.locale, diff --git a/js/src/builtin/intl/IntlObject.cpp b/js/src/builtin/intl/IntlObject.cpp index 6bb57adf41..893eb5ce40 100644 --- a/js/src/builtin/intl/IntlObject.cpp +++ b/js/src/builtin/intl/IntlObject.cpp @@ -454,10 +454,12 @@ GlobalObject::initIntlObject(JSContext* cx, Handle global) RootedObject collatorProto(cx, CreateCollatorPrototype(cx, intl, global)); if (!collatorProto) return false; - RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global)); + RootedObject dateTimeFormatProto(cx), dateTimeFormat(cx); + dateTimeFormatProto = CreateDateTimeFormatPrototype(cx, intl, global, &dateTimeFormat); if (!dateTimeFormatProto) return false; - RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global)); + RootedObject numberFormatProto(cx), numberFormat(cx); + numberFormatProto = CreateNumberFormatPrototype(cx, intl, global, &numberFormat); if (!numberFormatProto) return false; RootedObject pluralRulesProto(cx, CreatePluralRulesPrototype(cx, intl, global)); @@ -487,7 +489,9 @@ GlobalObject::initIntlObject(JSContext* cx, Handle global) // |getPrototype(JSProto_*)|, but that has global-object-property-related // baggage we don't need or want, so we use one-off reserved slots. global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto)); + global->setReservedSlot(DATE_TIME_FORMAT, ObjectValue(*dateTimeFormat)); global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto)); + global->setReservedSlot(NUMBER_FORMAT, ObjectValue(*numberFormat)); global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto)); global->setReservedSlot(PLURAL_RULES_PROTO, ObjectValue(*pluralRulesProto)); global->setReservedSlot(RELATIVE_TIME_FORMAT_PROTO, ObjectValue(*relativeTimeFmtProto)); diff --git a/js/src/builtin/intl/IntlObject.js b/js/src/builtin/intl/IntlObject.js index 826ad27ff0..4baef4eca4 100644 --- a/js/src/builtin/intl/IntlObject.js +++ b/js/src/builtin/intl/IntlObject.js @@ -3,44 +3,79 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ function Intl_getCanonicalLocales(locales) { - let codes = CanonicalizeLocaleList(locales); - let result = []; + // Step 1. + var localeList = CanonicalizeLocaleList(locales); - let len = codes.length; - let k = 0; + // Step 2 (Inlined CreateArrayFromList). + var array = []; - while (k < len) { - _DefineDataProperty(result, k, codes[k]); - k++; - } - return result; -} + for (var n = 0, len = localeList.length; n < len; n++) + _DefineDataProperty(array, n, localeList[n]); -function Intl_getCalendarInfo(locales) { - const requestedLocales = CanonicalizeLocaleList(locales); - - const DateTimeFormat = dateTimeFormatInternalProperties; - const localeData = DateTimeFormat.localeData; - - const localeOpt = new Record(); - localeOpt.localeMatcher = "best fit"; - - const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat), - requestedLocales, - localeOpt, - DateTimeFormat.relevantExtensionKeys, - localeData); - - const result = intl_GetCalendarInfo(r.locale); - result.calendar = r.ca; - result.locale = r.locale; - - return result; + return array; } /** - * This function is a custom method designed after Intl API, but currently - * not part of the spec or spec proposal. + * This function is a custom function in the style of the standard Intl.* + * functions, that isn't part of any spec or proposal yet. + * + * Returns an object with the following properties: + * locale: + * The actual resolved locale. + * + * calendar: + * The default calendar of the resolved locale. + * + * firstDayOfWeek: + * The first day of the week for the resolved locale. + * + * minDays: + * The minimum number of days in a week for the resolved locale. + * + * weekendStart: + * The day considered the beginning of a weekend for the resolved locale. + * + * weekendEnd: + * The day considered the end of a weekend for the resolved locale. + * + * Days are encoded as integers in the range 1=Sunday to 7=Saturday. + */ +function Intl_getCalendarInfo(locales) { + // 1. Let requestLocales be ? CanonicalizeLocaleList(locales). + const requestedLocales = CanonicalizeLocaleList(locales); + + const DateTimeFormat = dateTimeFormatInternalProperties; + + // 2. Let localeData be %DateTimeFormat%.[[localeData]]. + const localeData = DateTimeFormat.localeData; + + // 3. Let localeOpt be a new Record. + const localeOpt = new Record(); + + // 4. Set localeOpt.[[localeMatcher]] to "best fit". + localeOpt.localeMatcher = "best fit"; + + // 5. Let r be ResolveLocale(%DateTimeFormat%.[[availableLocales]], + // requestedLocales, localeOpt, + // %DateTimeFormat%.[[relevantExtensionKeys]], localeData). + const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat), + requestedLocales, + localeOpt, + DateTimeFormat.relevantExtensionKeys, + localeData); + + // 6. Let result be GetCalendarInfo(r.[[locale]]). + const result = intl_GetCalendarInfo(r.locale); + _DefineDataProperty(result, "calendar", r.ca); + _DefineDataProperty(result, "locale", r.locale); + + // 7. Return result. + return result; +} + +/** + * This function is a custom function in the style of the standard Intl.* + * functions, that isn't part of any spec or proposal yet. * We want to use it internally to retrieve translated values from CLDR in * order to ensure they're aligned with what Intl API returns. * @@ -86,8 +121,9 @@ function Intl_getDisplayNames(locales, options) { // 4. Let localeData be %DateTimeFormat%.[[localeData]]. const localeData = DateTimeFormat.localeData; - // 5. Let opt be a new Record. + // 5. Let localeOpt be a new Record. const localeOpt = new Record(); + // 6. Set localeOpt.[[localeMatcher]] to "best fit". localeOpt.localeMatcher = "best fit"; @@ -101,6 +137,7 @@ function Intl_getDisplayNames(locales, options) { // 8. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow" », "long"). const style = GetOption(options, "style", "string", ["long", "short", "narrow"], "long"); + // 9. Let keys be ? Get(options, "keys"). let keys = options.keys; @@ -119,8 +156,10 @@ function Intl_getDisplayNames(locales, options) { // |intl_ComputeDisplayNames| may infallibly access the list's length via // |ArrayObject::length|.) let processedKeys = []; + // 13. Let len be ? ToLength(? Get(keys, "length")). let len = ToLength(keys.length); + // 14. Let i be 0. // 15. Repeat, while i < len for (let i = 0; i < len; i++) { diff --git a/js/src/builtin/intl/NumberFormat.cpp b/js/src/builtin/intl/NumberFormat.cpp index 0e6e367ae9..52c5747c99 100644 --- a/js/src/builtin/intl/NumberFormat.cpp +++ b/js/src/builtin/intl/NumberFormat.cpp @@ -92,63 +92,34 @@ static const JSFunctionSpec numberFormat_methods[] = { static bool NumberFormat(JSContext* cx, const CallArgs& args, bool construct) { - RootedObject obj(cx); + // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code). - // We're following ECMA-402 1st Edition when NumberFormat is called - // because of backward compatibility issues. - // See https://github.com/tc39/ecma402/issues/57 - if (!construct) { - // ES Intl 1st ed., 11.1.2.1 step 3 - JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global()); - if (!intl) - return false; - RootedValue self(cx, args.thisv()); - if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { - // ES Intl 1st ed., 11.1.2.1 step 4 - obj = ToObject(cx, self); - if (!obj) - return false; - - // ES Intl 1st ed., 11.1.2.1 step 5 - bool extensible; - if (!IsExtensible(cx, obj, &extensible)) - return false; - if (!extensible) - return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); - } else { - // ES Intl 1st ed., 11.1.2.1 step 3.a - construct = true; - } - } - if (construct) { - // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). - RootedObject proto(cx); - if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) - return false; - - if (!proto) { - proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global()); - if (!proto) - return false; - } - - obj = NewObjectWithGivenProto(cx, proto); - if (!obj) - return false; - - obj->as().setReservedSlot(NumberFormatObject::INTERNALS_SLOT, NullValue()); - obj->as().setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); - } - - RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); - RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); - - // Step 3. - if (!intl::InitializeObject(cx, obj, cx->names().InitializeNumberFormat, locales, options)) + // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; - args.rval().setObject(*obj); - return true; + if (!proto) { + proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global()); + if (!proto) + return false; + } + + Rooted numberFormat(cx); + numberFormat = NewObjectWithGivenProto(cx, proto); + if (!numberFormat) + return false; + + numberFormat->setReservedSlot(NumberFormatObject::INTERNALS_SLOT, NullValue()); + numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); + + RootedValue thisValue(cx, construct ? ObjectValue(*numberFormat) : args.thisv()); + RootedValue locales(cx, args.get(0)); + RootedValue options(cx, args.get(1)); + + // Step 3. + return intl::LegacyIntlInitialize(cx, numberFormat, cx->names().InitializeNumberFormat, thisValue, + locales, options, args.rval()); } static bool @@ -175,30 +146,23 @@ js::NumberFormatObject::finalize(FreeOp* fop, JSObject* obj) { MOZ_ASSERT(fop->onMainThread()); - // This is-undefined check shouldn't be necessary, but for internal - // brokenness in object allocation code. For the moment, hack around it by - // explicitly guarding against the possibility of the reserved slot not - // containing a private. See bug 949220. - const Value& slot = obj->as().getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT); - if (!slot.isUndefined()) { - if (UNumberFormat* nf = static_cast(slot.toPrivate())) - unum_close(nf); - } + const Value& slot = obj->as().getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT); + if (UNumberFormat* nf = static_cast(slot.toPrivate())) + unum_close(nf); } JSObject* -js::CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle global) +js::CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle global, + MutableHandleObject constructor) { RootedFunction ctor(cx); ctor = GlobalObject::createConstructor(cx, &NumberFormat, cx->names().NumberFormat, 0); if (!ctor) return nullptr; - RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, - &NumberFormatObject::class_)); + RootedObject proto(cx, GlobalObject::createBlankPrototype(cx, global)); if (!proto) return nullptr; - proto->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); if (!LinkConstructorAndPrototype(cx, ctor, proto)) return nullptr; @@ -229,22 +193,12 @@ js::CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handlenames().InitializeNumberFormat, UndefinedHandleValue, - options)) - { - return nullptr; - } - // 8.1 RootedValue ctorValue(cx, ObjectValue(*ctor)); if (!DefineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, nullptr, nullptr, 0)) return nullptr; + constructor.set(ctor); return proto; } @@ -295,7 +249,7 @@ js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp) * of the given NumberFormat. */ static UNumberFormat* -NewUNumberFormat(JSContext* cx, HandleObject numberFormat) +NewUNumberFormat(JSContext* cx, Handle numberFormat) { RootedValue value(cx); @@ -854,50 +808,23 @@ js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp) MOZ_ASSERT(args[1].isNumber()); MOZ_ASSERT(args[2].isBoolean()); - RootedObject numberFormat(cx, &args[0].toObject()); + Rooted numberFormat(cx, &args[0].toObject().as()); - // Obtain a UNumberFormat object, cached if possible. - bool isNumberFormatInstance = numberFormat->getClass() == &NumberFormatObject::class_; - UNumberFormat* nf; - if (isNumberFormatInstance) { - void* priv = - numberFormat->as().getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT).toPrivate(); - nf = static_cast(priv); - if (!nf) { - nf = NewUNumberFormat(cx, numberFormat); - if (!nf) - return false; - numberFormat->as().setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nf)); - } - } else { - // There's no good place to cache the ICU number format for an object - // that has been initialized as a NumberFormat but is not a - // NumberFormat instance. One possibility might be to add a - // NumberFormat instance as an internal property to each such object. + // Obtain a cached UNumberFormat object. + void* priv = + numberFormat->getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT).toPrivate(); + UNumberFormat* nf = static_cast(priv); + if (!nf) { nf = NewUNumberFormat(cx, numberFormat); if (!nf) return false; + numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nf)); } // Use the UNumberFormat to actually format the number. - double d = args[1].toNumber(); - RootedValue result(cx); - - bool success; if (args[2].toBoolean()) { - success = intl_FormatNumberToParts(cx, nf, d, &result); - } else { - MOZ_ASSERT(!args[2].toBoolean(), - "shouldn't be doing formatToParts without an ICU that " - "supports it"); - success = js::intl_FormatNumber(cx, nf, d, &result); + return intl_FormatNumberToParts(cx, nf, args[1].toNumber(), args.rval()); } - - if (!isNumberFormatInstance) - unum_close(nf); - if (!success) - return false; - args.rval().set(result); - return true; + return intl_FormatNumber(cx, nf, args[1].toNumber(), args.rval()); } diff --git a/js/src/builtin/intl/NumberFormat.h b/js/src/builtin/intl/NumberFormat.h index e3005b56b3..bf5cd84ca1 100644 --- a/js/src/builtin/intl/NumberFormat.h +++ b/js/src/builtin/intl/NumberFormat.h @@ -37,7 +37,8 @@ class NumberFormatObject : public NativeObject }; extern JSObject* -CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle global); +CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle global, + MutableHandleObject constructor); /** * Returns a new instance of the standard built-in NumberFormat constructor. @@ -76,7 +77,7 @@ intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp); * * Spec: ECMAScript Internationalization API Specification, 11.3.2. * - * Usage: formatted = intl_FormatNumber(numberFormat, x) + * Usage: formatted = intl_FormatNumber(numberFormat, x, formatToParts) */ extern MOZ_MUST_USE bool intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp); diff --git a/js/src/builtin/intl/NumberFormat.js b/js/src/builtin/intl/NumberFormat.js index 0118db768f..3b8471cdb8 100644 --- a/js/src/builtin/intl/NumberFormat.js +++ b/js/src/builtin/intl/NumberFormat.js @@ -96,11 +96,13 @@ function resolveNumberFormatInternals(lazyNumberFormatData) { /** - * Returns an object containing the NumberFormat internal properties of |obj|, - * or throws a TypeError if |obj| isn't NumberFormat-initialized. + * Returns an object containing the NumberFormat internal properties of |obj|. */ -function getNumberFormatInternals(obj, methodName) { - var internals = getIntlObjectInternals(obj, "NumberFormat", methodName); +function getNumberFormatInternals(obj) { + assert(IsObject(obj), "getNumberFormatInternals called with non-object"); + assert(IsNumberFormat(obj), "getNumberFormatInternals called with non-NumberFormat"); + + var internals = getIntlObjectInternals(obj); assert(internals.type === "NumberFormat", "bad type escaped getIntlObjectInternals"); // If internal properties have already been computed, use them. @@ -114,6 +116,25 @@ function getNumberFormatInternals(obj, methodName) { return internalProps; } + +/** + * UnwrapNumberFormat(nf) + */ +function UnwrapNumberFormat(nf, methodName) { + // Step 1. + if ((!IsObject(nf) || !IsNumberFormat(nf)) && nf instanceof GetNumberFormatConstructor()) { + nf = nf[intlFallbackSymbol()]; + } + + // Step 2. + if (!IsObject(nf) || !IsNumberFormat(nf)) + ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "NumberFormat", methodName, "NumberFormat"); + + // Step 3. + return nf; +} + + /** * Applies digit options used for number formatting onto the intl object. * @@ -199,15 +220,12 @@ function IsWellFormedCurrencyCode(currency) { * * Spec: ECMAScript Internationalization API Specification, 11.1.1. */ -function InitializeNumberFormat(numberFormat, locales, options) { - assert(IsObject(numberFormat), "InitializeNumberFormat"); +function InitializeNumberFormat(numberFormat, thisValue, locales, options) { + assert(IsObject(numberFormat), "InitializeNumberFormat called with non-object"); + assert(IsNumberFormat(numberFormat), "InitializeNumberFormat called with non-NumberFormat"); - // Step 1. - if (isInitializedIntlObject(numberFormat)) - ThrowTypeError(JSMSG_INTL_OBJECT_REINITED); - - // Step 2. - var internals = initializeIntlObject(numberFormat); + // Steps 1-2 (These steps are no longer required and should be removed + // from the spec; https://github.com/tc39/ecma402/issues/115). // Lazy NumberFormat data has the following structure: // @@ -312,7 +330,19 @@ function InitializeNumberFormat(numberFormat, locales, options) { // // We've done everything that must be done now: mark the lazy data as fully // computed and install it. - setLazyData(internals, "NumberFormat", lazyNumberFormatData); + initializeIntlObject(numberFormat, "NumberFormat", lazyNumberFormatData); + + if (numberFormat !== thisValue && thisValue instanceof GetNumberFormatConstructor()) { + if (!IsObject(thisValue)) + ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof thisValue); + + _DefineDataProperty(thisValue, intlFallbackSymbol(), numberFormat, + ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE); + + return thisValue; + } + + return numberFormat; } @@ -446,28 +476,32 @@ function numberFormatFormatToBind(value) { * Spec: ECMAScript Internationalization API Specification, 11.3.2. */ function Intl_NumberFormat_format_get() { - // Check "this NumberFormat object" per introduction of section 11.3. - var internals = getNumberFormatInternals(this, "format"); + // Steps 1-3. + var nf = UnwrapNumberFormat(this, "format"); - // Step 1. + var internals = getNumberFormatInternals(nf); + + // Step 4. if (internals.boundFormat === undefined) { - // Step 1.a. + // Step 4.a. var F = numberFormatFormatToBind; - // Step 1.b-d. - var bf = callFunction(FunctionBind, F, this); + // Steps 4.b-d. + var bf = callFunction(FunctionBind, F, nf); internals.boundFormat = bf; } - // Step 2. + + // Step 5. return internals.boundFormat; } +_SetCanonicalName(Intl_NumberFormat_format_get, "get format"); function Intl_NumberFormat_formatToParts(value) { - // Step 1. - var nf = this; + // Steps 1-3. + var nf = UnwrapNumberFormat(this, "formatToParts"); - // Steps 2-3. - getNumberFormatInternals(nf, "formatToParts"); + // Ensure the NumberFormat internals are resolved. + getNumberFormatInternals(nf); // Step 4. var x = ToNumber(value); @@ -482,8 +516,10 @@ function Intl_NumberFormat_formatToParts(value) { * Spec: ECMAScript Internationalization API Specification, 11.3.3 and 11.4. */ function Intl_NumberFormat_resolvedOptions() { - // Check "this NumberFormat object" per introduction of section 11.3. - var internals = getNumberFormatInternals(this, "resolvedOptions"); + // Invoke |UnwrapNumberFormat| per introduction of section 11.3. + var nf = UnwrapNumberFormat(this, "resolvedOptions"); + + var internals = getNumberFormatInternals(nf); var result = { locale: internals.locale, diff --git a/js/src/builtin/intl/PluralRules.cpp b/js/src/builtin/intl/PluralRules.cpp index c166a4b4de..22ba60ace1 100644 --- a/js/src/builtin/intl/PluralRules.cpp +++ b/js/src/builtin/intl/PluralRules.cpp @@ -113,8 +113,8 @@ PluralRules(JSContext* cx, const CallArgs& args, bool construct) if (!obj) return false; - obj->as().setReservedSlot(PluralRulesObject::INTERNALS_SLOT, NullValue()); - obj->as().setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr)); + obj->as().setReservedSlot(PluralRulesObject::INTERNALS_SLOT, NullValue()); + obj->as().setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr)); } RootedValue locales(cx, args.get(0)); @@ -147,15 +147,9 @@ js::PluralRulesObject::finalize(FreeOp* fop, JSObject* obj) { MOZ_ASSERT(fop->onMainThread()); - // This is-undefined check shouldn't be necessary, but for internal - // brokenness in object allocation code. For the moment, hack around it by - // explicitly guarding against the possibility of the reserved slot not - // containing a private. See bug 949220. const Value& slot = obj->as().getReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT); - if (!slot.isUndefined()) { - if (UPluralRules* pr = static_cast(slot.toPrivate())) - uplrules_close(pr); - } + if (UPluralRules* pr = static_cast(slot.toPrivate())) + uplrules_close(pr); } JSObject* @@ -166,10 +160,9 @@ js::CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle(cx, global)); if (!proto) return nullptr; - proto->setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr)); if (!LinkConstructorAndPrototype(cx, ctor, proto)) return nullptr; @@ -180,16 +173,6 @@ js::CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handlenames().InitializePluralRules, UndefinedHandleValue, - options)) - { - return nullptr; - } - RootedValue ctorValue(cx, ObjectValue(*ctor)); if (!DefineProperty(cx, Intl, cx->names().PluralRules, ctorValue, nullptr, nullptr, 0)) return nullptr; @@ -222,7 +205,7 @@ js::intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp) * */ static UNumberFormat* -NewUNumberFormatForPluralRules(JSContext* cx, HandleObject pluralRules) +NewUNumberFormatForPluralRules(JSContext* cx, Handle pluralRules) { RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules)); if (!internals) @@ -299,7 +282,7 @@ js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - RootedObject pluralRules(cx, &args[0].toObject()); + Rooted pluralRules(cx, &args[0].toObject().as()); UNumberFormat* nf = NewUNumberFormatForPluralRules(cx, pluralRules); if (!nf) diff --git a/js/src/builtin/intl/PluralRules.js b/js/src/builtin/intl/PluralRules.js index eafe4c2bd4..d02da59f02 100644 --- a/js/src/builtin/intl/PluralRules.js +++ b/js/src/builtin/intl/PluralRules.js @@ -63,11 +63,13 @@ function resolvePluralRulesInternals(lazyPluralRulesData) { } /** - * 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) { - var internals = getIntlObjectInternals(obj, "PluralRules", methodName); +function getPluralRulesInternals(obj) { + assert(IsObject(obj), "getPluralRulesInternals called with non-object"); + assert(IsPluralRules(obj), "getPluralRulesInternals called with non-PluralRules"); + + var internals = getIntlObjectInternals(obj); assert(internals.type === "PluralRules", "bad type escaped getIntlObjectInternals"); var internalProps = maybeInternalProperties(internals); @@ -91,13 +93,11 @@ function getPluralRulesInternals(obj, methodName) { * Spec: ECMAScript 402 API, PluralRules, 1.1.1. */ function InitializePluralRules(pluralRules, locales, options) { - assert(IsObject(pluralRules), "InitializePluralRules"); + assert(IsObject(pluralRules), "InitializePluralRules called with non-object"); + assert(IsPluralRules(pluralRules), "InitializePluralRules called with non-PluralRules"); - // Step 1. - if (isInitializedIntlObject(pluralRules)) - ThrowTypeError(JSMSG_INTL_OBJECT_REINITED); - - let internals = initializeIntlObject(pluralRules); + // Steps 1-2 (These steps are no longer required and should be removed + // from the spec; https://github.com/tc39/ecma402/issues/115). // Lazy PluralRules data has the following structure: // @@ -156,7 +156,7 @@ function InitializePluralRules(pluralRules, locales, options) { std_Math_max(lazyPluralRulesData.minimumFractionDigits, 3); } - setLazyData(internals, "PluralRules", lazyPluralRulesData) + initializeIntlObject(pluralRules, "PluralRules", lazyPluralRulesData) } /** @@ -189,8 +189,13 @@ function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) { 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); // Steps 3-4. let n = ToNumber(value); @@ -205,7 +210,13 @@ function Intl_PluralRules_select(value) { * Spec: ECMAScript 402 API, PluralRules, 1.4.4. */ function Intl_PluralRules_resolvedOptions() { - var internals = getPluralRulesInternals(this, "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); var result = { locale: internals.locale, diff --git a/js/src/builtin/intl/RelativeTimeFormat.cpp b/js/src/builtin/intl/RelativeTimeFormat.cpp index 22769af155..bb66c30cc9 100644 --- a/js/src/builtin/intl/RelativeTimeFormat.cpp +++ b/js/src/builtin/intl/RelativeTimeFormat.cpp @@ -108,8 +108,8 @@ RelativeTimeFormat(JSContext* cx, unsigned argc, Value* vp) if (!relativeTimeFormat) return false; - relativeTimeFormat->as().setReservedSlot(RelativeTimeFormatObject::INTERNALS_SLOT, NullValue()); - relativeTimeFormat->as().setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr)); + relativeTimeFormat->as().setReservedSlot(RelativeTimeFormatObject::INTERNALS_SLOT, NullValue()); + relativeTimeFormat->as().setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr)); RootedValue locales(cx, args.get(0)); RootedValue options(cx, args.get(1)); @@ -127,15 +127,9 @@ js::RelativeTimeFormatObject::finalize(FreeOp* fop, JSObject* obj) { MOZ_ASSERT(fop->onMainThread()); - // This is-undefined check shouldn't be necessary, but for internal - // brokenness in object allocation code. For the moment, hack around it by - // explicitly guarding against the possibility of the reserved slot not - // containing a private. See bug 949220. const Value& slot = obj->as().getReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT); - if (!slot.isUndefined()) { - if (URelativeDateTimeFormatter* rtf = static_cast(slot.toPrivate())) - ureldatefmt_close(rtf); - } + if (URelativeDateTimeFormatter* rtf = static_cast(slot.toPrivate())) + ureldatefmt_close(rtf); } JSObject* @@ -146,10 +140,9 @@ js::CreateRelativeTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle(cx, global)); if (!proto) return nullptr; - proto->setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr)); if (!LinkConstructorAndPrototype(cx, ctor, proto)) return nullptr; @@ -163,16 +156,6 @@ js::CreateRelativeTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handlenames().InitializeRelativeTimeFormat, UndefinedHandleValue, - options)) - { - return nullptr; - } - RootedValue ctorValue(cx, ObjectValue(*ctor)); if (!DefineProperty(cx, Intl, cx->names().RelativeTimeFormat, ctorValue, nullptr, nullptr, 0)) { return nullptr; diff --git a/js/src/builtin/intl/RelativeTimeFormat.js b/js/src/builtin/intl/RelativeTimeFormat.js index ba9032128f..92af7b0dca 100644 --- a/js/src/builtin/intl/RelativeTimeFormat.js +++ b/js/src/builtin/intl/RelativeTimeFormat.js @@ -69,8 +69,11 @@ function resolveRelativeTimeFormatInternals(lazyRelativeTimeFormatData) { * Returns an object containing the RelativeTimeFormat internal properties of |obj|, * or throws a TypeError if |obj| isn't RelativeTimeFormat-initialized. */ -function getRelativeTimeFormatInternals(obj, methodName) { - var internals = getIntlObjectInternals(obj, "RelativeTimeFormat", methodName); +function getRelativeTimeFormatInternals(obj) { + assert(IsObject(obj), "getRelativeTimeFormatInternals called with non-object"); + assert(IsRelativeTimeFormat(obj), "getRelativeTimeFormatInternals called with non-RelativeTimeFormat"); + + var internals = getIntlObjectInternals(obj); assert(internals.type === "RelativeTimeFormat", "bad type escaped getIntlObjectInternals"); var internalProps = maybeInternalProperties(internals); @@ -94,12 +97,8 @@ function getRelativeTimeFormatInternals(obj, methodName) { * Spec: ECMAScript 402 API, RelativeTimeFormat, 1.1.1. */ function InitializeRelativeTimeFormat(relativeTimeFormat, locales, options) { - assert(IsObject(relativeTimeFormat), "InitializeRelativeTimeFormat"); - - if (isInitializedIntlObject(relativeTimeFormat)) - ThrowTypeError(JSMSG_INTL_OBJECT_REINITED); - - let internals = initializeIntlObject(relativeTimeFormat); + assert(IsObject(relativeTimeFormat), "InitializeRelativeTimeFormat called with non-object"); + assert(IsRelativeTimeFormat(relativeTimeFormat), "InitializeRelativeTimeFormat called with non-RelativeTimeFormat"); // Lazy RelativeTimeFormat data has the following structure: // @@ -146,7 +145,7 @@ function InitializeRelativeTimeFormat(relativeTimeFormat, locales, options) { const numeric = GetOption(options, "numeric", "string", ["always", "auto"], "always"); lazyRelativeTimeFormatData.numeric = numeric; - setLazyData(internals, "RelativeTimeFormat", lazyRelativeTimeFormatData) + initializeIntlObject(relativeTimeFormat, "RelativeTimeFormat", lazyRelativeTimeFormatData) } /** @@ -181,7 +180,11 @@ function Intl_RelativeTimeFormat_format(value, unit) { let relativeTimeFormat = this; // Step 2. - let internals = getRelativeTimeFormatInternals(relativeTimeFormat, "format"); + if (!IsObject(relativeTimeFormat) || !IsRelativeTimeFormat(relativeTimeFormat)) + ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "RelativeTimeFormat", "format", "RelativeTimeFormat"); + + // Ensure the RelativeTimeFormat internals are resolved. + let internals = getRelativeTimeFormatInternals(relativeTimeFormat); // Step 3. let t = ToNumber(value); @@ -227,7 +230,13 @@ function Intl_RelativeTimeFormat_format(value, unit) { * Spec: ECMAScript 402 API, RelativeTimeFormat, 1.4.4. */ function Intl_RelativeTimeFormat_resolvedOptions() { - var internals = getRelativeTimeFormatInternals(this, "resolvedOptions"); + // Check "this RelativeTimeFormat object" per introduction of section 1.4. + if (!IsObject(this) || !IsRelativeTimeFormat(this)) { + ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "RelativeTimeFormat", "resolvedOptions", + "RelativeTimeFormat"); + } + + var internals = getRelativeTimeFormatInternals(this); // Steps 4-5. var result = { diff --git a/js/src/jit-test/tests/auto-regress/bug1263558.js b/js/src/jit-test/tests/auto-regress/bug1263558.js index 66aa81d8dc..08443144e7 100644 --- a/js/src/jit-test/tests/auto-regress/bug1263558.js +++ b/js/src/jit-test/tests/auto-regress/bug1263558.js @@ -13,5 +13,5 @@ evalcx(` '); oomTest(() => eval('Array(..."")')); if ('Intl' in this) - Intl.NumberFormat.prototype.format(0); + new Intl.NumberFormat().format(0); `, newGlobal()); diff --git a/js/src/jit-test/tests/basic/bug920484.js b/js/src/jit-test/tests/basic/bug920484.js index 0c9e4c9ac6..d2312b0e16 100644 --- a/js/src/jit-test/tests/basic/bug920484.js +++ b/js/src/jit-test/tests/basic/bug920484.js @@ -4,15 +4,8 @@ load(libdir + "asserts.js"); // any value except "best fit" or "lookup" is okay. Object.prototype.localeMatcher = "invalid matcher option"; -// The Intl API may not be available in the testing environment. Note that |hasOwnProperty("Intl")| -// initializes the Intl API if present, so this if-statement needs to appear after "localeMatcher" -// was added to Object.prototype. +// The Intl API may not be available in the testing environment. if (this.hasOwnProperty("Intl")) { - // Intl prototypes are properly initialized despite changed Object.prototype. - Intl.Collator.prototype.compare("a", "b"); - Intl.NumberFormat.prototype.format(10); - Intl.DateTimeFormat.prototype.format(new Date); - // Intl constructors no longer work properly, because "localeMatcher" defaults to the invalid // value from Object.prototype. Except for Intl.DateTimeFormat, cf. ECMA-402 ToDateTimeOptions. assertThrowsInstanceOf(() => new Intl.Collator(), RangeError); diff --git a/js/src/jit-test/tests/ion/bug913749.js b/js/src/jit-test/tests/ion/bug913749.js index a952caf1c8..fd74753595 100644 --- a/js/src/jit-test/tests/ion/bug913749.js +++ b/js/src/jit-test/tests/ion/bug913749.js @@ -15,6 +15,7 @@ for (var i = 0; i < 3; i++) { x.toString(); assertEq(0, 1); } catch (e) { - assertEq(e.message, "y is undefined"); + assertEq(e.message === "y is undefined" || + e.message === "undefined has no properties", true); } } diff --git a/js/src/jit/InlinableNatives.h b/js/src/jit/InlinableNatives.h index 01b5f78522..561eb4b2db 100644 --- a/js/src/jit/InlinableNatives.h +++ b/js/src/jit/InlinableNatives.h @@ -26,6 +26,12 @@ _(AtomicsXor) \ _(AtomicsIsLockFree) \ \ + _(IntlIsCollator) \ + _(IntlIsDateTimeFormat) \ + _(IntlIsNumberFormat) \ + _(IntlIsPluralRules) \ + _(IntlIsRelativeTimeFormat) \ + \ _(MathAbs) \ _(MathFloor) \ _(MathCeil) \ diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 23e894ffb2..1a98432ffa 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -13,6 +13,11 @@ #include "builtin/SIMD.h" #include "builtin/TestingFunctions.h" #include "builtin/TypedObject.h" +#include "builtin/intl/Collator.h" +#include "builtin/intl/DateTimeFormat.h" +#include "builtin/intl/NumberFormat.h" +#include "builtin/intl/PluralRules.h" +#include "builtin/intl/RelativeTimeFormat.h" #include "jit/BaselineInspector.h" #include "jit/InlinableNatives.h" #include "jit/IonBuilder.h" @@ -104,6 +109,18 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target) case InlinableNative::AtomicsIsLockFree: return inlineAtomicsIsLockFree(callInfo); + // Intl natives. + case InlinableNative::IntlIsCollator: + return inlineHasClass(callInfo, &CollatorObject::class_); + case InlinableNative::IntlIsDateTimeFormat: + return inlineHasClass(callInfo, &DateTimeFormatObject::class_); + case InlinableNative::IntlIsNumberFormat: + return inlineHasClass(callInfo, &NumberFormatObject::class_); + case InlinableNative::IntlIsPluralRules: + return inlineHasClass(callInfo, &PluralRulesObject::class_); + case InlinableNative::IntlIsRelativeTimeFormat: + return inlineHasClass(callInfo, &RelativeTimeFormatObject::class_); + // Math natives. case InlinableNative::MathAbs: return inlineMathAbs(callInfo); diff --git a/js/src/js.msg b/js/src/js.msg index fe69fba60c..1b77cf6a31 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -487,7 +487,6 @@ MSG_DEF(JSMSG_TRACELOGGER_ENABLE_FAIL, 1, JSEXN_ERR, "enabling tracelogger faile MSG_DEF(JSMSG_DATE_NOT_FINITE, 2, JSEXN_RANGEERR, "date value is not finite in {0}.{1}()") MSG_DEF(JSMSG_INTERNAL_INTL_ERROR, 0, JSEXN_ERR, "internal error while computing Intl data") MSG_DEF(JSMSG_INTL_OBJECT_NOT_INITED, 3, JSEXN_TYPEERR, "Intl.{0}.prototype.{1} called on value that's not an object initialized as a {2}") -MSG_DEF(JSMSG_INTL_OBJECT_REINITED, 0, JSEXN_TYPEERR, "can't initialize object twice as an object of an Intl constructor") MSG_DEF(JSMSG_INVALID_CURRENCY_CODE, 1, JSEXN_RANGEERR, "invalid currency code in NumberFormat(): {0}") MSG_DEF(JSMSG_INVALID_DIGITS_VALUE, 1, JSEXN_RANGEERR, "invalid digits value: {0}") MSG_DEF(JSMSG_INVALID_KEYS_TYPE, 0, JSEXN_TYPEERR, "calendar info keys must be an object or undefined") diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 85a001d6a3..542160ce56 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -524,6 +524,7 @@ GlobalObject::initSelfHostingBuiltins(JSContext* cx, Handle globa InitBareBuiltinCtor(cx, global, JSProto_TypedArray) && InitBareBuiltinCtor(cx, global, JSProto_Uint8Array) && InitBareBuiltinCtor(cx, global, JSProto_Int32Array) && + InitBareSymbolCtor(cx, global) && InitBareWeakMapCtor(cx, global) && InitStopIterationClass(cx, global) && DefineFunctions(cx, global, builtins, AsIntrinsic); diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index 2e1b6ce87b..bf9255e85e 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -107,7 +107,9 @@ class GlobalObject : public NativeObject MAP_ITERATOR_PROTO, SET_ITERATOR_PROTO, COLLATOR_PROTO, + NUMBER_FORMAT, NUMBER_FORMAT_PROTO, + DATE_TIME_FORMAT, DATE_TIME_FORMAT_PROTO, PLURAL_RULES_PROTO, RELATIVE_TIME_FORMAT_PROTO, @@ -499,11 +501,23 @@ class GlobalObject : public NativeObject return getOrCreateObject(cx, global, COLLATOR_PROTO, initIntlObject); } + static JSFunction* + getOrCreateNumberFormatConstructor(JSContext* cx, Handle global) { + JSObject* obj = getOrCreateObject(cx, global, NUMBER_FORMAT, initIntlObject); + return obj ? &obj->as() : nullptr; + } + static JSObject* getOrCreateNumberFormatPrototype(JSContext* cx, Handle global) { return getOrCreateObject(cx, global, NUMBER_FORMAT_PROTO, initIntlObject); } + static JSFunction* + getOrCreateDateTimeFormatConstructor(JSContext* cx, Handle global) { + JSObject* obj = getOrCreateObject(cx, global, DATE_TIME_FORMAT, initIntlObject); + return obj ? &obj->as() : nullptr; + } + static JSObject* getOrCreateDateTimeFormatPrototype(JSContext* cx, Handle global) { return getOrCreateObject(cx, global, DATE_TIME_FORMAT_PROTO, initIntlObject); diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 4b22001a4e..98e29b25a4 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -1905,6 +1905,23 @@ intrinsic_RuntimeDefaultLocale(JSContext* cx, unsigned argc, Value* vp) return true; } +using GetOrCreateIntlConstructor = JSFunction* (*)(JSContext*, Handle); + +template +static bool +intrinsic_GetBuiltinIntlConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 0); + + JSFunction* constructor = getOrCreateIntlConstructor(cx, cx->global()); + if (!constructor) + return false; + + args.rval().setObject(*constructor); + return true; +} + static bool intrinsic_ConstructFunction(JSContext* cx, unsigned argc, Value* vp) { @@ -2469,6 +2486,28 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("intl_RelativeTimeFormat_availableLocales", intl_RelativeTimeFormat_availableLocales, 0,0), JS_FN("intl_FormatRelativeTime", intl_FormatRelativeTime, 3,0), + JS_INLINABLE_FN("IsCollator", + intrinsic_IsInstanceOfBuiltin, 1,0, + IntlIsCollator), + JS_INLINABLE_FN("IsDateTimeFormat", + intrinsic_IsInstanceOfBuiltin, 1,0, + IntlIsDateTimeFormat), + JS_INLINABLE_FN("IsNumberFormat", + intrinsic_IsInstanceOfBuiltin, 1,0, + IntlIsNumberFormat), + JS_INLINABLE_FN("IsPluralRules", + intrinsic_IsInstanceOfBuiltin, 1,0, + IntlIsPluralRules), + JS_INLINABLE_FN("IsRelativeTimeFormat", + intrinsic_IsInstanceOfBuiltin, 1,0, + IntlIsRelativeTimeFormat), + JS_FN("GetDateTimeFormatConstructor", + intrinsic_GetBuiltinIntlConstructor, + 0,0), + JS_FN("GetNumberFormatConstructor", + intrinsic_GetBuiltinIntlConstructor, + 0,0), + JS_INLINABLE_FN("IsRegExpObject", intrinsic_IsInstanceOfBuiltin, 1,0, IsRegExpObject),