Repository navigation
Backport #4176, #4183 to 4.x: keep a ShadowRealm failure copy from killing the host or running script - #4220
Merged
Merged
Conversation
…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>
This was referenced Oct 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
WrappedFunction.[[Call]]andWrappedFunctionCreatethrew the cross-realmTypeErrorfrom inside thecatchthat handled the failure. That adds one nested exception dispatch per hop, and no stack probe sees it. So withStackOverflowGuardon, the guard's catchableRangeErrorat the bottom of a wrapped-function chain became a process-ending stack overflow on the way back up. A three-linenamegetter needed no depth at all. TheCross-Realm Error:prefix is now also added once, not once per hop.ShadowRealm.prototype.evaluatebuilt the copy's message withToStringof the thrown value. That rantoString,@@toPrimitive,name/messagegetters and proxy traps, and if one of them threw, the shadow realm's own object reached the caller instead of theTypeError. 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
StackOverflowGuardis off by default on 4.x because Enable the stack-overflow guard by default #3057 is not here. The newHostNativeRecursionGuardTestsrows therefore use the file's existingGuarded()engine instead of main'snew Engine(). Without the guard, nothing turns the recursion into aRangeError, 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 xUnitTheoryData, not main'sTestCases.BoundFunctionChainWalkTeststakes main's one-line change, which drops the doubled prefix from the expected message.Jint/Constraints/AGENTS.md, so the one-line gotcha ("nothing probes an exception dispatch...") goes in the rootAGENTS.md, next to the other constraint bullets. 4.x also has noJint.Tests/SpecAnchors.txt, so nothing needs registering.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 inShadowRealm.cs:DataPropertyText,ConstructorNameandFindOnPrototypeChain. They refuse accessors, refuse proxies and stop after 32 hops. TheIErrorTextSlotsbranch is left out because its only implementer,DOMException, lives inJint/WebApi, which 4.x does not have. The[[ErrorData]]test isis JsError, the same brand 4.x'sError.isErroruses (IErrorDataexists only on main). The newShadowRealmErrorCopyTestsuse xUnitInlineData.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) plusBoundFunctionChainWalkTests(3 tests)Stack overflow., withSystem.Runtime.EH.DispatchExon top of about 1,128 nestedWrappedFunction.CallframesProcess is terminated due to StackOverflowException.Stack overflow.Process is terminated due to StackOverflowException.BoundFunctionChainWalkTests#4183:
ShadowRealmErrorCopyTests(26 cases)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 ortoString's own exception replaced the copy, nested realms printedCross-Realm Error: TypeError: Cross-Realm Error: ..., orimportValuereported'caller', 'callee', and 'arguments' properties may....Skipped
ConcurrentShadowRealmParsingAndEvaluationIsRejectedfromTask.Runto a LongRunning task. That test, and theJint.Tests.PublicInterface/ParsingLimitTests.csfile holding it, do not exist on 4.x: there is noConcurrentShadowRealm,entered.WaitorTask.Run(() => shadowRealm...anywhere in the 4.x tree, so there is nothing to port.Test totals (Release, full projects, no
--timeout)FullyQualifiedName~ShadowRealmThe public API Verify snapshots are byte-identical: everything added is
privateor aprivate sealednested type, and the PublicInterface run left no received files.🤖 Generated with Claude Code