Issue #2046 - Move SharedIntlData into its own builtin/intl/SharedIntlData.* files so the world doesn't have to import all shared Intl functionality.

This commit is contained in:
Martok 2023-02-15 22:58:28 +01:00 committed by roytam1
commit ce98604d2e
7 changed files with 471 additions and 410 deletions

View file

@ -27,7 +27,7 @@
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/NumberFormat.h"
#include "builtin/intl/ScopedICUObject.h"
#include "builtin/IntlTimeZoneData.h"
#include "builtin/intl/SharedIntlData.h"
#include "ds/Sort.h"
#include "vm/DateTime.h"
#include "vm/GlobalObject.h"
@ -51,6 +51,7 @@ using js::intl::CallICU;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::SharedIntlData;
using js::intl::StringsAreEqual;
/******************** DateTimeFormat ********************/
@ -363,280 +364,6 @@ js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp)
return true;
}
template<typename Char>
static constexpr Char
ToUpperASCII(Char c)
{
return ('a' <= c && c <= 'z')
? (c & ~0x20)
: c;
}
static_assert(ToUpperASCII('a') == 'A', "verifying 'a' uppercases correctly");
static_assert(ToUpperASCII('m') == 'M', "verifying 'm' uppercases correctly");
static_assert(ToUpperASCII('z') == 'Z', "verifying 'z' uppercases correctly");
static_assert(ToUpperASCII(u'a') == u'A', "verifying u'a' uppercases correctly");
static_assert(ToUpperASCII(u'k') == u'K', "verifying u'k' uppercases correctly");
static_assert(ToUpperASCII(u'z') == u'Z', "verifying u'z' uppercases correctly");
template<typename Char1, typename Char2>
static bool
EqualCharsIgnoreCaseASCII(const Char1* s1, const Char2* s2, size_t len)
{
for (const Char1* s1end = s1 + len; s1 < s1end; s1++, s2++) {
if (ToUpperASCII(*s1) != ToUpperASCII(*s2))
return false;
}
return true;
}
template<typename Char>
static js::HashNumber
HashStringIgnoreCaseASCII(const Char* s, size_t length)
{
uint32_t hash = 0;
for (size_t i = 0; i < length; i++)
hash = mozilla::AddToHash(hash, ToUpperASCII(s[i]));
return hash;
}
js::SharedIntlData::TimeZoneHasher::Lookup::Lookup(JSFlatString* timeZone)
: isLatin1(timeZone->hasLatin1Chars()), length(timeZone->length())
{
if (isLatin1) {
latin1Chars = timeZone->latin1Chars(nogc);
hash = HashStringIgnoreCaseASCII(latin1Chars, length);
} else {
twoByteChars = timeZone->twoByteChars(nogc);
hash = HashStringIgnoreCaseASCII(twoByteChars, length);
}
}
bool
js::SharedIntlData::TimeZoneHasher::match(TimeZoneName key, const Lookup& lookup)
{
if (key->length() != lookup.length)
return false;
// Compare time zone names ignoring ASCII case differences.
if (key->hasLatin1Chars()) {
const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
if (lookup.isLatin1)
return EqualCharsIgnoreCaseASCII(keyChars, lookup.latin1Chars, lookup.length);
return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length);
}
const char16_t* keyChars = key->twoByteChars(lookup.nogc);
if (lookup.isLatin1)
return EqualCharsIgnoreCaseASCII(lookup.latin1Chars, keyChars, lookup.length);
return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length);
}
static bool
IsLegacyICUTimeZone(const char* timeZone)
{
for (const auto& legacyTimeZone : js::timezone::legacyICUTimeZones) {
if (StringsAreEqual(timeZone, legacyTimeZone))
return true;
}
return false;
}
bool
js::SharedIntlData::ensureTimeZones(JSContext* cx)
{
if (timeZoneDataInitialized)
return true;
// If initTimeZones() was called previously, but didn't complete due to
// OOM, clear all sets/maps and start from scratch.
if (availableTimeZones.initialized())
availableTimeZones.finish();
if (!availableTimeZones.init()) {
ReportOutOfMemory(cx);
return false;
}
UErrorCode status = U_ZERO_ERROR;
UEnumeration* values = ucal_openTimeZones(&status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UEnumeration, uenum_close> toClose(values);
RootedAtom timeZone(cx);
while (true) {
int32_t size;
const char* rawTimeZone = uenum_next(values, &size, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
if (rawTimeZone == nullptr)
break;
// Skip legacy ICU time zone names.
if (IsLegacyICUTimeZone(rawTimeZone))
continue;
MOZ_ASSERT(size >= 0);
timeZone = Atomize(cx, rawTimeZone, size_t(size));
if (!timeZone)
return false;
TimeZoneHasher::Lookup lookup(timeZone);
TimeZoneSet::AddPtr p = availableTimeZones.lookupForAdd(lookup);
// ICU shouldn't report any duplicate time zone names, but if it does,
// just ignore the duplicate name.
if (!p && !availableTimeZones.add(p, timeZone)) {
ReportOutOfMemory(cx);
return false;
}
}
if (ianaZonesTreatedAsLinksByICU.initialized())
ianaZonesTreatedAsLinksByICU.finish();
if (!ianaZonesTreatedAsLinksByICU.init()) {
ReportOutOfMemory(cx);
return false;
}
for (const char* rawTimeZone : timezone::ianaZonesTreatedAsLinksByICU) {
MOZ_ASSERT(rawTimeZone != nullptr);
timeZone = Atomize(cx, rawTimeZone, strlen(rawTimeZone));
if (!timeZone)
return false;
TimeZoneHasher::Lookup lookup(timeZone);
TimeZoneSet::AddPtr p = ianaZonesTreatedAsLinksByICU.lookupForAdd(lookup);
MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaZonesTreatedAsLinksByICU");
if (!ianaZonesTreatedAsLinksByICU.add(p, timeZone)) {
ReportOutOfMemory(cx);
return false;
}
}
if (ianaLinksCanonicalizedDifferentlyByICU.initialized())
ianaLinksCanonicalizedDifferentlyByICU.finish();
if (!ianaLinksCanonicalizedDifferentlyByICU.init()) {
ReportOutOfMemory(cx);
return false;
}
RootedAtom linkName(cx);
RootedAtom& target = timeZone;
for (const auto& linkAndTarget : timezone::ianaLinksCanonicalizedDifferentlyByICU) {
const char* rawLinkName = linkAndTarget.link;
const char* rawTarget = linkAndTarget.target;
MOZ_ASSERT(rawLinkName != nullptr);
linkName = Atomize(cx, rawLinkName, strlen(rawLinkName));
if (!linkName)
return false;
MOZ_ASSERT(rawTarget != nullptr);
target = Atomize(cx, rawTarget, strlen(rawTarget));
if (!target)
return false;
TimeZoneHasher::Lookup lookup(linkName);
TimeZoneMap::AddPtr p = ianaLinksCanonicalizedDifferentlyByICU.lookupForAdd(lookup);
MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaLinksCanonicalizedDifferentlyByICU");
if (!ianaLinksCanonicalizedDifferentlyByICU.add(p, linkName, target)) {
ReportOutOfMemory(cx);
return false;
}
}
MOZ_ASSERT(!timeZoneDataInitialized, "ensureTimeZones is neither reentrant nor thread-safe");
timeZoneDataInitialized = true;
return true;
}
bool
js::SharedIntlData::validateTimeZoneName(JSContext* cx, HandleString timeZone,
MutableHandleString result)
{
if (!ensureTimeZones(cx))
return false;
Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx));
if (!timeZoneFlat)
return false;
TimeZoneHasher::Lookup lookup(timeZoneFlat);
if (TimeZoneSet::Ptr p = availableTimeZones.lookup(lookup))
result.set(*p);
return true;
}
bool
js::SharedIntlData::tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, HandleString timeZone,
MutableHandleString result)
{
if (!ensureTimeZones(cx))
return false;
Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx));
if (!timeZoneFlat)
return false;
TimeZoneHasher::Lookup lookup(timeZoneFlat);
MOZ_ASSERT(availableTimeZones.has(lookup), "Invalid time zone name");
if (TimeZoneMap::Ptr p = ianaLinksCanonicalizedDifferentlyByICU.lookup(lookup)) {
// The effectively supported time zones aren't known at compile time,
// when
// 1. SpiderMonkey was compiled with "--with-system-icu".
// 2. ICU's dynamic time zone data loading feature was used.
// (ICU supports loading time zone files at runtime through the
// ICU_TIMEZONE_FILES_DIR environment variable.)
// Ensure ICU supports the new target zone before applying the update.
TimeZoneName targetTimeZone = p->value();
TimeZoneHasher::Lookup targetLookup(targetTimeZone);
if (availableTimeZones.has(targetLookup))
result.set(targetTimeZone);
} else if (TimeZoneSet::Ptr p = ianaZonesTreatedAsLinksByICU.lookup(lookup)) {
result.set(*p);
}
return true;
}
void
js::SharedIntlData::destroyInstance()
{
availableTimeZones.finish();
ianaZonesTreatedAsLinksByICU.finish();
ianaLinksCanonicalizedDifferentlyByICU.finish();
}
void
js::SharedIntlData::trace(JSTracer* trc)
{
// Atoms are always tenured.
if (!trc->runtime()->isHeapMinorCollecting()) {
availableTimeZones.trace(trc);
ianaZonesTreatedAsLinksByICU.trace(trc);
ianaLinksCanonicalizedDifferentlyByICU.trace(trc);
}
}
size_t
js::SharedIntlData::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
{
return availableTimeZones.sizeOfExcludingThis(mallocSizeOf) +
ianaZonesTreatedAsLinksByICU.sizeOfExcludingThis(mallocSizeOf) +
ianaLinksCanonicalizedDifferentlyByICU.sizeOfExcludingThis(mallocSizeOf);
}
bool
js::intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp)
{

View file

@ -31,140 +31,6 @@ namespace js {
extern JSObject*
InitIntlClass(JSContext* cx, HandleObject obj);
/**
* Stores Intl data which can be shared across compartments (but not contexts).
*
* Used for data which is expensive when computed repeatedly or is not
* available through ICU.
*/
class SharedIntlData
{
/**
* Information tracking the set of the supported time zone names, derived
* from the IANA time zone database <https://www.iana.org/time-zones>.
*
* There are two kinds of IANA time zone names: Zone and Link (denoted as
* such in database source files). Zone names are the canonical, preferred
* name for a time zone, e.g. Asia/Kolkata. Link names simply refer to
* target Zone names for their meaning, e.g. Asia/Calcutta targets
* Asia/Kolkata. That a name is a Link doesn't *necessarily* reflect a
* sense of deprecation: some Link names also exist partly for convenience,
* e.g. UTC and GMT as Link names targeting the Zone name Etc/UTC.
*
* Two data sources determine the time zone names we support: those ICU
* supports and IANA's zone information.
*
* Unfortunately the names ICU and IANA support, and their Link
* relationships from name to target, aren't identical, so we can't simply
* implicitly trust ICU's name handling. We must perform various
* preprocessing of user-provided zone names and post-processing of
* ICU-provided zone names to implement ECMA-402's IANA-consistent behavior.
*
* Also see <https://ssl.icu-project.org/trac/ticket/12044> and
* <http://unicode.org/cldr/trac/ticket/9892>.
*/
using TimeZoneName = JSAtom*;
struct TimeZoneHasher
{
struct Lookup
{
union {
const JS::Latin1Char* latin1Chars;
const char16_t* twoByteChars;
};
bool isLatin1;
size_t length;
JS::AutoCheckCannotGC nogc;
HashNumber hash;
explicit Lookup(JSFlatString* timeZone);
};
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
static bool match(TimeZoneName key, const Lookup& lookup);
};
using TimeZoneSet = js::GCHashSet<TimeZoneName,
TimeZoneHasher,
js::SystemAllocPolicy>;
using TimeZoneMap = js::GCHashMap<TimeZoneName,
TimeZoneName,
TimeZoneHasher,
js::SystemAllocPolicy>;
/**
* As a threshold matter, available time zones are those time zones ICU
* supports, via ucal_openTimeZones. But ICU supports additional non-IANA
* time zones described in intl/icu/source/tools/tzcode/icuzones (listed in
* IntlTimeZoneData.cpp's |legacyICUTimeZones|) for its own backwards
* compatibility purposes. This set consists of ICU's supported time zones,
* minus all backwards-compatibility time zones.
*/
TimeZoneSet availableTimeZones;
/**
* IANA treats some time zone names as Zones, that ICU instead treats as
* Links. For example, IANA considers "America/Indiana/Indianapolis" to be
* a Zone and "America/Fort_Wayne" a Link that targets it, but ICU
* considers the former a Link that targets "America/Indianapolis" (which
* IANA treats as a Link).
*
* ECMA-402 requires that we respect IANA data, so if we're asked to
* canonicalize a time zone name in this set, we must *not* return ICU's
* canonicalization.
*/
TimeZoneSet ianaZonesTreatedAsLinksByICU;
/**
* IANA treats some time zone names as Links to one target, that ICU
* instead treats as either Zones, or Links to different targets. An
* example of the former is "Asia/Calcutta, which IANA assigns the target
* "Asia/Kolkata" but ICU considers its own Zone. An example of the latter
* is "America/Virgin", which IANA assigns the target
* "America/Port_of_Spain" but ICU assigns the target "America/St_Thomas".
*
* ECMA-402 requires that we respect IANA data, so if we're asked to
* canonicalize a time zone name that's a key in this map, we *must* return
* the corresponding value and *must not* return ICU's canonicalization.
*/
TimeZoneMap ianaLinksCanonicalizedDifferentlyByICU;
bool timeZoneDataInitialized = false;
/**
* Precomputes the available time zone names, because it's too expensive to
* call ucal_openTimeZones() repeatedly.
*/
bool ensureTimeZones(JSContext* cx);
public:
/**
* Returns the validated time zone name in |result|. If the input time zone
* isn't a valid IANA time zone name, |result| remains unchanged.
*/
bool validateTimeZoneName(JSContext* cx, JS::HandleString timeZone,
JS::MutableHandleString result);
/**
* Returns the canonical time zone name in |result|. If no canonical name
* was found, |result| remains unchanged.
*
* This method only handles time zones which are canonicalized differently
* by ICU when compared to IANA.
*/
bool tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, JS::HandleString timeZone,
JS::MutableHandleString result);
void destroyInstance();
void trace(JSTracer* trc);
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
};
/*
* The following functions are for use by self-hosted code.
*/

View file

@ -16,6 +16,7 @@
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/ScopedICUObject.h"
#include "builtin/intl/SharedIntlData.h"
#include "js/TypeDecls.h"
#include "vm/GlobalObject.h"
#include "vm/Runtime.h"
@ -27,6 +28,7 @@ using namespace js;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::ReportInternalError;
using js::intl::SharedIntlData;
using js::intl::StringsAreEqual;
/******************** Collator ********************/

View file

@ -0,0 +1,300 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Runtime-wide Intl data shared across compartments. */
#include "builtin/intl/SharedIntlData.h"
#include "mozilla/Assertions.h"
#include "mozilla/HashFunctions.h"
#include <stdint.h>
#include "jsatom.h"
#include "jsstr.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/ScopedICUObject.h"
#include "builtin/IntlTimeZoneData.h"
#include "js/Utility.h"
using js::HashNumber;
using js::intl::StringsAreEqual;
template<typename Char>
static constexpr Char
ToUpperASCII(Char c)
{
return ('a' <= c && c <= 'z')
? (c & ~0x20)
: c;
}
static_assert(ToUpperASCII('a') == 'A', "verifying 'a' uppercases correctly");
static_assert(ToUpperASCII('m') == 'M', "verifying 'm' uppercases correctly");
static_assert(ToUpperASCII('z') == 'Z', "verifying 'z' uppercases correctly");
static_assert(ToUpperASCII(u'a') == u'A', "verifying u'a' uppercases correctly");
static_assert(ToUpperASCII(u'k') == u'K', "verifying u'k' uppercases correctly");
static_assert(ToUpperASCII(u'z') == u'Z', "verifying u'z' uppercases correctly");
template<typename Char>
static HashNumber
HashStringIgnoreCaseASCII(const Char* s, size_t length)
{
uint32_t hash = 0;
for (size_t i = 0; i < length; i++)
hash = mozilla::AddToHash(hash, ToUpperASCII(s[i]));
return hash;
}
template<typename Char1, typename Char2>
static bool
EqualCharsIgnoreCaseASCII(const Char1* s1, const Char2* s2, size_t len)
{
for (const Char1* s1end = s1 + len; s1 < s1end; s1++, s2++) {
if (ToUpperASCII(*s1) != ToUpperASCII(*s2))
return false;
}
return true;
}
js::intl::SharedIntlData::TimeZoneHasher::Lookup::Lookup(JSFlatString* timeZone)
: isLatin1(timeZone->hasLatin1Chars()), length(timeZone->length())
{
if (isLatin1) {
latin1Chars = timeZone->latin1Chars(nogc);
hash = HashStringIgnoreCaseASCII(latin1Chars, length);
} else {
twoByteChars = timeZone->twoByteChars(nogc);
hash = HashStringIgnoreCaseASCII(twoByteChars, length);
}
}
bool
js::intl::SharedIntlData::TimeZoneHasher::match(TimeZoneName key, const Lookup& lookup)
{
if (key->length() != lookup.length)
return false;
// Compare time zone names ignoring ASCII case differences.
if (key->hasLatin1Chars()) {
const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
if (lookup.isLatin1)
return EqualCharsIgnoreCaseASCII(keyChars, lookup.latin1Chars, lookup.length);
return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length);
}
const char16_t* keyChars = key->twoByteChars(lookup.nogc);
if (lookup.isLatin1)
return EqualCharsIgnoreCaseASCII(lookup.latin1Chars, keyChars, lookup.length);
return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length);
}
static bool
IsLegacyICUTimeZone(const char* timeZone)
{
for (const auto& legacyTimeZone : js::timezone::legacyICUTimeZones) {
if (StringsAreEqual(timeZone, legacyTimeZone))
return true;
}
return false;
}
bool
js::intl::SharedIntlData::ensureTimeZones(JSContext* cx)
{
if (timeZoneDataInitialized)
return true;
// If initTimeZones() was called previously, but didn't complete due to
// OOM, clear all sets/maps and start from scratch.
if (availableTimeZones.initialized())
availableTimeZones.finish();
if (!availableTimeZones.init()) {
ReportOutOfMemory(cx);
return false;
}
UErrorCode status = U_ZERO_ERROR;
UEnumeration* values = ucal_openTimeZones(&status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UEnumeration, uenum_close> toClose(values);
RootedAtom timeZone(cx);
while (true) {
int32_t size;
const char* rawTimeZone = uenum_next(values, &size, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
if (rawTimeZone == nullptr)
break;
// Skip legacy ICU time zone names.
if (IsLegacyICUTimeZone(rawTimeZone))
continue;
MOZ_ASSERT(size >= 0);
timeZone = Atomize(cx, rawTimeZone, size_t(size));
if (!timeZone)
return false;
TimeZoneHasher::Lookup lookup(timeZone);
TimeZoneSet::AddPtr p = availableTimeZones.lookupForAdd(lookup);
// ICU shouldn't report any duplicate time zone names, but if it does,
// just ignore the duplicate name.
if (!p && !availableTimeZones.add(p, timeZone)) {
ReportOutOfMemory(cx);
return false;
}
}
if (ianaZonesTreatedAsLinksByICU.initialized())
ianaZonesTreatedAsLinksByICU.finish();
if (!ianaZonesTreatedAsLinksByICU.init()) {
ReportOutOfMemory(cx);
return false;
}
for (const char* rawTimeZone : timezone::ianaZonesTreatedAsLinksByICU) {
MOZ_ASSERT(rawTimeZone != nullptr);
timeZone = Atomize(cx, rawTimeZone, strlen(rawTimeZone));
if (!timeZone)
return false;
TimeZoneHasher::Lookup lookup(timeZone);
TimeZoneSet::AddPtr p = ianaZonesTreatedAsLinksByICU.lookupForAdd(lookup);
MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaZonesTreatedAsLinksByICU");
if (!ianaZonesTreatedAsLinksByICU.add(p, timeZone)) {
ReportOutOfMemory(cx);
return false;
}
}
if (ianaLinksCanonicalizedDifferentlyByICU.initialized())
ianaLinksCanonicalizedDifferentlyByICU.finish();
if (!ianaLinksCanonicalizedDifferentlyByICU.init()) {
ReportOutOfMemory(cx);
return false;
}
RootedAtom linkName(cx);
RootedAtom& target = timeZone;
for (const auto& linkAndTarget : timezone::ianaLinksCanonicalizedDifferentlyByICU) {
const char* rawLinkName = linkAndTarget.link;
const char* rawTarget = linkAndTarget.target;
MOZ_ASSERT(rawLinkName != nullptr);
linkName = Atomize(cx, rawLinkName, strlen(rawLinkName));
if (!linkName)
return false;
MOZ_ASSERT(rawTarget != nullptr);
target = Atomize(cx, rawTarget, strlen(rawTarget));
if (!target)
return false;
TimeZoneHasher::Lookup lookup(linkName);
TimeZoneMap::AddPtr p = ianaLinksCanonicalizedDifferentlyByICU.lookupForAdd(lookup);
MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaLinksCanonicalizedDifferentlyByICU");
if (!ianaLinksCanonicalizedDifferentlyByICU.add(p, linkName, target)) {
ReportOutOfMemory(cx);
return false;
}
}
MOZ_ASSERT(!timeZoneDataInitialized, "ensureTimeZones is neither reentrant nor thread-safe");
timeZoneDataInitialized = true;
return true;
}
bool
js::intl::SharedIntlData::validateTimeZoneName(JSContext* cx, HandleString timeZone,
MutableHandleString result)
{
if (!ensureTimeZones(cx))
return false;
Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx));
if (!timeZoneFlat)
return false;
TimeZoneHasher::Lookup lookup(timeZoneFlat);
if (TimeZoneSet::Ptr p = availableTimeZones.lookup(lookup))
result.set(*p);
return true;
}
bool
js::intl::SharedIntlData::tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, HandleString timeZone,
MutableHandleString result)
{
if (!ensureTimeZones(cx))
return false;
Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx));
if (!timeZoneFlat)
return false;
TimeZoneHasher::Lookup lookup(timeZoneFlat);
MOZ_ASSERT(availableTimeZones.has(lookup), "Invalid time zone name");
if (TimeZoneMap::Ptr p = ianaLinksCanonicalizedDifferentlyByICU.lookup(lookup)) {
// The effectively supported time zones aren't known at compile time,
// when
// 1. SpiderMonkey was compiled with "--with-system-icu".
// 2. ICU's dynamic time zone data loading feature was used.
// (ICU supports loading time zone files at runtime through the
// ICU_TIMEZONE_FILES_DIR environment variable.)
// Ensure ICU supports the new target zone before applying the update.
TimeZoneName targetTimeZone = p->value();
TimeZoneHasher::Lookup targetLookup(targetTimeZone);
if (availableTimeZones.has(targetLookup))
result.set(targetTimeZone);
} else if (TimeZoneSet::Ptr p = ianaZonesTreatedAsLinksByICU.lookup(lookup)) {
result.set(*p);
}
return true;
}
void
js::intl::SharedIntlData::destroyInstance()
{
availableTimeZones.finish();
ianaZonesTreatedAsLinksByICU.finish();
ianaLinksCanonicalizedDifferentlyByICU.finish();
}
void
js::intl::SharedIntlData::trace(JSTracer* trc)
{
// Atoms are always tenured.
if (!trc->runtime()->isHeapMinorCollecting()) {
availableTimeZones.trace(trc);
ianaZonesTreatedAsLinksByICU.trace(trc);
ianaLinksCanonicalizedDifferentlyByICU.trace(trc);
}
}
size_t
js::intl::SharedIntlData::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
{
return availableTimeZones.sizeOfExcludingThis(mallocSizeOf) +
ianaZonesTreatedAsLinksByICU.sizeOfExcludingThis(mallocSizeOf) +
ianaLinksCanonicalizedDifferentlyByICU.sizeOfExcludingThis(mallocSizeOf);
}

View file

@ -0,0 +1,164 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef builtin_intl_SharedIntlData_h
#define builtin_intl_SharedIntlData_h
#include "mozilla/MemoryReporting.h"
#include <stddef.h>
#include "jsalloc.h"
#include "js/CharacterEncoding.h"
#include "js/GCAPI.h"
#include "js/GCHashTable.h"
#include "js/RootingAPI.h"
#include "js/Utility.h"
#include "vm/String.h"
namespace js {
namespace intl {
/**
* Stores Intl data which can be shared across compartments (but not contexts).
*
* Used for data which is expensive when computed repeatedly or is not
* available through ICU.
*/
class SharedIntlData
{
/**
* Information tracking the set of the supported time zone names, derived
* from the IANA time zone database <https://www.iana.org/time-zones>.
*
* There are two kinds of IANA time zone names: Zone and Link (denoted as
* such in database source files). Zone names are the canonical, preferred
* name for a time zone, e.g. Asia/Kolkata. Link names simply refer to
* target Zone names for their meaning, e.g. Asia/Calcutta targets
* Asia/Kolkata. That a name is a Link doesn't *necessarily* reflect a
* sense of deprecation: some Link names also exist partly for convenience,
* e.g. UTC and GMT as Link names targeting the Zone name Etc/UTC.
*
* Two data sources determine the time zone names we support: those ICU
* supports and IANA's zone information.
*
* Unfortunately the names ICU and IANA support, and their Link
* relationships from name to target, aren't identical, so we can't simply
* implicitly trust ICU's name handling. We must perform various
* preprocessing of user-provided zone names and post-processing of
* ICU-provided zone names to implement ECMA-402's IANA-consistent behavior.
*
* Also see <https://ssl.icu-project.org/trac/ticket/12044> and
* <http://unicode.org/cldr/trac/ticket/9892>.
*/
using TimeZoneName = JSAtom*;
struct TimeZoneHasher
{
struct Lookup
{
union {
const JS::Latin1Char* latin1Chars;
const char16_t* twoByteChars;
};
bool isLatin1;
size_t length;
JS::AutoCheckCannotGC nogc;
HashNumber hash;
explicit Lookup(JSFlatString* timeZone);
};
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
static bool match(TimeZoneName key, const Lookup& lookup);
};
using TimeZoneSet = js::GCHashSet<TimeZoneName,
TimeZoneHasher,
js::SystemAllocPolicy>;
using TimeZoneMap = js::GCHashMap<TimeZoneName,
TimeZoneName,
TimeZoneHasher,
js::SystemAllocPolicy>;
/**
* As a threshold matter, available time zones are those time zones ICU
* supports, via ucal_openTimeZones. But ICU supports additional non-IANA
* time zones described in intl/icu/source/tools/tzcode/icuzones (listed in
* IntlTimeZoneData.cpp's |legacyICUTimeZones|) for its own backwards
* compatibility purposes. This set consists of ICU's supported time zones,
* minus all backwards-compatibility time zones.
*/
TimeZoneSet availableTimeZones;
/**
* IANA treats some time zone names as Zones, that ICU instead treats as
* Links. For example, IANA considers "America/Indiana/Indianapolis" to be
* a Zone and "America/Fort_Wayne" a Link that targets it, but ICU
* considers the former a Link that targets "America/Indianapolis" (which
* IANA treats as a Link).
*
* ECMA-402 requires that we respect IANA data, so if we're asked to
* canonicalize a time zone name in this set, we must *not* return ICU's
* canonicalization.
*/
TimeZoneSet ianaZonesTreatedAsLinksByICU;
/**
* IANA treats some time zone names as Links to one target, that ICU
* instead treats as either Zones, or Links to different targets. An
* example of the former is "Asia/Calcutta, which IANA assigns the target
* "Asia/Kolkata" but ICU considers its own Zone. An example of the latter
* is "America/Virgin", which IANA assigns the target
* "America/Port_of_Spain" but ICU assigns the target "America/St_Thomas".
*
* ECMA-402 requires that we respect IANA data, so if we're asked to
* canonicalize a time zone name that's a key in this map, we *must* return
* the corresponding value and *must not* return ICU's canonicalization.
*/
TimeZoneMap ianaLinksCanonicalizedDifferentlyByICU;
bool timeZoneDataInitialized = false;
/**
* Precomputes the available time zone names, because it's too expensive to
* call ucal_openTimeZones() repeatedly.
*/
bool ensureTimeZones(JSContext* cx);
public:
/**
* Returns the validated time zone name in |result|. If the input time zone
* isn't a valid IANA time zone name, |result| remains unchanged.
*/
bool validateTimeZoneName(JSContext* cx, JS::HandleString timeZone,
JS::MutableHandleString result);
/**
* Returns the canonical time zone name in |result|. If no canonical name
* was found, |result| remains unchanged.
*
* This method only handles time zones which are canonicalized differently
* by ICU when compared to IANA.
*/
bool tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, JS::HandleString timeZone,
JS::MutableHandleString result);
void destroyInstance();
void trace(JSTracer* trc);
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
};
} // namespace intl
} // namespace js
#endif /* builtin_intl_SharedIntlData_h */

View file

@ -118,6 +118,7 @@ UNIFIED_SOURCES += [
'builtin/intl/Collator.cpp',
'builtin/intl/CommonFunctions.cpp',
'builtin/intl/NumberFormat.cpp',
'builtin/intl/SharedIntlData.cpp',
'builtin/MapObject.cpp',
'builtin/ModuleObject.cpp',
'builtin/Object.cpp',

View file

@ -26,6 +26,7 @@
#endif
#include "builtin/AtomicsObject.h"
#include "builtin/Intl.h"
#include "builtin/intl/SharedIntlData.h"
#include "builtin/Promise.h"
#include "ds/FixedSizeHash.h"
#include "frontend/NameCollections.h"
@ -810,7 +811,7 @@ struct JSRuntime : public JS::shadow::Runtime,
const char* getDefaultLocale();
/* Shared Intl data for this runtime. */
js::SharedIntlData sharedIntlData;
js::intl::SharedIntlData sharedIntlData;
void traceSharedIntlData(JSTracer* trc);