Skip to content

Backport #4176, #4183 to 4.x: keep a ShadowRealm failure copy from killing the host or running script - #4220

Merged
lahma merged 2 commits into
sebastienros:4.xfrom
lahma:backport/4x-shadowrealm
Oct 5, 2026
Merged

lahma merged 2 commits into
sebastienros:4.xfrom
lahma:backport/4x-shadowrealm

Conversation

@lahma

@lahma lahma commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

Two ShadowRealm fixes from main, ported to 4.x. Both are correctness and conformance fixes: one stops a script from killing the host process through a ShadowRealm boundary, and the other stops the TypeError copy of a failure from running script. Neither changes a public API or a default.

4.x commit main PR What it fixes
bb15b1a #4176 (c1c180b) WrappedFunction.[[Call]] and WrappedFunctionCreate threw the cross-realm TypeError from inside the catch that handled the failure. That adds one nested exception dispatch per hop, and no stack probe sees it. So with StackOverflowGuard on, the guard's catchable RangeError at the bottom of a wrapped-function chain became a process-ending stack overflow on the way back up. A three-line name getter needed no depth at all. The Cross-Realm Error: prefix is now also added once, not once per hop.
ea5d8cc #4183 (8557142) ShadowRealm.prototype.evaluate built the copy's message with ToString of the thrown value. That ran toString, @@toPrimitive, name/message getters and proxy traps, and if one of them threw, the shadow realm's own object reached the caller instead of the TypeError. CreateTypeErrorCopy must not run any ECMAScript code. importValue's rejection handler was %ThrowTypeError%, which reported a failed import as a 'caller'/'callee'/'arguments' access. It is now the proposal's ImportValueError function, which makes the same copy.

Adapted for 4.x

  • Throw a ShadowRealm cross-realm TypeError after its catch block, not inside it #4176, opt-in stack guard. StackOverflowGuard is off by default on 4.x because Enable the stack-overflow guard by default #3057 is not here. The new HostNativeRecursionGuardTests rows therefore use the file's existing Guarded() engine instead of main's new Engine(). Without the guard, nothing turns the recursion into a RangeError, and the process dies whether or not the fix is present. I checked this: with an unguarded engine the fixed build still overflowed, as plain recursion with no exception dispatch involved. The rows are xUnit TheoryData, not main's TestCases. BoundFunctionChainWalkTests takes main's one-line change, which drops the doubled prefix from the expected message.
  • Throw a ShadowRealm cross-realm TypeError after its catch block, not inside it #4176, docs. 4.x has no Jint/Constraints/AGENTS.md, so the one-line gotcha ("nothing probes an exception dispatch...") goes in the root AGENTS.md, next to the other constraint bullets. 4.x also has no Jint.Tests/SpecAnchors.txt, so nothing needs registering.
  • Describe a value thrown out of a ShadowRealm without running any of its code #4183, call-free reader. On main the copy reads the error through Jint.Diagnostics.ValueSlotReader.ErrorText/ErrorLine, which 4.x does not have. I did not port that class. The same descriptor-only walk is written as private helpers in ShadowRealm.cs: DataPropertyText, ConstructorName and FindOnPrototypeChain. They refuse accessors, refuse proxies and stop after 32 hops. The IErrorTextSlots branch is left out because its only implementer, DOMException, lives in Jint/WebApi, which 4.x does not have. The [[ErrorData]] test is is JsError, the same brand 4.x's Error.isError uses (IErrorData exists only on main). The new ShadowRealmErrorCopyTests use xUnit InlineData.

Evidence

Each port's new tests were run against unfixed 4.x first (for #4183, unfixed means on top of the #4176 port), then with the fix.

#4176: AFailureCopiedAcrossAShadowRealmBoundaryAtEveryLevelIsCatchable (2 rows) plus BoundFunctionChainWalkTests (3 tests)

