Skip to content

IntlLocale: throw RangeError (not TypeError) when ICU canonicalization fails on a structurally valid tag - #350

Open
robobun wants to merge 1 commit into
mainfrom
farm/3a44b927/intl-locale-rangeerror
Open

IntlLocale: throw RangeError (not TypeError) when ICU canonicalization fails on a structurally valid tag#350
robobun wants to merge 1 commit into
mainfrom
farm/3a44b927/intl-locale-rangeerror

Conversation

@robobun

@robobun robobun commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Problem

new Intl.Locale(tag) throws TypeError ("failed to initialize Locale") for a structurally valid BCP-47 tag that exceeds ICU's internal ULOC_FULLNAME_CAPACITY buffer during canonicalization. Every other Intl entry point (Intl.DateTimeFormat, Intl.NumberFormat, Intl.Collator, Intl.getCanonicalLocales, String.prototype.localeCompare) throws RangeError for the same input, because canonicalizeLocaleList in IntlObject.cpp reports canonicalization failure as RangeError. V8 also throws RangeError here.

const tag = 'en' + Array.from({ length: 23 }, (_, i) => '-' + String(i).padStart(5, 'v') + 'ab').join('');
// 186 chars, 23 distinct 7-char variants: structurally valid unicode_locale_id

new Intl.Locale(tag);          // TypeError: failed to initialize Locale
new Intl.DateTimeFormat(tag);  // RangeError: invalid language tag: ...
Intl.getCanonicalLocales(tag); // RangeError: invalid language tag: ...

So catch (e) { if (e instanceof RangeError) /* invalid locale */ } misses only the Intl.Locale constructor.

Cause

IntlLocale::initializeLocale calls isStructurallyValidLanguageTag at the top (throwing RangeError on failure), so by the time LocaleIDBuilder::canonicalize() / toCanonical() run the tag is known to be well-formed. Their only failure mode is ICU's fixed-size buffer overflow. The two failure sites threw throwTypeError while the equivalent path in canonicalizeLocaleList throws createRangeError.

Fix

Change both sites to throwRangeError, matching canonicalizeLocaleList and V8.

Branched from 549170099226f816 (Bun's current WEBKIT_VERSION) so the preview tarball is a minimal diff against what Bun links today. A companion Bun PR will bump WEBKIT_VERSION to this preview and add a regression test in test/js/web/intl/intl.test.ts.

…n fails on a structurally valid tag

initializeLocale() validates the input tag with isStructurallyValidLanguageTag
before reaching either canonicalize() or toCanonical(). When those return
failure the tag is well-formed BCP-47; the only failure mode is ICU's
ULOC_FULLNAME_CAPACITY buffer overflowing on long (23+ variant) tags.

canonicalizeLocaleList in IntlObject.cpp surfaces the same canonicalization
failure as RangeError, so Intl.DateTimeFormat, Intl.NumberFormat,
Intl.Collator, Intl.getCanonicalLocales and String.prototype.localeCompare
all throw RangeError for the identical input while Intl.Locale alone threw
TypeError. V8 also throws RangeError here.

A RangeError is what user code's locale-validation catch guards check for, so
the inconsistency made Intl.Locale uniquely slip past them.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fcd6843f-bd88-43b9-aa1e-2ae3d5807a79

📥 Commits

Reviewing files that changed from the base of the PR and between d103ebd and db4518f.

📒 Files selected for processing (1)
  • Source/JavaScriptCore/runtime/IntlLocale.cpp

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — straightforward error-type swap for spec/V8 consistency.

What was reviewed:

  • Both call sites: throwRangeError has the same signature as throwTypeError and is used identically throughout IntlLocale.cpp and the rest of runtime/.
  • Confirmed canonicalizeLocaleList in IntlObject.cpp:821 throws RangeError on the same failure path, so this brings Intl.Locale in line.
  • No control-flow change — both sites still return; immediately after throwing.
Extended reasoning...

Overview

Two-line functional change in Source/JavaScriptCore/runtime/IntlLocale.cpp: two throwTypeError(...) calls in IntlLocale::initializeLocale become throwRangeError(...), plus explanatory comments. These sites fire when ICU canonicalization fails on a tag that has already passed isStructurallyValidLanguageTag (i.e., ICU's ULOC_FULLNAME_CAPACITY overflow). The equivalent path in canonicalizeLocaleList (IntlObject.cpp:821) already throws RangeError, and every other error branch in initializeLocale (invalid tag, invalid language/script/region/variants/calendar/etc.) also throws RangeError. This makes the two outliers consistent.

Security risks

None. No new inputs are accepted, no buffers are resized, no parsing logic changes. Only the constructor of the thrown error object differs. throwRangeError(JSGlobalObject*, ThrowScope&, ASCIILiteral) is a well-established helper used ~258 times across runtime/ with the same signature as throwTypeError.

Level of scrutiny

Low. This is a targeted error-type correction with a clear rationale (match other Intl entry points and V8) and no behavioral change on the happy path. The failure paths still return immediately after throwing. The added comments accurately describe why RangeError is correct here.

Other factors

  • The PR description references a companion Bun PR that will add a regression test in test/js/web/intl/intl.test.ts.
  • No outstanding reviewer comments; the only timeline entry is a CodeRabbit rate-limit notice.
  • Bug-hunting system found nothing.

@github-actions

Copy link
Copy Markdown

Preview Builds

Commit Release Date
db4518f8 autobuild-preview-pr-350-db4518f8 2026-07-26 07:56:13 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants