Skip to content

fix(runtime): unwrap Proxy receivers and arguments before native dispatch - #485

Draft
edusperoni wants to merge 1 commit into
mainfrom
fix/proxy-receivers
Draft

edusperoni wants to merge 1 commit into
mainfrom
fix/proxy-receivers

Conversation

@edusperoni

@edusperoni edusperoni commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Native object wrappers wrapped in a JS Proxy — which is what Vue 3's reactive() does to anything stored in component data() — expose no internal fields, so the runtime could not find the native object behind them:

Call through a Proxy Before
proxy.someMethod() dispatched as a class method → NSInvalidArgumentException: +[Klass someMethod]: unrecognized selector sent to class
proxy.someProperty undefined
String(proxy) / console.log TypeError: Cannot convert object to primitive value
nsArray.addObject(proxy) process abort (tns::Assert in Interop::ToArray — a Proxy has no creation context)

The same behavior exists on every previous release (the gates are identical in 8.9.2), so this is not a 9.1 regression; Vue 3 users have been working around it with markRaw/toRaw.

Changes

  • tns::UnwrapProxy walks Proxy::GetTarget() chains (empty when revoked). tns::GetValue resolves through it, so every wrapper lookup sees the target.
  • MethodCallback, property getter/setter and toString resolve a Proxy receiver to its native target (or class constructor, for methods) and dispatch exactly as on the target. A revoked Proxy, or one whose target is not a native object, throws a catchable TypeError instead of a static call or an assert.
  • WriteValue / WriteTypeValue / ToArray unwrap arguments before marshalling, so proxied native objects, proxied JS arrays of native objects, proxied dictionaries and struct initializers all convert as their targets do. A revoked Proxy argument throws a TypeError. ToObject and ffi-closure return values (where a C++ throw can't propagate) treat a revoked Proxy as nil.
  • Every GetCreationContext() on a caller-supplied value falls back to the current context instead of asserting (incl. setTimeout/requestAnimationFrame callbacks that are callable Proxies).

Proxy traps are consulted only for the JS-side lookup; the native call runs on the target (no reactivity tracking of native state — expected, same as Vue's own guidance for third-party class instances).

Related Pull Requests

Tests

TestRunner/app/tests/ProxyReceiverTests.js — 14 specs (instance/nested/class-constructor receivers, property get/set, string coercion, proxied object/array/dictionary/struct arguments, revoked-proxy receiver and argument, non-native target, traps-are-consulted). Full suite: 1748 specs, 0 failures.

Shared-submodule versions of these specs can follow once both runtimes land.

…atch

Native wrappers wrapped in a JS Proxy (e.g. by Vue's reactive()) expose no
internal fields, so method calls were dispatched as class methods, property
accessors returned undefined, toString returned an object, and passing a
proxy as an argument aborted the process in GetCreationContext.

- tns::UnwrapProxy walks Proxy::GetTarget chains; tns::GetValue resolves
  through it, so every wrapper lookup sees the target.
- MethodCallback, property getter/setter and toString resolve a proxy
  receiver to its native target (or class constructor for methods) and throw
  a TypeError for revoked proxies or non-native targets.
- WriteValue/WriteTypeValue/ToArray unwrap before marshalling and throw a
  TypeError for revoked proxies; ToObject and callback return values treat
  a revoked proxy as nil.
- GetCreationContext call sites fall back to the current context instead of
  asserting when the object has none.
@coderabbitai

coderabbitai Bot commented Oct 1, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
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