Add ProxyHandler for implementing Proxy traps in .NET code#2678
Merged
Conversation
New public API in Jint.Runtime.Interop: - abstract class ProxyHandler with one virtual method per ECMAScript Proxy trap (get/set/has/deleteProperty/getOwnPropertyDescriptor/defineProperty/ ownKeys/apply/construct/getPrototypeOf/setPrototypeOf/isExtensible/ preventExtensions). A trap returning CLR null means "forward to the target", exactly like an absent trap on a JavaScript handler object; bool?/PropertyDescriptor?/ObjectInstance? return types map trap results without JsValue round-trips. - readonly struct RevocableProxy mirroring Proxy.revocable(). - Engine.Advanced.CreateProxy / CreateRevocableProxy factories. JsProxy integration: - CLR-handled proxies dispatch through the same per-trap funnel and the exact same Validate*TrapResult invariant enforcement as JavaScript handler objects; only the trap acquisition/invocation differs. - Revocation unified on _target being null (IsRevoked + internal Revoke(), now also used by Proxy.revocable); external reader in Function.GetFunctionRealm switched from _handler to IsRevoked so CLR-handled proxies are not mistaken for revoked ones. - Every trap entry point asserts non-revocation up front and captures the target before the observable GetMethod step (matching spec order); GetTrap no longer re-asserts. - apply/construct CLR traps receive a private element-wise copy of the argument array (interpreter arrays may be pooled or Unsafe.As- reinterpreted object[] instances and must not escape to user code). Exception routing mirrors ClrFunction: with the default handler, CLR trap exceptions bubble untouched (the catch filter never engages); with Options.Interop.ExceptionHandler configured they can become catchable JavaScript errors. Docs spell out the get-vs-apply method-call semantics: proxy.method(x) fires the get trap and then a plain call on the result, so intercepting method calls means returning a memoized wrapping function from Get. README gains an "Intercepting access to .NET objects" interop subsection showing composition with SetWrapObjectHandler. Tests (Jint.Tests.PublicInterface/ClrProxyHandlerTests.cs): all 13 traps individually, forward-sentinel semantics (null vs JsValue.Undefined), invariant violations from lying traps, the memoized method-interception recipe with this passthrough and identity, revocation (idempotent, typeof stays "function"), WrapObjectHandler composition with round-trip unwrapping, Reflect.get receiver, exception routing both modes, and argument-null validation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EsHuKuE4UihKZp5HYapDHE
This was referenced Jul 13, 2026
This was referenced Jul 15, 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.
Adds the CLR side of the Proxy story: a public
Jint.Runtime.Interop.ProxyHandlerabstract class with 13 virtual trap methods, created viaEngine.Advanced.CreateProxy(target, handler)/CreateRevocableProxy(...). Closes the long-standing interop gap (#1468): nothing inOptions.Interopcould intercept method calls or property writes on objects handed to a script —MemberAccessoris read-only, and full interception required subclassingObjectInstanceby hand.Design points:
nullmeans "trap not implemented — forward", mirroring absent JS traps, and enabling per-invocation conditional forwarding. Typed signatures (bool?,PropertyDescriptor?,ObjectInstance?for construct) enforce spec result types at compile time._clrHandlerbranch ahead of the JS trap fetch feeds the sameValidate*TrapResultinvariant code — CLR handlers cannot create spec-impossible object states, and the CLR path skips trap-name lookup, argument arrays, and apply/construct array packing entirely.Unsafe.As-reinterpreted, so the raw array is never exposed (documented as private to the invocation).JsProxy.Revoke(), shared withProxy.revocable);GetFunctionRealmswitched from a_handlernull-test toIsRevoked(would have misfired on CLR-handled proxies).Options.Interop.ExceptionHandlerwithClrFunction's exact semantics (bubble by default; convertible to catchable JS errors), via allocation-free catch filters.proxy.method(x)fires the get trap then a plain call — the apply trap only fires when the proxy itself is invoked, so method interception means returning a (memoized) wrapping function fromGet. The test suite includes that exact recipe against a wrapped CLR object, includingproxy.add === proxy.addidentity andthisunwrapping.SetWrapObjectHandlercomposition for automatic proxying.Verification: 27 new tests in
Jint.Tests.PublicInterface(per-trap coverage vsJsObjectandObjectWrappertargets, forward sentinel, invariant enforcement against lying CLR traps, revocation, receiver pass-through viaReflect.get, exception routing, WrapObjectHandler composition + unwrap-into-CLR-method). Full test262: 99,429 passed / 0 failed — the JS-handler path is regression-free. Zero warnings on all TFMs.Perf: JS-handler path flat — ProxyBenchmark medians (launchCount 5, same base): TrapGet 22.4→20.6 ms, TrapSet 30.5→28.4 ms, ApplyTrap 53.8→55.7 ms (overlapping distributions); allocations identical except +8 B/proxy instance for the two new fields. Non-proxy paths untouched.
Supersedes #1569 (see closing rationale there).
🤖 Generated with Claude Code
https://claude.ai/code/session_01EsHuKuE4UihKZp5HYapDHE