Skip to content

Temporal: a difference past a calendar's range raises RangeError instead of spinning (#3452) - #3555

Merged
lahma merged 1 commit into
sebastienros:4.xfrom
lahma:backport/3452-temporal-range-hang
Sep 1, 2026
Merged

lahma merged 1 commit into
sebastienros:4.xfrom
lahma:backport/3452-temporal-range-hang

Conversation

@lahma

@lahma lahma commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Backport of #3452 to 4.x. Fixes #3428 there too — the defect predates every released 4.x, and the hang
reproduces on this branch with the same shape and the same failure count as on main.

What was wrong

NonIsoCalendars.CalendarDateUntil measures a difference by walking one month at a time from the source
towards the target, and its only exit is "this step passed the target". NonIsoCalendars.CalendarDateAdd
takes those steps, and when the conversion back to ISO left the range of the backing
System.Globalization.Calendar it did not report that — it answered with ClampToCalendarRange, which
ignored its year, month and day arguments entirely and returned cal.MaxSupportedDateTime:

private static IsoDate ClampToCalendarRange(Calendar cal, int year, int month, int day)
{
    var dt = cal.MaxSupportedDateTime;
    return new IsoDate(dt.Year, dt.Month, dt.Day);
}

The calendar's maximum, whichever end had been overrun. So every further step of a backwards walk landed
on that same date, no step ever passed the target, and the loop had no other exit.

Temporal.PlainDate.from('1910-01-01').withCalendar('chinese')
    .until(Temporal.PlainDate.from('1900-01-03').withCalendar('chinese'), { largestUnit: 'year' });
// never returned

ChineseLunisolarCalendar spans ISO 1901-02-19 to 2101-01-28, so the target is outside it.

What an embedder saw is the thread, and nothing else. It is a CLR loop inside one interpreter step, so
it crosses no statement boundary: LimitStatements never counted, LimitExecutionTime never got a check,
and a CancellationToken was never observed. A host that bounded untrusted script the documented way still
lost the thread, and the only way back was to end the process. Reachable from any script that can name a
chinese, dangi, hebrew or persian date, through until, since and total on PlainDate,
PlainDateTime, PlainYearMonth, ZonedDateTime and Duration.

The same clamp was also answering, rather than hanging, on the addition side — with the wrong date, and with
no signal that it was wrong. Measured on unfixed 4.x:

Temporal.PlainDate.from('1950-01-01').withCalendar('chinese').subtract({ years: 100 });
// 2101-01-28[u-ca=chinese]   -- subtracting a century moved it a century and a half forward
Temporal.PlainDate.from('2050-01-01').withCalendar('dangi').add({ years: 100 });
// 2051-02-10[u-ca=dangi]
Temporal.PlainDate.from('2050-01-01').withCalendar('hebrew').add({ years: 500 });
// 2239-09-29[u-ca=hebrew]
Temporal.PlainDate.from('0700-01-01').withCalendar('persian').subtract({ years: 200 });
// 9999-12-31[u-ca=persian]

What changed

Identical in substance to #3452. Out-of-range calendar arithmetic reports itself instead of clamping.
NonISODateAdd is implementation-defined and is declared as returning "either a normal completion
containing an ISO Date Record or a throw completion"
(spec), and CalendarDateAdd raises a
RangeError for a result it cannot represent
(spec). Both overflow modes report it:
leaving the calendar's range is not the month-or-day overflow constrain is allowed to clamp, because there
is no nearby valid date to clamp to — only the calendar's own boundary.

  • NonIsoCalendars.CalendarDateAddClampToCalendarRange is gone; the ArgumentOutOfRangeException
    from cal.ToDateTime becomes a CalendarRangeException.
  • NonIsoCalendars.IndianCalendarDateAdd and IslamicTabularCalendarDateAdd — both answered
    result ?? isoDate, handing back their input date when the conversion failed. That is the same
    no-progress answer by a different route: add reports that adding a year changed nothing, and the walk
    takes the same step forever. Both now report instead.
  • NonIsoCalendars.CalendarDateUntil — the walk keeps a no-progress guard of its own. With the three fixes
    above it is unreachable today; it stays as the structural guarantee that this loop terminates, so a clamp
    reintroduced anywhere below it cannot become a hang again.
  • TemporalHelpers.CalendarDateAdd / CalendarDateUntil map it to Throw.RangeError. realm is threaded
    through DifferenceISODateTime and is now required on CalendarDateUntil, so no call site can lose it
    and turn a script-visible RangeError into a CLR exception escaping Engine.Evaluate.

All internal; no public API change.

Divergence from the main PR

Three adaptations, all mechanical, and none of them changes what the fix does:

  • ThrowCalendarArithmeticUnavailable does not exist on 4.x. It arrived on main in separate work,
    and Temporal: a month difference past the calendar's range raises RangeError instead of spinning #3452 merely added a sibling beside it. That hunk is dropped: 4.x keeps its
    throw new NotSupportedException($"Calendar '{calendar}' not yet supported") for a calendar with no
    arithmetic, and only ThrowCalendarRange is added. using System.Diagnostics.CodeAnalysis; had to come
    with it for the [DoesNotReturn], which main already had a use for in that file.
  • CalendarDateUntil had no realm parameter at all on 4.x. On main it was already there as
    Realm? realm = null and Temporal: a month difference past the calendar's range raises RangeError instead of spinning #3452 only made it required; here it is added, required, and threaded through
    the three call sites that lacked it — PlainDatePrototype.DifferenceTemporalPlainDate and
    PlainYearMonthPrototype.DifferenceYearMonth (both pass _realm), and the week arm of
    RoundRelativeDuration in TemporalHelpers (which already held a realm). Every one had a realm at
    hand, so nothing had to be widened to carry one.
  • Jint.Tests is xUnit v3 on 4.x. [Test][Fact], [TestCase(…, TestName: …)][Theory] +
    [InlineData] (the case names move into the doc comment, since InlineData has nowhere to put them),
    and DedicatedThread's join timeout keeps throwing XunitException rather than NUnit's
    AssertionException.

NonIsoCalendars.cs is still one file on this branch — the NonIsoCalendars.Lunisolar.cs split is later
main work, after #3452 — so all four engine hunks in it applied unchanged, and the two NonIsoCalendars
entry points #3452 wraps are still the only two callers in the assembly here.

Jint.Tests/DedicatedThread.cs also carries #3452's second half: a body starts at normal priority and is
demoted to ThreadPriority.Lowest only once its join has timed out, at which point it is known to be a
runaway that must be kept off the cores the rest of the run needs. Starting every body at Lowest cost a
0.1 ms evaluation 2.5 s under two competitors on main's measurement, which is what made the net472 CI
leg red there. 4.x has the same helper and the same starting priority, so it inherits the same fix.

Failing-test evidence, per target framework

Jint.Tests/Runtime/NonIsoCalendarRangeTests.cs runs every case on a dedicated thread with a 15-second
join, so a regression fails the run rather than wedging it. Against the unfixed 4.x engine (this
branch with Jint/ reverted, test file and DedicatedThread change in place):

net472 (the net462 asset) net10.0
before Failed: 15, Passed: 7, Total: 22 — 2 m 15 s Failed: 15, Passed: 7, Total: 22 — 2 m 15 s
after Passed: 22, Failed: 0 — 569 ms Passed: 22, Failed: 0 — 332 ms

Both frameworks fail the identical fifteen. Nine of them are the hang and account for two of those two and
a quarter minutes — each burning its whole 15-second budget in an uninterruptible CLR loop, which is exactly
what an embedder's thread was doing:

Failed …NonIsoCalendarRangeTests.TheReportedMonthDifferencePastTheChineseRangeRaisesRangeError [15 s]
  Error Message:
   did not finish within 00:00:15: Temporal.PlainDate.from('1910-01-01').withCalendar('chinese')
     .until(Temporal.PlainDate.from('1900-01-03').withCalendar('chinese'), { largestUnit: 'year' })

The other six are the boundary date answered as if it were the result, in a millisecond:

Failed …ArithmeticPastACalendarRangeRaisesRangeErrorInsteadOfAnsweringWithTheBoundary(
    calendar: "chinese", from: "1950-01-01", call: "subtract({ years: 100 })") [1 ms]
  Expected string to be the same string, but they differ at index 0:
  "2101-01-28[u-ca=chinese]"   (actual)
  "RangeError"                 (expected)

Test suites

One dotnet test -c Release run on 1cfe8c34a:

suite net472 net10.0
Jint.Tests 6857 passed, 0 failed, 4 skipped 6942 passed, 0 failed, 4 skipped
Jint.Tests.PublicInterface 1495 passed, 0 failed, 9 skipped 1502 passed, 0 failed, 9 skipped
Jint.Tests.CommonScripts 28 passed, 0 failed 28 passed, 0 failed
Jint.Tests.SourceGenerators 52 passed, 0 failed

Jint.Tests.Test262: 102,499 passed, 0 failed, 185 skipped (102,684 total) — the branch control,
unchanged. The full run reported two failures, both variants of
intl402/supportedLocalesOf-unicode-extensions-ignored.js timing out at 34 s against the engine's own
30-second default while three other test assemblies and a sibling suite shared the box. Re-run alone on the
same tree it passes in 2 s, and nothing in this change reaches Intl.supportedLocalesOf.

dotnet build -c Release: 0 errors, no new warnings.

🤖 Generated with Claude Code

https://claude.ai/code/session_014W5mbjGhyvgAS4pivXoc4S

…ead of spinning

Backport of sebastienros#3452 (b942c6a) to 4.x. Fixes sebastienros#3428.

NonIsoCalendars.CalendarDateUntil measures a difference by walking one month at a time
towards the target, and its only exit is "this step passed the target". CalendarDateAdd
takes those steps, and when the conversion back to ISO left the range of the backing
System.Globalization.Calendar it answered with ClampToCalendarRange, which ignored its
year/month/day arguments and returned cal.MaxSupportedDateTime -- the calendar's maximum,
whichever end had been overrun. Every further step of a backwards walk landed on that same
date, so no step ever passed the target and the loop had no other exit.

It is a CLR loop inside one interpreter step, so it crosses no statement boundary: no
execution constraint can interrupt it and a CancellationToken is never observed.

Out-of-range calendar arithmetic now reports itself instead of clamping. The Indian and
Islamic-tabular arms answered `result ?? isoDate`, handing back their input date, which is
the same no-progress answer by a different route; both now report too. CalendarDateUntil
keeps a no-progress guard of its own so a clamp reintroduced below it cannot become a hang
again. TemporalHelpers maps CalendarRangeException to Throw.RangeError, and realm is
threaded through DifferenceISODateTime and is required on CalendarDateUntil so no call site
can turn a script-visible RangeError into a CLR exception escaping Engine.Evaluate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014W5mbjGhyvgAS4pivXoc4S
@lahma
lahma merged commit 54a5b86 into sebastienros:4.x Sep 1, 2026
5 checks passed
PatrickSt1991 pushed a commit to Apps2Samsung/Apps2Samsung that referenced this pull request Sep 14, 2026
Updated [Avalonia](https://github.com/AvaloniaUI/Avalonia/) from 11.3.20
to 11.3.22.

<details>
<summary>Release notes</summary>

_Sourced from [Avalonia's
releases](https://github.com/AvaloniaUI/Avalonia//releases)._

No release notes found for this version range.

Commits viewable in [compare
view](https://github.com/AvaloniaUI/Avalonia//commits).
</details>

Updated [Avalonia.Desktop](https://github.com/AvaloniaUI/Avalonia/) from
11.3.20 to 11.3.22.

<details>
<summary>Release notes</summary>

_Sourced from [Avalonia.Desktop's
releases](https://github.com/AvaloniaUI/Avalonia//releases)._

No release notes found for this version range.

Commits viewable in [compare
view](https://github.com/AvaloniaUI/Avalonia//commits).
</details>

Updated [Avalonia.Diagnostics](https://github.com/AvaloniaUI/Avalonia/)
from 11.3.20 to 11.3.22.

<details>
<summary>Release notes</summary>

_Sourced from [Avalonia.Diagnostics's
releases](https://github.com/AvaloniaUI/Avalonia//releases)._

No release notes found for this version range.

Commits viewable in [compare
view](https://github.com/AvaloniaUI/Avalonia//commits).
</details>

Updated [Avalonia.Fonts.Inter](https://github.com/AvaloniaUI/Avalonia/)
from 11.3.20 to 11.3.22.

<details>
<summary>Release notes</summary>

_Sourced from [Avalonia.Fonts.Inter's
releases](https://github.com/AvaloniaUI/Avalonia//releases)._

No release notes found for this version range.

Commits viewable in [compare
view](https://github.com/AvaloniaUI/Avalonia//commits).
</details>

Updated
[Avalonia.Themes.Fluent](https://github.com/AvaloniaUI/Avalonia/) from
11.3.20 to 11.3.22.

<details>
<summary>Release notes</summary>

_Sourced from [Avalonia.Themes.Fluent's
releases](https://github.com/AvaloniaUI/Avalonia//releases)._

No release notes found for this version range.

Commits viewable in [compare
view](https://github.com/AvaloniaUI/Avalonia//commits).
</details>

Updated [Jint](https://github.com/sebastienros/jint) from 4.16.1 to
4.16.2.

<details>
<summary>Release notes</summary>

_Sourced from [Jint's
releases](https://github.com/sebastienros/jint/releases)._

## 4.16.2

Jint 4.16.2 is a maintenance release from the `4.x` branch:
**correctness and conformance fixes backported from `main`, and nothing
that changes an existing API or an existing default.** If you are on
4.16.1 it is a drop-in update — every public signature is the one 4.16.0
shipped, on all five target frameworks, and the per-framework snapshots
in `Jint.Tests.PublicInterface/Verify/` are unchanged. `main` remains
5.0.0 development; what is coming there is recorded as it lands in
[`docs/v5-migration.md`](https://github.com/sebastienros/jint/blob/main/docs/v5-migration.md).

### Highlights

**Failures that used to end the process, or never end.** A native error
raised while a call's arguments are being evaluated is propagated
instead of leaving an empty value behind, which on 4.16.1 could recurse
until the process died — `decodeURIComponent` on a malformed sequence
was enough (#​4009). Native recursion and the forwarding paths through
bound functions and proxies are guarded so a deep native chain raises a
catchable error (#​4007). A module graph too deep to link raises an
error the host can catch instead of overflowing the stack (#​3548).
Temporal and Intl parsing cannot throw an uncatchable
`RegexMatchTimeoutException` because the machine was busy (#​3543), a
Temporal difference past a calendar's range raises `RangeError` instead
of spinning forever (#​3555), and the process-wide Intl culture cache
and Temporal zone cache are read-only and bounded, with a rejected zone
no longer remembered — closing a script-driven unbounded growth
(#​3546).

**Generators and built-ins, step by step.** A `yield*` delegation
reached again by a loop both re-delegates and keeps its place:
`countdown(3)` in a loop no longer hangs, and a delegating generator no
longer returns the memoized first result (#​3545). `Array.prototype.map`
and `slice` hand a `@@​species` constructor the length `ToLength`
produced, and a non-callable `map` argument is a `TypeError` (#​3547). A
trailing NUL pads neither a numeric string nor an array index (#​3552).
A removed property slot is a tombstone rather than a free slot to reuse,
so enumeration order survives a delete-and-readd (#​3318), and
`LengthOfArrayLike` no longer clamps through a `uint` overload (#​3328).

**Interop that answers for the right engine.** Two engines in one
process no longer decide each other's conversions and operators
(#​3559), a host type converter's answer stays with the engine whose
converter gave it (#​3563), and a value the host registers on a
`ShadowRealm` — and the members its wrapper builds eagerly — belong to
that realm (#​3557). Realm construction state is restored after nesting
or a failure (#​4008). Overload selection is by the arguments in hand:
an operator overload is chosen that way (#​3611), a `params` overload is
chosen by the array's element type with a failing element declining
rather than throwing (#​3782), an overload the argument cannot bind to
is not a match, and a host operator that throws reports what it threw
(#​3554). An index on a wrapped host collection is one property however
it is spelled, and a member filter that hides the indexer hides it
(#​3562); a read-only host collection refuses a write with a JavaScript
`TypeError` rather than the CLR's `NotSupportedException` (#​3556).

**Internationalization and Temporal.** The Persian calendar extends into
proleptic years on its 33-year cycle, so the ends of Temporal's range
land in the right Persian year (#​4006); a calendar that counts
Gregorian months writes their names (#​3612); and a `-u-` extension
carrying more than one key is read whole (#​3613).

**Errors.** Only a string-valued `stack` counts as a pre-existing stack
when a `JavaScriptException` is built, so an accessor or non-string
`stack` on a thrown object no longer breaks error reporting (#​3677,
reported by @​jeske).

Every change was verified failing-first against the unfixed branch on
both .NET Framework and .NET 10, and the release was gated on a paired
SunSpider and Dromaeo comparison against 4.16.1 on an idle machine: no
row regressed outside run-to-run noise, most run 1–4 % faster.

## What's Changed
* Backport: a removed property slot is a tombstone, not a free slot to
reuse (#​3273) by @​lahma in
sebastienros/jint#3318
* Backport: LengthOfArrayLike, delete the uint overload rather than
clamp it (#​3248) by @​lahma in
sebastienros/jint#3328
* Temporal and Intl parsing cannot fail because the machine was busy
(#​3486) by @​lahma in sebastienros/jint#3543
* Backport: the process-wide Intl culture cache and Temporal zone cache
are read-only and bounded, and a rejected zone is not remembered by
@​lahma in sebastienros/jint#3546
* Array: map and slice hand a @@​species constructor the length ToLength
produced (#​3510) by @​lahma in
sebastienros/jint#3547
* Generators: a yield* delegation both re-delegates and keeps its place
(backport of #​3506 and #​3518) by @​lahma in
sebastienros/jint#3545
* A module graph too deep to link raises an error the host can catch,
instead of ending the process (#​3415) by @​lahma in
sebastienros/jint#3548
* String to number: a trailing NUL pads neither a number string nor an
array index (backport of #​3544) by @​lahma in
sebastienros/jint#3552
* Interop: a host operator reports what it threw, and an overload the
argument cannot bind to is not a match by @​lahma in
sebastienros/jint#3554
* Temporal: a difference past a calendar's range raises RangeError
instead of spinning (#​3452) by @​lahma in
sebastienros/jint#3555
* Interop: a read-only host collection refuses script with a JavaScript
error, not the CLR's own (backport of #​3385) by @​lahma in
sebastienros/jint#3556
* ShadowRealm: a value the host registers, and the members its wrapper
builds eagerly, belong to that realm by @​lahma in
sebastienros/jint#3557
* Interop: two engines in one process do not decide each other's
conversions and operators (backport of #​3521 and #​3526) by @​lahma in
sebastienros/jint#3559
* Interop: an index on a wrapped host collection is one property, and a
filter that hides the indexer hides it by @​lahma in
sebastienros/jint#3562
* Interop: a host type converter's answer stays with the engine whose
converter gave it by @​lahma in
sebastienros/jint#3563
* Interop: an operator overload is chosen by the arguments in hand
(backport of #​3578) by @​lahma in
sebastienros/jint#3611
* Intl: a calendar counting Gregorian months writes their names
(backport of #​3589) by @​lahma in
sebastienros/jint#3612
* Intl: a `-u-` extension carrying more than one key is read whole
(backport of #​3594) by @​lahma in
sebastienros/jint#3613
* JavaScriptException: only a string "stack" counts as a pre-existing
stack (#​3607 backport) by @​lahma in
sebastienros/jint#3677
* Interop: a params overload is chosen by the array's element type, and
a failing element declines instead of throwing (#​3764) by @​lahma in
sebastienros/jint#3782
* Backport #​3751 to 4.x: Temporal: the persian calendar extends into
proleptic years on the 33-year cycle by @​lahma in
sebastienros/jint#4006
* Backport #​3922 to 4.x: Restore realm construction state after nesting
or failure by @​lahma in sebastienros/jint#4008
* Backport #​3845 to 4.x: Propagate native errors during call argument
evaluation by @​lahma in sebastienros/jint#4009
* Backport #​3877 to 4.x: Guard native recursion and forwarding paths by
@​lahma in sebastienros/jint#4007


**Full Changelog**:
sebastienros/jint@v4.16.1...v4.16.2


Commits viewable in [compare
view](sebastienros/jint@v4.16.1...v4.16.2).
</details>

Updated [Microsoft.AspNetCore](https://github.com/dotnet/aspnetcore)
from 2.3.12 to 2.3.13.

<details>
<summary>Release notes</summary>

_Sourced from [Microsoft.AspNetCore's
releases](https://github.com/dotnet/aspnetcore/releases)._

No release notes found for this version range.

Commits viewable in [compare
view](https://github.com/dotnet/aspnetcore/commits).
</details>

Updated
[Microsoft.AspNetCore.Server.Kestrel.Core](https://github.com/dotnet/aspnetcore)
from 2.3.12 to 2.3.13.

<details>
<summary>Release notes</summary>

_Sourced from [Microsoft.AspNetCore.Server.Kestrel.Core's
releases](https://github.com/dotnet/aspnetcore/releases)._

No release notes found for this version range.

Commits viewable in [compare
view](https://github.com/dotnet/aspnetcore/commits).
</details>

Updated
[System.Security.Cryptography.Xml](https://github.com/dotnet/dotnet)
from 10.0.11 to 10.0.12.

<details>
<summary>Release notes</summary>

_Sourced from [System.Security.Cryptography.Xml's
releases](https://github.com/dotnet/dotnet/releases)._

No release notes found for this version range.

Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
legrab added a commit to legrab/pocok that referenced this pull request Sep 15, 2026
Updated [Jint](https://github.com/sebastienros/jint) from 4.16.1 to
4.16.2.

<details>
<summary>Release notes</summary>

_Sourced from [Jint's
releases](https://github.com/sebastienros/jint/releases)._

## 4.16.2

Jint 4.16.2 is a maintenance release from the `4.x` branch:
**correctness and conformance fixes backported from `main`, and nothing
that changes an existing API or an existing default.** If you are on
4.16.1 it is a drop-in update — every public signature is the one 4.16.0
shipped, on all five target frameworks, and the per-framework snapshots
in `Jint.Tests.PublicInterface/Verify/` are unchanged. `main` remains
5.0.0 development; what is coming there is recorded as it lands in
[`docs/v5-migration.md`](https://github.com/sebastienros/jint/blob/main/docs/v5-migration.md).

### Highlights

**Failures that used to end the process, or never end.** A native error
raised while a call's arguments are being evaluated is propagated
instead of leaving an empty value behind, which on 4.16.1 could recurse
until the process died — `decodeURIComponent` on a malformed sequence
was enough (#​4009). Native recursion and the forwarding paths through
bound functions and proxies are guarded so a deep native chain raises a
catchable error (#​4007). A module graph too deep to link raises an
error the host can catch instead of overflowing the stack (#​3548).
Temporal and Intl parsing cannot throw an uncatchable
`RegexMatchTimeoutException` because the machine was busy (#​3543), a
Temporal difference past a calendar's range raises `RangeError` instead
of spinning forever (#​3555), and the process-wide Intl culture cache
and Temporal zone cache are read-only and bounded, with a rejected zone
no longer remembered — closing a script-driven unbounded growth
(#​3546).

**Generators and built-ins, step by step.** A `yield*` delegation
reached again by a loop both re-delegates and keeps its place:
`countdown(3)` in a loop no longer hangs, and a delegating generator no
longer returns the memoized first result (#​3545). `Array.prototype.map`
and `slice` hand a `@@​species` constructor the length `ToLength`
produced, and a non-callable `map` argument is a `TypeError` (#​3547). A
trailing NUL pads neither a numeric string nor an array index (#​3552).
A removed property slot is a tombstone rather than a free slot to reuse,
so enumeration order survives a delete-and-readd (#​3318), and
`LengthOfArrayLike` no longer clamps through a `uint` overload (#​3328).

**Interop that answers for the right engine.** Two engines in one
process no longer decide each other's conversions and operators
(#​3559), a host type converter's answer stays with the engine whose
converter gave it (#​3563), and a value the host registers on a
`ShadowRealm` — and the members its wrapper builds eagerly — belong to
that realm (#​3557). Realm construction state is restored after nesting
or a failure (#​4008). Overload selection is by the arguments in hand:
an operator overload is chosen that way (#​3611), a `params` overload is
chosen by the array's element type with a failing element declining
rather than throwing (#​3782), an overload the argument cannot bind to
is not a match, and a host operator that throws reports what it threw
(#​3554). An index on a wrapped host collection is one property however
it is spelled, and a member filter that hides the indexer hides it
(#​3562); a read-only host collection refuses a write with a JavaScript
`TypeError` rather than the CLR's `NotSupportedException` (#​3556).

**Internationalization and Temporal.** The Persian calendar extends into
proleptic years on its 33-year cycle, so the ends of Temporal's range
land in the right Persian year (#​4006); a calendar that counts
Gregorian months writes their names (#​3612); and a `-u-` extension
carrying more than one key is read whole (#​3613).

**Errors.** Only a string-valued `stack` counts as a pre-existing stack
when a `JavaScriptException` is built, so an accessor or non-string
`stack` on a thrown object no longer breaks error reporting (#​3677,
reported by @​jeske).

Every change was verified failing-first against the unfixed branch on
both .NET Framework and .NET 10, and the release was gated on a paired
SunSpider and Dromaeo comparison against 4.16.1 on an idle machine: no
row regressed outside run-to-run noise, most run 1–4 % faster.

## What's Changed
* Backport: a removed property slot is a tombstone, not a free slot to
reuse (#​3273) by @​lahma in
sebastienros/jint#3318
* Backport: LengthOfArrayLike, delete the uint overload rather than
clamp it (#​3248) by @​lahma in
sebastienros/jint#3328
* Temporal and Intl parsing cannot fail because the machine was busy
(#​3486) by @​lahma in sebastienros/jint#3543
* Backport: the process-wide Intl culture cache and Temporal zone cache
are read-only and bounded, and a rejected zone is not remembered by
@​lahma in sebastienros/jint#3546
* Array: map and slice hand a @@​species constructor the length ToLength
produced (#​3510) by @​lahma in
sebastienros/jint#3547
* Generators: a yield* delegation both re-delegates and keeps its place
(backport of #​3506 and #​3518) by @​lahma in
sebastienros/jint#3545
* A module graph too deep to link raises an error the host can catch,
instead of ending the process (#​3415) by @​lahma in
sebastienros/jint#3548
* String to number: a trailing NUL pads neither a number string nor an
array index (backport of #​3544) by @​lahma in
sebastienros/jint#3552
* Interop: a host operator reports what it threw, and an overload the
argument cannot bind to is not a match by @​lahma in
sebastienros/jint#3554
* Temporal: a difference past a calendar's range raises RangeError
instead of spinning (#​3452) by @​lahma in
sebastienros/jint#3555
* Interop: a read-only host collection refuses script with a JavaScript
error, not the CLR's own (backport of #​3385) by @​lahma in
sebastienros/jint#3556
* ShadowRealm: a value the host registers, and the members its wrapper
builds eagerly, belong to that realm by @​lahma in
sebastienros/jint#3557
* Interop: two engines in one process do not decide each other's
conversions and operators (backport of #​3521 and #​3526) by @​lahma in
sebastienros/jint#3559
* Interop: an index on a wrapped host collection is one property, and a
filter that hides the indexer hides it by @​lahma in
sebastienros/jint#3562
* Interop: a host type converter's answer stays with the engine whose
converter gave it by @​lahma in
sebastienros/jint#3563
* Interop: an operator overload is chosen by the arguments in hand
(backport of #​3578) by @​lahma in
sebastienros/jint#3611
* Intl: a calendar counting Gregorian months writes their names
(backport of #​3589) by @​lahma in
sebastienros/jint#3612
* Intl: a `-u-` extension carrying more than one key is read whole
(backport of #​3594) by @​lahma in
sebastienros/jint#3613
* JavaScriptException: only a string "stack" counts as a pre-existing
stack (#​3607 backport) by @​lahma in
sebastienros/jint#3677
* Interop: a params overload is chosen by the array's element type, and
a failing element declines instead of throwing (#​3764) by @​lahma in
sebastienros/jint#3782
* Backport #​3751 to 4.x: Temporal: the persian calendar extends into
proleptic years on the 33-year cycle by @​lahma in
sebastienros/jint#4006
* Backport #​3922 to 4.x: Restore realm construction state after nesting
or failure by @​lahma in sebastienros/jint#4008
* Backport #​3845 to 4.x: Propagate native errors during call argument
evaluation by @​lahma in sebastienros/jint#4009
* Backport #​3877 to 4.x: Guard native recursion and forwarding paths by
@​lahma in sebastienros/jint#4007


**Full Changelog**:
sebastienros/jint@v4.16.1...v4.16.2


Commits viewable in [compare
view](sebastienros/jint@v4.16.1...v4.16.2).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Jint&package-manager=nuget&previous-version=4.16.1&new-version=4.16.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>
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.

1 participant