net10.0 net472
unfixed, "wrapped call" row process killed: Stack overflow., with System.Runtime.EH.DispatchEx on top of about 1,128 nested WrappedFunction.Call frames process killed: Process is terminated due to StackOverflowException.
unfixed, "wrapped create" row (run alone) process killed: Stack overflow. process killed: Process is terminated due to StackOverflowException.
unfixed, BoundFunctionChainWalkTests 1 failed / 2 passed (the doubled prefix) 1 failed / 2 passed
fixed, all 5 5 passed / 0 failed 5 passed / 0 failed

#4183: ShadowRealmErrorCopyTests (26 cases)

net10.0 net472
unfixed 18 failed / 8 passed 18 failed / 8 passed
fixed 26 passed / 0 failed 26 passed / 0 failed

The 8 cases that pass unfixed are the ones pinning that ordinary errors and primitives are still described as before. The 18 failures are: user code ran (ran=1), a getter's or toString's own exception replaced the copy, nested realms printed Cross-Realm Error: TypeError: Cross-Realm Error: ..., or importValue reported 'caller', 'callee', and 'arguments' properties may....

Skipped

Test totals (Release, full projects, no --timeout)

Project net10.0 net472
Jint.Tests 7681 passed, 4 skipped, 0 failed (7685) 7596 passed, 4 skipped, 0 failed (7600)
Jint.Tests.PublicInterface 1905 passed, 9 skipped, 0 failed (1914) 1897 passed, 9 skipped, 0 failed (1906)
Jint.Tests.Test262, filtered to FullyQualifiedName~ShadowRealm 124 passed, 0 failed n/a

The public API Verify snapshots are byte-identical: everything added is private or a private sealed nested type, and the PublicInterface run left no received files.

🤖 Generated with Claude Code

lahma and others added 2 commits October 5, 2026 11:52
…peError after its catch block, not inside it

WrappedFunction's [[Call]] and WrappedFunctionCreate copied a failure into
a TypeError by throwing from inside the catch handling it. A throw from a
handler is dispatched on top of every frame down to the throw it handles,
so a failure crossing a chain of wrapped functions nested one exception
dispatch per hop, which no native stack probe sees: the StackOverflowGuard's
catchable RangeError at the bottom of the chain became a process-ending
stack overflow on the way back up.

Both sites now record the failure and throw once the handler has returned,
and the "Cross-Realm Error: " mark is added once instead of once per hop.

Adapted for 4.x: StackOverflowGuard is opt-in on this branch, so the new
test rows run on the file's Guarded() engine; xUnit TheoryData instead of
main's TestCases; the gotcha goes in the root AGENTS.md (4.x has no
Jint/Constraints/AGENTS.md) and there is no SpecAnchors.txt to register.

Adapted from c1c180b

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…hadowRealm without running any of its code

ShadowRealm.prototype.evaluate copied a failure into the caller's TypeError
by formatting the thrown value with ToString, which calls toString,
@@toPrimitive, a name or message getter and every proxy trap on the way.
CreateTypeErrorCopy must not cause any ECMAScript code execution, and when
one of those calls threw, its exception - an object of the shadow realm -
reached the caller instead of the TypeError.

The copy's message is now read without calling anything: a primitive's own
string form, and for an error its name and message where each is a data
property holding a string. Any other object, a proxy included, is described
by kind alone. An error that is itself a copy keeps its message, so a failure
leaving nested shadow realms is marked "Cross-Realm Error: " once.

importValue's rejection handler was %ThrowTypeError%, which reported a failed
import as a 'caller'/'callee'/'arguments' access. It is now the proposal's
ImportValueError function, performing the same copy.

Adapted for 4.x: main reads the error through Jint.Diagnostics.ValueSlotReader
(ErrorText/ErrorLine), which this branch does not have; the same call-free
descriptor walk is written as private helpers in ShadowRealm.cs, without the
IErrorTextSlots branch (DOMException lives in Jint/WebApi, absent here). The
[[ErrorData]] test is `is JsError`, as Error.isError's is on this branch.
Tests use xUnit InlineData; there is no SpecAnchors.txt to register.

Adapted from 8557142

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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