Commit 246d985c authored by Frank Tang's avatar Frank Tang Committed by Commit Bot

[Intl] No throwing RangeError when "calendar" and "numberingSystem" are well-formed

* Throws RangeError only when the calendar and numberingSystem is
  ill-formed.
* Set the calendar and numberingSystem to the locale only if
the values are valid.
* Fix the order of GetOption of "localeMatcher".
* Add more unit tests.
See https://github.com/tc39/ecma402/pull/175 for details.

Bug: v8:9786, v8:9787, v8:9788
Change-Id: Ic0f918ad7d9afb0b7c8df39caa0f44ef07ca10c0
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1830345
Commit-Queue: Frank Tang <ftang@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64070}
parent 28472420
......@@ -1582,14 +1582,16 @@ bool IsValidCollation(const icu::Locale& locale, const std::string& value) {
} // namespace
bool Intl::IsWellFormedCalendar(const std::string& value) {
return JSLocale::Is38AlphaNumList(value);
}
bool Intl::IsValidCalendar(const icu::Locale& locale,
const std::string& value) {
return IsValidExtension<icu::Calendar>(locale, "calendar", value);
}
namespace {
bool IsValidNumberingSystem(const std::string& value) {
bool Intl::IsValidNumberingSystem(const std::string& value) {
std::set<std::string> invalid_values = {"native", "traditio", "finance"};
if (invalid_values.find(value) != invalid_values.end()) return false;
UErrorCode status = U_ZERO_ERROR;
......@@ -1598,6 +1600,12 @@ bool IsValidNumberingSystem(const std::string& value) {
return U_SUCCESS(status) && numbering_system.get() != nullptr;
}
namespace {
bool IsWellFormedNumberingSystem(const std::string& value) {
return JSLocale::Is38AlphaNumList(value);
}
std::map<std::string, std::string> LookupAndValidateUnicodeExtensions(
icu::Locale* icu_locale, const std::set<std::string>& relevant_keys) {
std::map<std::string, std::string> extensions;
......@@ -1659,7 +1667,7 @@ std::map<std::string, std::string> LookupAndValidateUnicodeExtensions(
std::set<std::string> valid_values = {"upper", "lower", "false"};
is_valid_value = valid_values.find(bcp47_value) != valid_values.end();
} else if (strcmp("nu", bcp47_key) == 0) {
is_valid_value = IsValidNumberingSystem(bcp47_value);
is_valid_value = Intl::IsValidNumberingSystem(bcp47_value);
}
if (is_valid_value) {
extensions.insert(
......@@ -1985,7 +1993,7 @@ Maybe<bool> Intl::GetNumberingSystem(Isolate* isolate,
empty_values, method, result);
MAYBE_RETURN(maybe, Nothing<bool>());
if (maybe.FromJust() && *result != nullptr) {
if (!IsValidNumberingSystem(result->get())) {
if (!IsWellFormedNumberingSystem(result->get())) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate,
NewRangeError(
......
......@@ -271,6 +271,12 @@ class Intl {
static bool IsValidCalendar(const icu::Locale& locale,
const std::string& value);
// Check the numberingSystem is valid.
static bool IsValidNumberingSystem(const std::string& value);
// Check the calendar is well formed.
static bool IsWellFormedCalendar(const std::string& value);
struct ResolvedLocale {
std::string locale;
icu::Locale icu_locale;
......
......@@ -1225,6 +1225,10 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::New(
// 4. Let matcher be ? GetOption(options, "localeMatcher", "string",
// « "lookup", "best fit" », "best fit").
// 5. Set opt.[[localeMatcher]] to matcher.
Maybe<Intl::MatcherOption> maybe_locale_matcher =
Intl::GetLocaleMatcher(isolate, options, service);
MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSDateTimeFormat>());
Intl::MatcherOption locale_matcher = maybe_locale_matcher.FromJust();
std::unique_ptr<char[]> calendar_str = nullptr;
std::unique_ptr<char[]> numbering_system_str = nullptr;
......@@ -1237,7 +1241,7 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::New(
MAYBE_RETURN(maybe_calendar, MaybeHandle<JSDateTimeFormat>());
if (maybe_calendar.FromJust() && calendar_str != nullptr) {
icu::Locale default_locale;
if (!Intl::IsValidCalendar(default_locale, calendar_str.get())) {
if (!Intl::IsWellFormedCalendar(calendar_str.get())) {
THROW_NEW_ERROR(
isolate,
NewRangeError(
......@@ -1254,11 +1258,6 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::New(
MAYBE_RETURN(maybe_numberingSystem, MaybeHandle<JSDateTimeFormat>());
}
Maybe<Intl::MatcherOption> maybe_locale_matcher =
Intl::GetLocaleMatcher(isolate, options, service);
MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSDateTimeFormat>());
Intl::MatcherOption locale_matcher = maybe_locale_matcher.FromJust();
// 6. Let hour12 be ? GetOption(options, "hour12", "boolean", undefined,
// undefined).
bool hour12;
......@@ -1298,12 +1297,14 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::New(
DCHECK(!icu_locale.isBogus());
UErrorCode status = U_ZERO_ERROR;
if (calendar_str != nullptr) {
if (calendar_str != nullptr &&
Intl::IsValidCalendar(icu_locale, calendar_str.get())) {
icu_locale.setUnicodeKeywordValue("ca", calendar_str.get(), status);
CHECK(U_SUCCESS(status));
}
if (numbering_system_str != nullptr) {
if (numbering_system_str != nullptr &&
Intl::IsValidNumberingSystem(numbering_system_str.get())) {
icu_locale.setUnicodeKeywordValue("nu", numbering_system_str.get(), status);
CHECK(U_SUCCESS(status));
}
......
......@@ -170,6 +170,15 @@ bool IsExtensionSingleton(const std::string& value) {
}
} // namespace
bool JSLocale::Is38AlphaNumList(const std::string& value) {
std::size_t found = value.find("-");
if (found == std::string::npos) {
return IsAlphanum(value, 3, 8);
}
return IsAlphanum(value.substr(0, found), 3, 8) &&
JSLocale::Is38AlphaNumList(value.substr(found + 1));
}
// TODO(ftang) Replace the following check w/ icu::LocaleBuilder
// once ICU64 land in March 2019.
bool JSLocale::StartsWithUnicodeLanguageId(const std::string& value) {
......
......@@ -52,6 +52,10 @@ class JSLocale : public JSObject {
// Help function to validate locale by other Intl objects.
static bool StartsWithUnicodeLanguageId(const std::string& value);
// Help function to check well-formed
// "(3*8alphanum) *("-" (3*8alphanum)) sequence" sequence
static bool Is38AlphaNumList(const std::string& value);
DECL_CAST(JSLocale)
DECL_ACCESSORS(icu_locale, Managed<icu::Locale>)
......
......@@ -867,7 +867,8 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::New(Isolate* isolate,
requested_locales, matcher, relevant_extension_keys);
UErrorCode status = U_ZERO_ERROR;
if (numbering_system_str != nullptr) {
if (numbering_system_str != nullptr &&
Intl::IsValidNumberingSystem(numbering_system_str.get())) {
r.icu_locale.setUnicodeKeywordValue("nu", numbering_system_str.get(),
status);
CHECK(U_SUCCESS(status));
......
......@@ -112,7 +112,8 @@ MaybeHandle<JSRelativeTimeFormat> JSRelativeTimeFormat::New(
// 14. Let dataLocale be r.[[DataLocale]].
icu::Locale icu_locale = r.icu_locale;
UErrorCode status = U_ZERO_ERROR;
if (numbering_system_str != nullptr) {
if (numbering_system_str != nullptr &&
Intl::IsValidNumberingSystem(numbering_system_str.get())) {
icu_locale.setUnicodeKeywordValue("nu", numbering_system_str.get(), status);
CHECK(U_SUCCESS(status));
}
......
......@@ -7,6 +7,15 @@
let invalidCalendar = [
"invalid",
"abce",
"abc-defghi",
];
let illFormedCalendar = [
"",
"i",
"ij",
"abcdefghi",
"abc-ab",
];
// https://www.unicode.org/repos/cldr/tags/latest/common/bcp47/calendar.xml
......@@ -36,8 +45,17 @@ let locales = [
"ar",
];
invalidCalendar.forEach(function(calendar) {
locales.forEach(function(base) {
var df;
assertDoesNotThrow(() => df = new Intl.DateTimeFormat([base], {calendar}));
assertEquals(
(new Intl.DateTimeFormat([base])).resolvedOptions().calendar,
df.resolvedOptions().calendar);
});
});
illFormedCalendar.forEach(function(calendar) {
assertThrows(
() => new Intl.DateTimeFormat(["en"], {calendar}),
RangeError);
......
......@@ -10,6 +10,15 @@ let invalidNumberingSystem = [
"finance",
"native",
"traditio",
"abc-defghi",
];
let illFormedNumberingSystem = [
"",
"i",
"ij",
"abcdefghi",
"abc-ab",
];
// https://tc39.github.io/ecma402/#table-numbering-system-digits
......@@ -43,13 +52,22 @@ let locales = [
"ar",
];
invalidNumberingSystem.forEach(function(numberingSystem) {
locales.forEach(function(base) {
var df;
assertDoesNotThrow(
() => df = new Intl.DateTimeFormat([base], {numberingSystem}));
assertEquals(
(new Intl.DateTimeFormat([base])).resolvedOptions().numberingSystem,
df.resolvedOptions().numberingSystem);
});
});
illFormedNumberingSystem.forEach(function(numberingSystem) {
assertThrows(
() => new Intl.DateTimeFormat(["en"], {numberingSystem}),
RangeError);
}
);
});
let value = new Date();
validNumberingSystem.forEach(function(numberingSystem) {
......
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-add-calendar-numbering-system
const actual = [];
const options = {
get localeMatcher() {
actual.push("localeMatcher");
return undefined;
},
get calendar() {
actual.push("calendar");
return undefined;
},
get numberingSystem() {
actual.push("numberingSystem");
return undefined;
},
get hour12() {
actual.push("hour12");
return undefined;
},
};
const expected = [
"localeMatcher",
"calendar",
"numberingSystem",
"hour12"
];
let df = new Intl.DateTimeFormat(undefined, options);
assertEquals(actual.join(":"), expected.join(":"));
......@@ -10,6 +10,15 @@ let invalidNumberingSystem = [
"finance",
"native",
"traditio",
"abc-defghi",
];
let illFormedNumberingSystem = [
"",
"i",
"ij",
"abcdefghi",
"abc-ab",
];
// https://tc39.github.io/ecma402/#table-numbering-system-digits
......@@ -45,11 +54,21 @@ let locales = [
invalidNumberingSystem.forEach(function(numberingSystem) {
locales.forEach(function(base) {
var df;
assertDoesNotThrow(
() => df = new Intl.NumberFormat([base], {numberingSystem}));
assertEquals(
(new Intl.NumberFormat([base])).resolvedOptions().numberingSystem,
df.resolvedOptions().numberingSystem);
});
});
illFormedNumberingSystem.forEach(function(numberingSystem) {
assertThrows(
() => new Intl.NumberFormat(["en"], {numberingSystem}),
RangeError);
}
);
});
let value = 1234567.89;
validNumberingSystem.forEach(function(numberingSystem) {
......
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-add-calendar-numbering-system
const actual = [];
const options = {
get localeMatcher() {
actual.push("localeMatcher");
return undefined;
},
get numberingSystem() {
actual.push("numberingSystem");
return undefined;
},
get style() {
actual.push("style");
return undefined;
},
};
const expected = [
"localeMatcher",
"numberingSystem",
"style"
];
let nf = new Intl.NumberFormat(undefined, options);
assertEquals(actual.join(":"), expected.join(":"));
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-add-calendar-numbering-system
// Well-formed but invalid calendar should not throw RangeError.
var calendar = "abc";
var len = 3;
var expected = new Intl.DateTimeFormat("en").resolvedOptions().calendar;
var df;
for (var i = 3; i < 20; i++, len++, calendar += "a") {
assertDoesNotThrow(() => df = new Intl.DateTimeFormat("en", {calendar}),
"Well-formed calendar should not throw");
assertEquals(expected, df.resolvedOptions().calendar);
if (len == 8) {
calendar += "-ab";
len = 2;
}
}
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-add-calendar-numbering-system
// ill-formed and valid calendar should throw RangeError.
assertThrows(
'new Intl.DateTimeFormat("en", {calendar: "gregorian"})',
RangeError);
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-add-calendar-numbering-system
// Well-formed but invalid numberingSystem should not throw RangeError.
var numberingSystem = "abc";
var len = 3;
const intlClasses = [
Intl.DateTimeFormat,
Intl.NumberFormat,
Intl.RelativeTimeFormat
];
intlClasses.forEach(function(cls) {
var expected = new cls("en").resolvedOptions().numberingSystem;
var obj;
for (var i = 3; i < 20; i++, len++, numberingSystem += "a") {
assertDoesNotThrow(() => obj = new cls("en", {numberingSystem}),
"Well-formed numberingSystem should not throw");
assertEquals(expected, obj.resolvedOptions().numberingSystem);
if (len == 8) {
numberingSystem += "-ab";
len = 2;
}
}
});
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
let invalidNumberingSystem = [
"invalid",
"abce",
"finance",
"native",
"traditio",
"abc-defghi",
];
let illFormedNumberingSystem = [
"",
"i",
"ij",
"abcdefghi",
"abc-ab",
];
// https://tc39.github.io/ecma402/#table-numbering-system-digits
let validNumberingSystem= [
"arab",
"arabext",
"bali",
"beng",
"deva",
"fullwide",
"gujr",
"guru",
"hanidec",
"khmr",
"knda",
"laoo",
"latn",
"limb",
"mlym",
"mong",
"mymr",
"orya",
"tamldec",
"telu",
"thai",
"tibt",
];
let locales = [
"en",
"ar",
];
invalidNumberingSystem.forEach(function(numberingSystem) {
locales.forEach(function(base) {
var df;
assertDoesNotThrow(
() => df = new Intl.RelativeTimeFormat([base], {numberingSystem}));
assertEquals(
(new Intl.RelativeTimeFormat([base])).resolvedOptions().numberingSystem,
df.resolvedOptions().numberingSystem);
});
});
illFormedNumberingSystem.forEach(function(numberingSystem) {
assertThrows(
() => new Intl.RelativeTimeFormat(["en"], {numberingSystem}),
RangeError);
});
let value = 1234567.89;
validNumberingSystem.forEach(function(numberingSystem) {
locales.forEach(function(base) {
let l = base + "-u-nu-" + numberingSystem;
let nf = new Intl.RelativeTimeFormat([base], {numberingSystem});
assertEquals(l, nf.resolvedOptions().locale);
assertEquals(numberingSystem, nf.resolvedOptions().numberingSystem);
// Test the formatting result is the same as passing in via u-nu-
// in the locale.
let nf2 = new Intl.RelativeTimeFormat([l]);
assertEquals(nf2.format(value, "day"), nf.format(value, "day"));
});
}
);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment