lib/VM/JSLib/Intl.cpp (1,377 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "JSLibInternal.h" #include "hermes/Platform/Intl/PlatformIntl.h" #ifdef HERMES_ENABLE_INTL #include "hermes/VM/ArrayLike.h" #include "hermes/VM/JSLib/DateUtil.h" #include "hermes/VM/PrimitiveBox.h" #include "hermes/VM/Runtime.h" #include "hermes/VM/StackFrame-inline.h" #include "hermes/VM/StringView.h" namespace hermes { // TODO T65916424: Move this out of the hermes::vm namespace. namespace vm { // This implementation tries to avoid errors in platform code causing // crashes or throwing JS exceptions. However, it can still result in // non-compliant behavior. For example, resolvedOptions methods are // required to have a particular set of properties, but if the // platform result doesn't include them, then they will simply not be // present. This could manifest as a JS error later if one of the // properties is referenced. namespace { CallResult<std::u16string> stringFromJS( Runtime &runtime, PseudoHandle<> value) { CallResult<PseudoHandle<StringPrimitive>> strRes = toString_RJS(runtime, runtime.makeHandle(std::move(value))); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto view = vm::StringPrimitive::createStringView( runtime, runtime.makeHandle(std::move(*strRes))); return std::u16string(view.begin(), view.end()); } CallResult<HermesValue> localesToJS( Runtime &runtime, CallResult<std::vector<std::u16string>> result) { if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<Handle<JSArray>> arrayRes = JSArray::create(runtime, result->size(), result->size()); if (LLVM_UNLIKELY(arrayRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } Handle<JSArray> array = *arrayRes; MutableHandle<> name{runtime}; uint64_t index = 0; for (auto &locale : *result) { // No handles are allocated in this loop, so I don't need a flush, // but I can't use NoAllocScope to verify, because string // allocations cause it to assert. CallResult<HermesValue> nameRes = StringPrimitive::createEfficient(runtime, std::move(locale)); if (LLVM_UNLIKELY(nameRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } name = *nameRes; JSArray::setElementAt(array, runtime, index++, name); } return array.getHermesValue(); } CallResult<HermesValue> optionsToJS( Runtime &runtime, platform_intl::Options result) { CallResult<PseudoHandle<JSObject>> objRes = JSObject::create(runtime); if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } Handle<JSObject> obj = runtime.makeHandle(std::move(*objRes)); MutableHandle<> key{runtime}; MutableHandle<> value{runtime}; GCScopeMarkerRAII marker{runtime}; for (auto &kv : result) { marker.flush(); CallResult<HermesValue> keyRes = StringPrimitive::createEfficient( runtime, createUTF16Ref(kv.first.c_str())); if (LLVM_UNLIKELY(keyRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } key = *keyRes; if (kv.second.isBool()) { value = HermesValue::encodeBoolValue(kv.second.getBool()); } else if (kv.second.isNumber()) { value = HermesValue::encodeNumberValue(kv.second.getNumber()); } else { assert(kv.second.isString() && "Option is neither bool nor string"); CallResult<HermesValue> strRes = StringPrimitive::createEfficient( runtime, std::move(kv.second.getString())); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } value = *strRes; } auto putRes = JSObject::putComputed_RJS(obj, runtime, key, value); if (LLVM_UNLIKELY(putRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } assert(*putRes && "put returned false on a plain object"); } return obj.getHermesValue(); } CallResult<Handle<JSObject>> partToJS( Runtime &runtime, std::unordered_map<std::u16string, std::u16string> result) { CallResult<PseudoHandle<JSObject>> objRes = JSObject::create(runtime); if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } Handle<JSObject> obj = runtime.makeHandle(std::move(*objRes)); MutableHandle<> key{runtime}; MutableHandle<> value{runtime}; GCScopeMarkerRAII marker{runtime}; for (auto &kv : result) { marker.flush(); CallResult<HermesValue> keyRes = StringPrimitive::createEfficient( runtime, createUTF16Ref(kv.first.c_str())); if (LLVM_UNLIKELY(keyRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } key = *keyRes; CallResult<HermesValue> valueRes = StringPrimitive::createEfficient(runtime, std::move(kv.second)); if (LLVM_UNLIKELY(valueRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } value = *valueRes; auto putRes = JSObject::putComputed_RJS(obj, runtime, key, value); if (LLVM_UNLIKELY(putRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } assert(*putRes && "put returned false on a plain object"); } return obj; } CallResult<HermesValue> partsToJS( Runtime &runtime, CallResult<std::vector<std::unordered_map<std::u16string, std::u16string>>> result) { if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<Handle<JSArray>> arrayRes = JSArray::create(runtime, result->size(), result->size()); if (LLVM_UNLIKELY(arrayRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } Handle<JSArray> array = *arrayRes; uint64_t index = 0; GCScopeMarkerRAII marker{runtime}; for (auto &part : *result) { marker.flush(); CallResult<Handle<JSObject>> partRes = partToJS(runtime, std::move(part)); if (LLVM_UNLIKELY(partRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } JSArray::setElementAt(array, runtime, index++, *partRes); } return array.getHermesValue(); } CallResult<std::vector<std::u16string>> normalizeLocales( Runtime &runtime, Handle<> locales) { std::vector<std::u16string> ret; if (locales->isUndefined()) { return ret; } if (locales->isString()) { auto view = StringPrimitive::createStringView( runtime, Handle<StringPrimitive>::vmcast(locales)); ret.emplace_back(view.begin(), view.end()); return ret; } CallResult<HermesValue> objRes = toObject(runtime, locales); if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto localeObj = runtime.makeHandle(vmcast<JSObject>(*objRes)); CallResult<uint64_t> lengthRes = getArrayLikeLength(localeObj, runtime); if (LLVM_UNLIKELY(lengthRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } ret.reserve(*lengthRes); bool isProxy = localeObj->isProxyObject(); if (LLVM_UNLIKELY( createListFromArrayLike( localeObj, runtime, *lengthRes, [&ret, isProxy]( Runtime &runtime, uint64_t index, PseudoHandle<> value) { // When the locales list is a proxy object, gaps are allowed. if (isProxy && value->isUndefined()) return ExecutionStatus::RETURNED; if (!value->isString() && !value->isObject()) { return runtime.raiseTypeError("Incorrect object type"); } CallResult<std::u16string> strRes = stringFromJS(runtime, std::move(value)); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } ret.emplace_back(*strRes); return ExecutionStatus::RETURNED; }) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return std::move(ret); } constexpr int kDateRequired = 1 << 0; constexpr int kTimeRequired = 1 << 1; constexpr int kDateDefault = 1 << 2; constexpr int kTimeDefault = 1 << 3; // To normalize options, we need to know the valid options names, and // the expected type of each. struct OptionData { const char16_t *name; platform_intl::Option::Kind kind; int flags; }; const OptionData kSupportedLocalesOfOptions[] = { {u"localeMatcher", platform_intl::Option::Kind::String, 0}, // Iteration over OptionData expects arrays to be name-nullptr-terminated. {nullptr, platform_intl::Option::Kind::Bool, 0}}; const OptionData kCollatorOptions[] = { {u"usage", platform_intl::Option::Kind::String, 0}, {u"localeMatcher", platform_intl::Option::Kind::String, 0}, {u"numeric", platform_intl::Option::Kind::Bool, 0}, {u"caseFirst", platform_intl::Option::Kind::String, 0}, {u"sensitivity", platform_intl::Option::Kind::String, 0}, {u"ignorePunctuation", platform_intl::Option::Kind::Bool, 0}, {u"collation", platform_intl::Option::Kind::String, 0}, {nullptr, platform_intl::Option::Kind::Bool, 0}, }; const OptionData kDTFOptions[] = { {u"localeMatcher", platform_intl::Option::Kind::String, 0}, {u"calendar", platform_intl::Option::Kind::String, 0}, {u"numberingSystem", platform_intl::Option::Kind::String, 0}, {u"hour12", platform_intl::Option::Kind::Bool, 0}, {u"hourCycle", platform_intl::Option::Kind::String, 0}, {u"timeZone", platform_intl::Option::Kind::String, 0}, {u"formatMatcher", platform_intl::Option::Kind::String, 0}, {u"weekday", platform_intl::Option::Kind::String, 0}, {u"era", platform_intl::Option::Kind::String, 0}, {u"year", platform_intl::Option::Kind::String, kDateRequired | kDateDefault}, {u"month", platform_intl::Option::Kind::String, kDateRequired | kDateDefault}, {u"day", platform_intl::Option::Kind::String, kDateRequired | kDateDefault}, {u"hour", platform_intl::Option::Kind::String, kTimeRequired | kTimeDefault}, {u"minute", platform_intl::Option::Kind::String, kTimeRequired | kTimeDefault}, {u"second", platform_intl::Option::Kind::String, kTimeRequired | kTimeDefault}, {u"timeZoneName", platform_intl::Option::Kind::String, 0}, {nullptr, platform_intl::Option::Kind::Bool, 0}, }; const OptionData kNumberFormatOptions[] = { {u"localeMatcher", platform_intl::Option::Kind::String, 0}, {u"numberingSystem", platform_intl::Option::Kind::String, 0}, {u"style", platform_intl::Option::Kind::String, 0}, {u"currency", platform_intl::Option::Kind::String, 0}, {u"currencyDisplay", platform_intl::Option::Kind::String, 0}, {u"currencySign", platform_intl::Option::Kind::String, 0}, {u"unit", platform_intl::Option::Kind::String, 0}, {u"unitDisplay", platform_intl::Option::Kind::String, 0}, {u"notation", platform_intl::Option::Kind::String, 0}, {u"minimumIntegerDigits", platform_intl::Option::Kind::Number, 0}, {u"minimumFractionDigits", platform_intl::Option::Kind::Number, 0}, {u"maximumFractionDigits", platform_intl::Option::Kind::Number, 0}, {u"minimumSignificantDigits", platform_intl::Option::Kind::Number, 0}, {u"maximumSignificantDigits", platform_intl::Option::Kind::Number, 0}, {u"compactDisplay", platform_intl::Option::Kind::String, 0}, {u"useGrouping", platform_intl::Option::Kind::Bool, 0}, {u"signDisplay", platform_intl::Option::Kind::String, 0}, {nullptr, platform_intl::Option::Kind::Bool, 0}, }; CallResult<platform_intl::Options> normalizeOptions( Runtime &runtime, Handle<> options, const OptionData optionData[]) { platform_intl::Options ret; if (options->isNull()) return runtime.raiseTypeError("Options object can't be null !"); auto optionsObj = Handle<JSObject>::dyn_vmcast(options); if (!optionsObj) { return ret; } MutableHandle<> name{runtime}; MutableHandle<> value{runtime}; MutableHandle<StringPrimitive> strValue{runtime}; GCScopeMarkerRAII marker{runtime}; for (const OptionData *pod = optionData; pod->name; ++pod) { marker.flush(); CallResult<HermesValue> nameRes = StringPrimitive::createEfficient(runtime, createUTF16Ref(pod->name)); if (LLVM_UNLIKELY(nameRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } name = *nameRes; CallResult<PseudoHandle<>> valRes = JSObject::getComputed_RJS(optionsObj, runtime, name); if (LLVM_UNLIKELY(valRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } if ((*valRes)->isUndefined()) { continue; } if (pod->kind == platform_intl::Option::Kind::Bool) { ret.emplace( pod->name, platform_intl::Option(toBoolean(valRes->getHermesValue()))); } else if (pod->kind == platform_intl::Option::Kind::Number) { value = std::move(*valRes); CallResult<HermesValue> numRes = toNumber_RJS(runtime, value); if (LLVM_UNLIKELY(numRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } ret.emplace(pod->name, platform_intl::Option(numRes->getNumber())); } else { assert( pod->kind == platform_intl::Option::Kind::String && "Unknown option kind"); value = std::move(*valRes); CallResult<PseudoHandle<StringPrimitive>> strRes = toString_RJS(runtime, value); if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } strValue = std::move(*strRes); auto view = StringPrimitive::createStringView(runtime, strValue); ret.emplace( pod->name, platform_intl::Option(std::u16string(view.begin(), view.end()))); } } return std::move(ret); } template <typename T> ExecutionStatus checkOptions(Runtime &runtime, const platform_intl::Options &) { return ExecutionStatus::RETURNED; } template <> ExecutionStatus checkOptions<platform_intl::NumberFormat>( Runtime &runtime, const platform_intl::Options &options) { // As best as I can tell, there are only two places (in // SetNumberFormatUnitOptions) where option value handling can throw // a TypeError, and there is no other place where the platform code // needs a TypeError. So, rather than have to deal with multiple // Java error classes, we just handle those two cases right here. auto styleIt = options.find(u"style"); if (styleIt == options.end()) { return ExecutionStatus::RETURNED; } if (styleIt->second.isString() && styleIt->second.getString() == u"currency" && options.count(u"currency") == 0) { return runtime.raiseTypeError( "Style 'currency' requires option 'currency'"); } if (styleIt->second.isString() && styleIt->second.getString() == u"unit" && options.count(u"unit") == 0) { return runtime.raiseTypeError("Style 'unit' requires option 'unit'"); } return ExecutionStatus::RETURNED; } // T here is one of the platform_intl types. This exists to avoid // duplicating code, since all the ctors are basically the same. template <typename T> CallResult<HermesValue> intlServiceConstructor( Runtime &runtime, NativeArgs args, const OptionData optionData[], Handle<JSObject> servicePrototype, unsigned int additionalSlots) { CallResult<std::vector<std::u16string>> localesRes = normalizeLocales(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(localesRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<platform_intl::Options> optionsRes = normalizeOptions(runtime, args.getArgHandle(1), optionData); if (LLVM_UNLIKELY(optionsRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // NumberFormat has a couple extra checks to make. if (LLVM_UNLIKELY( checkOptions<T>(runtime, *optionsRes) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto native = std::make_unique<T>(); if (LLVM_UNLIKELY( native->initialize(runtime, *localesRes, *optionsRes) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto typeHandle = runtime.makeHandle( HermesValue::encodeNumberValue((uint32_t)T::getNativeType())); auto setType = [&](Handle<DecoratedObject> obj) { auto res = JSObject::defineNewOwnProperty( obj, runtime, Predefined::getSymbolID(Predefined::InternalPropertyIntlNativeType), PropertyFlags::defaultNewNamedPropertyFlags(), typeHandle); assert(res != ExecutionStatus::EXCEPTION && "Setting type cannot fail."); }; // If constructor, use the allocated object if (args.isConstructorCall()) { Handle<DecoratedObject> selfHandle = args.vmcastThis<DecoratedObject>(); selfHandle->setDecoration(std::move(native)); setType(selfHandle); return HermesValue::encodeUndefinedValue(); } // Otherwise allocate a new one. auto newHandle = runtime.makeHandle(DecoratedObject::create( runtime, servicePrototype, std::move(native), additionalSlots)); setType(newHandle); return newHandle.getHermesValue(); } // T is a class from platform_intl which has an appropriate // supportedLocalesOf method. template <typename T> CallResult<HermesValue> intlServiceSupportedLocalesOf( Runtime &runtime, NativeArgs args) { CallResult<std::vector<std::u16string>> localesRes = normalizeLocales(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(localesRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<platform_intl::Options> optionsRes = normalizeOptions( runtime, args.getArgHandle(1), kSupportedLocalesOfOptions); if (LLVM_UNLIKELY(optionsRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return localesToJS( runtime, T::supportedLocalesOf(runtime, *localesRes, *optionsRes)); } // This checks that the handle is valid (the caller may have used // dyn_vmcast to make it), and that the native decoration on the // handle corresponds to the expected class. template <typename T> CallResult<T *> verifyDecoration( Runtime &runtime, Handle<DecoratedObject> handle, const char *what) { if (!handle) { return runtime.raiseTypeError( TwineChar16(what) + " called with incompatible 'this'"); } NamedPropertyDescriptor desc; bool exists = JSObject::getOwnNamedDescriptor( handle, runtime, Predefined::getSymbolID(Predefined::InternalPropertyIntlNativeType), desc); if (!exists) { return runtime.raiseTypeError( TwineChar16(what) + " called with incompatible 'this'"); } auto val = JSObject::getNamedSlotValueUnsafe(*handle, runtime, desc); if (val.getNumber(runtime) != (uint32_t)T::getNativeType()) { return runtime.raiseTypeError( TwineChar16(what) + " called with incompatible 'this'"); } return static_cast<T *>(handle->getDecoration()); } } // namespace namespace { // Collator impl stuff. // Collator internal slots. enum class CollatorSlotIndexes { boundCompare, COUNT }; PseudoHandle<NativeFunction> getBoundCompare( DecoratedObject *collator, Runtime &runtime) { return createPseudoHandle(dyn_vmcast<NativeFunction>( DecoratedObject::getAdditionalSlotValue( collator, runtime, static_cast<unsigned int>(CollatorSlotIndexes::boundCompare)) .unboxToHV(runtime))); } void setBoundCompare( DecoratedObject *collator, Runtime &runtime, PseudoHandle<NativeFunction> value) { DecoratedObject::setAdditionalSlotValue( collator, runtime, static_cast<unsigned int>(CollatorSlotIndexes::boundCompare), SmallHermesValue::encodeObjectValue(value.get(), runtime)); } // Collator compare internal slots enum class CollatorCompareSlotIndexes { collator, COUNT }; PseudoHandle<DecoratedObject> getCollator( PseudoHandle<NativeFunction> compare, Runtime &runtime) { return createPseudoHandle(vmcast<DecoratedObject>( NativeFunction::getAdditionalSlotValue( compare.get(), runtime, static_cast<unsigned int>(CollatorCompareSlotIndexes::collator)) .getObject(runtime))); } void setCollator( PseudoHandle<NativeFunction> compare, Runtime &runtime, PseudoHandle<DecoratedObject> value) { NativeFunction::setAdditionalSlotValue( compare.get(), runtime, static_cast<unsigned int>(CollatorCompareSlotIndexes::collator), SmallHermesValue::encodeObjectValue(value.get(), runtime)); } CallResult<PseudoHandle<JSObject>> intlCollatorCreator(Runtime &runtime, Handle<JSObject> parentHandle, void *) { return createPseudoHandle<JSObject>( DecoratedObject::create( runtime, parentHandle, nullptr, static_cast<unsigned int>(CollatorSlotIndexes::COUNT)) .get()); } void defineIntlCollator(Runtime &runtime, Handle<JSObject> intl) { // Create %CollatorPrototype% intrinsic. Properties will be added later. Handle<JSObject> prototype = runtime.makeHandle(JSObject::create(runtime)); runtime.intlCollatorPrototype = prototype.getHermesValue(); // Create %Collator% intrinsic. Handle<NativeConstructor> constructor = defineSystemConstructor( runtime, Predefined::getSymbolID(Predefined::Collator), intlCollatorConstructor, prototype, 0, intlCollatorCreator, CellKind::DecoratedObjectKind); runtime.intlCollator = constructor.getHermesValue(); { DefinePropertyFlags dpf{}; dpf.setValue = 1; defineProperty( runtime, constructor, Predefined::getSymbolID(Predefined::prototype), prototype, dpf); } defineMethod( runtime, constructor, Predefined::getSymbolID(Predefined::supportedLocalesOf), nullptr, intlCollatorSupportedLocalesOf, 1); // Add properties to prototype. defineProperty( runtime, prototype, Predefined::getSymbolID(Predefined::constructor), constructor); { auto dpf = DefinePropertyFlags::getDefaultNewPropertyFlags(); dpf.writable = 0; dpf.enumerable = 0; defineProperty( runtime, prototype, Predefined::getSymbolID(Predefined::SymbolToStringTag), runtime.getPredefinedStringHandle(Predefined::IntlCollator), dpf); } defineAccessor( runtime, prototype, Predefined::getSymbolID(Predefined::compare), nullptr, intlCollatorPrototypeCompareGetter, nullptr, false, true); defineMethod( runtime, prototype, Predefined::getSymbolID(Predefined::resolvedOptions), nullptr, intlCollatorPrototypeResolvedOptions, 0); // Add Collator to Intl defineProperty( runtime, intl, Predefined::getSymbolID(Predefined::Collator), constructor); } } // namespace CallResult<HermesValue> intlCollatorConstructor(void *, Runtime &runtime, NativeArgs args) { return intlServiceConstructor<platform_intl::Collator>( runtime, args, kCollatorOptions, Handle<JSObject>::vmcast(&runtime.intlCollatorPrototype), static_cast<unsigned int>(CollatorSlotIndexes::COUNT)); } CallResult<HermesValue> intlCollatorSupportedLocalesOf(void *, Runtime &runtime, NativeArgs args) { return intlServiceSupportedLocalesOf<platform_intl::Collator>(runtime, args); } CallResult<HermesValue> intlCollatorCompare(void *, Runtime &runtime, NativeArgs args) { auto *nf = vmcast<NativeFunction>( runtime.getCurrentFrame()->getCalleeClosureUnsafe()); PseudoHandle<DecoratedObject> collatorHandle = getCollator(createPseudoHandle(nf), runtime); // Since collatorHandle came out of an internal slot, it's an // assertable failure if it has the wrong type. platform_intl::Collator *collator = static_cast<platform_intl::Collator *>(collatorHandle->getDecoration()); assert(collator && "Intl.Collator platform part is nullptr"); CallResult<std::u16string> xRes = stringFromJS(runtime, createPseudoHandle(args.getArg(0))); if (LLVM_UNLIKELY(xRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<std::u16string> yRes = stringFromJS(runtime, createPseudoHandle(args.getArg(1))); if (LLVM_UNLIKELY(yRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return HermesValue::encodeNumberValue(collator->compare(*xRes, *yRes)); } CallResult<HermesValue> intlCollatorPrototypeCompareGetter(void *, Runtime &runtime, NativeArgs args) { Handle<DecoratedObject> collatorHandle = args.dyncastThis<DecoratedObject>(); CallResult<platform_intl::Collator *> collatorRes = verifyDecoration<platform_intl::Collator>( runtime, collatorHandle, "Intl.Collator.prototype.compare getter"); if (LLVM_UNLIKELY(collatorRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } PseudoHandle<NativeFunction> boundCompare = getBoundCompare(*collatorHandle, runtime); if (boundCompare) { return boundCompare.getHermesValue(); } Handle<NativeFunction> compare = NativeFunction::createWithoutPrototype( runtime, nullptr, intlCollatorCompare, Predefined::getSymbolID(Predefined::emptyString), 2, static_cast<unsigned int>(CollatorCompareSlotIndexes::COUNT)); setCollator(compare, runtime, collatorHandle); setBoundCompare(*collatorHandle, runtime, compare); return compare.getHermesValue(); } CallResult<HermesValue> intlCollatorPrototypeResolvedOptions( void *, Runtime &runtime, NativeArgs args) { Handle<DecoratedObject> collatorHandle = args.dyncastThis<DecoratedObject>(); CallResult<platform_intl::Collator *> collatorRes = verifyDecoration<platform_intl::Collator>( runtime, collatorHandle, "Intl.Collator.prototype.resolvedOptions"); if (LLVM_UNLIKELY(collatorRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return optionsToJS(runtime, (*collatorRes)->resolvedOptions()); } namespace { // DateTimeFormat impl stuff. // DateTimeFormat internal slots. enum class DTFSlotIndexes { boundFormat, COUNT }; PseudoHandle<NativeFunction> getDTFBoundFormat( PseudoHandle<DecoratedObject> dtf, Runtime &runtime) { return createPseudoHandle(dyn_vmcast<NativeFunction>( DecoratedObject::getAdditionalSlotValue( dtf.get(), runtime, static_cast<unsigned int>(DTFSlotIndexes::boundFormat)) .unboxToHV(runtime))); } void setDTFBoundFormat( PseudoHandle<DecoratedObject> dtf, Runtime &runtime, PseudoHandle<NativeFunction> value) { DecoratedObject::setAdditionalSlotValue( dtf.get(), runtime, static_cast<unsigned int>(DTFSlotIndexes::boundFormat), SmallHermesValue::encodeObjectValue(value.get(), runtime)); } // DateTimeFormat format internal slots enum class DTFFormatSlotIndexes { dateTimeFormat, COUNT }; PseudoHandle<DecoratedObject> getDateTimeFormat( PseudoHandle<NativeFunction> format, Runtime &runtime) { return createPseudoHandle(vmcast<DecoratedObject>( NativeFunction::getAdditionalSlotValue( format.get(), runtime, static_cast<unsigned int>(DTFFormatSlotIndexes::dateTimeFormat)) .getObject(runtime))); } void setDateTimeFormat( PseudoHandle<NativeFunction> format, Runtime &runtime, PseudoHandle<DecoratedObject> value) { NativeFunction::setAdditionalSlotValue( format.get(), runtime, static_cast<unsigned int>(DTFFormatSlotIndexes::dateTimeFormat), SmallHermesValue::encodeObjectValue(value.get(), runtime)); } CallResult<PseudoHandle<JSObject>> intlDateTimeFormatCreator( Runtime &runtime, Handle<JSObject> parentHandle, void *) { return createPseudoHandle<JSObject>( DecoratedObject::create( runtime, parentHandle, nullptr, static_cast<unsigned int>(DTFSlotIndexes::COUNT)) .get()); } void defineIntlDateTimeFormat(Runtime &runtime, Handle<JSObject> intl) { // Create %DateTimeFormatPrototype% intrinsic. Properties will be added // later. Handle<JSObject> prototype = runtime.makeHandle(JSObject::create(runtime)); runtime.intlDateTimeFormatPrototype = prototype.getHermesValue(); // Create %DateTimeFormat% intrinsic. Handle<NativeConstructor> constructor = defineSystemConstructor( runtime, Predefined::getSymbolID(Predefined::DateTimeFormat), intlDateTimeFormatConstructor, prototype, 0, intlDateTimeFormatCreator, CellKind::DecoratedObjectKind); runtime.intlDateTimeFormat = constructor.getHermesValue(); { DefinePropertyFlags dpf{}; dpf.setValue = 1; defineProperty( runtime, constructor, Predefined::getSymbolID(Predefined::prototype), prototype, dpf); } defineMethod( runtime, constructor, Predefined::getSymbolID(Predefined::supportedLocalesOf), nullptr, intlDateTimeFormatSupportedLocalesOf, 1); // Add properties to prototype. defineProperty( runtime, prototype, Predefined::getSymbolID(Predefined::constructor), constructor); { auto dpf = DefinePropertyFlags::getDefaultNewPropertyFlags(); dpf.writable = 0; dpf.enumerable = 0; defineProperty( runtime, prototype, Predefined::getSymbolID(Predefined::SymbolToStringTag), runtime.getPredefinedStringHandle(Predefined::IntlDateTimeFormat), dpf); } defineAccessor( runtime, prototype, Predefined::getSymbolID(Predefined::format), nullptr, intlDateTimeFormatPrototypeFormatGetter, nullptr, false, true); defineMethod( runtime, prototype, Predefined::getSymbolID(Predefined::formatToParts), nullptr, intlDateTimeFormatPrototypeFormatToParts, 1); defineMethod( runtime, prototype, Predefined::getSymbolID(Predefined::resolvedOptions), nullptr, intlDateTimeFormatPrototypeResolvedOptions, 0); // Add DateTimeFormat to Intl defineProperty( runtime, intl, Predefined::getSymbolID(Predefined::DateTimeFormat), constructor); } } // namespace CallResult<HermesValue> intlDateTimeFormatConstructor(void *, Runtime &runtime, NativeArgs args) { return intlServiceConstructor<platform_intl::DateTimeFormat>( runtime, args, kDTFOptions, Handle<JSObject>::vmcast(&runtime.intlDateTimeFormatPrototype), static_cast<unsigned int>(DTFSlotIndexes::COUNT)); } CallResult<HermesValue> intlDateTimeFormatSupportedLocalesOf( void *, Runtime &runtime, NativeArgs args) { return intlServiceSupportedLocalesOf<platform_intl::DateTimeFormat>( runtime, args); } namespace { CallResult<double> dateNowValue(Runtime &runtime, NativeArgs args) { // ECMA 402 13.1.5: // 1 and 2 are implicit in the callers. // 3. If date is not provided or is undefined, then if (args.getArg(0).isUndefined()) { // a. Let x be Call(%Date_now%, undefined). // Reusing NativeArgs is kind of a hack, but dateNow doesn't use // it (or the context arg), so this is ok. CallResult<HermesValue> dateRes = dateNow(nullptr, runtime, args); if (LLVM_UNLIKELY(dateRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return dateRes->getNumber(); } // 4.a. Let x be ? ToNumber(date). CallResult<HermesValue> xRes = toNumber_RJS(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(xRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // ECMA 402 13.1.6, shared by the callers to this function: // 1. Let x be TimeClip(x). double x = timeClip(xRes->getNumber()); // 2. If x is NaN, throw a RangeError exception. if (std::isnan(x)) { return runtime.raiseRangeError("Invalid time value"); } return x; } } // namespace CallResult<HermesValue> intlDateTimeFormatFormat(void *, Runtime &runtime, NativeArgs args) { auto *nf = vmcast<NativeFunction>( runtime.getCurrentFrame()->getCalleeClosureUnsafe()); PseudoHandle<DecoratedObject> dateTimeFormatHandle = getDateTimeFormat(createPseudoHandle(nf), runtime); // Since dateTimeFormatHandle came out of an internal slot, it's an // assertable failure if it has the wrong type. platform_intl::DateTimeFormat *dateTimeFormat = static_cast<platform_intl::DateTimeFormat *>( dateTimeFormatHandle->getDecoration()); assert(dateTimeFormat && "Intl.DateTimeFormat platform part is nullptr"); CallResult<double> dateRes = dateNowValue(runtime, args); if (LLVM_UNLIKELY(dateRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<std::u16string> formatRes = dateTimeFormat->format(*dateRes); if (LLVM_UNLIKELY(formatRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return StringPrimitive::createEfficient(runtime, std::move(*formatRes)); } CallResult<HermesValue> intlDateTimeFormatPrototypeFormatGetter( void *, Runtime &runtime, NativeArgs args) { Handle<DecoratedObject> dateTimeFormatHandle = args.dyncastThis<DecoratedObject>(); CallResult<platform_intl::DateTimeFormat *> dateTimeFormatRes = verifyDecoration<platform_intl::DateTimeFormat>( runtime, dateTimeFormatHandle, "Intl.DateTimeFormat.prototype.format getter"); if (LLVM_UNLIKELY(dateTimeFormatRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } PseudoHandle<NativeFunction> boundFormat = getDTFBoundFormat(dateTimeFormatHandle, runtime); if (boundFormat) { return boundFormat.getHermesValue(); } Handle<NativeFunction> format = NativeFunction::createWithoutPrototype( runtime, nullptr, intlDateTimeFormatFormat, Predefined::getSymbolID(Predefined::emptyString), 1, static_cast<unsigned int>(DTFFormatSlotIndexes::COUNT)); setDateTimeFormat(format, runtime, dateTimeFormatHandle); setDTFBoundFormat(dateTimeFormatHandle, runtime, format); return format.getHermesValue(); } CallResult<HermesValue> intlDateTimeFormatPrototypeFormatToParts( void *, Runtime &runtime, NativeArgs args) { Handle<DecoratedObject> dateTimeFormatHandle = args.dyncastThis<DecoratedObject>(); CallResult<platform_intl::DateTimeFormat *> dateTimeFormatRes = verifyDecoration<platform_intl::DateTimeFormat>( runtime, dateTimeFormatHandle, "Intl.DateTimeFormat.prototype.formatToParts"); if (LLVM_UNLIKELY(dateTimeFormatRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<double> dateRes = dateNowValue(runtime, args); if (LLVM_UNLIKELY(dateRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return partsToJS(runtime, (*dateTimeFormatRes)->formatToParts(*dateRes)); } CallResult<HermesValue> intlDateTimeFormatPrototypeResolvedOptions( void *, Runtime &runtime, NativeArgs args) { Handle<DecoratedObject> dateTimeFormatHandle = args.dyncastThis<DecoratedObject>(); CallResult<platform_intl::DateTimeFormat *> dateTimeFormatRes = verifyDecoration<platform_intl::DateTimeFormat>( runtime, dateTimeFormatHandle, "Intl.DateTimeFormat.prototype.resolvedOptions"); if (LLVM_UNLIKELY(dateTimeFormatRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return optionsToJS(runtime, (*dateTimeFormatRes)->resolvedOptions()); } namespace { // NumberFormat impl stuff. // NumberFormat internal slots. enum class NFSlotIndexes { boundFormat, COUNT }; PseudoHandle<NativeFunction> getNFBoundFormat( PseudoHandle<DecoratedObject> nf, Runtime &runtime) { return createPseudoHandle(dyn_vmcast<NativeFunction>( DecoratedObject::getAdditionalSlotValue( nf.get(), runtime, static_cast<unsigned int>(NFSlotIndexes::boundFormat)) .unboxToHV(runtime))); } void setNFBoundFormat( PseudoHandle<DecoratedObject> nf, Runtime &runtime, PseudoHandle<NativeFunction> value) { DecoratedObject::setAdditionalSlotValue( nf.get(), runtime, static_cast<unsigned int>(NFSlotIndexes::boundFormat), SmallHermesValue::encodeObjectValue(value.get(), runtime)); } // NumberFormat format internal slots enum class NFFormatSlotIndexes { numberFormat, COUNT }; PseudoHandle<DecoratedObject> getNumberFormat( PseudoHandle<NativeFunction> format, Runtime &runtime) { return createPseudoHandle(vmcast<DecoratedObject>( NativeFunction::getAdditionalSlotValue( format.get(), runtime, static_cast<unsigned int>(NFFormatSlotIndexes::numberFormat)) .getObject(runtime))); } void setNumberFormat( PseudoHandle<NativeFunction> format, Runtime &runtime, PseudoHandle<DecoratedObject> value) { NativeFunction::setAdditionalSlotValue( format.get(), runtime, static_cast<unsigned int>(NFFormatSlotIndexes::numberFormat), SmallHermesValue::encodeObjectValue(value.get(), runtime)); } CallResult<PseudoHandle<JSObject>> intlNumberFormatCreator( Runtime &runtime, Handle<JSObject> parentHandle, void *) { return createPseudoHandle<JSObject>( DecoratedObject::create( runtime, parentHandle, nullptr, static_cast<unsigned int>(NFSlotIndexes::COUNT)) .get()); } void defineIntlNumberFormat(Runtime &runtime, Handle<JSObject> intl) { // Create %NumberFormatPrototype% intrinsic. Properties will be added later. Handle<JSObject> prototype = runtime.makeHandle(JSObject::create(runtime)); runtime.intlNumberFormatPrototype = prototype.getHermesValue(); // Create %NumberFormat% intrinsic. Handle<NativeConstructor> constructor = defineSystemConstructor( runtime, Predefined::getSymbolID(Predefined::NumberFormat), intlNumberFormatConstructor, prototype, 0, intlNumberFormatCreator, CellKind::DecoratedObjectKind); runtime.intlNumberFormat = constructor.getHermesValue(); { DefinePropertyFlags dpf{}; dpf.setValue = 1; defineProperty( runtime, constructor, Predefined::getSymbolID(Predefined::prototype), prototype, dpf); } defineMethod( runtime, constructor, Predefined::getSymbolID(Predefined::supportedLocalesOf), nullptr, intlNumberFormatSupportedLocalesOf, 1); // Add properties to prototype. defineProperty( runtime, prototype, Predefined::getSymbolID(Predefined::constructor), constructor); { auto dpf = DefinePropertyFlags::getDefaultNewPropertyFlags(); dpf.writable = 0; dpf.enumerable = 0; defineProperty( runtime, prototype, Predefined::getSymbolID(Predefined::SymbolToStringTag), runtime.getPredefinedStringHandle(Predefined::IntlNumberFormat), dpf); } defineAccessor( runtime, prototype, Predefined::getSymbolID(Predefined::format), nullptr, intlNumberFormatPrototypeFormatGetter, nullptr, false, true); defineMethod( runtime, prototype, Predefined::getSymbolID(Predefined::formatToParts), nullptr, intlNumberFormatPrototypeFormatToParts, 1); defineMethod( runtime, prototype, Predefined::getSymbolID(Predefined::resolvedOptions), nullptr, intlNumberFormatPrototypeResolvedOptions, 0); // Add NumberFormat to Intl defineProperty( runtime, intl, Predefined::getSymbolID(Predefined::NumberFormat), constructor); } } // namespace CallResult<HermesValue> intlNumberFormatConstructor(void *, Runtime &runtime, NativeArgs args) { return intlServiceConstructor<platform_intl::NumberFormat>( runtime, args, kNumberFormatOptions, Handle<JSObject>::vmcast(&runtime.intlNumberFormatPrototype), static_cast<unsigned int>(NFSlotIndexes::COUNT)); } CallResult<HermesValue> intlNumberFormatSupportedLocalesOf(void *, Runtime &runtime, NativeArgs args) { return intlServiceSupportedLocalesOf<platform_intl::NumberFormat>( runtime, args); } CallResult<HermesValue> intlNumberFormatFormat(void *, Runtime &runtime, NativeArgs args) { auto *nf = vmcast<NativeFunction>( runtime.getCurrentFrame()->getCalleeClosureUnsafe()); PseudoHandle<DecoratedObject> numberFormatHandle = getNumberFormat(createPseudoHandle(nf), runtime); // Since numberFormatHandle came out of an internal slot, it's an // assertable failure if it has the wrong type. platform_intl::NumberFormat *numberFormat = static_cast<platform_intl::NumberFormat *>( numberFormatHandle->getDecoration()); assert(numberFormat && "Intl.NumberFormat platform part is nullptr"); CallResult<HermesValue> xRes = toNumeric_RJS(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(xRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<std::u16string> formatRes = numberFormat->format(xRes->getNumber()); if (LLVM_UNLIKELY(formatRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return StringPrimitive::createEfficient(runtime, std::move(*formatRes)); } CallResult<HermesValue> intlNumberFormatPrototypeFormatGetter( void *, Runtime &runtime, NativeArgs args) { Handle<DecoratedObject> numberFormatHandle = args.dyncastThis<DecoratedObject>(); CallResult<platform_intl::NumberFormat *> numberFormatRes = verifyDecoration<platform_intl::NumberFormat>( runtime, numberFormatHandle, "Intl.NumberFormat.prototype.format getter"); if (LLVM_UNLIKELY(numberFormatRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } PseudoHandle<NativeFunction> boundFormat = getNFBoundFormat(numberFormatHandle, runtime); if (boundFormat) { return boundFormat.getHermesValue(); } Handle<NativeFunction> format = NativeFunction::createWithoutPrototype( runtime, nullptr, intlNumberFormatFormat, Predefined::getSymbolID(Predefined::emptyString), 1, static_cast<unsigned int>(NFFormatSlotIndexes::COUNT)); setNumberFormat(format, runtime, numberFormatHandle); setNFBoundFormat(numberFormatHandle, runtime, format); return format.getHermesValue(); } CallResult<HermesValue> intlNumberFormatPrototypeFormatToParts( void *, Runtime &runtime, NativeArgs args) { Handle<DecoratedObject> numberFormatHandle = args.dyncastThis<DecoratedObject>(); CallResult<platform_intl::NumberFormat *> numberFormatRes = verifyDecoration<platform_intl::NumberFormat>( runtime, numberFormatHandle, "Intl.NumberFormat.prototype.formatToParts"); if (LLVM_UNLIKELY(numberFormatRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<HermesValue> xRes = toNumeric_RJS(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(xRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return partsToJS( runtime, (*numberFormatRes)->formatToParts(xRes->getNumber())); } CallResult<HermesValue> intlNumberFormatPrototypeResolvedOptions( void *, Runtime &runtime, NativeArgs args) { Handle<DecoratedObject> numberFormatHandle = args.dyncastThis<DecoratedObject>(); CallResult<platform_intl::NumberFormat *> numberFormatRes = verifyDecoration<platform_intl::NumberFormat>( runtime, numberFormatHandle, "Intl.NumberFormat.prototype.resolvedOptions"); if (LLVM_UNLIKELY(numberFormatRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return optionsToJS(runtime, (*numberFormatRes)->resolvedOptions()); } // ECMA 402 supersedes some definitionss in ECMA 262 namespace { constexpr int kDTODate = 1 << 0; constexpr int kDTOTime = 1 << 1; void toDateTimeOptions(platform_intl::Options &options, int dtoFlags) { // The behavior of format with respect to default options is to // check if any of a set of date and time keys are present in // options. If none are, then a default set of date keys is used. // // So, as long as this code sets any of the date or time keys, then // the default options behavior of format will not apply. bool needDefaults = true; for (const OptionData *pod = kDTFOptions; pod->name; ++pod) { if (dtoFlags & kDTODate && pod->flags & kDateRequired && options.find(pod->name) != options.end()) { needDefaults = false; break; } if (needDefaults) { if (dtoFlags & kDTOTime && pod->flags & kTimeRequired && options.find(pod->name) != options.end()) { needDefaults = false; break; } } } if (!needDefaults) { return; } for (const OptionData *pod = kDTFOptions; pod->name; ++pod) { if ((dtoFlags & kDTODate && pod->flags & kDateDefault) || (dtoFlags & kDTOTime && pod->flags & kTimeDefault)) { options.emplace(pod->name, std::u16string(u"numeric")); } } } CallResult<HermesValue> intlDatePrototypeToSomeLocaleString( Runtime &runtime, const NativeArgs &args, JSDate *date, int dtoFlags) { double x = date->getPrimitiveValue(); std::u16string str; if (std::isnan(x)) { str = u"Invalid Date"; } else { CallResult<std::vector<std::u16string>> localesRes = normalizeLocales(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(localesRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<platform_intl::Options> optionsRes = normalizeOptions(runtime, args.getArgHandle(1), kDTFOptions); if (LLVM_UNLIKELY(optionsRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } toDateTimeOptions(*optionsRes, dtoFlags); platform_intl::DateTimeFormat dtf; if (LLVM_UNLIKELY( dtf.initialize(runtime, *localesRes, *optionsRes) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // Naively, the spec requires TimeClip to be called here, but // since in this code path, x comes from a Date slot which has // already been clipped, there's no reason to do it again. CallResult<std::u16string> formatRes = dtf.format(x); if (LLVM_UNLIKELY(formatRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } str = std::move(*formatRes); } return StringPrimitive::createEfficient(runtime, std::move(str)); } } // namespace CallResult<HermesValue> intlDatePrototypeToLocaleDateString(void *, Runtime &runtime, NativeArgs args) { JSDate *date = dyn_vmcast<JSDate>(args.getThisArg()); if (!date) { return runtime.raiseTypeError( "Date.prototype.toLocaleString() called on non-Date object"); } return intlDatePrototypeToSomeLocaleString(runtime, args, date, kDTODate); } CallResult<HermesValue> intlDatePrototypeToLocaleString(void *, Runtime &runtime, NativeArgs args) { JSDate *date = dyn_vmcast<JSDate>(args.getThisArg()); if (!date) { return runtime.raiseTypeError( "Date.prototype.toLocaleString() called on non-Date object"); } return intlDatePrototypeToSomeLocaleString( runtime, args, date, kDTODate | kDTOTime); } CallResult<HermesValue> intlDatePrototypeToLocaleTimeString(void *, Runtime &runtime, NativeArgs args) { JSDate *date = dyn_vmcast<JSDate>(args.getThisArg()); if (!date) { return runtime.raiseTypeError( "Date.prototype.toLocaleString() called on non-Date object"); } return intlDatePrototypeToSomeLocaleString(runtime, args, date, kDTOTime); } CallResult<HermesValue> intlNumberPrototypeToLocaleString(void *, Runtime &runtime, NativeArgs args) { double x; if (args.getThisArg().isNumber()) { x = args.getThisArg().getNumber(); } else { auto numPtr = Handle<JSNumber>::dyn_vmcast(args.getThisHandle()); if (LLVM_UNLIKELY(!numPtr)) { return runtime.raiseTypeError( "Number.prototype.toLocaleString() can only be used on numbers"); } x = numPtr->getPrimitiveNumber(); } CallResult<std::vector<std::u16string>> localesRes = normalizeLocales(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(localesRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<platform_intl::Options> optionsRes = normalizeOptions(runtime, args.getArgHandle(1), kNumberFormatOptions); if (LLVM_UNLIKELY(optionsRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } platform_intl::NumberFormat nf; if (LLVM_UNLIKELY( nf.initialize(runtime, *localesRes, *optionsRes) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<std::u16string> formatRes = nf.format(x); if (LLVM_UNLIKELY(formatRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return StringPrimitive::createEfficient(runtime, std::move(*formatRes)); } CallResult<HermesValue> intlStringPrototypeLocaleCompare(void *, Runtime &runtime, NativeArgs args) { if (args.getThisArg().isUndefined() || args.getThisArg().isNull()) { return runtime.raiseTypeError( "String.prototype.localeCompare called on null or undefined"); } CallResult<std::u16string> thisRes = stringFromJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(thisRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<std::u16string> thatRes = stringFromJS(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(thatRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<std::vector<std::u16string>> localesRes = normalizeLocales(runtime, args.getArgHandle(1)); if (LLVM_UNLIKELY(localesRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<platform_intl::Options> optionsRes = normalizeOptions(runtime, args.getArgHandle(2), kCollatorOptions); if (LLVM_UNLIKELY(optionsRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } platform_intl::Collator collator; if (LLVM_UNLIKELY( collator.initialize(runtime, *localesRes, *optionsRes) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return HermesValue::encodeNumberValue(collator.compare(*thisRes, *thatRes)); } CallResult<HermesValue> intlStringPrototypeToLocaleLowerCase( void *, Runtime &runtime, NativeArgs args) { if (args.getThisArg().isUndefined() || args.getThisArg().isNull()) { return runtime.raiseTypeError( "String.prototype.localeCompare called on null or undefined"); } CallResult<std::u16string> thisRes = stringFromJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(thisRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<std::vector<std::u16string>> localesRes = normalizeLocales(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(localesRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<std::u16string> lowerRes = platform_intl::toLocaleLowerCase(runtime, *localesRes, *thisRes); if (LLVM_UNLIKELY(lowerRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return StringPrimitive::createEfficient(runtime, std::move(*lowerRes)); } CallResult<HermesValue> intlStringPrototypeToLocaleUpperCase( void *, Runtime &runtime, NativeArgs args) { if (args.getThisArg().isUndefined() || args.getThisArg().isNull()) { return runtime.raiseTypeError( "String.prototype.localeCompare called on null or undefined"); } CallResult<std::u16string> thisRes = stringFromJS(runtime, args.getThisHandle()); if (LLVM_UNLIKELY(thisRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<std::vector<std::u16string>> localesRes = normalizeLocales(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(localesRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } CallResult<std::u16string> upperRes = platform_intl::toLocaleUpperCase(runtime, *localesRes, *thisRes); if (LLVM_UNLIKELY(upperRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return StringPrimitive::createEfficient(runtime, std::move(*upperRes)); } } // namespace vm namespace intl { namespace { // Intl impl stuff. vm::CallResult<vm::HermesValue> intlGetCanonicalLocales(void *, vm::Runtime &runtime, vm::NativeArgs args) { vm::CallResult<std::vector<std::u16string>> localesRes = vm::normalizeLocales(runtime, args.getArgHandle(0)); if (LLVM_UNLIKELY(localesRes == vm::ExecutionStatus::EXCEPTION)) { return vm::ExecutionStatus::EXCEPTION; } return vm::localesToJS( runtime, platform_intl::getCanonicalLocales(runtime, *localesRes)); } } // namespace vm::Handle<vm::JSObject> createIntlObject(vm::Runtime &runtime) { vm::Handle<vm::JSObject> intl = runtime.makeHandle(vm::JSObject::create(runtime)); defineMethod( runtime, intl, vm::Predefined::getSymbolID(vm::Predefined::getCanonicalLocales), nullptr, intlGetCanonicalLocales, 1); { auto dpf = vm::DefinePropertyFlags::getDefaultNewPropertyFlags(); dpf.writable = 0; dpf.enumerable = 0; defineProperty( runtime, intl, vm::Predefined::getSymbolID(vm::Predefined::SymbolToStringTag), runtime.getPredefinedStringHandle(vm::Predefined::Intl), dpf); } vm::defineIntlCollator(runtime, intl); vm::defineIntlDateTimeFormat(runtime, intl); vm::defineIntlNumberFormat(runtime, intl); return intl; } } // namespace intl } // namespace hermes #endif