From adc9db312532f0b0394f4a1646b36edc7ae20d54 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Mon, 19 Oct 2026 18:30:29 +0200 Subject: [PATCH] fixed 6 test262 errors: Intl.Locale now recognizes u-extension keys starting with a digit and applies CanonicalizeUValue to option values --- intl/intl.c | 4 +- intl/locale.c | 120 +++++++++++++++++++++++++++++++++++---------- test262_errors.txt | 6 --- 3 files changed, 96 insertions(+), 34 deletions(-) diff --git a/intl/intl.c b/intl/intl.c index 2ce1e14..d1cfbbd 100644 --- a/intl/intl.c +++ b/intl/intl.c @@ -1194,8 +1194,8 @@ char *canonicalize_locale(JSContext *ctx, const char *locale) const char *sub = subtags[ext_blocks[e].start + j]; int slen = (int)strlen(sub); /* u-extension: canonicalize yes -> true for specific keys, then strip true */ - if (singleton == 'u' && slen == 2 && is_alpha(sub, 1) && - (is_alpha(sub + 1, 1) || is_digit(sub + 1, 1))) { + if (singleton == 'u' && slen == 2 && is_alphanum(sub, 1) && + is_alpha(sub + 1, 1)) { int skip_value = 0; const char *value = NULL; if (j + 1 < ext_blocks[e].len) { diff --git a/intl/locale.c b/intl/locale.c index 2f271a4..507e2a1 100644 --- a/intl/locale.c +++ b/intl/locale.c @@ -252,6 +252,31 @@ static const char *canonicalize_keyword_value(const char *key, const char *value return value; } +/* CanonicalizeUValue (ecma402): ASCII-lowercase the value, then canonicalize + it as a value of key per UTS35 Annex C. A value which canonicalizes to + "true" is removed, i.e. the empty String is returned. + Returns a newly allocated string. */ +static char *canonicalize_u_value(JSContext *ctx, const char *key, const char *value) +{ + char *lower = js_strdup(ctx, value); + const char *canon; + if (!lower) + return NULL; + to_lower_case(lower); + canon = canonicalize_keyword_value(key, lower); + if (canon != lower) { + js_free(ctx, lower); + lower = js_strdup(ctx, canon); + if (!lower) + return NULL; + } + if (!strcmp(lower, "true")) { + js_free(ctx, lower); + lower = js_strdup(ctx, ""); + } + return lower; +} + static int locale_parse_u_keywords(JSContext *ctx, const char *u_ext, int u_len, UKeyword *keywords, int max_kw, char **attributes) @@ -263,8 +288,8 @@ static int locale_parse_u_keywords(JSContext *ctx, const char *u_ext, int u_len, int j = i; while (j < u_len && u_ext[j] != '-') j++; int slen = j - i; - if (slen == 2 && isalpha((unsigned char)u_ext[i]) && - (isalpha((unsigned char)u_ext[i+1]) || isdigit((unsigned char)u_ext[i+1]))) { + if (slen == 2 && isalnum((unsigned char)u_ext[i]) && + isalpha((unsigned char)u_ext[i+1])) { /* keyword key */ char key[3] = {u_ext[i], u_ext[i+1], '\0'}; i = j + 1; @@ -637,7 +662,12 @@ if (!str) goto fail; goto fail; } { - const char *cal_val = canonicalize_keyword_value("ca", str); + const char *cal_val_raw = canonicalize_keyword_value("ca", str); + char *cal_val = canonicalize_u_value(ctx, "ca", cal_val_raw); + if (!cal_val) { + JS_FreeCString(ctx, str); + goto fail; + } /* replace or add keyword */ for (i = 0; i < num_u_keywords; i++) { if (!strcmp(u_keywords[i].key, "ca")) { @@ -651,7 +681,9 @@ if (!str) goto fail; u_keywords[num_u_keywords].value = js_strdup(ctx, cal_val); num_u_keywords++; } + js_free(ctx, out->calendar); out->calendar = js_strdup(ctx, cal_val); + js_free(ctx, cal_val); JS_FreeCString(ctx, str); } } @@ -668,19 +700,33 @@ if (!str) goto fail; JS_ThrowRangeError(ctx, "invalid collation"); goto fail; } - for (i = 0; i < num_u_keywords; i++) { - if (!strcmp(u_keywords[i].key, "co")) { - js_free(ctx, u_keywords[i].value); - u_keywords[i].value = js_strdup(ctx, str); - break; - } + if (!is_valid_type(str)) { + JS_FreeCString(ctx, str); + JS_ThrowRangeError(ctx, "invalid collation"); + goto fail; } - if (i == num_u_keywords && num_u_keywords < 16) { - u_keywords[num_u_keywords].key = js_strdup(ctx, "co"); - u_keywords[num_u_keywords].value = js_strdup(ctx, str); - num_u_keywords++; + { + char *co_val = canonicalize_u_value(ctx, "co", str); + if (!co_val) { + JS_FreeCString(ctx, str); + goto fail; + } + for (i = 0; i < num_u_keywords; i++) { + if (!strcmp(u_keywords[i].key, "co")) { + js_free(ctx, u_keywords[i].value); + u_keywords[i].value = js_strdup(ctx, co_val); + break; + } + } + if (i == num_u_keywords && num_u_keywords < 16) { + u_keywords[num_u_keywords].key = js_strdup(ctx, "co"); + u_keywords[num_u_keywords].value = js_strdup(ctx, co_val); + num_u_keywords++; + } + js_free(ctx, out->collation); + out->collation = js_strdup(ctx, co_val); + js_free(ctx, co_val); } - out->collation = js_strdup(ctx, str); JS_FreeCString(ctx, str); } @@ -693,6 +739,7 @@ JS_FreeValue(ctx, val); if (!str) goto fail; { const char *fw_val = weekday_to_u_value(str); + char *fw_canon = NULL; if (!fw_val) { /* not a weekday name, validate as type */ if (!is_valid_type(str)) { @@ -700,7 +747,12 @@ if (!str) goto fail; JS_ThrowRangeError(ctx, "invalid firstDayOfWeek"); goto fail; } - fw_val = str; + fw_canon = canonicalize_u_value(ctx, "fw", str); + if (!fw_canon) { + JS_FreeCString(ctx, str); + goto fail; + } + fw_val = fw_canon; } for (i = 0; i < num_u_keywords; i++) { if (!strcmp(u_keywords[i].key, "fw")) { @@ -714,7 +766,9 @@ if (!str) goto fail; u_keywords[num_u_keywords].value = js_strdup(ctx, fw_val); num_u_keywords++; } + js_free(ctx, out->firstDayOfWeek); out->firstDayOfWeek = js_strdup(ctx, fw_val); + js_free(ctx, fw_canon); } JS_FreeCString(ctx, str); } @@ -811,19 +865,33 @@ if (!str) goto fail; JS_ThrowRangeError(ctx, "invalid numberingSystem"); goto fail; } - for (i = 0; i < num_u_keywords; i++) { - if (!strcmp(u_keywords[i].key, "nu")) { - js_free(ctx, u_keywords[i].value); - u_keywords[i].value = js_strdup(ctx, str); - break; - } + if (!is_valid_type(str)) { + JS_FreeCString(ctx, str); + JS_ThrowRangeError(ctx, "invalid numberingSystem"); + goto fail; } - if (i == num_u_keywords && num_u_keywords < 16) { - u_keywords[num_u_keywords].key = js_strdup(ctx, "nu"); - u_keywords[num_u_keywords].value = js_strdup(ctx, str); - num_u_keywords++; + { + char *nu_val = canonicalize_u_value(ctx, "nu", str); + if (!nu_val) { + JS_FreeCString(ctx, str); + goto fail; + } + for (i = 0; i < num_u_keywords; i++) { + if (!strcmp(u_keywords[i].key, "nu")) { + js_free(ctx, u_keywords[i].value); + u_keywords[i].value = js_strdup(ctx, nu_val); + break; + } + } + if (i == num_u_keywords && num_u_keywords < 16) { + u_keywords[num_u_keywords].key = js_strdup(ctx, "nu"); + u_keywords[num_u_keywords].value = js_strdup(ctx, nu_val); + num_u_keywords++; + } + js_free(ctx, out->numberingSystem); + out->numberingSystem = js_strdup(ctx, nu_val); + js_free(ctx, nu_val); } - out->numberingSystem = js_strdup(ctx, str); JS_FreeCString(ctx, str); } } diff --git a/test262_errors.txt b/test262_errors.txt index 5100cd4..657b4d9 100644 --- a/test262_errors.txt +++ b/test262_errors.txt @@ -1,8 +1,3 @@ -test262/test/intl402/Locale/constructor-unicode-extension-uvalue-true.js:20: Test262Error: new Intl.Locale("en-u-0a-true").toString() must be "en-u-0a" Expected SameValue(«"en-u-true"», «"en-u-0a"») to be true -test262/test/intl402/Locale/constructor-unicode-extension-uvalue-yes.js:20: Test262Error: new Intl.Locale("en-u-0a-yes").toString() must be "en-u-0a-yes" Expected SameValue(«"en-u-yes"», «"en-u-0a-yes"») to be true -test262/test/intl402/Locale/prototype/calendar/canonicalize-case.js:26: Test262Error: new Intl.Locale("de", {calendar: "GREGORY"}).calendar Expected SameValue(«"GREGORY"», «"gregory"») to be true -test262/test/intl402/Locale/prototype/collation/canonicalize-case.js:26: Test262Error: new Intl.Locale("de", {collation: "PHONEBK"}).collation Expected SameValue(«"PHONEBK"», «"phonebk"») to be true -test262/test/intl402/Locale/prototype/firstDayOfWeek/valid-options.js:15: Test262Error: new Intl.Locale("en", { firstDayOfWeek: MON }).firstDayOfWeek returns "mon" Expected SameValue(«"MON"», «"mon"») to be true test262/test/intl402/Locale/prototype/getCalendars/likely-subtags-region.js:42: Test262Error: No suitable test data found in this implementation. Consider updating the candidate list test262/test/intl402/Locale/prototype/getCalendars/region-override.js:41: Test262Error: No suitable test data found in this implementation. Consider updating the candidate list test262/test/intl402/Locale/prototype/getCalendars/region-priority.js:60: Test262Error: Inconclusive: the "rg" region override and the region subtag select regions with identical calendars in this implementation. Consider updating the test data @@ -18,7 +13,6 @@ test262/test/intl402/Locale/prototype/getWeekInfo/likely-subtags-region.js:46: T test262/test/intl402/Locale/prototype/getWeekInfo/region-override.js:45: Test262Error: No suitable test data found in this implementation. Consider updating the candidate list test262/test/intl402/Locale/prototype/getWeekInfo/region-priority.js:59: Test262Error: Inconclusive: the "rg" region override and the region subtag selected regions with identical week info in this implementation. Consider updating the test data test262/test/intl402/Locale/prototype/getWeekInfo/subdivision-region.js:39: Test262Error: No suitable test data found in this implementation. Consider updating the candidate list -test262/test/intl402/Locale/prototype/numberingSystem/canonicalize-case.js:26: Test262Error: new Intl.Locale("de", {numberingSystem: "LATN"}).numberingSystem Expected SameValue(«"LATN"», «"latn"») to be true test262/test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-imported-module.js:11: SyntaxError: exported variable 'resource' does not exist test262/test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-imported-module.js:11: SyntaxError: exported variable 'resource' does not exist test262/test/language/statements/using/initializer-disposed-at-end-of-imported-module.js:11: SyntaxError: exported variable 'resource' does not exist -- 2.47.3