NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
NuGet · #1279 most downloaded on NuGet
Javascript interpreter for .NET.
Last release today
07 Oct 2026
Ships fairly regularly
a new release about every 2 weeks
Rarely documented
notes for 14 of the last 60 stable releases
55 versions withdrawn
withdrawn after publishing
127 years old
128 releases · first in 1900
Jint 4.17.0 is a release from the 4.x branch. It backports correctness and conformance fixes from main (four of them for scripts that could end the ho
Jint 4.17.0 is a release from the 4.x branch. It backports correctness and conformance fixes from main (four of them for scripts that could end the host process), linear string building, and two new opt-in APIs for hosts. That is why it is a minor release rather than a patch. Nothing in it changes an existing API or an existing default, so it is a drop-in update from 4.16.4.
A pooled engine can let go of a finished request's objects. Hosts that reuse one engine across many evaluations (CaptureGlobalSnapshot once, RestoreGlobalSnapshot between runs) keep the interpreter's warmed caches, and that is the point of reusing it. But a warmed call site remembers the last function it called, and a warmed member read remembers the last object it read from. In a pooled web host, that can be a delegate built from a finished request's services, which then stay alive until the same script runs again. The new Engine.Advanced.DiscardInterpreterCaches() drops those caches, so the objects can be collected. The engine, its intrinsics and its global object stay as they are. The next run of each script rebuilds its cache, which costs roughly what a first run on a new engine costs, minus building the engine. A good place to call it is when an engine has been idle for a while, rather than after every use (#4229, from #4227).
A host can carry its own state across await. Jint now runs ECMAScript's HostMakeJobCallback and HostCallJobCallback steps for every promise reaction, every thenable job and the FinalizationRegistry cleanup callback. A host can take part through the new Options.Host.JobCallbacks, which accepts a JobCallbackHooks with Capture, Enter and Exit callbacks. That lets each branch of a Promise.all fan-out keep its own host context, such as a logging scope or ambient request data, through every await (#4231, from #4230; issue #4200). The default is no hooks, in which case each promise registration costs one null check and nothing a script can observe changes.
Four more ways for a script to end the host process are closed.
TypeError copy from inside the catch that was handling the original failure. That added a nested exception dispatch at every hop, and no stack check could see it. Even with the stack-overflow guard on, a long chain of wrapped functions ended the process on the way back out, and a three-line script with a name getter needed no chain at all. Building that copy also ran script (toString, getters, proxy traps); it no longer does. A failed importValue now reports the import failure instead of an unrelated error (#4220, from #4176 and #4183).instanceof and eval. instanceof now checks the native stack before calling a custom Symbol.hasInstance method, and eval checks it before parsing. A self-referencing Symbol.hasInstance, or var s = 'eval(s)'; eval(s), now raises a RangeError the script can catch instead of ending the process. On the MaxExecutionStackCount path, eval also stopped hopping to a new thread at each overflow without ever throwing. instanceof over a bound function now consults every bound link's own Symbol.hasInstance, as the specification requires (#4221, from #4172, #4184 and #4187).Options.Constraints.StackOverflowGuard is on, so turn it on for any script you do not fully trust.Building a string with s = s + x is linear. A + whose result reaches 512 characters now produces a deferred string instead of copying both sides. Building with s = s + x, s = x + s and s = s + a + b therefore becomes linear, as s += x already was. Short concatenations take a separate path and pay nothing for this. The deferred string is safe to read from several engines that share one Prepared<Script>, and it is charged to LimitMemory when it is built, so a memory limit still bounds it. Binding an already-bound function also no longer copies its whole "bound …" name at every level (#4160, from #3386, #3571, #4130, #4164 and #4173).
Array.prototype.concat spreads host arrays and lists. Since 4.14, CLR arrays and lists reach script as live views by default. concat added such a view as a single element instead of spreading its items, so a.concat(b) returned the two wrappers. It now spreads anything script sees as an array (#4219, from #4216; issue #4201).
TypedArray.prototype.with survives a shrinking coercion. When converting the index or the value made a resizable buffer shorter, with copied past its new end and let a CLR ArgumentException escape. It now copies only what the buffer still holds (#4218, from #4157).
Every backport was verified failing-first: its tests were run against the unfixed 4.x tree on .NET 10 and .NET Framework 4.7.2, then with the change. The release candidate was measured against 4.16.4 on SunSpider and Dromaeo in six paired rounds, alternating which build ran first in each round. The four Dromaeo ObjectRegExp rows ran 12–15% faster and SunSpider's 3d-raytrace 2.3% faster. No row regressed by 1% or more; the two rows whose interval did not include zero (one Dromaeo Cube variant, +0.70%, and SunSpider's controlflow-recursive, +0.88%) are within this machine's run-to-run noise.
Full Changelog: v4.16.4...v4.17.0
One column per quarter.
Jint 4.16.4 is a maintenance release from the 4.x branch. It backports correctness and conformance fixes from main — three of them for scripts that co
Jint 4.16.4 is a maintenance release from the 4.x branch. It backports correctness and conformance fixes from main — three of them for scripts that could end the host process — together with a few measured performance improvements, and nothing in it changes an existing API or an existing default. If you are on 4.16.3 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.
A number reads as the same double on every target framework. Before .NET 9, the runtime's ulong-to-double and string-to-double conversions double-round, and Jint inherited that: a whole-number literal in [2⁶³, 2⁶⁴) held a different double on .NET Framework and .NET 8 than on .NET 10 (#3531); parseFloat, Number and JSON.parse mis-rounded past their integer window on .NET Framework, and JSON.parse('1e999') threw a CLR OverflowException out of the engine instead of answering Infinity (#3535); a fraction or exponent literal could land one ULP away on .NET Framework (#3538); and parseInt and wide radix literals now hold the Number nearest the integer they denote on every framework, with a legacy octal no longer re-read as decimal (#3537). The string-to-number lanes and trim now accept exactly the white space the parser does — U+0085 NEL no longer counts, and a byte-order mark no longer breaks Number or BigInt (#3542) — and an exponent scan no longer clamps at 10⁶ (#3593). All six arrive together as #4120, because they share one parser. This changes numeric results — to the correct ones. The literal and string-to-number fixes change answers on .NET Framework only; parseInt, radix literals, the white-space set and the exponent clamp change them on every framework.
BigInt('+12') is 12n. The decimal spelling of a StringIntegerLiteral may carry a sign, so a leading + is accepted there and only there; BigInt('+0x10') stays a SyntaxError (#4121).
A split keeps its segments when a constraint runs script. String.prototype.split filled a thread-shared scratch list and checks constraints every 10,000 segments; a host Constraint that ran script on the same thread could clear the list an outer split was still filling, which then returned only the segments added afterwards (#4122).
An overload is not a fit for a number it cannot hold. Overload scoring gated float, short, byte and their kin on the value fitting, but not int and long — so 3000000000 was a perfect match for an int parameter, the wider overload beside it was never tried, and the host saw a CLR OverflowException rather than a catchable JavaScript error (#4123).
Intl's locale lookup stops paying for a culture it does not need. BestAvailableLocale resolved a CultureInfo on every truncation step even though the available-locale set answers almost every lookup by itself; it now resolves one only when the set cannot answer (#4124).
Intl.Locale canonicalizes every Unicode extension keyword value. UTS #35 canonicalizes a keyword's value in two halves — the CLDR bcp47 aliases, and the removal of a value of "true" — and Jint did only the first, while Intl.Locale's own tag scanner did neither. So en-u-ca-true kept its true, en-u-kb-yes kept its yes although the data aliases it to true, and en-u-ks-primary was never aliased to level1 on the tag path. The firstDayOfWeek option was read as a Number before a String and cast to int, so 0.5 resolved to "sun" where the spec rejects it, and NaN and Infinity became whatever the framework's cast made of them — which differed between .NET and .NET Framework (#4156).
An object used as a rotating cache stops compacting forever. A property removed from an object's store leaves a tombstone, so that a re-added key keeps its creation order. Removing the newest entry already reclaimed its slot; removing the oldest — the shape of a bounded cache, a fresh name in and the oldest out — left a hole each time, and the table compacted every capacity − live additions although the live set never grew: a 16-key rotation settled on a capacity of 64 and compacted 209 times per 10,000 steps. The entries are now a window that may wrap around the array, so a removal at either end retires its slot; the same rotation never resizes and settles on 32 (#4155).
Chains that script can make as long as it likes no longer end the process. Resolving a property through a prototype chain recursed one native frame per link, so a 20,000-deep { __proto__: x } chain overflowed the native stack on a read, a write, an in or a with lookup and ended the process — nothing thrown, nothing for a catch to see (#4170, from #4078; issue #4076, reported by @Tielem). [[Get]], [[Set]] and [[HasProperty]] now walk the chain in a loop, so an ordinary or shaped-host-prototype chain of any depth simply answers. Function.prototype.bind chains had the same shape in IsConstructor and in the realm lookup new and Reflect.construct perform, and those are loops now too (#4169, from #4165); on .NET Framework the JIT happened to turn both into tail calls, so the process death was a .NET 8 / .NET 10 one.
Two chains cannot be flattened, because each link has work of its own to do on the way back out: a proxy forwarding to a proxy, and host object wrappers stacked on each other (#4170, from #4125; issue #4087). Those are probed instead, and raise a catchable RangeError — when Options.Constraints.StackOverflowGuard is on. On 4.x that guard remains opt-in (it becomes the default only in 5.0), so an engine left at its defaults still ends the process on a deep enough proxy chain. If you run script you do not control, turn the guard on; with it on, a 20,000-link proxy chain used as an array's constructor or as Reflect.construct's newTarget now raises RangeError where it used to end the process.
% stops allocating for numbers. The remainder operator now takes the unboxed numeric lane multiplication and division already used, so sum += i % 97 reads a numeric counter without materialising a JsNumber per step — on the benchmark's arithmetic loop, allocation per run drops from 2.87 MB to under 1 KB (#4167, from #4154).
Intl.Locale.prototype.getWeekInfo reads the region the specification picks. It read CLDR's week data for the tag's literal region subtag only, so a tag without one got the world's week and the -u-rg- and -u-sd- keywords were ignored. It now follows RegionPreference: a -u-rg- override, then the region subtag, then a -u-sd- subdivision's region, then the region Add Likely Subtags supplies, then 001. This changes answers — new Intl.Locale('en').getWeekInfo().firstDay is now 7 (en is likely en-US, where the week starts on Sunday) rather than 1, and en-US-u-rg-gbzzzz answers 1 rather than 7 — in each case to what the specification and every browser give (#4168, from #4163).
Every backport was verified failing-first: its tests were run against the unfixed 4.x tree on .NET 10 and .NET Framework 4.7.2, then with the change.
| PR | tests | unfixed .NET Framework | unfixed .NET 10 | after |
|---|---|---|---|---|
| #4120 (#3531) | whole-number literals, 22 | 15 fail | 0 fail | all pass |
| #4120 (#3535) | string to number, 41; JsonTests, 170 |
22 + 8 fail | 0 fail | all pass |
| #4120 (#3538) | fraction / exponent literals, 50 | 16 fail | 0 fail | all pass |
| #4120 (#3537) | parseInt, 124; radix literals, 21 |
96 + 12 fail | 96 + 12 fail | all pass |
| #4120 (#3542) | white space, 35 | 22 fail | 22 fail | all pass |
| #4120 (#3593) | exponent clamp, 5 | 1 fail | 1 fail | all pass |
| #4121 | StringToBigInt, 46 |
16 fail | 16 fail | all pass |
| #4122 | constraint re-entrancy, 2 | 1 fail | 1 fail | all pass |
| #4123 | numeric overload range, 20 | 10 fail | 10 fail | all pass |
| #4124 | culture lookup count, 8 | 1 fail | 1 fail | all pass |
| #4155 | creation order across rotation, 53 | 6 fail | 6 fail | all pass |
| #4156 | Intl.Locale canonicalization, 81 |
30 fail | 30 fail | all pass |
| #4167 | modulo lane, 8 (semantics only) | 0 fail | 0 fail | all pass — the lane is proven by allocation: 2,873,104 B → 784 B per run |
| #4168 | getWeekInfo region preference, 26; test262 getWeekInfo region files, 8 |
13 + 8 fail | 13 + 8 fail | all pass |
| #4169 | bound and proxy chain walks, 3 | pass (JIT tail-calls) | 3 end the process | all pass |
| #4170 | deep prototype (9), shaped-prototype (8), trapless proxy (3) and wrapper (2) chain rows, plus the PlainObject census |
12 end the process, 1 fails, census fails | 19 end the process, census fails | all pass |
Release diagnostics on the tagged commit's tree (a19802dda703), in Release: Jint.Tests 7,655 (net10.0) and 7,570 (net472); Jint.Tests.PublicInterface 1,903 and 1,895; Jint.Tests.CommonScripts 28 and 28; Jint.Tests.SourceGenerators 52; the host-contract verification leg (JINT_HOST_CONTRACT_VERIFICATION=1) 7,655 / 7,570 and 1,907 / 1,899 — zero failures anywhere, and the PublicInterface surface snapshots unchanged. test262: 102,509 passed, 0 failed, 175 skipped — the 4.x control plus the eight getWeekInfo cases #4168 un-excluded. CI passed the same tree on Linux x64, Linux ARM64, Windows, macOS and the host-contract leg.
The release was gated before the tag on a paired comparison against 4.16.3 — DefaultJob, alternating order, on an idle machine with nothing else running and Defender's real-time scanning off — measuring the exact tree that was tagged. A three-round screen over 98 rows (SunSpider, Dromaeo, and the benchmarks for each backported lane) was followed by an eight-round re-measurement of every row that agreed in sign across the screen.
| lane | row | change against 4.16.3 |
|---|---|---|
| prototype-chain walk (#4170) | HostPrototypeShape.AbsentNameMissOverChain |
−17.7% / −18.1% |
PrototypeChainRead.AbsentNameRead |
−15.6% | |
HostPrototypeShape.DeepHitOverChain |
−7.2% / −8.6% | |
% without allocation (#4167) |
ModuloMaterialization.Arithmetic |
−21.0% |
| numeric parsing (#4120) | NumberParse.ParseFloatLoop |
−14.2% |
NumberParse.NumberCoerce |
−3.6% | |
| rotating key sets (#4155) | PropertyDeleteChurn.RotateOldest[64] |
−2.7% |
| JSON | Json.Parse / Json.Stringify |
no change, or faster |
| SunSpider + Dromaeo | 50 rows | no row regressed past run-to-run noise; the largest moves were faster (up to −5.1%) |
One small, confirmed cost: PropertyDeleteChurn.RotateBehindPinnedKey — rotating keys through an object whose oldest key is never deleted — is ~2% slower (+2.33% [+0.92, +2.46] 8/8 and +1.97% [+1.30, +2.77] 7/8). It is the one shape #4155's window cannot help, because the pinned key stops the window advancing and the store falls back to compacting as before; main carries the identical code. It is the price of removing the unbounded compaction the ordinary rotation paid.
Full Changelog: v4.16.3...v4.16.4
Jint 4.16.3 is a maintenance release from the 4.x branch: correctness and conformance fixes backported from main , and nothing that changes an existin
Jint 4.16.3 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.2 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.
A long-lived engine stops accumulating what it has already run. Evaluate(string) and Execute(string) parse a fresh Script on every call, and the engine kept every one of them. Three of the four per-engine handler-tree caches already reset wholesale at 2048 entries so a host streaming endless distinct sources cannot grow them without bound; the fourth, _evaluatedScripts, never got that ceiling and held its keys strongly, retaining the AST of every distinct script the engine had ever evaluated — about 528 bytes per call, climbing forever and reclaimed by nothing short of dropping the engine (#4116). The realm's tagged-template map had the same shape and a harder constraint: Realm._templateMap was a Dictionary<Node, JsArray>, strong on both ends and never cleared, costing roughly 1.35 KB per call for a frozen array and its raw array. A ceiling is no remedy there, because evicting a live template site is script-visible — f() === f() must hold for one site — so it becomes a ConditionalWeakTable<Node, WeakReference<JsArray>>, weak on both halves (#4119). Both matter most to exactly the embedding that looks innocuous: one engine, kept for the lifetime of the process, handed ad-hoc source.
A suspended frame no longer dereferences what the suspension produced. await and yield suspend by returning a plain undefined, and the enclosing member link turns that into a sentinel reference that every consumer must recognise before reading. Nine did not, so they read undefined.undefined and raised a TypeError inside a frame that was already suspended. AsyncBlockStart swallowed that throw, but not before the statement-list resume position had been cleared on the way out — so the resume replayed the body from the first statement: one extra run of every un-awaited side effect per suspension point, and a re-entrancy guard silently truncating the rest. In a generator nothing swallows it and the TypeError comes straight out of next(). Two shapes were wrong answers rather than repeated ones — (await p).x = 1 rejected the promise, and o[await k] = 1 assigned to the literal key "undefined" instead of the real one — and for await ((await p).a of it) never terminated at all. Optional chaining was not the trigger despite where the report put it: the guarded fast lane needs a literal property name, so every computed member read of an awaited or yielded value fell through, (await p)[0] as much as (await p)[k] (#4089, reported by @salihvatanseverv in #4086).
Every change was verified failing-first against the unfixed branch. The suspension fix is pinned by 35 new cases in Jint.Tests/Runtime/SuspendedOptionalChainTests.cs: against 4.16.2's code 28 fail and 6 pass on both .NET 10 and .NET Framework 4.7.2, with a 35th — the for await shape — hanging the test host outright rather than failing; after the fix all 35 pass on both. The retention fixes are pinned by Jint.Tests/Runtime/GarbageCollectionTests.cs and TaggedTemplateCacheTests.cs.
Release diagnostics on the tagged commit, in Release: Jint.Tests 7,170 (net10.0) and 7,085 (net472); Jint.Tests.PublicInterface 1,852 and 1,844; Jint.Tests.CommonScripts 28 and 28; Jint.Tests.SourceGenerators 52; the host-contract verification leg (JINT_HOST_CONTRACT_VERIFICATION=1) 7,170 / 7,085 and 1,856 / 1,848 — zero failures anywhere. test262: 102,498 passed, 183 skipped, with three files crossing the engine's default 30-second budget under whole-suite CPU contention and passing in three seconds when run alone.
The paired SunSpider and Dromaeo comparison against 4.16.2 was run after the tag rather than before it, which is a departure from how 4.16.2 was gated; it is recorded here because the result is what the release notes should carry, not the order it arrived in. No row regressed. Fifty-one rows, paired, alternating order, DefaultJob, on an idle machine: the three-round screen left two candidates clearing the sign-agreement and magnitude bar, both of them Dromaeo.StringBase64, and re-measuring those at eight rounds read −1.72% [−3.01, +1.98] and +0.30% [−3.10, +4.26] — no change, with a third parameter combination coming out faster. StringBase64 is the row Jint.Benchmark/AGENTS.md already documents as a three-round false positive, and it behaved as documented. The Cube control rows moved +0.6% to +0.8%, which is this machine's floor on rows the change cannot reach.
Nothing here is a performance change by intent. #4119 does move a tagged-template lookup from a Dictionary to a ConditionalWeakTable and #4089 adds suspension checks to several interpreter lanes, and neither is visible above the noise floor.
Full Changelog: v4.16.2...v4.16.3
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 existin
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.
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.
-u- extension carrying more than one key is read whole (backport of #3594) by @lahma in #3613Full Changelog: v4.16.1...v4.16.2
Jint 4.16.1 is the first release from the new 4.x maintenance branch , and it marks the point where the two lines separate: main is now 5.0.0 developm
Jint 4.16.1 is the first release from the new 4.x maintenance branch, and it marks the point where the two lines separate: main is now 5.0.0 development, and 4.x is where the 4.16.x line continues.
What that means for you. If you are on 4.16.0, this is a drop-in update — it is correctness and conformance work only, no API change and no changed default. Every public signature is the same one 4.16.0 shipped, on all five target frameworks. If you want the 4.x line, take it from 4.x and expect fixes rather than features. If you want to follow where the engine is going, watch main — v5 brings breaking API changes, an opt-in WHATWG web API surface, Web Workers, Node compatibility and a raised .NET Framework floor, and every one of them is recorded as it lands in docs/v5-migration.md.
From this release onward the 4.x public surface is snapshotted per target framework in Jint.Tests.PublicInterface/Verify/, so "did the API move?" is a diff rather than a judgement call — on this branch a diff there is a bug, and comparing those files against main's is the v4→v5 delta.
Conformance, from a suite that now runs more of test262. The staging/ directory is generated and executed for the first time (#3016), which is roughly 2,800 additional cases — largely SpiderMonkey's own suite contributed upstream, covering behaviour the stable directories never reach. Much of the work below is what it found.
Built-ins do what the spec says, step by step. The array built-ins perform the internal methods they name rather than equivalents (#3066); Array.from honours IsConstructor and a typed array's length write throws (#3043); an array truncation walks downwards and the generics report the writes they fail (#3072); argument validation and evaluation order are corrected in five built-ins (#3069); Map and Set get the [[SetData]] tombstone their traversals are specified over (#3073); Date.prototype.setTime stores the clipped time value (#3042); and Array.prototype.values/keys/entries no longer gate on an array-like receiver (#3236).
Iterators and control flow. A throw from the iterator step no longer closes the iterator (#3047); the done flag is consulted before stepping again (#3048); a rejected return() propagates out of an abandoned for await loop (#3113); an optional-chain short circuit is distinguished from a genuine undefined (#3040); a computed property key is evaluated even when spelled as a literal (#3039) and survives an await or yield intact (#3144, #3150); and destructuring the rest of an exhausted array yields an empty array rather than 2³² elements (#3263).
Numeric and string accuracy. Math.acosh, asinh, atanh, cbrt, expm1 and log1p are ported from fdlibm for correctly-rounded results across every target framework (#3050); toFixed formats from the double's exact value and reads this from [[NumberData]] (#3071); String.prototype case conversion derives from Jint's own Unicode tables rather than the host's culture data (#3068); and the regex engine is chosen per subject, with RegExp.prototype.replace no longer rewriting lastIndex (#3070).
Bounds that hold. JavaScript strings have a maximum length instead of a wrapped array rent (#3015); a JSON document too long to become a string is refused while it is being built (#3028); a frame displaced by a proper tail call keeps counting while its trampoline runs, so MaxRecursionDepth cannot be evaded by leaving and re-entering the trampoline (#3022); and an Atomics waiter is released when nothing can ever notify it again (#3029).
Error messages no longer run user JavaScript (#3041) — rendering a message for a value with a script-supplied toString used to invoke it, from inside the failure path.
Internationalization. The five Temporal members the proposal removed are dropped (#3014), and u-extension options are canonicalized with every date format the spec allows (#3018).
Two fixes in this release come from @svenrog — a sloppy function answering its own arguments (#3061) and the outer link on a parked Function-constructor environment (#3063).
Full Changelog: v4.16.0...v4.16.1
Breaking changes. Int32Extensions / Int64Extensions / DoubleExtensions — polyfill hosts that leaked into the public API — are now internal; on net462/…
Jint 4.16.0 is a correctness- and reliability-focused release: alongside asynchronous module loading, proper tail calls and four new iterator built-ins, a pre-tag review swept the whole engine and fixed what it found — including long-standing defects that predate this cycle. No option defaults changed. Behaviour changes to note up front: JSON.stringify and other machine-readable output now format invariantly under every host culture — under Swedish or Finnish locales on .NET 8+ it used to emit a Unicode minus sign no JSON parser accepts; JSON.parse now rejects trailing commas as the grammar requires; bare identifiers at global scope resolve through the global's prototype chain per spec; IModuleLoader.Resolve is consulted once per (referrer, specifier) pair, so a loader using it as a per-import access-control checkpoint should move the check to LoadModule; and an inconsistent sort comparator now finishes with an implementation-defined order on every target framework instead of hanging (net462/netstandard) or throwing a CLR exception at script (net8+).
Proper tail calls (#2975). Strict-mode calls in tail position reuse their frame, so "use strict" tail recursion runs in constant stack — the first ES2015 PTC implementation among the .NET engines.
Asynchronous module loading (#2872). IAsyncModuleLoader and the AsyncModuleLoader template let a host fetch module source over I/O without blocking a thread; Engine.Modules.StartImport returns an operation a game loop drives via ProcessTasks(), and ImportAsync awaits without holding a thread. The spec's load phase now exists as written, a warm-cache async loader keeps the blocking Import fully synchronous, and the blocking drain wakes on a work-arrived signal instead of polling. A module served over a transport keeps its whole url as Module.Location so its own relative imports resolve, a deferred namespace evaluates its module instead of exposing uninitialized bindings, and an import abandoned by a global snapshot restore reports itself faulted instead of polling forever.
The process no longer dies for recoverable reasons. Options.LimitRecursion used to kill the host process for most useful limits — the constraint fired, and the unwind itself overflowed the stack; exception filters now let it unwind ~7× deeper. The new opt-in Options.Constraints.StackOverflowGuard converts unbounded recursion — reachable through eighteen distinct routes, new, accessors, coercions and Proxy traps included — from a process kill into a catchable RangeError, exempting strict tail calls, which grow no stack. And a family of CLR exceptions that escaped engine.Evaluate past every script catch are now proper JavaScript errors or correct results: sorting with an inconsistent comparator, destructuring with a function-valued default (const { onChange = () => {} } = opts), toLocaleString outside DateTime's range, typed-array defineProperty without a value, DataView reads at 2³¹, String.replace $' with a lying exec, and the first instant of year 10000.
New built-ins. Iterator.prototype.join, chunks, windows and includes; take/drop now throw RangeError for a finite limit above 2^53−1 per the updated proposals. Intl.Locale.prototype.getCollations reports CLDR-cited collation data that Intl.Collator accepts in full, a malformed collation option is a RangeError, and Intl.supportedValuesOf("collation") derives from the same lists so the three can never drift.
Conformance, from a review that ran what the suite does not. Two of the fixed defects had test262 coverage only under the never-generated staging/ directory, and several had none at all: parseInt strips the sign before testing for a hex prefix, so parseInt("-0x10") is −16; a suspended finally no longer swallows a pending break/continue; a Proxy (or exotic host object) as the global's prototype answers bare identifiers through its get trap; Date.prototype.toISOString emits the spec's six-digit expanded year and round-trips through Date.parse in every spelling including year 0; iterator helpers close their receiver exactly once and only when the spec says so, and carry their own @@toStringTag; Map/Set size is the prototype accessor the spec defines rather than a phantom own property; a Proxy's defineProperty trap receives the partial descriptor the caller wrote; a string's @@iterator is read once, with the primitive as receiver; Array.prototype.join re-asks the array when a side effect fills a hole mid-join; a direct eval reaches the enclosing function's arguments in both modes; and Temporal.Now drops the methods the proposal removed.
Embedder surface. OperationDeadlineConstraint bounds a whole multi-entry host operation; ScriptPreparationOptions.StaticAnalysis trades prepare-time analysis for per-engine materialization on shared graphs; ModuleFactory.LocationOf exposes the module-naming rule a host must match; Engine.Advanced.HostDefined carries per-request state on a pooled engine; the CLR exception behind an interop error is reachable through JintException.TryGetClrException with opt-in ChainClrExceptions(), and a host method's own TargetException is no longer mistaken for a receiver mismatch; and a recursion-limit failure propagates out of a module load instead of becoming a catchable rejection.
Performance, gated. Against v4.15.3 on idle hardware, medians of three paired runs: controlflow-recursive −15.6% time and −40.4% allocation (proper tail calls), bitops-3bit-bits-in-byte −8.9%, math-spectral-norm −7.3%, crypto-sha1 −6.9%, 3d-raytrace −5.9%, math-cordic −5.8%, with a broad −1–4% tail across the call- and string-heavy rows; no row moved outside its own measured cross-run envelope in the other direction, and allocation is flat within ±0.2% suite-wide. Warmed parseInt call sites take the frameless fast-call lane (−13% on the parse loop), joined by the Number predicates, String.prototype.indexOf/startsWith/endsWith/includes/at/substr, global isNaN/isFinite and Array.isArray (−3% to −19%) and the Map/Set method family (map.get hit loop −13%); existence questions on a wrapped dictionary answer from ContainsKey, taking in −33% with −98% allocation and Object.keys −37%; resolving an inherited global no longer allocates per miss (−99.99% on the read loop) and a global created through an inherited write keeps the in-place store; JSON replacer/reviver eligibility is decided once per document, built-in callback dispatch once per loop, a call site's arguments reach an interpreted callee in registers, and function-local let/const live in fixed slots.
Breaking changes. Int32Extensions/Int64Extensions/DoubleExtensions — polyfill hosts that leaked into the public API — are now internal; on net462/netstandard2.0, code with using Jint; may have bound span Parse/TryParse members through them. JsonParser rejects trailing commas. Number.parseInt.length/Number.parseFloat.length report their spec values. Post-construction mutation of an Options instance no longer reaches an already-built engine, and Options.Configure callbacks work again. UnwrapIfPromise reports a cancelled engine as ExecutionCanceledException instead of a timeout. Time-zone matching is ASCII-case-insensitive per ECMA-402.
On the engine comparison benchmarks, Jint 4.16.0 is the fastest engine outright on 5 of 12 scripts — leading dromaeo-object-regexp-modern over native V8 by 1.25× — in a statistical tie for first on interop-collection-traversal, the fastest managed engine on 10 of 12, the fastest interpreter on all 12, and 8.6×–11.2× ahead of ClearScript (native V8) on every interop row while allocating 3.9×–12.4× less than the nearest managed competitor.
Note truncated.
Jint 4.15.3 rounds out the 4.15 embedder line: every item here answers friction a real integration reported while adopting the host-integration surfac
Jint 4.15.3 rounds out the 4.15 embedder line: every item here answers friction a real integration reported while adopting the host-integration surface 4.15.0 introduced. Everything is additive — no option defaults changed and no behavior changes for existing code.
Engine.Advanced.AddLazyGlobal (#2862) — install a lazy global on a live engine, so a host whose globals are computed from per-request data can defer building them until script reads the name; the same PR adds Engine.Advanced.WithRestoredGlobals(snapshot, action), the try/finally every snapshot-reusing host was writing by hand.PropertyDescriptor.CreateLazy (#2865) — a public lazy property descriptor that materializes once and then rejoins the read and write inline caches, which a hand-rolled CustomJsValue descriptor never could; it is the sanctioned way to build for any host object property what AddLazyGlobal does for a global.Options.AddImmutableCrossing(params Type[]) (#2863) — a host promise that instances of the declared CLR types do not change while they are exposed to the engine, in exchange for which a wrapped object memoizes its resolved reads. On the nested-document walk it was built for that measures −43% to −84% time and −99% allocation against the undeclared path, with dictionary and JsonNode sources converging to identical steady-state cost. It is a promise: a declared object mutated anyway will serve stale reads.Jint.EnableHostContractVerification AppContext switch before the first use of any Jint type and the checks that catch a host answering one extension point in a way that contradicts another run in Release, throwing with a descriptive message. Embedders can now run their suites against the exact package they deploy instead of building a Debug Jint from source, and CI now runs this repository's own host suites that way too (#2866).Engine.Advanced.HasSharedShape (#2861) — a stable, pinnable predicate for whether JsObject.Create, CreateFromEntries or JsObjectShape.Instantiate actually produced a shared-layout object, which the explicitly non-contractual ObjectRepresentation diagnostic could never be.JsString.Create(string) is now public (#2860) — the counterpart of JsNumber.Create, answering the empty string and single-character ASCII from interned instances instead of allocating.Base holds an internal sentinel rather than undefined, and resolver authors returning it were leaking that sentinel string into scripts; the docs and the in-repo sample now show the right idiom.Full Changelog: v4.15.2...v4.15.3
Async and generator suspension — loop iteration state is preserved across suspensions in async generators and for await...of ( #2852 ), an await suspe
Jint 4.15.2 is a fix release.
for await...of (#2852), an await suspending a right-hand side no longer stores the suspension sentinel into the target (#2855), and suspension-node resolution unwraps correctly (#2856).instanceof work on bound functions whose target is itself bound (#2853), and inherited accessors reached through ObjectInstance.TryGetValue receive the original receiver (#2854).JsObject.Create values span is now nullable-annotated so a lazy slot's required null needs no suppression (#2851).Full Changelog: v4.15.1...v4.15.2
Jint 4.15.1 is a small refinement release shaped by the first real-world adoptions of 4.15.0's host-integration surface — every change answers a need
Jint 4.15.1 is a small refinement release shaped by the first real-world adoptions of 4.15.0's host-integration surface — every change answers a need a shipping embedder hit within days of the release. No behavior changes for existing code, with one deliberate spec-path improvement: Object.freeze no longer forces lazily-declared properties into existence just to validate attribute-only redefinitions (so freezing globalThis no longer materializes every lazy global).
JsObjectLayout lazy slots (#2850) — a fresh shaped object per item can now defer expensive members: declare AddLazy(name, factory) on the layout, pass per-instance state to JsObject.Create, and the member materializes on first read while every item keeps sharing one hidden class. In the motivating host shape (a 15-member event envelope with 4 expensive members), builds measure ~3.6× faster with 4× fewer allocations than the eager layout, and ~1.6× faster than the dictionary-mode workaround it replaces.Engine.Advanced.GetPropertyAccessSemantics (#2847) lets a test pin the access semantics the engine derived for a host type, and GetInteropConversionDiagnostics (#2848) counts CLR array crossings so a host can audit its ArrayConversion exposure — including through dependencies it doesn't own. Both carry the same non-contractual, diagnostics-only framing as GetObjectRepresentation.PropertyFlag.NonWritable / OnlyConfigurable (#2849) complete the named combination lattice for the descriptor shapes hosts actually build.JsonSerializer reuse and its Undefined sentinel, the BigInt.prototype.toJSON escape hatch, what does not route through GetOwnProperties(), and the snapshot reuse recipe.Full Changelog: v4.15.0...v4.15.1
Jint 4.15.0 is an embedder-focused release : the host-integration surface was widened after auditing six real-world integrations, engine reuse got fir
Jint 4.15.0 is an embedder-focused release: the host-integration surface was widened after auditing six real-world integrations, engine reuse got first-class support, and an adversarial pre-release review verified every change since 4.14.0 test-first. No option defaults changed. One behavior change to note: re-importing a module whose evaluation failed now rethrows the recorded error instead of returning a namespace (#2827).
Host objects
TryGetOwnPropertyValue (#2808) and existence/enumerability questions without materializing descriptors with ProbeOwnProperty (#2803); access semantics are derived from the type automatically (#2804). Warm host reads cost zero probes, and Debug builds verify every answer.ArrayLikeObject (#2835, #2841) projects a live indexed collection by implementing two members — indexed reads, for-of, spread, generics and JSON.stringify cost one virtual call per element.JsObjectShape (#2830, #2836, #2840) declares shared prototypes once per process with lazily materialized per-realm members — and a shaped prototype can serve the prototype-method inline cache, which no host subclass can.Engine reuse
CaptureGlobalSnapshot / RestoreGlobalSnapshot (#2834) restore a configured global between evaluations: top-level let/const cleared (nothing else can), stale promise continuations fenced, warm per-engine caches kept. Configuration reuse — deliberately not an isolation boundary.AddLazyGlobal, #2805) or selectively via Prepared<T>.ReferencedGlobals (#2831). The two compose with the snapshot.Interop
EnumConversionMode.Name (#2796) keep the lanes a blanket converter used to cost.IBufferWriter<byte> (#2822).NullPropagatingReferenceResolver.Instance (#2833) makes nullish member reads yield undefined through a recognized inline lane.Performance, gated
object-regexp −25% with 48% fewer allocations, object-string −21%, string-base64 −15%); SunSpider improved on eleven scripts, zero regressions.Math.max(a,b) −22%, push(x,y) −19% (#2828, #2843, #2844).encodeURI on clean input −85%; dense toReversed/with up to −86% (#2843).On the engine comparison benchmarks, Jint 4.15.0 is the fastest engine outright on 5 of 12 scripts — taking dromaeo-object-regexp-modern from native V8 at −42% — the fastest managed engine on 10 of 12, the fastest interpreter on all 12, and 8.9×–11.6× ahead of ClearScript (native V8) on every interop row.
IBufferWriter<byte> overload to JsonSerializer.Serialize by @lahma in #2822Note truncated.
Jint 4.14.0 is an interop-focused performance release : CLR arrays now cross into script as live views instead of copies, recently wrapped host object
Jint 4.14.0 is an interop-focused performance release: CLR arrays now cross into script as live views instead of copies, recently wrapped host objects reuse their wrappers, single-candidate interop method calls dispatch through compiled invokers, and JSON.parse interns repeated keys and values. Host collection traversal is 10.9× faster than 4.13.0. Two interop defaults changed in this release — read the first two highlights if you pass CLR arrays to scripts or rely on per-crossing conversion behavior; everything else needs no code changes to benefit.
CLR arrays are live views by default (behavior change). Options.Interop.ArrayConversion now defaults to ArrayConversionMode.LiveView (#2721, #2728, #2735): a single-rank T[] crossing into script becomes a live, fixed-size view over the underlying array — the way wrapped List<T> already behaves — instead of being copied into a new JS array on every read. Writes go through in both directions, and arrays exposed through read-only-declared members (e.g. IReadOnlyList<T>) produce read-only views. Iteration, Array.prototype methods, JSON serialization, index-key enumeration (Object.keys / for..in yield "0".."n-1") and undefined for out-of-range reads all behave array-like, but Array.isArray returns false, and because CLR arrays are fixed-size, resizing operations (push/pop/length writes) throw a TypeError like integer-indexed exotic objects do — shift/splice may move elements before their length change throws, as for typed arrays. Set Options.Interop.ArrayConversion = ArrayConversionMode.Copy to restore the 4.13 behavior.
Recently wrapped CLR objects reuse their wrappers (behavior change). The new Options.Interop.CacheRecentObjectWrappers defaults to true (#2734): a small bounded ring (8 entries, keyed by reference identity and exposed type) reuses wrappers for host objects that repeatedly cross into script. Wrapper identity becomes stable (host.Obj === host.Obj), script-attached state (freeze, defineProperty, expandos) survives crossings, and the per-crossing wrapper allocation disappears. Under Copy array conversion this also means repeated reads of the same CLR array reuse the first JsArray snapshot while it stays cached — CLR-side mutations are not re-copied; set the option to false for the pre-4.14 fresh-snapshot-per-crossing behavior. Engine.Dispose() releases the ring.
Interop fast lanes. Single-candidate method calls run through a compiled invoker that binds and invokes without argument arrays or boxing (#2733), with per-parameter binding flags precomputed (#2719). Resolved ObjectWrapper members get a per-call-site inline cache (#2722) and the member-call fast path covers primitive string receivers (#2717). Array-like wrapper creation is a cached factory call with lazily materialized length (#2730), primitive elements convert without boxing on both indexed reads and Array.prototype iteration (#2731, #2735), the wrapper identity caches cover CLR arrays (#2716), and implicitly implemented interface methods are deduplicated in member resolution (#2711).
JSON. JSON.parse interns property keys and string values within a parse, parses numbers off the span with an exactly-rounded fast path and scans string content in bulk (#2718, #2725, #2732) — the json-parse-modern comparison row is 6% faster with 23% less allocation than 4.13.0. Parsing is also aligned with the JSON grammar (#2738): malformed numbers like -09 and 1. are now rejected as in V8, while raw U+2028/U+2029 in strings and escaped control characters in keys — both valid JSON — are now accepted.
Strings. Chained slice/substring and split segments stay zero-copy views (#2720), whole-string substring/substr return the receiver, and mismatched-length comparisons no longer materialize views (#2740).
Execution constraints at host boundaries. Timeouts and cancellation are re-checked when control returns from host CLR code, so detection latency is bounded by one host call instead of a statement-count window, without adding per-statement cost — gated on execution depth so host-side reads of wrapped objects on an idle engine never observe a stale timer (#2713, #2714, #2715). Execution-context depth stays balanced when constraint exceptions unwind generator/async frames, and a host callback that re-enters the engine no longer resets the outer script's budget (#2736).
Correctness (including a pre-release review). A review of everything since 4.13.0 fixed: spurious TDZ when a for-header reads a name the loop body shadows (#2709) and stale closure captures from destructuring defaults in for-loop headers (#2739); the compiled-invoker lane now defers to custom ITypeConverters and preserves reflection exception types (#2737); and the new wrapper defaults were hardened — declared-type contracts for arrays (an IReadOnlyList<T>-typed member no longer yields a writable view), a static type-mapper poisoning crash, Engine.Dispose releasing the wrapper caches, and JS-array in/enumeration/out-of-range semantics on array views (#2735). Closure reads memoize slot-cache chain reachability (#2726).
On the engine comparison benchmarks, Jint 4.14.0 beats ClearScript (native V8) by 7.1×–9.1× on every script ↔ host interop row — host collection traversal went from last to second among all engines at 15,597 → 1,433 µs with 99% less allocation — while remaining the fastest managed engine on 10 of 12 pure-JS scripts and the fastest interpreter on all 12, and now leading array-stress and dromaeo-object-array, rows V8 narrowly led at 4.13.0.
Full Changelog: v4.13.0...v4.14.0
Jint 4.13.0 is a performance- and correctness-focused release . It brings a Proxy overhaul — trap dispatch rebuilt to forward with near-zero allocatio
Jint 4.13.0 is a performance- and correctness-focused release. It brings a Proxy overhaul — trap dispatch rebuilt to forward with near-zero allocation, plus a new public API for implementing traps in .NET — extends the unboxed interpreter fast lanes to more operators and loop shapes, and cuts allocations on for..of, nested-function calls and array enumeration. A thorough pre-release review of everything since 4.12.0 also fixed several correctness bugs. No code changes are required to benefit.
Proxy overhaul, and a CLR trap API. Proxy trap dispatch was rebuilt around a shared skeleton with lazy argument construction and pooled arrays, so a proxy with no matching trap forwards to its target with effectively zero allocation (#2674, #2675, #2676). Proxies can now be implemented from .NET: Engine.Advanced.CreateProxy / CreateRevocableProxy accept a ProxyHandler whose virtual methods are the traps, with the same invariant enforcement as JavaScript handlers (#2678). Several Proxy spec fixes came along — getPrototypeOf / setPrototypeOf with null prototypes (#2668), the construct trap's argument array (#2670), capturing [[Construct]] at creation (#2669), and the get trap firing for a property named revoke (#2667) — and the ObjectWrapper iterator helpers are hardened against foreign and revoked receivers (#2681).
Interpreter fast lanes. New unboxed operand lanes for the arithmetic binary operators (#2664) and an int32 fast lane for remainder (#2671) remove per-iteration boxing; flag-proven casts use Unsafe.As on the hot paths (#2673) and JsNumber.Create avoids a native fmod (#2662). Strict-equality guards against undefined / null / typeof are fused (#2658), member-expression identifier reads route through the identifier caches (#2660), and the identifier slot cache is restructured hop-0-first (#2689). The tight-loop fast lane now covers while and do-while bodies (#2688).
Lower allocations. for..of over an array no longer allocates an iterator-result object per element (#2700); per-call nested-function instantiation is allocation-free (#2684); for-in over arrays enumerates dense indices lazily without materializing a key list (#2656); and observation-only constraint checks are amortized so tight loops stay fast under a timeout (#2672).
RegExp. Quantified groups without capture or lookaround hazards prefer the .NET Regex engine (#2682), reused .NET adaptations adaptively upgrade to RegexOptions.Compiled (#2690), and the custom engine's match timeout is enforced by an inline deadline rather than a thread-pool timer (#2686).
Correctness (including a pre-release review). A review of everything since 4.12.0 fixed: a regex routing regression that silently truncated matches for nullable non-capturing quantified groups (#2694) and a custom-engine bug dropping iterations for multi-atom quantified groups (#2699); Proxy trap dispatch is now atomic against a mid-dispatch revoke (#2696); top-level await of a .NET Task in a module (#2665), plus prompt cancellation of the await drain (#2697); the arguments object escaping a short-circuiting logical compound assignment un-materialized (#2698); for-in now includes inherited enumerable index properties on Array.prototype (#2655); and the memory limit stays exact in tight loops (#2695).
Across the managed JavaScript engines for .NET, Jint 4.13.0 is the fastest engine on 17 of the 21 comparison scripts — and the fastest interpreter on all 21 — while allocating far less memory than the other engines; dromaeo-3d-cube is ~9% faster and dromaeo-string-base64 ~10% faster than 4.12.0. See the engine comparison benchmarks for the full table.
Full Changelog: v4.12.0...v4.13.0
Jint 4.12.0 is a performance- and correctness-focused release . It completes the move to hidden-class shapes across the whole object model, extends th
Jint 4.12.0 is a performance- and correctness-focused release. It completes the move to hidden-class shapes across the whole object model, extends the unboxed interpreter fast lanes to more operators and call shapes, and adds a layer of per-engine caching so re-executed scripts and re-created functions reuse their compiled metadata and environments. A pre-release review of everything since 4.11.0 also fixed several correctness regressions. No code changes are required to benefit.
Object model — shapes everywhere. The hidden-class shape model now backs the built-in prototypes and constructors, TypedArrays, the global object, and Intl / Temporal (#2580, #2581, #2582, #2590, #2595, #2597). JSON.parse builds its result objects as shapes, so an array of like-shaped records costs one allocation per record instead of a property dictionary each (#2634). Object literals inside generator/async frames and object spread {...src} adopt shapes too (#2596, #2648, #2635), and a provably-simple constructor shapes its instances from the third construction (#2636).
Interpreter fast lanes. New unboxed operand lanes for equality, bitwise, modulo-equality and sum-of-products expressions remove per-iteration boxing (#2602, #2604, #2611, #2628), and comparison operands are served from the validated global-descriptor cache (#2603). Expression-only and if/else for-loop bodies run through a tight per-iteration cycle with a member-bound loop test (i < arr.length) (#2605, #2617, #2623), env-less leaf calls run against the captured environment directly (#2627), and functions that cannot observe their this skip this-binding (#2626).
Caching & reuse. Nested-scope global reads and writes are served from a validated global-binding cache (#2584, #2625); hoisted function and class definitions, and the top-level statement handler tree, are reused across re-evaluations on an engine (#2613, #2615, #2649); and for-of / for-in reuse a fixed-slot per-iteration environment, skipping per-iteration TDZ re-init where it is provably safe (#2586, #2632).
Lower allocations. A coverage campaign added benchmarks for common patterns the suite did not exercise and then closed the hotspots they surfaced (#2630): resolved await chains and engine-internal promise reactions (#2639), for-in enumeration (#2640), throw/catch (#2641), primitive number/boolean/bigint methods (no wrapper object, #2642), and tagged templates (#2638) all allocate far less.
Correctness. Fixes for sticky + global [Symbol.match] returning wrong results (#2600), an unlabeled break escaping a labeled switch (#2607), -0 in integer multiplication (#2620), and raw property writes on shaped hosts (#2591, #2601). A pre-release review (#2651) additionally fixed for-in re-enumerating a shadowed key (a mid-loop delete and a pooled-iterator reuse case), mapped-arguments writes being lost after the call returns (and duplicate-parameter mapping now follows the spec), and hardened the object-literal and built-in-shape paths.
Across the managed JavaScript engines for .NET, Jint 4.12.0 is the fastest engine on 17 of the 21 comparison scripts — and the fastest interpreter on all 21 — leading by up to ~5.4× over the next-fastest engine while allocating 2×–63× less memory than the closest competitor. See the engine comparison benchmarks for the full table.
Full Changelog: v4.11.0...v4.12.0
Jint 4.11.0 is a performance-focused release. It completes the move to a hidden-class shape model for the object system and adds a family of unboxed i
Jint 4.11.0 is a performance-focused release. It completes the move to a hidden-class shape model for the object system and adds a family of unboxed interpreter fast lanes, so the most common patterns — object and array construction, property access, tight numeric loops, and eval — do less work and allocate far less memory, with no change to behavior.
eval runs in slot-backed environments (#2565), direct-recursive calls pool their environments (#2549), and Function-constructor instances reuse a definition-level environment (#2579).Function.prototype.toString source-text retention is now opt-in (#2562), and the changes above cut allocations across the board — direct recursion, for example, allocates up to ~99% less.await (#2567), Map iteration during mutation (#2570), and ShadowRealm evaluation of super / new.target (#2573).Across the managed JavaScript engines for .NET, Jint 4.11.0 is the fastest on most object, string and regex workloads — 1.7–5× over the next-fastest engine — while allocating 2–63× less memory than the closest competitor. See the engine comparison benchmarks for the full table.
Warning
Function.prototype.toString() no longer returns source text by default. To cut memory use (#2560), the engine no longer retains each parsed function's source string, so toString() now returns a function name() { [native code] } placeholder instead of the original source. If your scripts — or a library you host — depend on toString() returning real source, re-enable it with new Engine(options => options.RetainFunctionSourceText()) (equivalently Options.RetainFunctionSourceText = true, or the matching RetainFunctionSourceText flag on ScriptParsingOptions / ModuleParsingOptions and prepared scripts).
Performance caveat: turning it back on restores the previous memory behavior — every parsed function pins its full source string, so caching many or large prepared scripts can retain hundreds of MB of duplicated source (the retention that #2560 was filed to fix). Enable it only when you actually need the source text.
obj.method resolved on the direct prototype by @lahma in #2558Full Changelog: v4.10.1...v4.11.0
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →