Skip to content

createRealm installs a full $262 on the new realm and returns it - #3044

Merged
lahma merged 1 commit into
sebastienros:mainfrom
lahma:staging/realm-262
Aug 18, 2026
Merged

lahma merged 1 commit into
sebastienros:mainfrom
lahma:staging/realm-262

Conversation

@lahma

@lahma lahma commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

test262's INTERPRETING.md specifies that $262.createRealm() "creates a new ECMAScript Realm, defines this API on the new realm's global object, and returns the $262 property of the new realm's global object".

Jint returned the new realm's global object and installed only global and evalScript on it. So otherRealm.$262 was undefined and otherRealm.$262.detachArrayBuffer could not be reached. It worked at all only because it set global on the global itself, so other.global happened to resolve.

The fix

Test262Object.Install gains a (Engine, Realm) overload that builds $262 from the target realm's intrinsics using the realm-pinned ClrFunction constructor, and createRealm now installs a full $262createRealm, detachArrayBuffer, evalScript, gc, IsHTMLDDA, AbstractModuleSource, global — on the new realm's global and returns it.

One thing worth recording, because it is not obvious: realm.Intrinsics.Object.Construct(...) does not give you an object of that realm. A plain construction lands on new JsObject(engine), whose prototype comes from the active realm. The $262 object and the %AbstractModuleSource% prototype therefore use OrdinaryObjectCreate(engine, realm.Intrinsics.Object.PrototypeObject) instead. That same behaviour is the root cause of the "wrong realm's result" exclusions still listed in the file.

Compatibility

All 284 test262 files that call createRealm were surveyed: 297 uses chain .global directly, and of the 9 that bind the result to a variable the only properties ever read are .global (24) and .evalScript (2). Both survive. No file reads .global off a variable already holding the new global, so dropping the old self-reference is safe. Test262AgentManager's minimal worker $262 is untouched.

Tests

Jint.Tests/Runtime/Test262HarnessObjectTests.cs — 4 tests, all failing against unfixed code. They assert the returned object carries every member, that result.global is a distinct global with distinct intrinsics, that the installed functions genuinely belong to the new realm, and that detachArrayBuffer detaches a buffer created in that realm.

Because this touches the harness every cross-realm test262 file depends on, the 219 generated test methods covering all 498 files that mention $262 were run in sequential chunks: 46,905 passed, 0 failed.

Exclusions

Frees staging/sm/TypedArray/set-wrapped.js.

constructor-buffer-sequence.js is not freed and stays excluded, moved to its own block with a precise reason: with the $262 gap closed it progresses to a realm-independent failure. InitializeTypedArrayFromArrayBuffer must raise the misaligned-byteOffset RangeError (step 3) before evaluating ToIndex(length) (step 5); Jint evaluates both ToIndex calls at the call site first, so new Int32Array(buffer, 1, poisonedValue) reports the poisoned value's own error. Those are the same two lines as the int-truncation bug behind constructor-byteoffsets-bounds.js, so one fix removes both entries.

The cross-realm banner is rewritten. Its claim that "an error Jint raises while running a function belonging to another realm is constructed from the current realm's intrinsics" is wrong as a general statement — most built-ins already raise through their own _realm. What remains is a handful of paths that reach for whichever realm is running, in two distinct shapes (wrong realm's error, wrong realm's result), and the comment now says which files are which.

Refs #3021.

test262's INTERPRETING.md says $262.createRealm() "creates a new ECMAScript
Realm, defines this API on the new realm's global object, and returns the $262
property of the new realm's global object". Jint returned the bare global and
put only `global` and `evalScript` on it, so `otherGlobal.$262` was undefined
and `otherGlobal.$262.detachArrayBuffer` unreachable; `other.global` resolved
only because the global was made to point at itself.

Install(Engine) now delegates to a new Install(Engine, Realm) that builds the
whole API out of the target realm's intrinsics and returns it, with the realm's
global carrying it under `$262`. Every function on it is created through the
realm-pinned ClrFunction constructor, and the $262 object and the
%AbstractModuleSource% prototype are created with OrdinaryObjectCreate against
the realm's Object.prototype rather than Intrinsics.Object.Construct - a plain
construction lands on `new JsObject(engine)`, which takes whichever realm is
active, so the object would have come back wearing the caller's prototype.
IsHTMLDDA takes a realm for the same reason. createRealm is part of the API it
installs, so realms nest, and evalScript now always routes through
EvaluateInRealm, which also gains RetainFunctionSourceText.

`$262.global` exists at the top level too, which it did not before, and the
created realm's global no longer carries a `global` self-reference: across all
284 test262 files that call createRealm, `.global` and `.evalScript` are the
only properties ever read off the result, and nothing reads `.global` off the
global itself.

Frees staging/sm/TypedArray/set-wrapped.js. Its neighbour
constructor-buffer-sequence.js gets further but still fails, for a reason that
has nothing to do with realms - Jint evaluates ToIndex(length) before the
"byteOffset modulo elementSize" RangeError of InitializeTypedArrayFromArrayBuffer
step 3 - so it moves to a block of its own with that reason. The cross-realm
banner is reworded: error creation is not realm-blind in general, most built-ins
already throw through their own _realm; what is left is a handful of paths that
take the running realm instead of the called function's, for the throw and for
the allocated result alike.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lahma
lahma merged commit 31d4885 into sebastienros:main Aug 18, 2026
9 of 10 checks passed
@lahma
lahma deleted the staging/realm-262 branch August 18, 2026 16:16
legrab added a commit to legrab/pocok that referenced this pull request Aug 25, 2026
Updated [Jint](https://github.com/sebastienros/jint) from 4.16.0 to
4.16.1.

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

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

## 4.16.1

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`](https://github.com/sebastienros/jint/blob/main/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.

### Highlights

**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).

## What's Changed
* Drop the five Temporal members the proposal removed by @​lahma in
sebastienros/jint#3014
* Canonicalize u-extension options and format every date the spec allows
by @​lahma in sebastienros/jint#3018
* Mark a global created by an unresolvable assignment, and stop a
waitAsync timeout outliving its engine by @​lahma in
sebastienros/jint#3019
* Run test262's staging/ directory too by @​lahma in
sebastienros/jint#3016
* Give JavaScript strings a maximum length instead of a wrapped array
rent by @​lahma in sebastienros/jint#3015
* Let a for-of frame decline the unwind it can only rethrow by @​lahma
in sebastienros/jint#3017
* Keep counting a frame a tail call replaced while its trampoline runs
by @​lahma in sebastienros/jint#3022
* Unpark staging/Temporal/removed-methods.js, which #​3014 already fixed
by @​lahma in sebastienros/jint#3023
* Drop the Islamic date conversions no calendar path reaches by @​lahma
in sebastienros/jint#3027
* Let an Atomics waiter go when nothing can ever notify it again by
@​lahma in sebastienros/jint#3029
* Refuse a JSON document too long to be a string while it is being built
by @​lahma in sebastienros/jint#3028
* Bump the microsoft group with 3 updates by @​dependabot[bot] in
sebastienros/jint#3033
* Bump the analyzers group with 1 update by @​dependabot[bot] in
sebastienros/jint#3031
* Add initial threat model for untrusted scripts by @​sebastienros in
sebastienros/jint#3030
* Stop ClassBenchmark rebuilding its engine per iteration by @​lahma in
sebastienros/jint#3053
* createRealm installs a full $262 on the new realm and returns it by
@​lahma in sebastienros/jint#3044
* Give the benchmark suite a measurement environment by @​lahma in
sebastienros/jint#3055
* Evaluate a computed property key even when it is spelled as a literal
by @​lahma in sebastienros/jint#3039
* Stop error messages from running user JavaScript by @​lahma in
sebastienros/jint#3041
* Array.from honours IsConstructor, and a typed array's length write
throws by @​lahma in sebastienros/jint#3043
* Consult the iterator's done flag before stepping it again by @​lahma
in sebastienros/jint#3048
* Date.prototype.setTime must store the clipped time value by @​lahma in
sebastienros/jint#3042
* Answer a sloppy function's own arguments instead of throwing by
@​svenrog in sebastienros/jint#3061
* Keep the outer link on a parked Function-constructor environment by
@​svenrog in sebastienros/jint#3063
* A throw from the iterator step must not close the iterator by @​lahma
in sebastienros/jint#3047
 ... (truncated)

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

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Jint&package-manager=nuget&previous-version=4.16.0&new-version=4.16.1)](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